From f6bc3b24926e68dcfc32654f7aa6037923678ee4 Mon Sep 17 00:00:00 2001 From: sinri Date: Tue, 18 Jun 2024 10:39:43 +0800 Subject: [PATCH] 3.2.12.1 Fix reflections Signed-off-by: sinri --- pom.xml | 4 +- .../sinri/keel/helper/KeelFileHelper.java | 109 ++++++++++++++---- .../keel/helper/KeelReflectionHelper.java | 65 ++++++++--- .../receptionist/KeelWebReceptionistKit.java | 1 - 4 files changed, 134 insertions(+), 45 deletions(-) diff --git a/pom.xml b/pom.xml index ae52a5e..ee8b2ff 100644 --- a/pom.xml +++ b/pom.xml @@ -6,8 +6,8 @@ io.github.sinri Keel - - 3.2.12 + + 3.2.12.1 Keel diff --git a/src/main/java/io/github/sinri/keel/helper/KeelFileHelper.java b/src/main/java/io/github/sinri/keel/helper/KeelFileHelper.java index c23257f..1dd6921 100644 --- a/src/main/java/io/github/sinri/keel/helper/KeelFileHelper.java +++ b/src/main/java/io/github/sinri/keel/helper/KeelFileHelper.java @@ -4,13 +4,13 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.security.CodeSource; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -63,7 +63,18 @@ static KeelFileHelper getInstance() { * @return the URL of target file; if not there, null return. */ @Nullable + @Deprecated(forRemoval = true, since = "3.2.12.1") public URL getUrlOfFileInJar(@Nonnull String filePath) { + return getUrlOfFileInRunningJar(filePath); + } + + /** + * @param filePath path string of the target file, or directory + * @return the URL of target file; if not there, null return. + * @since 3.2.12.1 original name is `getUrlOfFileInJar`. + */ + @Nullable + public URL getUrlOfFileInRunningJar(@Nonnull String filePath) { return KeelFileHelper.class.getClassLoader().getResource(filePath); } @@ -73,8 +84,21 @@ public URL getUrlOfFileInJar(@Nonnull String filePath) { * @param root ends with '/' * @return list of JarEntry */ + @Deprecated(forRemoval = true, since = "3.2.12.1") @Nonnull public List traversalInJar(@Nonnull String root) { + return traversalInRunningJar(root); + } + + /** + * Seek in JAR, under the root (exclusive) + * + * @param root ends with '/' + * @return list of JarEntry + * @since 3.2.12.1 original name is `traversalInJar`. + */ + @Nonnull + public List traversalInRunningJar(@Nonnull String root) { List jarEntryList = new ArrayList<>(); try { // should root ends with '/'? @@ -121,30 +145,26 @@ public Future crateTempFile(@Nullable String prefix, @Nullable String su /** * @since 3.2.11 + * @since 3.2.12.1 Changed the implementation with checking class paths. * Check if this process is running with JAR file. */ public boolean isRunningFromJAR() { - CodeSource src = this.getClass().getProtectionDomain().getCodeSource(); - if (src == null) { - throw new RuntimeException(); - } - URL location = src.getLocation(); - if (location == null) { - throw new RuntimeException(); + List classPathList = getClassPathList(); + for (var classPath : classPathList) { + if (!classPath.endsWith(".jar")) { + return false; + } } - // System.out.println("src.getLocation: "+location.toString()); - return location.toString().endsWith(".jar"); - -// ZipInputStream zip = new ZipInputStream(jar.openStream()); -// while (true) { -// ZipEntry e = zip.getNextEntry(); -// if (e == null) -// break; -// String name = e.getName(); -// if (name.startsWith("path/to/your/dir/")) { -// /* Do something with this entry. */ -// } -// } + return true; + } + + /** + * @since 3.2.12.1 + */ + public List getClassPathList() { + String classpath = System.getProperty("java.class.path"); + String[] classpathEntries = classpath.split(File.pathSeparator); + return new ArrayList<>(Arrays.asList(classpathEntries)); } /** @@ -152,13 +172,24 @@ public boolean isRunningFromJAR() { * * @since 3.2.11 */ + @Deprecated(since = "3.2.12.1", forRemoval = true) public Set seekPackageClassFilesInJar(@Nonnull String packageName) { + return seekPackageClassFilesInRunningJar(packageName); + } + + /** + * The in-class classes, i.e. subclasses, would be neglected. + * + * @since 3.2.12.1 original name is `seekPackageClassFilesInJar`. + */ + public Set seekPackageClassFilesInRunningJar(@Nonnull String packageName) { Set classes = new HashSet<>(); // Get the current class's class loader - ClassLoader classLoader = this.getClass().getClassLoader(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Get the URL of the JAR file containing the current class - URL jarUrl = classLoader.getResource(getClass().getName().replace('.', '/') + ".class"); + String currentClassUrlInJarFile = getClass().getName().replace('.', '/') + ".class"; + URL jarUrl = classLoader.getResource(currentClassUrlInJarFile); if (jarUrl != null && jarUrl.getProtocol().equals("jar")) { // Extract the JAR file path @@ -176,17 +207,45 @@ public Set seekPackageClassFilesInJar(@Nonnull String packageName) { if (entryName.endsWith(".class")) { // Convert the entry name to a fully qualified class name String className = entryName.replace('/', '.').replace('\\', '.').replace(".class", ""); - //System.out.println(className); if (className.startsWith(packageName + ".") && !className.contains("$")) { classes.add(className); } } } } catch (IOException e) { - Keel.getLogger().exception(e); + Keel.getLogger().debug(getClass() + " seekPackageClassFilesInRunningJar for package " + packageName + " error: " + e.getMessage()); } } return classes; } + + /** + * @param jarFile File built from JAR in class path. + * @since 3.2.12.1 + */ + public List traversalInJarFile(File jarFile) { + try (JarFile jar = new JarFile(jarFile)) { + List list = new ArrayList<>(); + + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + if ( + entryName.endsWith(".class") + && !entryName.contains("$") + && !entryName.startsWith("META-INF") + ) { + // 将路径形式的类名转换为 Java 类名 + String className = entryName.replace("/", ".").replace(".class", ""); + list.add(className); + } + } + + return list; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/io/github/sinri/keel/helper/KeelReflectionHelper.java b/src/main/java/io/github/sinri/keel/helper/KeelReflectionHelper.java index 7e3132a..555d0c9 100644 --- a/src/main/java/io/github/sinri/keel/helper/KeelReflectionHelper.java +++ b/src/main/java/io/github/sinri/keel/helper/KeelReflectionHelper.java @@ -3,6 +3,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.io.File; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -11,6 +12,7 @@ import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashSet; +import java.util.List; import java.util.Set; import static io.github.sinri.keel.facade.KeelInstance.Keel; @@ -77,32 +79,42 @@ public T[] getAnnotationsOfClass(@Nonnull Class anyCla * @param the target base class to seek its implementations * @return the sought classes in a set * @since 3.0.6 + * @since 3.2.12.1 rewrite */ public Set> seekClassDescendantsInPackage(@Nonnull String packageName, @Nonnull Class baseClass) { // Reflections reflections = new Reflections(packageName); // return reflections.getSubTypesOf(baseClass); - if (Keel.fileHelper().isRunningFromJAR()) { - return seekClassDescendantsInPackageForJar(packageName, baseClass); - } else { - return seekClassDescendantsInPackageForFileSystem(packageName, baseClass); + Set> set = new HashSet<>(); + + List classPathList = Keel.fileHelper().getClassPathList(); + for (String classPath : classPathList) { + if (classPath.endsWith(".jar")) { + Set> classes = seekClassDescendantsInPackageForProvidedJar(classPath, packageName, baseClass); + set.addAll(classes); + } else { + Set> classes = seekClassDescendantsInPackageForFileSystem(packageName, baseClass); + set.addAll(classes); + } } + + return set; } /** * @since 3.2.11 */ protected Set> seekClassDescendantsInPackageForFileSystem(@Nonnull String packageName, @Nonnull Class baseClass) { - String packagePath = packageName.replace('.', '/'); - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Set> descendantClasses = new HashSet<>(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + // in file system + String packagePath = packageName.replace('.', '/'); try { // Assuming classes are in a directory on the file system (e.g., not in a JAR) URL resource = classLoader.getResource(packagePath); if (resource != null) { URI uri = resource.toURI(); Path startPath = Paths.get(uri); - Files.walkFileTree(startPath, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { @@ -112,14 +124,11 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO try { Class clazz = (Class) classLoader.loadClass(className); - if ( - baseClass.isAssignableFrom(clazz) - //&& !baseClass.equals(clazz) - ) { + if (baseClass.isAssignableFrom(clazz)) { descendantClasses.add(clazz); } - } catch (ClassNotFoundException e) { - Keel.getLogger().exception(e); + } catch (Throwable e) { + Keel.getLogger().debug(getClass() + " seekClassDescendantsInPackageForFileSystem for " + className + " error: " + e.getMessage()); } } return FileVisitResult.CONTINUE; @@ -135,22 +144,44 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO /** * @since 3.2.11 */ - protected Set> seekClassDescendantsInPackageForJar(@Nonnull String packageName, @Nonnull Class baseClass) { + protected Set> seekClassDescendantsInPackageForRunningJar(@Nonnull String packageName, @Nonnull Class baseClass) { Set> descendantClasses = new HashSet<>(); - Set strings = Keel.fileHelper().seekPackageClassFilesInJar(packageName); + Set strings = Keel.fileHelper().seekPackageClassFilesInRunningJar(packageName); for (String s : strings) { try { Class aClass = Class.forName(s); if (baseClass.isAssignableFrom(aClass)) { descendantClasses.add((Class) aClass); } - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); + } catch (Throwable e) { + Keel.getLogger().debug(getClass() + " seekClassDescendantsInPackageForRunningJar for " + s + " error: " + e.getMessage()); } } return descendantClasses; } + /** + * @since 3.2.11 + */ + protected Set> seekClassDescendantsInPackageForProvidedJar(@Nonnull String jarInClassPath, @Nonnull String packageName, @Nonnull Class baseClass) { + Set> descendantClasses = new HashSet<>(); + List classNames = Keel.fileHelper().traversalInJarFile(new File(jarInClassPath)); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + classNames.forEach(className -> { + if (className.startsWith(packageName + ".")) { + try { + Class clazz = (Class) classLoader.loadClass(className); + if (baseClass.isAssignableFrom(clazz)) { + descendantClasses.add(clazz); + } + } catch (Throwable e) { + Keel.getLogger().debug(getClass() + " seekClassDescendantsInPackageForProvidedJar for " + className + " error: " + e.getMessage()); + } + } + }); + return descendantClasses; + } + /** * @return Whether the given `baseClass` is the base of the given `implementClass`. * @since 3.0.10 diff --git a/src/main/java/io/github/sinri/keel/web/http/receptionist/KeelWebReceptionistKit.java b/src/main/java/io/github/sinri/keel/web/http/receptionist/KeelWebReceptionistKit.java index 795fead..5ff2e7c 100644 --- a/src/main/java/io/github/sinri/keel/web/http/receptionist/KeelWebReceptionistKit.java +++ b/src/main/java/io/github/sinri/keel/web/http/receptionist/KeelWebReceptionistKit.java @@ -64,7 +64,6 @@ public KeelWebReceptionistKit(Class classOfReceptionist, Router router) { public void loadPackage(String packageName) { Set> allClasses = Keel.reflectionHelper() .seekClassDescendantsInPackage(packageName, classOfReceptionist); - try { allClasses.forEach(this::loadClass); } catch (Exception e) {