From 53745be62c939ee7a6891a2d9507d4da790fa47c Mon Sep 17 00:00:00 2001 From: asafbennatan Date: Fri, 17 Jul 2020 16:38:38 +0300 Subject: [PATCH] Better Customization for PluginClassLoader (#385) --- .../java/org/pf4j/ClassLoadingStrategy.java | 69 +++++++ .../main/java/org/pf4j/PluginClassLoader.java | 169 ++++++++---------- 2 files changed, 140 insertions(+), 98 deletions(-) create mode 100644 pf4j/src/main/java/org/pf4j/ClassLoadingStrategy.java diff --git a/pf4j/src/main/java/org/pf4j/ClassLoadingStrategy.java b/pf4j/src/main/java/org/pf4j/ClassLoadingStrategy.java new file mode 100644 index 0000000..5a261cc --- /dev/null +++ b/pf4j/src/main/java/org/pf4j/ClassLoadingStrategy.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pf4j; + +import java.util.Arrays; +import java.util.List; + +/** + * {@link ClassLoadingStrategy} will be used to configure {@link PluginClassLoader} loading order + * and contains all possible options supported by {@link PluginClassLoader} where + * A=Application Source ( load classes from parent classLoader) + * P=Plugin Source ( load classes from this classloader) + * D=Dependencies ( load classes from dependencies) + */ +public class ClassLoadingStrategy { + + /** + * application(parent)->plugin->dependencies + */ + public static final ClassLoadingStrategy APD = new ClassLoadingStrategy(Arrays.asList(Source.APPLICATION, Source.PLUGIN, Source.DEPENDENCIES)); + /** + * application(parent)->dependencies->plugin + */ + public static final ClassLoadingStrategy ADP=new ClassLoadingStrategy(Arrays.asList(Source.APPLICATION,Source.DEPENDENCIES,Source.APPLICATION)); + /** + * plugin->application(parent)->dependencies + */ + public static final ClassLoadingStrategy PAD=new ClassLoadingStrategy(Arrays.asList(Source.PLUGIN,Source.APPLICATION,Source.DEPENDENCIES)); + /** + * dependencies->application(parent)->plugin + */ + public static final ClassLoadingStrategy DAP=new ClassLoadingStrategy(Arrays.asList(Source.DEPENDENCIES,Source.APPLICATION,Source.PLUGIN)); + /** + * dependencies->plugin->application(parent) + */ + public static final ClassLoadingStrategy DPA=new ClassLoadingStrategy(Arrays.asList(Source.DEPENDENCIES,Source.PLUGIN,Source.APPLICATION)); + /** + * plugin->dependencies->application(parent) + */ + public static final ClassLoadingStrategy PDA =new ClassLoadingStrategy(Arrays.asList(Source.PLUGIN, Source.DEPENDENCIES, Source.APPLICATION)); + + + private final List sources; + + private ClassLoadingStrategy(List sources) { + this.sources = sources; + } + + public List getSources() { + return sources; + } + + public enum Source { + PLUGIN, APPLICATION,DEPENDENCIES; + } +} diff --git a/pf4j/src/main/java/org/pf4j/PluginClassLoader.java b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java index 6d609c1..fc771de 100644 --- a/pf4j/src/main/java/org/pf4j/PluginClassLoader.java +++ b/pf4j/src/main/java/org/pf4j/PluginClassLoader.java @@ -33,7 +33,7 @@ import java.util.Objects; * One instance of this class should be created by plugin manager for every available plug-in. * 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. + * Use {@link #classLoadingStrategy} to change the loading strategy. * * @author Decebal Suiu */ @@ -46,7 +46,7 @@ public class PluginClassLoader extends URLClassLoader { private PluginManager pluginManager; private PluginDescriptor pluginDescriptor; - private boolean parentFirst; + private ClassLoadingStrategy classLoadingStrategy; public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) { this(pluginManager, pluginDescriptor, parent, false); @@ -57,11 +57,19 @@ public class PluginClassLoader extends URLClassLoader { * before trying to load the a class through this loader. */ public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent, boolean parentFirst) { + this(pluginManager, pluginDescriptor, parent, parentFirst ? ClassLoadingStrategy.APD : ClassLoadingStrategy.PDA); + } + + + /** + * classloading according to {@code classLoadingStrategy} + */ + public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent, ClassLoadingStrategy classLoadingStrategy) { super(new URL[0], parent); this.pluginManager = pluginManager; this.pluginDescriptor = pluginDescriptor; - this.parentFirst = parentFirst; + this.classLoadingStrategy = classLoadingStrategy; } @Override @@ -83,7 +91,7 @@ public class PluginClassLoader extends URLClassLoader { * 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. + * Use {@link #classLoadingStrategy} to change the loading strategy. */ @Override public Class loadClass(String className) throws ClassNotFoundException { @@ -107,63 +115,39 @@ public class PluginClassLoader extends URLClassLoader { log.trace("Found loaded class '{}'", className); return loadedClass; } - - 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 + for (ClassLoadingStrategy.Source classLoadingSource : classLoadingStrategy.getSources()) { + Class c = null; try { - return super.loadClass(className); - } catch (ClassNotFoundException e) { - // try next step - } - - log.trace("Couldn't find class '{}' in parent. Delegating to plugin classpath", className); + switch (classLoadingSource) { + case APPLICATION: + c = super.loadClass(className); + break; + case PLUGIN: + c = findClass(className); + break; + case DEPENDENCIES: + c = loadClassFromDependencies(className); + break; + } + } catch (ClassNotFoundException ignored) { - // 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; + if (c != null) { + log.trace("Found class '{}' in {} classpath", className,classLoadingSource); + return c; + } else { + log.trace("Couldn't find class '{}' in {} classpath", className,classLoadingSource); } - throw new ClassNotFoundException(className); } + throw new ClassNotFoundException(className); } } /** * Load the named resource from this plugin. * By default, this implementation checks the plugin's classpath first then delegates to the parent. - * Use {@link #parentFirst} to change the loading strategy. + * Use {@link #classLoadingStrategy} to change the loading strategy. * * @param name the name of the resource. * @return the URL to the resource, {@code null} if the resource was not found. @@ -171,66 +155,55 @@ public class PluginClassLoader extends URLClassLoader { @Override public URL getResource(String name) { 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; + for (ClassLoadingStrategy.Source classLoadingSource : classLoadingStrategy.getSources()) { + URL url = null; + switch (classLoadingSource) { + case APPLICATION: + url = super.getResource(name); + break; + case PLUGIN: + url = findResource(name); + break; + case DEPENDENCIES: + url = findResourceFromDependencies(name); + break; } - - url = findResourceFromDependencies(name); if (url != null) { - log.trace("Found resource '{}' in plugin dependencies", name); + log.trace("Found resource '{}' in {} classpath", name,classLoadingSource); return url; + } else { + log.trace("Couldn't find resource '{}' in {}.", name,classLoadingSource); } - - log.trace("Couldn't find resource '{}' in plugin or dependencies 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 parent", name); - - url = findResourceFromDependencies(name); - - if (url != null) { - log.trace("Found resource '{}' in dependencies", name); - return url; - } - - return findResource(name); } + return null; + } @Override public Enumeration getResources(String name) throws IOException { - List resources = new ArrayList<>(); - - if (!parentFirst) { - resources.addAll(Collections.list(findResources(name))); - resources.addAll(findResourcesFromDependencies(name)); - - if (getParent() != null) { - resources.addAll(Collections.list(getParent().getResources(name))); - } - } else { - if (getParent() != null) { - resources.addAll(Collections.list(getParent().getResources(name))); + List resources = new ArrayList<>(); + + log.trace("Received request to load resources '{}'", name); + for (ClassLoadingStrategy.Source classLoadingSource : classLoadingStrategy.getSources()) { + switch (classLoadingSource) { + case APPLICATION: + if (getParent() != null) { + resources.addAll(Collections.list(getParent().getResources(name))); + } + break; + case PLUGIN: + resources.addAll(Collections.list(findResources(name))); + break; + case DEPENDENCIES: + resources.addAll(findResourcesFromDependencies(name)); + break; } - - resources.addAll(findResourcesFromDependencies(name)); - resources.addAll(Collections.list(super.findResources(name))); } + return Collections.enumeration(resources); - return Collections.enumeration(resources); } - private Class loadClassFromDependencies(String className) { + protected Class loadClassFromDependencies(String className) { log.trace("Search in dependencies for class '{}'", className); List dependencies = pluginDescriptor.getDependencies(); for (PluginDependency dependency : dependencies) { @@ -251,7 +224,7 @@ public class PluginClassLoader extends URLClassLoader { return null; } - private URL findResourceFromDependencies(String name) { + protected URL findResourceFromDependencies(String name) { log.trace("Search in dependencies for resource '{}'", name); List dependencies = pluginDescriptor.getDependencies(); for (PluginDependency dependency : dependencies) { @@ -264,14 +237,14 @@ public class PluginClassLoader extends URLClassLoader { URL url = classLoader.findResource(name); if (Objects.nonNull(url)) { - return url; + return url; } } return null; } - private Collection findResourcesFromDependencies(String name) throws IOException { + protected Collection findResourcesFromDependencies(String name) throws IOException { log.trace("Search in dependencies for resources '{}'", name); List results = new ArrayList<>(); List dependencies = pluginDescriptor.getDependencies(); -- 2.39.5