This is an automated email from the ASF dual-hosted git repository.

bamaer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 819d67ef5f Share one scan class loader per plugin jar, fixes #9148 
(#9152)
819d67ef5f is described below

commit 819d67ef5f53faae3b873196ab23923f3dd03945
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Fri Oct 30 06:34:32 2026 +0100

    Share one scan class loader per plugin jar, fixes #9148 (#9152)
---
 .../apache/hop/core/plugins/BasePluginType.java    | 20 +++++++++++++++-
 .../java/org/apache/hop/core/plugins/JarCache.java | 28 ++++++++++++++++++++++
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/core/src/main/java/org/apache/hop/core/plugins/BasePluginType.java 
b/core/src/main/java/org/apache/hop/core/plugins/BasePluginType.java
index 4be3a06f31..82c22ea443 100644
--- a/core/src/main/java/org/apache/hop/core/plugins/BasePluginType.java
+++ b/core/src/main/java/org/apache/hop/core/plugins/BasePluginType.java
@@ -729,13 +729,31 @@ public abstract class BasePluginType<T extends 
Annotation> implements IPluginTyp
     return new String[0];
   }
 
+  /** The class in this plugin type's hierarchy that declares 
createUrlClassLoader(). */
+  private Class<?> scanClassLoaderBuilder() {
+    for (Class<?> c = getClass(); c != null; c = c.getSuperclass()) {
+      try {
+        c.getDeclaredMethod("createUrlClassLoader", URL.class, 
ClassLoader.class);
+        return c;
+      } catch (NoSuchMethodException e) {
+        // look in the super class
+      }
+    }
+    return BasePluginType.class;
+  }
+
   protected void registerPluginJars() throws HopPluginException {
 
     List<PluginClassFile> pluginClassFiles = 
findAnnotatedClassFiles(pluginClass.getName());
+    Class<?> loaderBuilder = scanClassLoaderBuilder();
+    ClassLoader parent = getClass().getClassLoader();
     for (PluginClassFile pluginClassFile : pluginClassFiles) {
 
+      URL jarFile = pluginClassFile.getJarFile();
       URLClassLoader urlClassLoader =
-          createUrlClassLoader(pluginClassFile.getJarFile(), 
getClass().getClassLoader());
+          JarCache.getInstance()
+              .getScanClassLoader(
+                  jarFile, loaderBuilder, parent, () -> 
createUrlClassLoader(jarFile, parent));
 
       try {
         Class<?> clazz = 
urlClassLoader.loadClass(pluginClassFile.getClassName());
diff --git a/core/src/main/java/org/apache/hop/core/plugins/JarCache.java 
b/core/src/main/java/org/apache/hop/core/plugins/JarCache.java
index 26fcff6f93..c451a833a1 100644
--- a/core/src/main/java/org/apache/hop/core/plugins/JarCache.java
+++ b/core/src/main/java/org/apache/hop/core/plugins/JarCache.java
@@ -21,6 +21,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.net.URLClassLoader;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -29,6 +30,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 import java.util.jar.JarFile;
 import java.util.zip.ZipEntry;
 import org.apache.commons.lang3.StringUtils;
@@ -49,12 +51,14 @@ public class JarCache {
   private final Map<File, Set<File>> jarFiles;
   private final Set<File> nativeFiles;
   private final Set<File> pluginFiles;
+  private final Map<ScanClassLoaderKey, URLClassLoader> scanClassLoaders;
 
   private JarCache() {
     nativeFiles = new HashSet<>();
     pluginFiles = new HashSet<>();
     jarFiles = new HashMap<>();
     indexCache = new HashMap<>();
+    scanClassLoaders = new HashMap<>();
   }
 
   public static JarCache getInstance() {
@@ -180,11 +184,35 @@ public class JarCache {
     return index;
   }
 
+  /**
+   * The class loader the plugin scan uses to load the annotated classes of 
one jar. Plugin types
+   * that build their scan class loaders the same way share one per jar and 
parent, so a jar's lib/
+   * folder and dependencies.xml are read once and not for every annotated 
class in it.
+   *
+   * @param jarFile the jar with the annotated classes
+   * @param builder the class whose createUrlClassLoader() builds the loader
+   * @param parent the parent class loader
+   * @param factory creates the class loader the first time this combination 
is asked for
+   * @return the shared scan class loader
+   */
+  public URLClassLoader getScanClassLoader(
+      final URL jarFile,
+      final Class<?> builder,
+      final ClassLoader parent,
+      final Supplier<URLClassLoader> factory) {
+    return scanClassLoaders.computeIfAbsent(
+        new ScanClassLoaderKey(jarFile.toExternalForm(), builder, parent), k 
-> factory.get());
+  }
+
+  private record ScanClassLoaderKey(String jarFile, Class<?> builder, 
ClassLoader parent) {}
+
   public void clear() {
     nativeFiles.clear();
     pluginFiles.clear();
     indexCache.clear();
     jarFiles.clear();
+    // The loaders stay open: classes loaded during the scan can still be 
referenced.
+    scanClassLoaders.clear();
   }
 
   public Set<File> findJarFiles(final File folder) throws HopFileException {

Reply via email to