Jar in Jar处理逻辑优化

This commit is contained in:
aoshiguchen
2023-03-01 11:52:04 +08:00
parent 24d20d954c
commit b8d957fccb
10 changed files with 133 additions and 1088 deletions
@@ -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<Diagnostic<? extends JavaFileObject>> errors = new ArrayList<Diagnostic<? extends JavaFileObject>>();
private final List<Diagnostic<? extends JavaFileObject>> warnings = new ArrayList<Diagnostic<? extends JavaFileObject>>();
@@ -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 包名
@@ -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<String> lines;
private ClassPathIndexFile(File root, List<String> 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<URL> 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<String> loadLines(InputStream inputStream) throws IOException {
List<String> 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());
}
}
}
@@ -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<Archive> archives) throws Exception {
List<URL> 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<Archive> getClassPathArchivesIterator() throws Exception {
Archive.EntryFilter searchFilter = this::isSearchCandidate;
Iterator<Archive> 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<Archive> applyClassPathArchivePostProcessing(Iterator<Archive> archives) throws Exception {
List<Archive> 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<Archive> 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;
}
}
@@ -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/";
}
}
@@ -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<URL> 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> T doDefinePackage(DefinePackageCallType type, Supplier<T> 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<URL> {
private final Enumeration<URL> delegate;
UseFastConnectionExceptionsEnumeration(Enumeration<URL> 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
}
}
@@ -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<Archive> archives) throws Exception {
List<URL> 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<Archive> 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;
}
}
@@ -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<Archive> archives = getClassPathArchivesIterator(archive);
List<URL> 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<Archive> getClassPathArchivesIterator(Archive archive) throws Exception {
Archive.EntryFilter searchFilter = this::isSearchCandidate;
Iterator<Archive> archives = archive.getNestedArchives(searchFilter,
(entry) -> isNestedArchive(entry));
archives = applyClassPathArchivePostProcessing(archives);
return archives;
}
private Iterator<Archive> applyClassPathArchivePostProcessing(Iterator<Archive> archives) throws Exception {
List<Archive> 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/";
}
}
@@ -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.
* <p>
* 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.
* <p>
* 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<String> 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;
}
}
@@ -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;
@@ -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" +