mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Use a conventional delegation model in LaunchedURLClassLoader
When an application is run as an executable archive with nested jars, the application's own classes need to be able to load classes from within the nested jars. This means that the application's classes need to be loaded by the same class loader as is used for the nested jars. When an application is launched with java -jar the contents of the jar are on the class path of the app class loader, which is the parent of the LaunchedURLClassLoader that is used to load classes from within the nested jars. If the root of the jar includes the application's classes, they would be loaded by the app class loader and, therefore, would not be able to load classes from within the nested jars. Previously, this problem was resolved by LaunchedURLClassLoader being created with a copy of all of the app class laoder's URLs and by using an unconventional delegation model that caused it to skip its parent (the app class loader) and jump straight to its root class loader. This ensured that the LaunchedURLClassLoader would load both the application's own classes and those from within any nested jars. Unfortunately, this unusual delegation model has proved to be problematic. We have seen and worked around some problems with Java Agents (see gh-4911 and gh-863), but there are others (see gh-4868) that cannot be made to work with the current delegation model. This commit reworks LaunchedURLClassLoader to use a conventional delegate model with the app class loader as its parent. With this change in place, the application's own classes need to be hidden from the app class loader via some other means. This is now achieved by packaging application classes in BOOT-INF/classes (and, for symmetry, nested jars are now packaged in BOOT-INF/lib). Both the JarLauncher and the PropertiesLauncher (which supports the executable jar layout) have been updated to look for classes and nested jars in these new locations. Closes gh-4897 Fixes gh-4868
This commit is contained in:
parent
9be69f1ac7
commit
87fe0b2ade
@ -220,7 +220,7 @@
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/assembly/lib</outputDirectory>
|
||||
<outputDirectory>${project.build.directory}/assembly/BOOT-INF/lib</outputDirectory>
|
||||
<includeScope>runtime</includeScope>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
@ -15,6 +15,7 @@
|
||||
<include>${project.groupId}:${project.artifactId}</include>
|
||||
</includes>
|
||||
<unpack>true</unpack>
|
||||
<outputDirectory>BOOT-INF/classes/</outputDirectory>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
<fileSets>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -52,7 +52,7 @@ public final class PackagedSpringApplicationLauncher {
|
||||
}
|
||||
|
||||
private Object[] getSources(URLClassLoader classLoader) throws Exception {
|
||||
Enumeration<URL> urls = classLoader.findResources("META-INF/MANIFEST.MF");
|
||||
Enumeration<URL> urls = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||
while (urls.hasMoreElements()) {
|
||||
URL url = urls.nextElement();
|
||||
Manifest manifest = new Manifest(url.openStream());
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -42,8 +42,9 @@ public class MultiProjectRepackagingTests {
|
||||
File buildLibs = new File(
|
||||
"target/multi-project-transitive-file-dependency/main/build/libs");
|
||||
JarFile jarFile = new JarFile(new File(buildLibs, "main.jar"));
|
||||
assertThat(jarFile.getEntry("lib/commons-logging-1.1.3.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("lib/foo.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-logging-1.1.3.jar"))
|
||||
.isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/foo.jar")).isNotNull();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
@ -57,7 +58,7 @@ public class MultiProjectRepackagingTests {
|
||||
"target/multi-project-common-file-dependency/build/libs");
|
||||
JarFile jarFile = new JarFile(
|
||||
new File(buildLibs, "multi-project-common-file-dependency.jar"));
|
||||
assertThat(jarFile.getEntry("lib/foo.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/foo.jar")).isNotNull();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
@ -70,7 +71,7 @@ public class MultiProjectRepackagingTests {
|
||||
File buildLibs = new File(
|
||||
"target/multi-project-runtime-project-dependency/projectA/build/libs");
|
||||
JarFile jarFile = new JarFile(new File(buildLibs, "projectA.jar"));
|
||||
assertThat(jarFile.getEntry("lib/projectB.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/projectB.jar")).isNotNull();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -144,7 +144,7 @@ public class RepackagingTests {
|
||||
.run();
|
||||
File buildLibs = new File("target/repackage/build/libs");
|
||||
JarFile jarFile = new JarFile(new File(buildLibs, "repackage.jar"));
|
||||
assertThat(jarFile.getEntry("lib/foo.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/foo.jar")).isNotNull();
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
@ -166,7 +166,7 @@ public class RepackagingTests {
|
||||
private boolean isDevToolsJarIncluded(File repackageFile) throws IOException {
|
||||
JarFile jarFile = new JarFile(repackageFile);
|
||||
try {
|
||||
String name = "lib/spring-boot-devtools-" + BOOT_VERSION + ".jar";
|
||||
String name = "BOOT-INF/lib/spring-boot-devtools-" + BOOT_VERSION + ".jar";
|
||||
return jarFile.getEntry(name) != null;
|
||||
}
|
||||
finally {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -120,6 +120,11 @@ public class JarWriter {
|
||||
* @throws IOException if the entries cannot be written
|
||||
*/
|
||||
public void writeEntries(JarFile jarFile) throws IOException {
|
||||
this.writeEntries(jarFile, new IdentityEntryTransformer());
|
||||
}
|
||||
|
||||
void writeEntries(JarFile jarFile, EntryTransformer entryTransformer)
|
||||
throws IOException {
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
@ -133,7 +138,7 @@ public class JarWriter {
|
||||
jarFile.getInputStream(entry));
|
||||
}
|
||||
EntryWriter entryWriter = new InputStreamEntryWriter(inputStream, true);
|
||||
writeEntry(entry, entryWriter);
|
||||
writeEntry(entryTransformer.transform(entry), entryWriter);
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
@ -377,4 +382,25 @@ public class JarWriter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@code EntryTransformer} enables the transformation of {@link JarEntry jar
|
||||
* entries} during the writing process.
|
||||
*/
|
||||
interface EntryTransformer {
|
||||
|
||||
JarEntry transform(JarEntry jarEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@code EntryTransformer} that returns the entry unchanged.
|
||||
*/
|
||||
private static final class IdentityEntryTransformer implements EntryTransformer {
|
||||
|
||||
@Override
|
||||
public JarEntry transform(JarEntry jarEntry) {
|
||||
return jarEntry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -60,7 +60,7 @@ public final class Layouts {
|
||||
/**
|
||||
* Executable JAR layout.
|
||||
*/
|
||||
public static class Jar implements Layout {
|
||||
public static class Jar implements RepackagingLayout {
|
||||
|
||||
@Override
|
||||
public String getLauncherClassName() {
|
||||
@ -69,7 +69,7 @@ public final class Layouts {
|
||||
|
||||
@Override
|
||||
public String getLibraryDestination(String libraryName, LibraryScope scope) {
|
||||
return "lib/";
|
||||
return "BOOT-INF/lib/";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -77,6 +77,11 @@ public final class Layouts {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRepackagedClassesLocation() {
|
||||
return "BOOT-INF/classes/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutable() {
|
||||
return true;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -24,9 +24,12 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.springframework.boot.loader.tools.JarWriter.EntryTransformer;
|
||||
|
||||
/**
|
||||
* Utility class that can be used to repackage an archive so that it can be executed using
|
||||
* '{@literal java -jar}'.
|
||||
@ -189,7 +192,14 @@ public class Repackager {
|
||||
writer.writeManifest(buildManifest(sourceJar));
|
||||
Set<String> seen = new HashSet<String>();
|
||||
writeNestedLibraries(unpackLibraries, seen, writer);
|
||||
writer.writeEntries(sourceJar);
|
||||
if (this.layout instanceof RepackagingLayout) {
|
||||
writer.writeEntries(sourceJar,
|
||||
new RenamingEntryTransformer(((RepackagingLayout) this.layout)
|
||||
.getRepackagedClassesLocation()));
|
||||
}
|
||||
else {
|
||||
writer.writeEntries(sourceJar);
|
||||
}
|
||||
writeNestedLibraries(standardLibraries, seen, writer);
|
||||
if (this.layout.isExecutable()) {
|
||||
writer.writeLoaderClasses();
|
||||
@ -293,4 +303,47 @@ public class Repackager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@code EntryTransformer} that renames entries by applying a prefix.
|
||||
*/
|
||||
private static final class RenamingEntryTransformer implements EntryTransformer {
|
||||
|
||||
private final String namePrefix;
|
||||
|
||||
private RenamingEntryTransformer(String namePrefix) {
|
||||
this.namePrefix = namePrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JarEntry transform(JarEntry entry) {
|
||||
if (entry.getName().startsWith("META-INF/")
|
||||
|| entry.getName().startsWith("BOOT-INF/")) {
|
||||
return entry;
|
||||
}
|
||||
JarEntry renamedEntry = new JarEntry(this.namePrefix + entry.getName());
|
||||
renamedEntry.setTime(entry.getTime());
|
||||
renamedEntry.setSize(entry.getSize());
|
||||
renamedEntry.setMethod(entry.getMethod());
|
||||
if (entry.getComment() != null) {
|
||||
renamedEntry.setComment(entry.getComment());
|
||||
}
|
||||
renamedEntry.setCompressedSize(entry.getCompressedSize());
|
||||
renamedEntry.setCrc(entry.getCrc());
|
||||
if (entry.getCreationTime() != null) {
|
||||
renamedEntry.setCreationTime(entry.getCreationTime());
|
||||
}
|
||||
if (entry.getExtra() != null) {
|
||||
renamedEntry.setExtra(entry.getExtra());
|
||||
}
|
||||
if (entry.getLastAccessTime() != null) {
|
||||
renamedEntry.setLastAccessTime(entry.getLastAccessTime());
|
||||
}
|
||||
if (entry.getLastModifiedTime() != null) {
|
||||
renamedEntry.setLastModifiedTime(entry.getLastModifiedTime());
|
||||
}
|
||||
return renamedEntry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,24 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.net.URL;
|
||||
package org.springframework.boot.loader.tools;
|
||||
|
||||
/**
|
||||
* A strategy for detecting Java agents.
|
||||
* A specialization of {@link Layout} that repackages an existing archive by moving its
|
||||
* content to a new location.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface JavaAgentDetector {
|
||||
public interface RepackagingLayout extends Layout {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if {@code url} points to a Java agent jar file, otherwise
|
||||
* {@code false} is returned.
|
||||
* @param url The url to examine
|
||||
* @return if the URL points to a Java agent
|
||||
* Returns the location to which classes should be moved.
|
||||
* @return the repackaged classes location
|
||||
*/
|
||||
boolean isJavaAgentJar(URL url);
|
||||
String getRepackagedClassesLocation();
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -64,13 +64,13 @@ public class LayoutsTests {
|
||||
public void jarLayout() throws Exception {
|
||||
Layout layout = new Layouts.Jar();
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.COMPILE))
|
||||
.isEqualTo("lib/");
|
||||
.isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.CUSTOM))
|
||||
.isEqualTo("lib/");
|
||||
.isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.PROVIDED))
|
||||
.isEqualTo("lib/");
|
||||
.isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(layout.getLibraryDestination("lib.jar", LibraryScope.RUNTIME))
|
||||
.isEqualTo("lib/");
|
||||
.isEqualTo("BOOT-INF/lib/");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -291,7 +291,7 @@ public class RepackagerTests {
|
||||
final File libNonJarFile = this.temporaryFolder.newFile();
|
||||
FileCopyUtils.copy(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, libNonJarFile);
|
||||
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
|
||||
this.testJarFile.addFile("lib/" + libJarFileToUnpack.getName(),
|
||||
this.testJarFile.addFile("BOOT-INF/lib/" + libJarFileToUnpack.getName(),
|
||||
libJarFileToUnpack);
|
||||
File file = this.testJarFile.getFile();
|
||||
libJarFile.setLastModified(JAN_1_1980);
|
||||
@ -305,12 +305,13 @@ public class RepackagerTests {
|
||||
callback.library(new Library(libNonJarFile, LibraryScope.COMPILE));
|
||||
}
|
||||
});
|
||||
assertThat(hasEntry(file, "lib/" + libJarFile.getName())).isTrue();
|
||||
assertThat(hasEntry(file, "lib/" + libJarFileToUnpack.getName())).isTrue();
|
||||
assertThat(hasEntry(file, "lib/" + libNonJarFile.getName())).isFalse();
|
||||
JarEntry entry = getEntry(file, "lib/" + libJarFile.getName());
|
||||
assertThat(hasEntry(file, "BOOT-INF/lib/" + libJarFile.getName())).isTrue();
|
||||
assertThat(hasEntry(file, "BOOT-INF/lib/" + libJarFileToUnpack.getName()))
|
||||
.isTrue();
|
||||
assertThat(hasEntry(file, "BOOT-INF/lib/" + libNonJarFile.getName())).isFalse();
|
||||
JarEntry entry = getEntry(file, "BOOT-INF/lib/" + libJarFile.getName());
|
||||
assertThat(entry.getTime()).isEqualTo(JAN_1_1985);
|
||||
entry = getEntry(file, "lib/" + libJarFileToUnpack.getName());
|
||||
entry = getEntry(file, "BOOT-INF/lib/" + libJarFileToUnpack.getName());
|
||||
assertThat(entry.getComment()).startsWith("UNPACK:");
|
||||
assertThat(entry.getComment().length()).isEqualTo(47);
|
||||
}
|
||||
@ -395,9 +396,10 @@ public class RepackagerTests {
|
||||
});
|
||||
JarFile jarFile = new JarFile(file);
|
||||
try {
|
||||
assertThat(jarFile.getEntry("lib/" + nestedFile.getName()).getMethod())
|
||||
.isEqualTo(ZipEntry.STORED);
|
||||
assertThat(jarFile.getEntry("test/nested.jar").getMethod())
|
||||
assertThat(
|
||||
jarFile.getEntry("BOOT-INF/lib/" + nestedFile.getName()).getMethod())
|
||||
.isEqualTo(ZipEntry.STORED);
|
||||
assertThat(jarFile.getEntry("BOOT-INF/classes/test/nested.jar").getMethod())
|
||||
.isEqualTo(ZipEntry.STORED);
|
||||
}
|
||||
finally {
|
||||
@ -432,7 +434,8 @@ public class RepackagerTests {
|
||||
TestJarFile nested = new TestJarFile(this.temporaryFolder);
|
||||
nested.addClass("a/b/C.class", ClassWithoutMainMethod.class);
|
||||
final File nestedFile = nested.getFile();
|
||||
this.testJarFile.addFile("lib/" + nestedFile.getName(), nested.getFile());
|
||||
this.testJarFile.addFile("BOOT-INF/lib/" + nestedFile.getName(),
|
||||
nested.getFile());
|
||||
this.testJarFile.addClass("A.class", ClassWithMainMethod.class);
|
||||
File file = this.testJarFile.getFile();
|
||||
Repackager repackager = new Repackager(file);
|
||||
@ -446,8 +449,9 @@ public class RepackagerTests {
|
||||
});
|
||||
JarFile jarFile = new JarFile(file);
|
||||
try {
|
||||
assertThat(jarFile.getEntry("lib/" + nestedFile.getName()).getComment())
|
||||
.startsWith("UNPACK:");
|
||||
assertThat(
|
||||
jarFile.getEntry("BOOT-INF/lib/" + nestedFile.getName()).getComment())
|
||||
.startsWith("UNPACK:");
|
||||
}
|
||||
finally {
|
||||
jarFile.close();
|
||||
@ -460,7 +464,8 @@ public class RepackagerTests {
|
||||
TestJarFile nested = new TestJarFile(this.temporaryFolder);
|
||||
nested.addClass("a/b/C.class", ClassWithoutMainMethod.class);
|
||||
final File nestedFile = nested.getFile();
|
||||
this.testJarFile.addFile("lib/" + nestedFile.getName(), nested.getFile());
|
||||
this.testJarFile.addFile("BOOT-INF/lib/" + nestedFile.getName(),
|
||||
nested.getFile());
|
||||
this.testJarFile.addClass("A.class", ClassWithMainMethod.class);
|
||||
File file = this.testJarFile.getFile();
|
||||
Repackager repackager = new Repackager(file);
|
||||
@ -478,7 +483,7 @@ public class RepackagerTests {
|
||||
});
|
||||
JarFile jarFile = new JarFile(file);
|
||||
try {
|
||||
assertThat(jarFile.getEntry("lib/" + nestedFile.getName()).getSize())
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/" + nestedFile.getName()).getSize())
|
||||
.isEqualTo(sourceLength);
|
||||
}
|
||||
finally {
|
||||
|
@ -49,7 +49,7 @@
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/assembly/lib</outputDirectory>
|
||||
<outputDirectory>${project.build.directory}/assembly/BOOT-INF/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
@ -14,6 +14,7 @@
|
||||
<includes>
|
||||
<include>${project.groupId}:${project.artifactId}</include>
|
||||
</includes>
|
||||
<outputDirectory>BOOT-INF/classes</outputDirectory>
|
||||
<unpack>true</unpack>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,13 +16,8 @@
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
@ -40,24 +35,16 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
|
||||
|
||||
private final Archive archive;
|
||||
|
||||
private final JavaAgentDetector javaAgentDetector;
|
||||
|
||||
public ExecutableArchiveLauncher() {
|
||||
this(new InputArgumentsJavaAgentDetector());
|
||||
}
|
||||
|
||||
public ExecutableArchiveLauncher(JavaAgentDetector javaAgentDetector) {
|
||||
try {
|
||||
this.archive = createArchive();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
this.javaAgentDetector = javaAgentDetector;
|
||||
}
|
||||
|
||||
protected ExecutableArchiveLauncher(Archive archive) {
|
||||
this.javaAgentDetector = new InputArgumentsJavaAgentDetector();
|
||||
this.archive = archive;
|
||||
}
|
||||
|
||||
@ -92,31 +79,6 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
|
||||
return archives;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
|
||||
Set<URL> copy = new LinkedHashSet<URL>(urls.length);
|
||||
ClassLoader loader = getDefaultClassLoader();
|
||||
if (loader instanceof URLClassLoader) {
|
||||
for (URL url : ((URLClassLoader) loader).getURLs()) {
|
||||
if (addDefaultClassloaderUrl(urls, url)) {
|
||||
copy.add(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.addAll(copy, urls);
|
||||
return super.createClassLoader(copy.toArray(new URL[copy.size()]));
|
||||
}
|
||||
|
||||
private boolean addDefaultClassloaderUrl(URL[] urls, URL url) {
|
||||
String jarUrl = "jar:" + url + "!/";
|
||||
for (URL nestedUrl : urls) {
|
||||
if (nestedUrl.equals(url) || nestedUrl.toString().equals(jarUrl)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !this.javaAgentDetector.isJavaAgentJar(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the specified {@link JarEntry} is a nested item that should be added
|
||||
* to the classpath. The method is called once for each entry.
|
||||
@ -134,20 +96,4 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
|
||||
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
|
||||
}
|
||||
|
||||
private static ClassLoader getDefaultClassLoader() {
|
||||
ClassLoader classloader = null;
|
||||
try {
|
||||
classloader = Thread.currentThread().getContextClassLoader();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Cannot access thread context ClassLoader - falling back to system class
|
||||
// loader...
|
||||
}
|
||||
if (classloader == null) {
|
||||
// No thread context class loader -> use class loader of this class.
|
||||
classloader = ExecutableArchiveLauncher.class.getClassLoader();
|
||||
}
|
||||
return classloader;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.URL;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A {@link JavaAgentDetector} that detects jars supplied via the {@code -javaagent} JVM
|
||||
* input argument.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class InputArgumentsJavaAgentDetector implements JavaAgentDetector {
|
||||
|
||||
private static final String JAVA_AGENT_PREFIX = "-javaagent:";
|
||||
|
||||
private final Set<URL> javaAgentJars;
|
||||
|
||||
public InputArgumentsJavaAgentDetector() {
|
||||
this(getInputArguments());
|
||||
}
|
||||
|
||||
InputArgumentsJavaAgentDetector(List<String> inputArguments) {
|
||||
this.javaAgentJars = getJavaAgentJars(inputArguments);
|
||||
}
|
||||
|
||||
private static List<String> getInputArguments() {
|
||||
try {
|
||||
return AccessController.doPrivileged(new PrivilegedAction<List<String>>() {
|
||||
@Override
|
||||
public List<String> run() {
|
||||
return ManagementFactory.getRuntimeMXBean().getInputArguments();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<URL> getJavaAgentJars(List<String> inputArguments) {
|
||||
Set<URL> javaAgentJars = new HashSet<URL>();
|
||||
for (String argument : inputArguments) {
|
||||
String path = getJavaAgentJarPath(argument);
|
||||
if (path != null) {
|
||||
try {
|
||||
javaAgentJars.add(new File(path).getCanonicalFile().toURI().toURL());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to determine canonical path of Java agent at path '"
|
||||
+ path + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
return javaAgentJars;
|
||||
}
|
||||
|
||||
private String getJavaAgentJarPath(String arg) {
|
||||
if (arg.startsWith(JAVA_AGENT_PREFIX)) {
|
||||
String path = arg.substring(JAVA_AGENT_PREFIX.length());
|
||||
int equalsIndex = path.indexOf('=');
|
||||
if (equalsIndex > -1) {
|
||||
path = path.substring(0, equalsIndex);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJavaAgentJar(URL url) {
|
||||
return this.javaAgentJars.contains(url);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -22,13 +22,17 @@ import org.springframework.boot.loader.archive.Archive;
|
||||
|
||||
/**
|
||||
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
|
||||
* included inside a {@code /lib} directory.
|
||||
* included inside a {@code /BOOT-INF/lib} and that application classes are included
|
||||
* inside a {@code /BOOT-INF/classes} directory.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JarLauncher extends ExecutableArchiveLauncher {
|
||||
|
||||
private static final String LIB = "lib/";
|
||||
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
|
||||
|
||||
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
|
||||
|
||||
public JarLauncher() {
|
||||
}
|
||||
@ -39,7 +43,10 @@ public class JarLauncher extends ExecutableArchiveLauncher {
|
||||
|
||||
@Override
|
||||
protected boolean isNestedArchive(Archive.Entry entry) {
|
||||
return !entry.isDirectory() && entry.getName().startsWith(LIB);
|
||||
if (entry.isDirectory()) {
|
||||
return entry.getName().startsWith(BOOT_INF_CLASSES);
|
||||
}
|
||||
return entry.getName().startsWith(BOOT_INF_LIB);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -23,13 +23,10 @@ import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import org.springframework.boot.loader.jar.Handler;
|
||||
import org.springframework.boot.loader.jar.JarFile;
|
||||
import org.springframework.lang.UsesJava7;
|
||||
|
||||
/**
|
||||
* {@link ClassLoader} used by the {@link Launcher}.
|
||||
@ -40,10 +37,6 @@ import org.springframework.lang.UsesJava7;
|
||||
*/
|
||||
public class LaunchedURLClassLoader extends URLClassLoader {
|
||||
|
||||
private static LockProvider LOCK_PROVIDER = setupLockProvider();
|
||||
|
||||
private final ClassLoader rootClassLoader;
|
||||
|
||||
/**
|
||||
* Create a new {@link LaunchedURLClassLoader} instance.
|
||||
* @param urls the URLs from which to load classes and resources
|
||||
@ -51,57 +44,21 @@ public class LaunchedURLClassLoader extends URLClassLoader {
|
||||
*/
|
||||
public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
|
||||
super(urls, parent);
|
||||
this.rootClassLoader = findRootClassLoader(parent);
|
||||
}
|
||||
|
||||
private ClassLoader findRootClassLoader(ClassLoader classLoader) {
|
||||
while (classLoader != null) {
|
||||
if (classLoader.getParent() == null) {
|
||||
return classLoader;
|
||||
}
|
||||
classLoader = classLoader.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resource with the given {@code name}. Unlike a standard
|
||||
* {@link ClassLoader}, this method will first search the root class loader. If the
|
||||
* resource is not found, this method will call {@link #findResource(String)}.
|
||||
*/
|
||||
@Override
|
||||
public URL getResource(String name) {
|
||||
URL url = null;
|
||||
if (this.rootClassLoader != null) {
|
||||
url = this.rootClassLoader.getResource(name);
|
||||
}
|
||||
return (url == null ? findResource(name) : url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL findResource(String name) {
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
if (name.equals("") && hasURLs()) {
|
||||
return getURLs()[0];
|
||||
}
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
return super.findResource(name);
|
||||
}
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
return super.findResource(name);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
return null;
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<URL> findResources(String name) throws IOException {
|
||||
if (name.equals("") && hasURLs()) {
|
||||
return Collections.enumeration(Arrays.asList(getURLs()));
|
||||
}
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
return super.findResources(name);
|
||||
@ -111,106 +68,47 @@ public class LaunchedURLClassLoader extends URLClassLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasURLs() {
|
||||
return getURLs().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resources with the given {@code name}. Returns a combination of the
|
||||
* resources found by {@link #findResources(String)} and from
|
||||
* {@link ClassLoader#getResources(String) getResources(String)} on the root class
|
||||
* loader, if any.
|
||||
*/
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
if (this.rootClassLoader == null) {
|
||||
return findResources(name);
|
||||
}
|
||||
return new ResourceEnumeration(this.rootClassLoader.getResources(name),
|
||||
findResources(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load classes from the URLs before delegating to the parent loader.
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve)
|
||||
throws ClassNotFoundException {
|
||||
synchronized (LaunchedURLClassLoader.LOCK_PROVIDER.getLock(this, name)) {
|
||||
Class<?> loadedClass = findLoadedClass(name);
|
||||
if (loadedClass == null) {
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
loadedClass = doLoadClass(name);
|
||||
}
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
}
|
||||
if (resolve) {
|
||||
resolveClass(loadedClass);
|
||||
}
|
||||
return loadedClass;
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> doLoadClass(String name) throws ClassNotFoundException {
|
||||
|
||||
// 1) Try the root class loader
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
if (this.rootClassLoader != null) {
|
||||
return this.rootClassLoader.loadClass(name);
|
||||
}
|
||||
definePackageIfNecessary(name);
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore and continue
|
||||
}
|
||||
|
||||
// 2) Try to find locally
|
||||
try {
|
||||
findPackage(name);
|
||||
Class<?> cls = findClass(name);
|
||||
return cls;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore and continue
|
||||
}
|
||||
|
||||
// 3) Use standard loading
|
||||
return super.loadClass(name, false);
|
||||
}
|
||||
|
||||
private void findPackage(final String name) throws ClassNotFoundException {
|
||||
int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot != -1) {
|
||||
String packageName = name.substring(0, lastDot);
|
||||
if (getPackage(packageName) == null) {
|
||||
try {
|
||||
definePackageForFindClass(name, packageName);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Swallow and continue
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a package before a {@code findClass} call is made. This is necessary to
|
||||
* ensure that the appropriate manifest for nested JARs associated with the package.
|
||||
* @param name the class name being found
|
||||
* @param packageName the package
|
||||
* ensure that the appropriate manifest for nested JARs is associated with the
|
||||
* package.
|
||||
* @param className the class name being found
|
||||
*/
|
||||
private void definePackageForFindClass(final String name, final String packageName) {
|
||||
private void definePackageIfNecessary(String className) {
|
||||
int lastDot = className.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
String packageName = className.substring(0, lastDot);
|
||||
if (getPackage(packageName) == null) {
|
||||
definePackage(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void definePackage(final String packageName) {
|
||||
try {
|
||||
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
|
||||
@Override
|
||||
public Object run() throws ClassNotFoundException {
|
||||
String packageEntryName = packageName.replace(".", "/") + "/";
|
||||
for (URL url : getURLs()) {
|
||||
try {
|
||||
if (url.getContent() instanceof JarFile) {
|
||||
JarFile jarFile = (JarFile) url.getContent();
|
||||
if (jarFile.getManifest() != null) {
|
||||
if (jarFile.getEntry(packageEntryName) != null
|
||||
&& jarFile.getManifest() != null) {
|
||||
definePackage(packageName, jarFile.getManifest(),
|
||||
url);
|
||||
return null;
|
||||
@ -242,6 +140,7 @@ public class LaunchedURLClassLoader extends URLClassLoader {
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,76 +153,4 @@ public class LaunchedURLClassLoader extends URLClassLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@UsesJava7
|
||||
private static LockProvider setupLockProvider() {
|
||||
try {
|
||||
ClassLoader.registerAsParallelCapable();
|
||||
return new Java7LockProvider();
|
||||
}
|
||||
catch (NoSuchMethodError ex) {
|
||||
return new LockProvider();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy used to provide the synchronize lock object to use when loading classes.
|
||||
*/
|
||||
private static class LockProvider {
|
||||
|
||||
public Object getLock(LaunchedURLClassLoader classLoader, String className) {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Java 7 specific {@link LockProvider}.
|
||||
*/
|
||||
@UsesJava7
|
||||
private static class Java7LockProvider extends LockProvider {
|
||||
|
||||
@Override
|
||||
public Object getLock(LaunchedURLClassLoader classLoader, String className) {
|
||||
return classLoader.getClassLoadingLock(className);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Enumeration} implementation used for {@code getResources()}.
|
||||
*/
|
||||
private static class ResourceEnumeration implements Enumeration<URL> {
|
||||
|
||||
private final Enumeration<URL> rootResources;
|
||||
|
||||
private final Enumeration<URL> localResources;
|
||||
|
||||
ResourceEnumeration(Enumeration<URL> rootResources,
|
||||
Enumeration<URL> localResources) {
|
||||
this.rootResources = rootResources;
|
||||
this.localResources = localResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
try {
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
return this.rootResources.hasMoreElements()
|
||||
|| this.localResources.hasMoreElements();
|
||||
}
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL nextElement() {
|
||||
if (this.rootResources.hasMoreElements()) {
|
||||
return this.rootResources.nextElement();
|
||||
}
|
||||
return this.localResources.nextElement();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -22,11 +22,8 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
@ -60,7 +57,7 @@ import org.springframework.boot.loader.util.SystemPropertyUtils;
|
||||
* <ul>
|
||||
* <li>{@code loader.path}: a comma-separated list of directories to append to the
|
||||
* classpath (containing file resources and/or nested archives in *.jar or *.zip).
|
||||
* Defaults to {@code lib} in your application archive</li>
|
||||
* Defaults to {@code BOOT-INF/classes,BOOT-INF/lib} in your application archive</li>
|
||||
* <li>{@code loader.main}: the main method to delegate execution to once the class loader
|
||||
* is set up. No default, but will fall back to looking for a {@code Start-Class} in a
|
||||
* {@code MANIFEST.MF}, if there is one in <code>${loader.home}/META-INF</code>.</li>
|
||||
@ -123,12 +120,8 @@ public class PropertiesLauncher extends Launcher {
|
||||
|
||||
private static final Pattern WORD_SEPARATOR = Pattern.compile("\\W+");
|
||||
|
||||
private static final URL[] EMPTY_URLS = {};
|
||||
|
||||
private final File home;
|
||||
|
||||
private final JavaAgentDetector javaAgentDetector;
|
||||
|
||||
private List<String> paths = new ArrayList<String>();
|
||||
|
||||
private final Properties properties = new Properties();
|
||||
@ -136,13 +129,8 @@ public class PropertiesLauncher extends Launcher {
|
||||
private Archive parent;
|
||||
|
||||
public PropertiesLauncher() {
|
||||
this(new InputArgumentsJavaAgentDetector());
|
||||
}
|
||||
|
||||
PropertiesLauncher(JavaAgentDetector javaAgentDetector) {
|
||||
try {
|
||||
this.home = getHomeDirectory();
|
||||
this.javaAgentDetector = javaAgentDetector;
|
||||
initializeProperties(this.home);
|
||||
initializePaths();
|
||||
this.parent = createArchive();
|
||||
@ -158,7 +146,7 @@ public class PropertiesLauncher extends Launcher {
|
||||
}
|
||||
|
||||
private void initializeProperties(File home) throws Exception, IOException {
|
||||
String config = "classpath:"
|
||||
String config = "classpath:BOOT-INF/classes/"
|
||||
+ SystemPropertyUtils.resolvePlaceholders(
|
||||
SystemPropertyUtils.getProperty(CONFIG_NAME, "application"))
|
||||
+ ".properties";
|
||||
@ -425,7 +413,7 @@ public class PropertiesLauncher extends Launcher {
|
||||
lib.addAll(nested);
|
||||
}
|
||||
}
|
||||
addParentClassLoaderEntries(lib);
|
||||
addNestedEntries(lib);
|
||||
return lib;
|
||||
}
|
||||
|
||||
@ -482,82 +470,26 @@ public class PropertiesLauncher extends Launcher {
|
||||
return new FilteredArchive(this.parent, filter);
|
||||
}
|
||||
|
||||
private void addParentClassLoaderEntries(List<Archive> lib)
|
||||
throws IOException, URISyntaxException {
|
||||
ClassLoader parentClassLoader = getClass().getClassLoader();
|
||||
List<Archive> urls = new ArrayList<Archive>();
|
||||
for (URL url : getURLs(parentClassLoader)) {
|
||||
if (!this.javaAgentDetector.isJavaAgentJar(url)) {
|
||||
Archive archive = createArchiveIfPossible(url);
|
||||
if (archive != null) {
|
||||
urls.add(archive);
|
||||
private void addNestedEntries(List<Archive> lib) {
|
||||
// The parent archive might have "BOOT-INF/lib/" and "BOOT-INF/classes/"
|
||||
// directories, meaning we are running from an executable JAR. We add nested
|
||||
// entries from there with low priority (i.e. at end).
|
||||
try {
|
||||
lib.addAll(this.parent.getNestedArchives(new EntryFilter() {
|
||||
|
||||
@Override
|
||||
public boolean matches(Entry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
return entry.getName().startsWith(JarLauncher.BOOT_INF_CLASSES);
|
||||
}
|
||||
return entry.getName().startsWith(JarLauncher.BOOT_INF_LIB);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The parent archive might have a "lib/" directory, meaning we are running from
|
||||
// an executable JAR. We add nested entries from there with low priority (i.e. at
|
||||
// end).
|
||||
addNestedArchivesFromParent(urls);
|
||||
for (Archive archive : urls) {
|
||||
// But only add them if they are not already included
|
||||
if (findArchive(lib, archive) < 0) {
|
||||
lib.add(archive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Archive createArchiveIfPossible(URL url)
|
||||
throws IOException, URISyntaxException {
|
||||
if (url.toString().endsWith(".jar") || url.toString().endsWith(".zip")) {
|
||||
return new JarFileArchive(new File(url.toURI()));
|
||||
}));
|
||||
}
|
||||
if (url.toString().endsWith("/*")) {
|
||||
String name = url.getFile();
|
||||
File dir = new File(name.substring(0, name.length() - 1));
|
||||
return (dir.exists() ? new ExplodedArchive(dir, false) : null);
|
||||
catch (IOException ex) {
|
||||
// Ignore
|
||||
}
|
||||
String filename = URLDecoder.decode(url.getFile(), "UTF-8");
|
||||
return new ExplodedArchive(new File(filename));
|
||||
}
|
||||
|
||||
private void addNestedArchivesFromParent(List<Archive> urls) {
|
||||
int index = findArchive(urls, this.parent);
|
||||
if (index >= 0) {
|
||||
try {
|
||||
Archive nested = getNestedArchive("lib/");
|
||||
if (nested != null) {
|
||||
List<Archive> extra = new ArrayList<Archive>(
|
||||
nested.getNestedArchives(new ArchiveEntryFilter()));
|
||||
urls.addAll(index + 1, extra);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int findArchive(List<Archive> urls, Archive archive) {
|
||||
// Do not rely on Archive to have an equals() method. Look for the archive by
|
||||
// matching strings.
|
||||
if (archive == null) {
|
||||
return -1;
|
||||
}
|
||||
int i = 0;
|
||||
for (Archive url : urls) {
|
||||
if (url.toString().equals(archive.toString())) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private URL[] getURLs(ClassLoader classLoader) {
|
||||
if (classLoader instanceof URLClassLoader) {
|
||||
return ((URLClassLoader) classLoader).getURLs();
|
||||
}
|
||||
return EMPTY_URLS;
|
||||
}
|
||||
|
||||
private String cleanupPath(String path) {
|
||||
|
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive.Entry;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Tests for {@link ExecutableArchiveLauncher}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ExecutableArchiveLauncherTests {
|
||||
|
||||
@Mock
|
||||
private JavaAgentDetector javaAgentDetector;
|
||||
|
||||
private ExecutableArchiveLauncher launcher;
|
||||
|
||||
@Before
|
||||
public void setupMocks() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
this.launcher = new UnitTestExecutableArchiveLauncher(this.javaAgentDetector);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createdClassLoaderContainsUrlsFromThreadContextClassLoader()
|
||||
throws Exception {
|
||||
final URL[] urls = new URL[] { new URL("file:one"), new URL("file:two") };
|
||||
|
||||
doWithTccl(new URLClassLoader(urls), new Callable<Void>() {
|
||||
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
ClassLoader classLoader = ExecutableArchiveLauncherTests.this.launcher
|
||||
.createClassLoader(new URL[0]);
|
||||
assertClassLoaderUrls(classLoader, urls);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void javaAgentJarsAreExcludedFromClasspath() throws Exception {
|
||||
URL javaAgent = new File("my-agent.jar").getCanonicalFile().toURI().toURL();
|
||||
final URL one = new URL("file:one");
|
||||
given(this.javaAgentDetector.isJavaAgentJar(javaAgent)).willReturn(true);
|
||||
doWithTccl(new URLClassLoader(new URL[] { javaAgent, one }),
|
||||
new Callable<Void>() {
|
||||
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
ClassLoader classLoader = ExecutableArchiveLauncherTests.this.launcher
|
||||
.createClassLoader(new URL[0]);
|
||||
assertClassLoaderUrls(classLoader, new URL[] { one });
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void assertClassLoaderUrls(ClassLoader classLoader, URL[] urls) {
|
||||
assertThat(classLoader).isInstanceOf(URLClassLoader.class);
|
||||
assertThat(((URLClassLoader) classLoader).getURLs()).isEqualTo(urls);
|
||||
}
|
||||
|
||||
private void doWithTccl(ClassLoader classLoader, Callable<?> action)
|
||||
throws Exception {
|
||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Thread.currentThread().setContextClassLoader(classLoader);
|
||||
action.call();
|
||||
}
|
||||
finally {
|
||||
Thread.currentThread().setContextClassLoader(old);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UnitTestExecutableArchiveLauncher
|
||||
extends ExecutableArchiveLauncher {
|
||||
|
||||
UnitTestExecutableArchiveLauncher(JavaAgentDetector javaAgentDetector) {
|
||||
super(javaAgentDetector);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNestedArchive(Entry entry) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link InputArgumentsJavaAgentDetector}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class InputArgumentsJavaAgentDetectorTests {
|
||||
|
||||
@Test
|
||||
public void nonAgentJarsDoNotProduceFalsePositives()
|
||||
throws MalformedURLException, IOException {
|
||||
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
|
||||
Arrays.asList("-javaagent:my-agent.jar"));
|
||||
assertThat(detector.isJavaAgentJar(
|
||||
new File("something-else.jar").getCanonicalFile().toURI().toURL()))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleJavaAgent() throws MalformedURLException, IOException {
|
||||
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
|
||||
Arrays.asList("-javaagent:my-agent.jar"));
|
||||
assertThat(detector.isJavaAgentJar(
|
||||
new File("my-agent.jar").getCanonicalFile().toURI().toURL())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleJavaAgentWithOptions() throws MalformedURLException, IOException {
|
||||
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
|
||||
Arrays.asList("-javaagent:my-agent.jar=a=alpha,b=bravo"));
|
||||
assertThat(detector.isJavaAgentJar(
|
||||
new File("my-agent.jar").getCanonicalFile().toURI().toURL())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleJavaAgents() throws MalformedURLException, IOException {
|
||||
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
|
||||
Arrays.asList("-javaagent:my-agent.jar",
|
||||
"-javaagent:my-other-agent.jar"));
|
||||
assertThat(detector.isJavaAgentJar(
|
||||
new File("my-agent.jar").getCanonicalFile().toURI().toURL())).isTrue();
|
||||
assertThat(detector.isJavaAgentJar(
|
||||
new File("my-other-agent.jar").getCanonicalFile().toURI().toURL()))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -39,19 +39,6 @@ public class LaunchedURLClassLoaderTests {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void resolveResourceFromWindowsFilesystem() throws Exception {
|
||||
// This path is invalid - it should return null even on Windows.
|
||||
// A regular URLClassLoader will deal with it gracefully.
|
||||
assertThat(getClass().getClassLoader()
|
||||
.getResource("c:\\Users\\user\\bar.properties")).isNull();
|
||||
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(
|
||||
new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") },
|
||||
getClass().getClassLoader());
|
||||
// So we should too...
|
||||
assertThat(loader.getResource("c:\\Users\\user\\bar.properties")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceFromArchive() throws Exception {
|
||||
LaunchedURLClassLoader loader = new LaunchedURLClassLoader(
|
||||
|
@ -22,21 +22,17 @@ import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertiesLauncher}.
|
||||
@ -46,9 +42,6 @@ import static org.mockito.Mockito.verify;
|
||||
*/
|
||||
public class PropertiesLauncherTests {
|
||||
|
||||
@Mock
|
||||
private JavaAgentDetector javaAgentDetector;
|
||||
|
||||
@Rule
|
||||
public OutputCapture output = new OutputCapture();
|
||||
|
||||
@ -195,21 +188,6 @@ public class PropertiesLauncherTests {
|
||||
.isEqualTo("[foo, bar]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavaAgentJarsAreExcludedFromClasspath() throws Exception {
|
||||
List<Archive> allArchives = new PropertiesLauncher().getClassPathArchives();
|
||||
URL[] parentUrls = ((URLClassLoader) getClass().getClassLoader()).getURLs();
|
||||
for (URL url : parentUrls) {
|
||||
given(this.javaAgentDetector.isJavaAgentJar(url)).willReturn(true);
|
||||
}
|
||||
List<Archive> nonAgentArchives = new PropertiesLauncher(this.javaAgentDetector)
|
||||
.getClassPathArchives();
|
||||
assertThat(nonAgentArchives).hasSize(allArchives.size() - parentUrls.length);
|
||||
for (URL url : parentUrls) {
|
||||
verify(this.javaAgentDetector).isJavaAgentJar(url);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitFor(String value) throws Exception {
|
||||
int count = 0;
|
||||
boolean timeout = false;
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import org.springframework.boot.maven.*;
|
||||
|
||||
@ -6,8 +22,8 @@ new Verify.JarArchiveVerification(f, Verify.SAMPLE_APP) {
|
||||
@Override
|
||||
protected void verifyZipEntries(Verify.ArchiveVerifier verifier) throws Exception {
|
||||
super.verifyZipEntries(verifier)
|
||||
verifier.assertHasEntryNameStartingWith("lib/org.springframework.boot.maven.it-acme-lib-0.0.1.BUILD-SNAPSHOT.jar")
|
||||
verifier.assertHasEntryNameStartingWith("lib/org.springframework.boot.maven.it.another-acme-lib-0.0.1.BUILD-SNAPSHOT.jar")
|
||||
verifier.assertHasEntryNameStartingWith("BOOT-INF/lib/org.springframework.boot.maven.it-acme-lib-0.0.1.BUILD-SNAPSHOT.jar")
|
||||
verifier.assertHasEntryNameStartingWith("BOOT-INF/lib/org.springframework.boot.maven.it.another-acme-lib-0.0.1.BUILD-SNAPSHOT.jar")
|
||||
}
|
||||
}.verify();
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -22,7 +22,7 @@ new Verify.JarArchiveVerification(f, Verify.SAMPLE_APP) {
|
||||
@Override
|
||||
protected void verifyZipEntries(Verify.ArchiveVerifier verifier) throws Exception {
|
||||
super.verifyZipEntries(verifier)
|
||||
verifier.assertHasUnpackEntry("lib/spring-core-")
|
||||
verifier.assertHasNonUnpackEntry("lib/spring-context-")
|
||||
verifier.assertHasUnpackEntry("BOOT-INF/lib/spring-core-")
|
||||
verifier.assertHasNonUnpackEntry("BOOT-INF/lib/spring-context-")
|
||||
}
|
||||
}.verify();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -218,14 +218,15 @@ public final class Verify {
|
||||
@Override
|
||||
protected void verifyZipEntries(ArchiveVerifier verifier) throws Exception {
|
||||
super.verifyZipEntries(verifier);
|
||||
verifier.assertHasEntryNameStartingWith("lib/spring-context");
|
||||
verifier.assertHasEntryNameStartingWith("lib/spring-core");
|
||||
verifier.assertHasEntryNameStartingWith("lib/javax.servlet-api-3");
|
||||
verifier.assertHasEntryNameStartingWith("BOOT-INF/lib/spring-context");
|
||||
verifier.assertHasEntryNameStartingWith("BOOT-INF/lib/spring-core");
|
||||
verifier.assertHasEntryNameStartingWith("BOOT-INF/lib/javax.servlet-api-3");
|
||||
assertThat(verifier
|
||||
.hasEntry("org/" + "springframework/boot/loader/JarLauncher.class"))
|
||||
.hasEntry("org/springframework/boot/loader/JarLauncher.class"))
|
||||
.as("Unpacked launcher classes").isTrue();
|
||||
assertThat(verifier.hasEntry("org/" + "test/SampleApplication.class"))
|
||||
.as("Own classes").isTrue();
|
||||
assertThat(verifier
|
||||
.hasEntry("BOOT-INF/classes/org/test/SampleApplication.class"))
|
||||
.as("Own classes").isTrue();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user