From 87fe0b2adeef85c842c009bfeebac1c84af8a5d7 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 13 Jan 2016 15:52:39 +0000 Subject: [PATCH] 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 --- spring-boot-cli/pom.xml | 2 +- .../main/assembly/jar-with-dependencies.xml | 1 + .../PackagedSpringApplicationLauncher.java | 4 +- .../gradle/MultiProjectRepackagingTests.java | 11 +- .../boot/gradle/RepackagingTests.java | 6 +- .../boot/loader/tools/JarWriter.java | 30 ++- .../boot/loader/tools/Layouts.java | 11 +- .../boot/loader/tools/Repackager.java | 57 ++++- .../boot/loader/tools/RepackagingLayout.java} | 21 +- .../boot/loader/tools/LayoutsTests.java | 10 +- .../boot/loader/tools/RepackagerTests.java | 35 +-- .../src/it/executable-props/pom.xml | 2 +- .../main/assembly/jar-with-dependencies.xml | 1 + .../loader/ExecutableArchiveLauncher.java | 56 +---- .../InputArgumentsJavaAgentDetector.java | 100 -------- .../boot/loader/JarLauncher.java | 15 +- .../boot/loader/LaunchedURLClassLoader.java | 225 ++---------------- .../boot/loader/PropertiesLauncher.java | 106 ++------- .../ExecutableArchiveLauncherTests.java | 118 --------- .../InputArgumentsJavaAgentDetectorTests.java | 73 ------ .../loader/LaunchedURLClassLoaderTests.java | 15 +- .../boot/loader/PropertiesLauncherTests.java | 22 -- .../classes}/application.properties | 0 .../{ => BOOT-INF/classes}/foo.properties | 0 .../it/jar-lib-name-conflict/verify.groovy | 20 +- .../src/it/jar-with-unpack/verify.groovy | 6 +- .../springframework/boot/maven/Verify.java | 15 +- 27 files changed, 227 insertions(+), 735 deletions(-) rename spring-boot-tools/{spring-boot-loader/src/main/java/org/springframework/boot/loader/JavaAgentDetector.java => spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RepackagingLayout.java} (58%) delete mode 100644 spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetector.java delete mode 100644 spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExecutableArchiveLauncherTests.java delete mode 100644 spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetectorTests.java rename spring-boot-tools/spring-boot-loader/src/test/resources/{ => BOOT-INF/classes}/application.properties (100%) rename spring-boot-tools/spring-boot-loader/src/test/resources/{ => BOOT-INF/classes}/foo.properties (100%) diff --git a/spring-boot-cli/pom.xml b/spring-boot-cli/pom.xml index 7716e22a43f..e7ee726736e 100644 --- a/spring-boot-cli/pom.xml +++ b/spring-boot-cli/pom.xml @@ -220,7 +220,7 @@ copy-dependencies - ${project.build.directory}/assembly/lib + ${project.build.directory}/assembly/BOOT-INF/lib runtime diff --git a/spring-boot-cli/src/main/assembly/jar-with-dependencies.xml b/spring-boot-cli/src/main/assembly/jar-with-dependencies.xml index e58cf8cf218..b0687983c09 100644 --- a/spring-boot-cli/src/main/assembly/jar-with-dependencies.xml +++ b/spring-boot-cli/src/main/assembly/jar-with-dependencies.xml @@ -15,6 +15,7 @@ ${project.groupId}:${project.artifactId} true + BOOT-INF/classes/ diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.java index e05c6d12c9a..abc07ef8f22 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.java @@ -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 urls = classLoader.findResources("META-INF/MANIFEST.MF"); + Enumeration urls = classLoader.getResources("META-INF/MANIFEST.MF"); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Manifest manifest = new Manifest(url.openStream()); diff --git a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/MultiProjectRepackagingTests.java b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/MultiProjectRepackagingTests.java index ac83d90e310..b8296184e34 100644 --- a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/MultiProjectRepackagingTests.java +++ b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/MultiProjectRepackagingTests.java @@ -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(); } diff --git a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/RepackagingTests.java b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/RepackagingTests.java index 87b62ccdaf2..d52cf179fcd 100644 --- a/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/RepackagingTests.java +++ b/spring-boot-integration-tests/spring-boot-gradle-tests/src/test/java/org/springframework/boot/gradle/RepackagingTests.java @@ -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 { diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java index 0643a4afc96..81c055a9039 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/JarWriter.java @@ -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 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; + } + + } + } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java index 8eb4bf091ba..3cebdc7a05c 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java @@ -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; diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java index bbe27db7e42..c9c59376ad4 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java @@ -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 seen = new HashSet(); 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; + } + + } + } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JavaAgentDetector.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RepackagingLayout.java similarity index 58% rename from spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JavaAgentDetector.java rename to spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RepackagingLayout.java index 8fa5b9abb74..e14def52b0a 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JavaAgentDetector.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RepackagingLayout.java @@ -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(); } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/LayoutsTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/LayoutsTests.java index 42ee8fad23a..e7a32fed129 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/LayoutsTests.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/LayoutsTests.java @@ -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 diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java index 8d0e2707f4d..83f92b98bc0 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java @@ -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 { diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props/pom.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props/pom.xml index 2bbfe1c7ecb..ac5eaa5184d 100644 --- a/spring-boot-tools/spring-boot-loader/src/it/executable-props/pom.xml +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props/pom.xml @@ -49,7 +49,7 @@ copy-dependencies - ${project.build.directory}/assembly/lib + ${project.build.directory}/assembly/BOOT-INF/lib diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props/src/main/assembly/jar-with-dependencies.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props/src/main/assembly/jar-with-dependencies.xml index 44626f91aa1..1120acb7e2f 100644 --- a/spring-boot-tools/spring-boot-loader/src/it/executable-props/src/main/assembly/jar-with-dependencies.xml +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props/src/main/assembly/jar-with-dependencies.xml @@ -14,6 +14,7 @@ ${project.groupId}:${project.artifactId} + BOOT-INF/classes true diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java index 6ecb93d3604..38edb926a6d 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java @@ -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 copy = new LinkedHashSet(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 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; - } - } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetector.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetector.java deleted file mode 100644 index cc6fd47cf14..00000000000 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetector.java +++ /dev/null @@ -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 javaAgentJars; - - public InputArgumentsJavaAgentDetector() { - this(getInputArguments()); - } - - InputArgumentsJavaAgentDetector(List inputArguments) { - this.javaAgentJars = getJavaAgentJars(inputArguments); - } - - private static List getInputArguments() { - try { - return AccessController.doPrivileged(new PrivilegedAction>() { - @Override - public List run() { - return ManagementFactory.getRuntimeMXBean().getInputArguments(); - } - }); - } - catch (Exception ex) { - return Collections.emptyList(); - } - } - - private Set getJavaAgentJars(List inputArguments) { - Set javaAgentJars = new HashSet(); - 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); - } - -} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java index d004d0175f7..6f19c70c235 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java @@ -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 diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java index f9bfac9b2b2..1608792e7f8 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java @@ -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 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 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 - try { - if (this.rootClassLoader != null) { - return this.rootClassLoader.loadClass(name); - } - } - catch (Exception ex) { - // Ignore and continue - } - - // 2) Try to find locally + Handler.setUseFastConnectionExceptions(true); try { - findPackage(name); - Class cls = findClass(name); - return cls; + definePackageIfNecessary(name); + return super.loadClass(name, resolve); } - catch (Exception ex) { - // Ignore and continue + finally { + Handler.setUseFastConnectionExceptions(false); } - - // 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); + /** + * Define a package before a {@code findClass} call is made. This is necessary to + * ensure that the appropriate manifest for nested JARs is associated with the + * package. + * @param className the class name being found + */ + private void definePackageIfNecessary(String className) { + int lastDot = className.lastIndexOf('.'); + if (lastDot >= 0) { + String packageName = className.substring(0, lastDot); if (getPackage(packageName) == null) { - try { - definePackageForFindClass(name, packageName); - } - catch (Exception ex) { - // Swallow and continue - } + definePackage(packageName); } } } - /** - * 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 - */ - private void definePackageForFindClass(final String name, final String packageName) { + private void definePackage(final String packageName) { try { AccessController.doPrivileged(new PrivilegedExceptionAction() { @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 { - - private final Enumeration rootResources; - - private final Enumeration localResources; - - ResourceEnumeration(Enumeration rootResources, - Enumeration 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(); - } - - } - } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java index 59b810cfbe9..5607a4048d2 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java @@ -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; *
    *
  • {@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
  • + * Defaults to {@code BOOT-INF/classes,BOOT-INF/lib} in your application archive *
  • {@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 ${loader.home}/META-INF.
  • @@ -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 paths = new ArrayList(); 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 lib) - throws IOException, URISyntaxException { - ClassLoader parentClassLoader = getClass().getClassLoader(); - List urls = new ArrayList(); - for (URL url : getURLs(parentClassLoader)) { - if (!this.javaAgentDetector.isJavaAgentJar(url)) { - Archive archive = createArchiveIfPossible(url); - if (archive != null) { - urls.add(archive); - } - } - } - // 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); - } - String filename = URLDecoder.decode(url.getFile(), "UTF-8"); - return new ExplodedArchive(new File(filename)); - } + private void addNestedEntries(List 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() { - private void addNestedArchivesFromParent(List urls) { - int index = findArchive(urls, this.parent); - if (index >= 0) { - try { - Archive nested = getNestedArchive("lib/"); - if (nested != null) { - List extra = new ArrayList( - nested.getNestedArchives(new ArchiveEntryFilter())); - urls.addAll(index + 1, extra); + @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); } - } - catch (Exception ex) { - // ignore - } - } - } - private int findArchive(List 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(); + catch (IOException ex) { + // Ignore } - return EMPTY_URLS; } private String cleanupPath(String path) { diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExecutableArchiveLauncherTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExecutableArchiveLauncherTests.java deleted file mode 100644 index cadfba4fe5d..00000000000 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExecutableArchiveLauncherTests.java +++ /dev/null @@ -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() { - - @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() { - - @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; - } - } - -} diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetectorTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetectorTests.java deleted file mode 100644 index 5ca75e65e0d..00000000000 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/InputArgumentsJavaAgentDetectorTests.java +++ /dev/null @@ -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(); - } - -} diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java index bcb9b997d64..954f75ab0a4 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java @@ -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( diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java index d0fb1c49895..c7da9d08348 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java @@ -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 allArchives = new PropertiesLauncher().getClassPathArchives(); - URL[] parentUrls = ((URLClassLoader) getClass().getClassLoader()).getURLs(); - for (URL url : parentUrls) { - given(this.javaAgentDetector.isJavaAgentJar(url)).willReturn(true); - } - List 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; diff --git a/spring-boot-tools/spring-boot-loader/src/test/resources/application.properties b/spring-boot-tools/spring-boot-loader/src/test/resources/BOOT-INF/classes/application.properties similarity index 100% rename from spring-boot-tools/spring-boot-loader/src/test/resources/application.properties rename to spring-boot-tools/spring-boot-loader/src/test/resources/BOOT-INF/classes/application.properties diff --git a/spring-boot-tools/spring-boot-loader/src/test/resources/foo.properties b/spring-boot-tools/spring-boot-loader/src/test/resources/BOOT-INF/classes/foo.properties similarity index 100% rename from spring-boot-tools/spring-boot-loader/src/test/resources/foo.properties rename to spring-boot-tools/spring-boot-loader/src/test/resources/BOOT-INF/classes/foo.properties diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/it/jar-lib-name-conflict/verify.groovy b/spring-boot-tools/spring-boot-maven-plugin/src/it/jar-lib-name-conflict/verify.groovy index 18782c40193..aa5cb898e9e 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/it/jar-lib-name-conflict/verify.groovy +++ b/spring-boot-tools/spring-boot-maven-plugin/src/it/jar-lib-name-conflict/verify.groovy @@ -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(); diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/it/jar-with-unpack/verify.groovy b/spring-boot-tools/spring-boot-maven-plugin/src/it/jar-with-unpack/verify.groovy index f16e86e7410..ee238895662 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/it/jar-with-unpack/verify.groovy +++ b/spring-boot-tools/spring-boot-maven-plugin/src/it/jar-with-unpack/verify.groovy @@ -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(); diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/Verify.java b/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/Verify.java index 351d20ae4ee..390c1085eb4 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/Verify.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/Verify.java @@ -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