Browse Source

Add support for parent first loading strategy

tags/release-2.2.0
Decebal Suiu 6 years ago
parent
commit
0438f0ac25

+ 0
- 3
pf4j/src/main/java/org/pf4j/DefaultPluginDescriptor.java View File

@@ -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 {

+ 109
- 47
pf4j/src/main/java/org/pf4j/PluginClassLoader.java View File

@@ -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<PluginDependency> 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, <code>null</code> 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<PluginDependency> 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;
}

}

+ 2
- 2
pf4j/src/main/java/org/pf4j/PluginDescriptor.java View File

@@ -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<PluginDependency> getDependencies();

}

Loading…
Cancel
Save