From b8d957fccbbc4748372afa4da0e347020e872e11 Mon Sep 17 00:00:00 2001 From: aoshiguchen <1052045476@qq.com> Date: Wed, 1 Mar 2023 11:52:04 +0800 Subject: [PATCH] =?UTF-8?q?Jar=20in=20Jar=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/aop/compiler/AsgcCompiler.java | 36 +- .../compiler/internal/ClassPathIndexFile.java | 103 ----- .../internal/ExecutableArchiveLauncher.java | 199 ---------- .../aop/compiler/internal/JarLauncher.java | 88 ----- .../internal/LaunchedURLClassLoader.java | 354 ------------------ .../core/aop/compiler/internal/Launcher.java | 109 ------ .../internal/SpringBootJarClassLoader.java | 109 ++++++ .../internal/SystemPropertyUtils.java | 216 ----------- .../compiler/internal/archive/Archive.java | 2 - .../core/aop/compiler/AsgcCompilerTest.java | 5 +- 10 files changed, 133 insertions(+), 1088 deletions(-) delete mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/ClassPathIndexFile.java delete mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/ExecutableArchiveLauncher.java delete mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/JarLauncher.java delete mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/LaunchedURLClassLoader.java delete mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/Launcher.java create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/SpringBootJarClassLoader.java delete mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/SystemPropertyUtils.java diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompiler.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompiler.java index 1ff47f58..bcdc9e89 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompiler.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompiler.java @@ -22,7 +22,7 @@ package fun.asgc.neutrino.core.aop.compiler; import com.google.common.collect.Lists; -import fun.asgc.neutrino.core.aop.compiler.internal.JarLauncher; +import fun.asgc.neutrino.core.aop.compiler.internal.SpringBootJarClassLoader; import fun.asgc.neutrino.core.base.GlobalConfig; import fun.asgc.neutrino.core.util.CollectionUtil; import fun.asgc.neutrino.core.util.FileUtil; @@ -55,6 +55,7 @@ public class AsgcCompiler { private boolean isSaveClassFile; private String generatorCodeSavePath; private DynamicClassLoader dynamicClassLoader; + private SpringBootJarClassLoader springBootJarClassLoader; private final List> errors = new ArrayList>(); private final List> warnings = new ArrayList>(); @@ -74,23 +75,22 @@ public class AsgcCompiler { if (null == javaCompiler) { throw new RuntimeException("Can not load JavaCompiler from javax.tools.ToolProvider#getSystemJavaCompiler(),\n please confirm the application running in JDK not JRE."); } - ClassLoader parent = classLoader; - if (SystemUtil.isStartupFromJar() && GlobalConfig.isIsUseSpringBootJar()) { - try { - log.debug("使用LaunchedURLClassLoader jar路径:{}", SystemUtil.getCurrentJarFilePath()); - JarLauncher jarLauncher = new JarLauncher(parent, SystemUtil.getCurrentJarFilePath()); - parent = jarLauncher.createClassLoader(); - log.debug("使用LaunchedURLClassLoader instance:{}", parent); - } catch (Exception e) { - // ignore - log.error("使用LaunchedURLClassLoader异常", e); - } - } + this.springBootJarClassLoader = new SpringBootJarClassLoader(classLoader); +// if (SystemUtil.isStartupFromJar() && GlobalConfig.isIsUseSpringBootJar()) { +// try { +// log.debug("使用LaunchedURLClassLoader jar路径:{}", SystemUtil.getCurrentJarFilePath()); +// parent = new SpringBootJarClassLoader(classLoader); +// log.debug("使用LaunchedURLClassLoader instance:{}", parent); +// } catch (Exception e) { +// // ignore +// log.error("使用LaunchedURLClassLoader异常", e); +// } +// } this.collector = new DiagnosticCollector<>(); this.standardJavaFileManager = javaCompiler.getStandardFileManager(collector, null, null); this.isSaveClassFile = false; this.generatorCodeSavePath = GlobalConfig.getGeneratorCodeSavePath(); - this.dynamicClassLoader = new DynamicClassLoader(parent, this); + this.dynamicClassLoader = new DynamicClassLoader(springBootJarClassLoader, this); addOption("-Xlint:unchecked"); addOption("-implicit:class"); @@ -182,6 +182,14 @@ public class AsgcCompiler { compilationUnits.add(javaFileObject); } + public void addDependSpringBootJar(String path) { + try { + this.springBootJarClassLoader.addJar(path); + } catch (Exception e) { + e.printStackTrace(); + } + } + /** * 编译代码 * @param pkg 包名 diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/ClassPathIndexFile.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/ClassPathIndexFile.java deleted file mode 100644 index b7db262d..00000000 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/ClassPathIndexFile.java +++ /dev/null @@ -1,103 +0,0 @@ -package fun.asgc.neutrino.core.aop.compiler.internal; - -import java.io.*; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -/** - * A class path index file that provides ordering information for JARs. - * - * @author Madhura Bhave - * @author Phillip Webb - */ -final class ClassPathIndexFile { - - private final File root; - - private final List lines; - - private ClassPathIndexFile(File root, List lines) { - this.root = root; - this.lines = lines.stream().map(this::extractName).collect(Collectors.toList()); - } - - private String extractName(String line) { - if (line.startsWith("- \"") && line.endsWith("\"")) { - return line.substring(3, line.length() - 1); - } - throw new IllegalStateException("Malformed classpath index line [" + line + "]"); - } - - int size() { - return this.lines.size(); - } - - boolean containsEntry(String name) { - if (name == null || name.isEmpty()) { - return false; - } - return this.lines.contains(name); - } - - List getUrls() { - return this.lines.stream().map(this::asUrl).collect(Collectors.toList()); - } - - private URL asUrl(String line) { - try { - return new File(this.root, line).toURI().toURL(); - } - catch (MalformedURLException ex) { - throw new IllegalStateException(ex); - } - } - - static ClassPathIndexFile loadIfPossible(URL root, String location) throws IOException { - return loadIfPossible(asFile(root), location); - } - - private static ClassPathIndexFile loadIfPossible(File root, String location) throws IOException { - return loadIfPossible(root, new File(root, location)); - } - - private static ClassPathIndexFile loadIfPossible(File root, File indexFile) throws IOException { - if (indexFile.exists() && indexFile.isFile()) { - try (InputStream inputStream = new FileInputStream(indexFile)) { - return new ClassPathIndexFile(root, loadLines(inputStream)); - } - } - return null; - } - - private static List loadLines(InputStream inputStream) throws IOException { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); - String line = reader.readLine(); - while (line != null) { - if (!line.trim().isEmpty()) { - lines.add(line); - } - line = reader.readLine(); - } - return Collections.unmodifiableList(lines); - } - - private static File asFile(URL url) { - if (!"file".equals(url.getProtocol())) { - throw new IllegalArgumentException("URL does not reference a file"); - } - try { - return new File(url.toURI()); - } - catch (URISyntaxException ex) { - return new File(url.getPath()); - } - } - -} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/ExecutableArchiveLauncher.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/ExecutableArchiveLauncher.java deleted file mode 100644 index b475769c..00000000 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/ExecutableArchiveLauncher.java +++ /dev/null @@ -1,199 +0,0 @@ -package fun.asgc.neutrino.core.aop.compiler.internal; - -import fun.asgc.neutrino.core.aop.compiler.internal.archive.Archive; -import fun.asgc.neutrino.core.aop.compiler.internal.archive.ExplodedArchive; - -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.jar.Attributes; -import java.util.jar.Manifest; - -/** - * Base class for executable archive {@link Launcher}s. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Madhura Bhave - * @author Scott Frederick - * @since 1.0.0 - */ -public abstract class ExecutableArchiveLauncher extends Launcher { - - private static final String START_CLASS_ATTRIBUTE = "Start-Class"; - - protected static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index"; - - protected static final String DEFAULT_CLASSPATH_INDEX_FILE_NAME = "classpath.idx"; - - private Archive archive; - - private ClassPathIndexFile classPathIndex; - - public ExecutableArchiveLauncher() { -// try { -// this.archive = createArchive(); -// this.classPathIndex = getClassPathIndex(this.archive); -// } -// catch (Exception ex) { -// throw new IllegalStateException(ex); -// } - } - - protected ExecutableArchiveLauncher(Archive archive) { - try { - this.archive = archive; - this.classPathIndex = getClassPathIndex(this.archive); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException { - // Only needed for exploded archives, regular ones already have a defined order - if (archive instanceof ExplodedArchive) { - String location = getClassPathIndexFileLocation(archive); - return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location); - } - return null; - } - - private String getClassPathIndexFileLocation(Archive archive) throws IOException { - Manifest manifest = archive.getManifest(); - Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null; - String location = (attributes != null) ? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) : null; - return (location != null) ? location : getArchiveEntryPathPrefix() + DEFAULT_CLASSPATH_INDEX_FILE_NAME; - } - - @Override - protected String getMainClass() throws Exception { - Manifest manifest = this.archive.getManifest(); - String mainClass = null; - if (manifest != null) { - mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE); - } - if (mainClass == null) { - throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this); - } - return mainClass; - } - - @Override - protected ClassLoader createClassLoader(Iterator archives) throws Exception { - List urls = new ArrayList<>(guessClassPathSize()); - while (archives.hasNext()) { - urls.add(archives.next().getUrl()); - } - if (this.classPathIndex != null) { - urls.addAll(this.classPathIndex.getUrls()); - } - return createClassLoader(urls.toArray(new URL[0])); - } - - private int guessClassPathSize() { - if (this.classPathIndex != null) { - return this.classPathIndex.size() + 10; - } - return 50; - } - - @Override - protected Iterator getClassPathArchivesIterator() throws Exception { - Archive.EntryFilter searchFilter = this::isSearchCandidate; - Iterator archives = this.archive.getNestedArchives(searchFilter, - (entry) -> isNestedArchive(entry) && !isEntryIndexed(entry)); - if (isPostProcessingClassPathArchives()) { - archives = applyClassPathArchivePostProcessing(archives); - } - return archives; - } - - private boolean isEntryIndexed(Archive.Entry entry) { - if (this.classPathIndex != null) { - return this.classPathIndex.containsEntry(entry.getName()); - } - return false; - } - - private Iterator applyClassPathArchivePostProcessing(Iterator archives) throws Exception { - List list = new ArrayList<>(); - while (archives.hasNext()) { - list.add(archives.next()); - } - postProcessClassPathArchives(list); - return list.iterator(); - } - - /** - * Determine if the specified entry is a candidate for further searching. - * @param entry the entry to check - * @return {@code true} if the entry is a candidate for further searching - * @since 2.3.0 - */ - protected boolean isSearchCandidate(Archive.Entry entry) { - if (getArchiveEntryPathPrefix() == null) { - return true; - } - return entry.getName().startsWith(getArchiveEntryPathPrefix()); - } - - /** - * Determine if the specified entry is a nested item that should be added to the - * classpath. - * @param entry the entry to check - * @return {@code true} if the entry is a nested item (jar or directory) - */ - protected abstract boolean isNestedArchive(Archive.Entry entry); - - /** - * Return if post-processing needs to be applied to the archives. For back - * compatibility this method returns {@code true}, but subclasses that don't override - * {@link #postProcessClassPathArchives(List)} should provide an implementation that - * returns {@code false}. - * @return if the {@link #postProcessClassPathArchives(List)} method is implemented - * @since 2.3.0 - */ - protected boolean isPostProcessingClassPathArchives() { - return true; - } - - /** - * Called to post-process archive entries before they are used. Implementations can - * add and remove entries. - * @param archives the archives - * @throws Exception if the post-processing fails - * @see #isPostProcessingClassPathArchives() - */ - protected void postProcessClassPathArchives(List archives) throws Exception { - } - - /** - * Return the path prefix for entries in the archive. - * @return the path prefix - */ - protected String getArchiveEntryPathPrefix() { - return null; - } - - @Override - protected boolean isExploded() { - return this.archive.isExploded(); - } - - @Override - protected final Archive getArchive() { - return this.archive; - } - - - protected void setArchive(Archive archive) { - this.archive = archive; - } - - protected void setClassPathIndex(ClassPathIndexFile classPathIndex) { - this.classPathIndex = classPathIndex; - } -} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/JarLauncher.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/JarLauncher.java deleted file mode 100644 index a7734529..00000000 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/JarLauncher.java +++ /dev/null @@ -1,88 +0,0 @@ -package fun.asgc.neutrino.core.aop.compiler.internal; - -import fun.asgc.neutrino.core.aop.compiler.internal.archive.Archive; -import fun.asgc.neutrino.core.aop.compiler.internal.archive.ExplodedArchive; -import fun.asgc.neutrino.core.aop.compiler.internal.archive.JarFileArchive; - -import java.io.File; - -/** - * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are - * included inside a {@code /BOOT-INF/lib} directory and that application classes are - * included inside a {@code /BOOT-INF/classes} directory. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Madhura Bhave - * @author Scott Frederick - * @since 1.0.0 - */ -public class JarLauncher extends ExecutableArchiveLauncher { - - private String path; - - static final Archive.EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> { - if (entry.isDirectory()) { - return entry.getName().equals("BOOT-INF/classes/"); - } - return entry.getName().startsWith("BOOT-INF/lib/"); - }; - - public JarLauncher(String path) { - this.path = path; - try { - this.setArchive(createArchive(this.path)); - this.setClassPathIndex(getClassPathIndex(this.getArchive())); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - public JarLauncher(ClassLoader classLoader, String path) { - this.path = path; - try { - this.setArchive(createArchive(this.path)); - this.setClassPathIndex(getClassPathIndex(this.getArchive())); - this.setClassLoader(classLoader); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - public ClassLoader createClassLoader() throws Exception { - return createClassLoader(getClassPathArchivesIterator()); - } - - protected final Archive createArchive(String path) throws Exception { - 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)); - } - - protected JarLauncher(Archive archive) { - super(archive); - } - - @Override - protected boolean isPostProcessingClassPathArchives() { - return false; - } - - @Override - protected boolean isNestedArchive(Archive.Entry entry) { - return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry); - } - - @Override - protected String getArchiveEntryPathPrefix() { - return "BOOT-INF/"; - } - -} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/LaunchedURLClassLoader.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/LaunchedURLClassLoader.java deleted file mode 100644 index fd51f108..00000000 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/LaunchedURLClassLoader.java +++ /dev/null @@ -1,354 +0,0 @@ -package fun.asgc.neutrino.core.aop.compiler.internal; - -import fun.asgc.neutrino.core.aop.compiler.internal.archive.Archive; -import fun.asgc.neutrino.core.aop.compiler.internal.jar.Handler; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLConnection; -import java.util.Enumeration; -import java.util.function.Supplier; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -/** - * {@link ClassLoader} used by the {@link Launcher}. - * - * @author Phillip Webb - * @author Dave Syer - * @author Andy Wilkinson - * @since 1.0.0 - */ -public class LaunchedURLClassLoader extends URLClassLoader { - - private static final int BUFFER_SIZE = 4096; - - static { - ClassLoader.registerAsParallelCapable(); - } - - private final boolean exploded; - - private final Archive rootArchive; - - private final Object packageLock = new Object(); - - private volatile DefinePackageCallType definePackageCallType; - - /** - * Create a new {@link LaunchedURLClassLoader} instance. - * @param urls the URLs from which to load classes and resources - * @param parent the parent class loader for delegation - */ - public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) { - this(false, urls, parent); - } - - /** - * Create a new {@link LaunchedURLClassLoader} instance. - * @param exploded if the underlying archive is exploded - * @param urls the URLs from which to load classes and resources - * @param parent the parent class loader for delegation - */ - public LaunchedURLClassLoader(boolean exploded, URL[] urls, ClassLoader parent) { - this(exploded, null, urls, parent); - } - - /** - * Create a new {@link LaunchedURLClassLoader} instance. - * @param exploded if the underlying archive is exploded - * @param rootArchive the root archive or {@code null} - * @param urls the URLs from which to load classes and resources - * @param parent the parent class loader for delegation - * @since 2.3.1 - */ - public LaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) { - super(urls, parent); - this.exploded = exploded; - this.rootArchive = rootArchive; - } - - @Override - public URL findResource(String name) { - if (this.exploded) { - return super.findResource(name); - } - Handler.setUseFastConnectionExceptions(true); - try { - return super.findResource(name); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - } - - @Override - public Enumeration findResources(String name) throws IOException { - if (this.exploded) { - return super.findResources(name); - } - Handler.setUseFastConnectionExceptions(true); - try { - return new UseFastConnectionExceptionsEnumeration(super.findResources(name)); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - } - - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (name.startsWith("org.springframework.boot.loader.jarmode.")) { - try { - Class result = loadClassInLaunchedClassLoader(name); - if (resolve) { - resolveClass(result); - } - return result; - } - catch (ClassNotFoundException ex) { - } - } - if (this.exploded) { - return super.loadClass(name, resolve); - } - Handler.setUseFastConnectionExceptions(true); - try { - try { - definePackageIfNecessary(name); - } - catch (IllegalArgumentException ex) { - // Tolerate race condition due to being parallel capable -// if (getDefinedPackage(name) == null) { -// // This should never happen as the IllegalArgumentException indicates -// // that the package has already been defined and, therefore, -// // getDefinedPackage(name) should not return null. -// throw new AssertionError("Package " + name + " has already been defined but it could not be found"); -// } - } - return super.loadClass(name, resolve); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - } - - public void addURL(URL url) { - addURL(url); - } - - private Class loadClassInLaunchedClassLoader(String name) throws ClassNotFoundException { - String internalName = name.replace('.', '/') + ".class"; - InputStream inputStream = getParent().getResourceAsStream(internalName); - if (inputStream == null) { - throw new ClassNotFoundException(name); - } - try { - try { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead = -1; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - inputStream.close(); - byte[] bytes = outputStream.toByteArray(); - Class definedClass = defineClass(name, bytes, 0, bytes.length); - definePackageIfNecessary(name); - return definedClass; - } - finally { - inputStream.close(); - } - } - catch (IOException ex) { - throw new ClassNotFoundException("Cannot load resource for class [" + name + "]", ex); - } - } - - /** - * 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 (getDefinedPackage(packageName) == null) { - try { - definePackage(className, packageName); - } - catch (IllegalArgumentException ex) { - // Tolerate race condition due to being parallel capable -// if (getDefinedPackage(packageName) == null) { -// // This should never happen as the IllegalArgumentException -// // indicates that the package has already been defined and, -// // therefore, getDefinedPackage(name) should not have returned -// // null. -// throw new AssertionError( -// "Package " + packageName + " has already been defined but it could not be found"); -// } - } -// } - } - } - - private void definePackage(String className, String packageName) { - String packageEntryName = packageName.replace('.', '/') + "/"; - String classEntryName = className.replace('.', '/') + ".class"; - for (URL url : getURLs()) { - try { - URLConnection connection = url.openConnection(); - if (connection instanceof JarURLConnection) { - JarFile jarFile = ((JarURLConnection)connection).getJarFile(); - if (jarFile.getEntry(classEntryName) != null && jarFile.getEntry(packageEntryName) != null - && jarFile.getManifest() != null) { - definePackage(packageName, jarFile.getManifest(), url); - return; - } - } - } - catch (IOException ex) { - // Ignore - } - } - } - - @Override - protected Package definePackage(String name, Manifest man, URL url) throws IllegalArgumentException { - if (!this.exploded) { - return super.definePackage(name, man, url); - } - synchronized (this.packageLock) { - return doDefinePackage(DefinePackageCallType.MANIFEST, () -> super.definePackage(name, man, url)); - } - } - - @Override - protected Package definePackage(String name, String specTitle, String specVersion, String specVendor, - String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException { - if (!this.exploded) { - return super.definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, - sealBase); - } - synchronized (this.packageLock) { - if (this.definePackageCallType == null) { - // We're not part of a call chain which means that the URLClassLoader - // is trying to define a package for our exploded JAR. We use the - // manifest version to ensure package attributes are set - Manifest manifest = getManifest(this.rootArchive); - if (manifest != null) { - return definePackage(name, manifest, sealBase); - } - } - return doDefinePackage(DefinePackageCallType.ATTRIBUTES, () -> super.definePackage(name, specTitle, - specVersion, specVendor, implTitle, implVersion, implVendor, sealBase)); - } - } - - private Manifest getManifest(Archive archive) { - try { - return (archive != null) ? archive.getManifest() : null; - } - catch (IOException ex) { - return null; - } - } - - private T doDefinePackage(DefinePackageCallType type, Supplier call) { - DefinePackageCallType existingType = this.definePackageCallType; - try { - this.definePackageCallType = type; - return call.get(); - } - finally { - this.definePackageCallType = existingType; - } - } - - /** - * Clear URL caches. - */ - public void clearCache() { - if (this.exploded) { - return; - } - for (URL url : getURLs()) { - try { - URLConnection connection = url.openConnection(); - if (connection instanceof JarURLConnection) { - clearCache(connection); - } - } - catch (IOException ex) { - // Ignore - } - } - - } - - private void clearCache(URLConnection connection) throws IOException { - Object jarFile = ((JarURLConnection) connection).getJarFile(); - if (jarFile instanceof fun.asgc.neutrino.core.aop.compiler.internal.jar.JarFile) { - ((fun.asgc.neutrino.core.aop.compiler.internal.jar.JarFile) jarFile).clearCache(); - } - } - - private static class UseFastConnectionExceptionsEnumeration implements Enumeration { - - private final Enumeration delegate; - - UseFastConnectionExceptionsEnumeration(Enumeration delegate) { - this.delegate = delegate; - } - - @Override - public boolean hasMoreElements() { - Handler.setUseFastConnectionExceptions(true); - try { - return this.delegate.hasMoreElements(); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - - } - - @Override - public URL nextElement() { - Handler.setUseFastConnectionExceptions(true); - try { - return this.delegate.nextElement(); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - } - - } - - /** - * The different types of call made to define a package. We track these for exploded - * jars so that we can detect packages that should have manifest attributes applied. - */ - private enum DefinePackageCallType { - - /** - * A define package call from a resource that has a manifest. - */ - MANIFEST, - - /** - * A define package call with a direct set of attributes. - */ - ATTRIBUTES - - } - -} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/Launcher.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/Launcher.java deleted file mode 100644 index 305aa518..00000000 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/Launcher.java +++ /dev/null @@ -1,109 +0,0 @@ -package fun.asgc.neutrino.core.aop.compiler.internal; - -import fun.asgc.neutrino.core.aop.compiler.internal.archive.Archive; -import fun.asgc.neutrino.core.aop.compiler.internal.archive.ExplodedArchive; -import fun.asgc.neutrino.core.aop.compiler.internal.archive.JarFileArchive; - -import java.io.File; -import java.net.URI; -import java.net.URL; -import java.security.CodeSource; -import java.security.ProtectionDomain; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -/** - * Base class for launchers that can start an application with a fully configured - * classpath backed by one or more {@link Archive}s. - * - * @author Phillip Webb - * @author Dave Syer - * @since 1.0.0 - */ -public abstract class Launcher { - - private ClassLoader classLoader; - - /** - * Create a classloader for the specified archives. - * @param archives the archives - * @return the classloader - * @throws Exception if the classloader cannot be created - * @since 2.3.0 - */ - protected ClassLoader createClassLoader(Iterator archives) throws Exception { - List urls = new ArrayList<>(50); - while (archives.hasNext()) { - urls.add(archives.next().getUrl()); - } - return createClassLoader(urls.toArray(new URL[0])); - } - - /** - * Create a classloader for the specified URLs. - * @param urls the URLs - * @return the classloader - * @throws Exception if the classloader cannot be created - */ - protected ClassLoader createClassLoader(URL[] urls) throws Exception { - return new LaunchedURLClassLoader(isExploded(), - getArchive(), - urls, - (null == classLoader) ? getClass().getClassLoader() : classLoader); - } - - /** - * Returns the main class that should be launched. - * @return the name of the main class - * @throws Exception if the main class cannot be obtained - */ - protected abstract String getMainClass() throws Exception; - - /** - * Returns the archives that will be used to construct the class path. - * @return the class path archives - * @throws Exception if the class path archives cannot be obtained - * @since 2.3.0 - */ - protected abstract Iterator getClassPathArchivesIterator() throws Exception; - - protected final Archive createArchive() throws Exception { - ProtectionDomain protectionDomain = getClass().getProtectionDomain(); - CodeSource codeSource = protectionDomain.getCodeSource(); - URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null; - String path = (location != null) ? location.getSchemeSpecificPart() : null; - 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)); - } - - /** - * Returns if the launcher is running in an exploded mode. If this method returns - * {@code true} then only regular JARs are supported and the additional URL and - * ClassLoader support infrastructure can be optimized. - * @return if the jar is exploded. - * @since 2.3.0 - */ - protected boolean isExploded() { - return false; - } - - /** - * Return the root archive. - * @return the root archive - * @since 2.3.1 - */ - protected Archive getArchive() { - return null; - } - - public void setClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } -} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/SpringBootJarClassLoader.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/SpringBootJarClassLoader.java new file mode 100644 index 00000000..b4ab6421 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/SpringBootJarClassLoader.java @@ -0,0 +1,109 @@ +package fun.asgc.neutrino.core.aop.compiler.internal; + + +import fun.asgc.neutrino.core.aop.compiler.internal.archive.Archive; +import fun.asgc.neutrino.core.aop.compiler.internal.archive.JarFileArchive; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * {@link ClassLoader} used by the {@link Launcher}. + * + * @author Phillip Webb + * @author Dave Syer + * @author Andy Wilkinson + * @since 1.0.0 + */ +public class SpringBootJarClassLoader extends URLClassLoader { + protected static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index"; + protected static final String DEFAULT_CLASSPATH_INDEX_FILE_NAME = "classpath.idx"; + + static { + ClassLoader.registerAsParallelCapable(); + } + + /** + * Create a new {@link SpringBootJarClassLoader} instance. + * @param parent the parent class loader for delegation + */ + public SpringBootJarClassLoader(ClassLoader parent) { + super(new URL[0], parent); + } + + /** + * Create a new {@link SpringBootJarClassLoader} instance. + */ + public SpringBootJarClassLoader() { + super(new URL[0], ClassLoader.getSystemClassLoader()); + } + + public void addJar(String path) throws Exception { + Archive archive = createArchive(path); + Iterator archives = getClassPathArchivesIterator(archive); + List urls = new ArrayList<>(50); + while (archives.hasNext()) { + urls.add(archives.next().getUrl()); + } + urls.forEach(url -> addURL(url)); + } + + private Archive createArchive(String path) throws Exception { + 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 new JarFileArchive(root); + } + + protected Iterator getClassPathArchivesIterator(Archive archive) throws Exception { + Archive.EntryFilter searchFilter = this::isSearchCandidate; + Iterator archives = archive.getNestedArchives(searchFilter, + (entry) -> isNestedArchive(entry)); + archives = applyClassPathArchivePostProcessing(archives); + return archives; + } + + private Iterator applyClassPathArchivePostProcessing(Iterator archives) throws Exception { + List list = new ArrayList<>(); + while (archives.hasNext()) { + list.add(archives.next()); + } + return list.iterator(); + } + + static final Archive.EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> { + if (entry.isDirectory()) { + return entry.getName().equals("BOOT-INF/classes/"); + } + return entry.getName().startsWith("BOOT-INF/lib/"); + }; + + protected boolean isNestedArchive(Archive.Entry entry) { + return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry); + } + + /** + * Determine if the specified entry is a candidate for further searching. + * @param entry the entry to check + * @return {@code true} if the entry is a candidate for further searching + * @since 2.3.0 + */ + protected boolean isSearchCandidate(Archive.Entry entry) { + if (getArchiveEntryPathPrefix() == null) { + return true; + } + return entry.getName().startsWith(getArchiveEntryPathPrefix()); + } + + protected String getArchiveEntryPathPrefix() { + return "BOOT-INF/"; + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/SystemPropertyUtils.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/SystemPropertyUtils.java deleted file mode 100644 index 4f698fc2..00000000 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/SystemPropertyUtils.java +++ /dev/null @@ -1,216 +0,0 @@ -package fun.asgc.neutrino.core.aop.compiler.internal; - -import java.util.HashSet; -import java.util.Locale; -import java.util.Properties; -import java.util.Set; - -/** - * Helper class for resolving placeholders in texts. Usually applied to file paths. - *

- * A text may contain {@code $ ...} placeholders, to be resolved as system properties: - * e.g. {@code $ user.dir}. Default values can be supplied using the ":" separator between - * key and value. - *

- * Adapted from Spring. - * - * @author Juergen Hoeller - * @author Rob Harrop - * @author Dave Syer - * @since 1.0.0 - * @see System#getProperty(String) - */ -public abstract class SystemPropertyUtils { - - /** - * Prefix for system property placeholders: "${". - */ - public static final String PLACEHOLDER_PREFIX = "${"; - - /** - * Suffix for system property placeholders: "}". - */ - public static final String PLACEHOLDER_SUFFIX = "}"; - - /** - * Value separator for system property placeholders: ":". - */ - public static final String VALUE_SEPARATOR = ":"; - - private static final String SIMPLE_PREFIX = PLACEHOLDER_PREFIX.substring(1); - - /** - * Resolve ${...} placeholders in the given text, replacing them with corresponding - * system property values. - * @param text the String to resolve - * @return the resolved String - * @throws IllegalArgumentException if there is an unresolvable placeholder - * @see #PLACEHOLDER_PREFIX - * @see #PLACEHOLDER_SUFFIX - */ - public static String resolvePlaceholders(String text) { - if (text == null) { - return text; - } - return parseStringValue(null, text, text, new HashSet<>()); - } - - /** - * Resolve ${...} placeholders in the given text, replacing them with corresponding - * system property values. - * @param properties a properties instance to use in addition to System - * @param text the String to resolve - * @return the resolved String - * @throws IllegalArgumentException if there is an unresolvable placeholder - * @see #PLACEHOLDER_PREFIX - * @see #PLACEHOLDER_SUFFIX - */ - public static String resolvePlaceholders(Properties properties, String text) { - if (text == null) { - return text; - } - return parseStringValue(properties, text, text, new HashSet<>()); - } - - private static String parseStringValue(Properties properties, String value, String current, - Set visitedPlaceholders) { - - StringBuilder buf = new StringBuilder(current); - - int startIndex = current.indexOf(PLACEHOLDER_PREFIX); - while (startIndex != -1) { - int endIndex = findPlaceholderEndIndex(buf, startIndex); - if (endIndex != -1) { - String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); - String originalPlaceholder = placeholder; - if (!visitedPlaceholders.add(originalPlaceholder)) { - throw new IllegalArgumentException( - "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); - } - // Recursive invocation, parsing placeholders contained in the - // placeholder - // key. - placeholder = parseStringValue(properties, value, placeholder, visitedPlaceholders); - // Now obtain the value for the fully resolved key... - String propVal = resolvePlaceholder(properties, value, placeholder); - if (propVal == null) { - int separatorIndex = placeholder.indexOf(VALUE_SEPARATOR); - if (separatorIndex != -1) { - String actualPlaceholder = placeholder.substring(0, separatorIndex); - String defaultValue = placeholder.substring(separatorIndex + VALUE_SEPARATOR.length()); - propVal = resolvePlaceholder(properties, value, actualPlaceholder); - if (propVal == null) { - propVal = defaultValue; - } - } - } - if (propVal != null) { - // Recursive invocation, parsing placeholders contained in the - // previously resolved placeholder value. - propVal = parseStringValue(properties, value, propVal, visitedPlaceholders); - buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal); - startIndex = buf.indexOf(PLACEHOLDER_PREFIX, startIndex + propVal.length()); - } - else { - // Proceed with unprocessed value. - startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex + PLACEHOLDER_SUFFIX.length()); - } - visitedPlaceholders.remove(originalPlaceholder); - } - else { - startIndex = -1; - } - } - - return buf.toString(); - } - - private static String resolvePlaceholder(Properties properties, String text, String placeholderName) { - String propVal = getProperty(placeholderName, null, text); - if (propVal != null) { - return propVal; - } - return (properties != null) ? properties.getProperty(placeholderName) : null; - } - - public static String getProperty(String key) { - return getProperty(key, null, ""); - } - - public static String getProperty(String key, String defaultValue) { - return getProperty(key, defaultValue, ""); - } - - /** - * Search the System properties and environment variables for a value with the - * provided key. Environment variables in {@code UPPER_CASE} style are allowed where - * System properties would normally be {@code lower.case}. - * @param key the key to resolve - * @param defaultValue the default value - * @param text optional extra context for an error message if the key resolution fails - * (e.g. if System properties are not accessible) - * @return a static property value or null of not found - */ - public static String getProperty(String key, String defaultValue, String text) { - try { - String propVal = System.getProperty(key); - if (propVal == null) { - // Fall back to searching the system environment. - propVal = System.getenv(key); - } - if (propVal == null) { - // Try with underscores. - String name = key.replace('.', '_'); - propVal = System.getenv(name); - } - if (propVal == null) { - // Try uppercase with underscores as well. - String name = key.toUpperCase(Locale.ENGLISH).replace('.', '_'); - propVal = System.getenv(name); - } - if (propVal != null) { - return propVal; - } - } - catch (Throwable ex) { - System.err.println("Could not resolve key '" + key + "' in '" + text - + "' as system property or in environment: " + ex); - } - return defaultValue; - } - - private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) { - int index = startIndex + PLACEHOLDER_PREFIX.length(); - int withinNestedPlaceholder = 0; - while (index < buf.length()) { - if (substringMatch(buf, index, PLACEHOLDER_SUFFIX)) { - if (withinNestedPlaceholder > 0) { - withinNestedPlaceholder--; - index = index + PLACEHOLDER_SUFFIX.length(); - } - else { - return index; - } - } - else if (substringMatch(buf, index, SIMPLE_PREFIX)) { - withinNestedPlaceholder++; - index = index + SIMPLE_PREFIX.length(); - } - else { - index++; - } - } - return -1; - } - - private static boolean substringMatch(CharSequence str, int index, CharSequence substring) { - for (int j = 0; j < substring.length(); j++) { - int i = index + j; - if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { - return false; - } - } - return true; - } - -} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/archive/Archive.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/archive/Archive.java index 3cecdad2..9be22c70 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/archive/Archive.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/compiler/internal/archive/Archive.java @@ -1,7 +1,5 @@ package fun.asgc.neutrino.core.aop.compiler.internal.archive; -import fun.asgc.neutrino.core.aop.compiler.internal.Launcher; - import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompilerTest.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompilerTest.java index 83ae61df..a88e5a93 100644 --- a/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompilerTest.java +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/compiler/AsgcCompilerTest.java @@ -21,7 +21,6 @@ */ package fun.asgc.neutrino.core.aop.compiler; -import fun.asgc.neutrino.core.aop.compiler.internal.JarLauncher; import fun.asgc.neutrino.core.util.ReflectUtil; import org.junit.Test; @@ -122,8 +121,8 @@ public class AsgcCompilerTest { */ @Test public void compileAndLoadClass5() throws Exception { - JarLauncher jarLauncher = new JarLauncher(AsgcCompilerTest.class.getResource("/asgc-package-lab2.jar").getPath()); - AsgcCompiler compiler = new AsgcCompiler(jarLauncher.createClassLoader()); + AsgcCompiler compiler = new AsgcCompiler(); + compiler.addDependSpringBootJar(AsgcCompilerTest.class.getResource("/asgc-package-lab2.jar").getPath()); String code = "package a.b;\n" + "import fun.asgc.lab.pkg.lab2.Console;\n" + "import fun.asgc.lab.pkg.lab1.Dog;\n" +