From 0438f0ac258e37cec6dd9cd4720ac38942d45595 Mon Sep 17 00:00:00 2001 From: Decebal Suiu Date: Wed, 7 Feb 2018 00:53:38 +0200 Subject: [PATCH] Add support for parent first loading strategy --- .../org/pf4j/DefaultPluginDescriptor.java | 3 - .../main/java/org/pf4j/PluginClassLoader.java | 156 ++++++++++++------ .../main/java/org/pf4j/PluginDescriptor.java | 4 +- 3 files changed, 111 insertions(+), 52 deletions(-) diff --git a/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java b/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java index 5fa2935..61a7d2a 100644 --- a/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java +++ b/pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java @@ -20,9 +20,6 @@ import java.util.Collections; import java.util.List; /** - * A plugin descriptor contains information about a plug-in obtained - * from the manifest (META-INF) file. - * * @author Decebal Suiu */ public class DefaultPluginDescriptor implements PluginDescriptor { diff --git a/pf4j/src/main/java/org/pf4j/PluginClassLoader.java b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java index 586f1b3..6863c3d 100644 --- a/pf4j/src/main/java/org/pf4j/PluginClassLoader.java +++ b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java @@ -26,8 +26,9 @@ import java.util.List; /** * One instance of this class should be created by plugin manager for every available plug-in. - * This class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars + * By default, this class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars * before delegating to the parent class loader. + * Use {@link #parentFirst} to change the loading strategy. * * @author Decebal Suiu */ @@ -35,19 +36,30 @@ public class PluginClassLoader extends URLClassLoader { private static final Logger log = LoggerFactory.getLogger(PluginClassLoader.class); + private static final String JAVA_PACKAGE_PREFIX = "java."; private static final String PLUGIN_PACKAGE_PREFIX = "org.pf4j."; private PluginManager pluginManager; private PluginDescriptor pluginDescriptor; + private boolean parentFirst; - public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) { + public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) { + this(pluginManager, pluginDescriptor, parent, false); + } + + /** + * If {@code parentFirst} is {@code true}, indicates that the parent {@link ClassLoader} should be consulted + * before trying to load the a class through this loader. + */ + public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent, boolean parentFirst) { super(new URL[0], parent); this.pluginManager = pluginManager; this.pluginDescriptor = pluginDescriptor; + this.parentFirst = parentFirst; } - @Override + @Override public void addURL(URL url) { log.debug("Add '{}'", url); super.addURL(url); @@ -63,83 +75,133 @@ public class PluginClassLoader extends URLClassLoader { } /** - * Uses a child first delegation model rather than the standard parent first. + * By default, it uses a child first delegation model rather than the standard parent first. * If the requested class cannot be found in this class loader, the parent class loader will be consulted * via the standard {@link ClassLoader#loadClass(String)} mechanism. + * Use {@link #parentFirst} to change the loading strategy. */ @Override public Class loadClass(String className) throws ClassNotFoundException { synchronized (getClassLoadingLock(className)) { - log.trace("Received request to load class '{}'", className); + // first check whether it's a system class, delegate to the system loader + if (className.startsWith(JAVA_PACKAGE_PREFIX)) { + return findSystemClass(className); + } + // if the class it's a part of the plugin engine use parent class loader - if (className.startsWith(PLUGIN_PACKAGE_PREFIX)) { - log.trace("Delegate the loading of class '{}' to parent", className); - try { - return getClass().getClassLoader().loadClass(className); - } catch (ClassNotFoundException e) { - // try next step - } + if (className.startsWith(PLUGIN_PACKAGE_PREFIX) && !className.startsWith("org.pf4j.demo")) { +// log.trace("Delegate the loading of PF4J class '{}' to parent", className); + return getParent().loadClass(className); } + log.trace("Received request to load class '{}'", className); + // second check whether it's already been loaded - Class clazz = findLoadedClass(className); - if (clazz != null) { + Class loadedClass = findLoadedClass(className); + if (loadedClass != null) { log.trace("Found loaded class '{}'", className); - return clazz; + return loadedClass; } - // nope, try to load locally - try { - clazz = findClass(className); - log.trace("Found class '{}' in plugin classpath", className); - return clazz; - } catch (ClassNotFoundException e) { - // try next step - } + if (!parentFirst) { + // nope, try to load locally + try { + loadedClass = findClass(className); + log.trace("Found class '{}' in plugin classpath", className); + return loadedClass; + } catch (ClassNotFoundException e) { + // try next step + } + + // look in dependencies + loadedClass = loadClassFromDependencies(className); + if (loadedClass != null) { + log.trace("Found class '{}' in dependencies", className); + return loadedClass; + } + + log.trace("Couldn't find class '{}' in plugin classpath. Delegating to parent", className); + + // use the standard ClassLoader (which follows normal parent delegation) + return super.loadClass(className); + } else { + // try to load from parent + try { + return super.loadClass(className); + } catch (ClassCastException e) { + // try next step + } - // look in dependencies - log.trace("Search in dependencies for class '{}'", className); - List dependencies = pluginDescriptor.getDependencies(); - for (PluginDependency dependency : dependencies) { - ClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId()); + log.trace("Couldn't find class '{}' in parent. Delegating to plugin classpath", className); + + // nope, try to load locally try { - return classLoader.loadClass(className); + loadedClass = findClass(className); + log.trace("Found class '{}' in plugin classpath", className); + return loadedClass; } catch (ClassNotFoundException e) { - // try next dependency + // try next step } - } - log.trace("Couldn't find class '{}' in plugin classpath. Delegating to parent", className); + // look in dependencies + loadedClass = loadClassFromDependencies(className); + if (loadedClass != null) { + log.trace("Found class '{}' in dependencies", className); + return loadedClass; + } - // use the standard ClassLoader (which follows normal parent delegation) - return super.loadClass(className); + throw new ClassNotFoundException(className); + } } } /** * Load the named resource from this plugin. - * This implementation checks the plugin's classpath first then delegates to the parent. + * By default, this implementation checks the plugin's classpath first then delegates to the parent. + * Use {@link #parentFirst} to change the loading strategy. * * @param name the name of the resource. - * @return the URL to the resource, null if the resource was not found. + * @return the URL to the resource, {@code null} if the resource was not found. */ @Override public URL getResource(String name) { - log.trace("Trying to find resource '{}' in plugin classpath", name); - URL url = findResource(name); - if (url != null) { - log.trace("Found resource '{}' in plugin classpath", name); - return url; - } + log.trace("Received request to load resource '{}'", name); + if (!parentFirst) { + URL url = findResource(name); + if (url != null) { + log.trace("Found resource '{}' in plugin classpath", name); + return url; + } + + log.trace("Couldn't find resource '{}' in plugin classpath. Delegating to parent", name); + + return super.getResource(name); + } else { + URL url = super.getResource(name); + if (url != null) { + log.trace("Found resource '{}' in parent", name); + return url; + } - log.trace("Couldn't find resource '{}' in plugin classpath. Delegating to parent"); + log.trace("Couldn't find resource '{}' in parent", name); - return super.getResource(name); + return findResource(name); + } } - @Override - public URL findResource(String name) { - return super.findResource(name); + private Class loadClassFromDependencies(String className) { + log.trace("Search in dependencies for class '{}'", className); + List dependencies = pluginDescriptor.getDependencies(); + for (PluginDependency dependency : dependencies) { + ClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId()); + try { + return classLoader.loadClass(className); + } catch (ClassNotFoundException e) { + // try next dependency + } + } + + return null; } } diff --git a/pf4j/src/main/java/org/pf4j/PluginDescriptor.java b/pf4j/src/main/java/org/pf4j/PluginDescriptor.java index ab08d68..75bacfc 100644 --- a/pf4j/src/main/java/org/pf4j/PluginDescriptor.java +++ b/pf4j/src/main/java/org/pf4j/PluginDescriptor.java @@ -18,8 +18,7 @@ package org.pf4j; import java.util.List; /** - * A plugin descriptor contains information about a plug-in obtained - * from the manifest (META-INF) file. + * A plugin descriptor contains information about a plug-in. * * @author Decebal Suiu */ @@ -40,4 +39,5 @@ public interface PluginDescriptor { String getLicense(); List getDependencies(); + } -- 2.39.5