public SpringBootPluginsClassLoader(final ClassLoader parent) throws IOException { super(new URL[]{}, parent); // find all jar plugins final Collection<File> jarFiles = FileUtils.listFiles(new File("plugins/"), new String[]{"jar"}, false); for(final File jarFile : jarFiles) { // add the jar's own classes to classpath final URL jarURL = new URL("jar:file:" + jarFile.getPath() + "!/BOOT-INF/classes/"); addURL(jarURL); final JarFileArchive jarFileArchive = new JarFileArchive(jarFile); final List<Archive> nestedArchives = jarFileArchive .getNestedArchives(entry -> entry.getName().endsWith(".jar")); for(final Archive archive : nestedArchives) { // add all bundled dependencies to classpath addURL(archive.getUrl()); } } }
protected final Archive createArchive() throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); String path = (location == null ? null : location.getSchemeSpecificPart()); if (path == null) { throw new IllegalStateException("Unable to determine code source archive"); } File root = new File(path); if (!root.exists()) { throw new IllegalStateException( "Unable to determine code source archive from " + root); } return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); }
@Override protected List<Archive> getClassPathArchives() throws Exception { List<Archive> lib = new ArrayList<Archive>(); for (String path : this.paths) { for (Archive archive : getClassPathArchives(path)) { if (archive instanceof ExplodedArchive) { List<Archive> nested = new ArrayList<Archive>( archive.getNestedArchives(new ArchiveEntryFilter())); nested.add(0, archive); lib.addAll(nested); } else { lib.add(archive); } } } addNestedEntries(lib); return lib; }
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); } })); } catch (IOException ex) { // Ignore } }
@Test public void explodedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception { File warRoot = new File("target/exploded-war"); FileSystemUtils.deleteRecursively(warRoot); warRoot.mkdirs(); File webInfClasses = new File(warRoot, "WEB-INF/classes"); webInfClasses.mkdirs(); File webInfLib = new File(warRoot, "WEB-INF/lib"); webInfLib.mkdirs(); File webInfLibFoo = new File(webInfLib, "foo.jar"); new JarOutputStream(new FileOutputStream(webInfLibFoo)).close(); WarLauncher launcher = new WarLauncher(new ExplodedArchive(warRoot, true)); List<Archive> archives = launcher.getClassPathArchives(); assertThat(archives).hasSize(2); assertThat(getUrls(archives)).containsOnly(webInfClasses.toURI().toURL(), new URL("jar:" + webInfLibFoo.toURI().toURL() + "!/")); }
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 } } }
@Test public void explodedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception { File warRoot = new File("target/exploded-war"); FileSystemUtils.deleteRecursively(warRoot); warRoot.mkdirs(); File webInfClasses = new File(warRoot, "WEB-INF/classes"); webInfClasses.mkdirs(); File webInfLib = new File(warRoot, "WEB-INF/lib"); webInfLib.mkdirs(); File webInfLibFoo = new File(webInfLib, "foo.jar"); new JarOutputStream(new FileOutputStream(webInfLibFoo)).close(); WarLauncher launcher = new WarLauncher(new ExplodedArchive(warRoot, true)); List<Archive> archives = launcher.getClassPathArchives(); assertEquals(2, archives.size()); assertThat(getUrls(archives), hasItems(webInfClasses.toURI().toURL(), new URL("jar:" + webInfLibFoo.toURI().toURL() + "!/"))); }
/** * Return metadata about configuration properties that are documented via * <a href="http://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html"> * Spring Boot configuration metadata</a> and visible in an app. * @param app a Spring Cloud Stream app; typically a Boot uberjar, * but directories are supported as well */ public List<ConfigurationMetadataProperty> listProperties(Resource app, boolean exhaustive) { try { Archive archive = resolveAsArchive(app); return listProperties(archive, exhaustive); } catch (IOException e) { throw new RuntimeException("Failed to list properties for " + app, e); } }
public URLClassLoader createClassLoader() { boolean useBoot14Layout = false; for (Archive.Entry entry : archive) { if (entry.getName().startsWith(BOOT_14_LIBS_LOCATION)) { useBoot14Layout = true; break; } } ClassLoaderExposingLauncher launcher = useBoot14Layout ? new Boot14ClassLoaderExposingLauncher() : new Boot13ClassLoaderExposingLauncher(); return launcher.createClassLoader(); }
@Override protected boolean isNestedArchive(Archive.Entry entry) { if (entry.isDirectory()) { return entry.getName().equals(BOOT_INF_CLASSES); } return entry.getName().startsWith(BOOT_INF_LIB); }
/** * Create a classloader for the specified archives. * @param archives the archives * @return the classloader * @throws Exception if the classloader cannot be created */ protected ClassLoader createClassLoader(List<Archive> archives) throws Exception { List<URL> urls = new ArrayList<URL>(archives.size()); for (Archive archive : archives) { urls.add(archive.getUrl()); } return createClassLoader(urls.toArray(new URL[urls.size()])); }
@Override public boolean isNestedArchive(Archive.Entry entry) { if (entry.isDirectory()) { return entry.getName().equals(WEB_INF_CLASSES); } else { return entry.getName().startsWith(WEB_INF_LIB) || entry.getName().startsWith(WEB_INF_LIB_PROVIDED); } }
@Override protected ClassLoader createClassLoader(List<Archive> archives) throws Exception { ClassLoader loader = super.createClassLoader(archives); String customLoaderClassName = getProperty("loader.classLoader"); if (customLoaderClassName != null) { loader = wrapWithCustomClassLoader(loader, customLoaderClassName); log("Using custom class loader: " + customLoaderClassName); } return loader; }
private Archive getArchive(File file) throws IOException { String name = file.getName().toLowerCase(); if (name.endsWith(".jar") || name.endsWith(".zip")) { return new JarFileArchive(file); } return null; }
private Archive getNestedArchive(String root) throws Exception { if (root.startsWith("/") || this.parent.getUrl().equals(this.home.toURI().toURL())) { // If home dir is same as parent archive, no need to add it twice. return null; } EntryFilter filter = new PrefixMatchingArchiveFilter(root); if (this.parent.getNestedArchives(filter).isEmpty()) { return null; } // If there are more archives nested in this subdirectory (root) then create a new // virtual archive for them, and have it added to the classpath return new FilteredArchive(this.parent, filter); }
@Override public List<Archive> getNestedArchives(final EntryFilter filter) throws IOException { return this.parent.getNestedArchives(new EntryFilter() { @Override public boolean matches(Entry entry) { return FilteredArchive.this.filter.matches(entry) && filter.matches(entry); } }); }
@Override protected List<Archive> getClassPathArchives() throws Exception { List<Archive> archives = new ArrayList<Archive>( this.archive.getNestedArchives(new EntryFilter() { @Override public boolean matches(Entry entry) { return isNestedArchive(entry); } })); postProcessClassPathArchives(archives); return archives; }
@Test public void testCustomClassLoaderCreation() throws Exception { System.setProperty("loader.classLoader", TestLoader.class.getName()); PropertiesLauncher launcher = new PropertiesLauncher(); ClassLoader loader = launcher.createClassLoader(Collections.<Archive>emptyList()); assertThat(loader).isNotNull(); assertThat(loader.getClass().getName()).isEqualTo(TestLoader.class.getName()); }
@Test public void archivedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception { File warRoot = createWarArchive(); WarLauncher launcher = new WarLauncher(new JarFileArchive(warRoot)); List<Archive> archives = launcher.getClassPathArchives(); assertThat(archives).hasSize(2); assertThat(getUrls(archives)).containsOnly( new URL("jar:" + warRoot.toURI().toURL() + "!/WEB-INF/classes!/"), new URL("jar:" + warRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/")); }
private Set<URL> getUrls(List<Archive> archives) throws MalformedURLException { Set<URL> urls = new HashSet<URL>(archives.size()); for (Archive archive : archives) { urls.add(archive.getUrl()); } return urls; }
@Override public List<Archive> getNestedArchives(EntryFilter ignored) throws IOException { try { List<Archive> archives = new ArrayList<>(mavenProject.getRuntimeClasspathElements().size()); for (String dep : mavenProject.getRuntimeClasspathElements()) { File file = new File(dep); archives.add(file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file)); } return archives; } catch (DependencyResolutionRequiredException e) { throw new IOException("Could not create boot archive", e); } }
/** * Return metadata about configuration properties that are documented via <a href= * "http://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html"> * Spring Boot configuration metadata</a> and visible in an app. * * @param app a Spring Cloud Stream app; typically a Boot uberjar, but directories are * supported as well */ public List<ConfigurationMetadataProperty> listProperties(Resource app, boolean exhaustive) { try { if (app != null) { Archive archive = resolveAsArchive(app); return listProperties(archive, exhaustive); } } catch (IOException e) { } return Collections.emptyList(); }