Browse Source

Bucketed caching in SingletonExtensionFactory (#402)

tags/release-3.5.0
Ajith Kumar 3 years ago
parent
commit
c2d9998350
No account linked to committer's email address

+ 15
- 5
pf4j/src/main/java/org/pf4j/SingletonExtensionFactory.java View File

import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap;


/** /**
* An {@link ExtensionFactory} that always returns a specific instance. * An {@link ExtensionFactory} that always returns a specific instance.
* Optional, you can specify the extension classes for which you want singletons. * Optional, you can specify the extension classes for which you want singletons.
* *
* @author Decebal Suiu * @author Decebal Suiu
* @author Ajith Kumar
*/ */
public class SingletonExtensionFactory extends DefaultExtensionFactory { public class SingletonExtensionFactory extends DefaultExtensionFactory {


private final List<String> extensionClassNames; private final List<String> extensionClassNames;


private Map<String, Object> cache;
private Map<ClassLoader, Map<String, Object>> cache;


public SingletonExtensionFactory(String... extensionClassNames) { public SingletonExtensionFactory(String... extensionClassNames) {
this.extensionClassNames = Arrays.asList(extensionClassNames); this.extensionClassNames = Arrays.asList(extensionClassNames);


cache = new HashMap<>(); // simple cache implementation
cache = new WeakHashMap<>(); // simple cache implementation
} }


@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T create(Class<T> extensionClass) { public <T> T create(Class<T> extensionClass) {
String extensionClassName = extensionClass.getName(); String extensionClassName = extensionClass.getName();
if (cache.containsKey(extensionClassName)) {
return (T) cache.get(extensionClassName);
ClassLoader extensionClassLoader = extensionClass.getClassLoader();

if (!cache.containsKey(extensionClassLoader)) {
cache.put(extensionClassLoader, new HashMap<>());
}

Map<String, Object> classLoaderBucket = cache.get(extensionClassLoader);

if (classLoaderBucket.containsKey(extensionClassName)) {
return (T) classLoaderBucket.get(extensionClassName);
} }


T extension = super.create(extensionClass); T extension = super.create(extensionClass);
if (extensionClassNames.isEmpty() || extensionClassNames.contains(extensionClassName)) { if (extensionClassNames.isEmpty() || extensionClassNames.contains(extensionClassName)) {
cache.put(extensionClassName, extension);
classLoaderBucket.put(extensionClassName, extension);
} }


return extension; return extension;

+ 40
- 0
pf4j/src/test/java/org/pf4j/SingletonExtensionFactoryTest.java View File

import org.pf4j.plugin.FailTestExtension; import org.pf4j.plugin.FailTestExtension;
import org.pf4j.plugin.TestExtension; import org.pf4j.plugin.TestExtension;


import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;


/** /**
* @author Decebal Suiu * @author Decebal Suiu
* @author Ajith Kumar
*/ */
public class SingletonExtensionFactoryTest { public class SingletonExtensionFactoryTest {


assertNotSame(extensionOne, extensionTwo); assertNotSame(extensionOne, extensionTwo);
} }


@Test
@SuppressWarnings("unchecked")
public void createNewEachTimeFromDifferentClassLoaders() throws Exception {
ExtensionFactory extensionFactory = new SingletonExtensionFactory();

// Get classpath locations
URL[] classpathReferences = getClasspathReferences();

// Create different classloaders for the classpath references and load classes respectively
ClassLoader klassLoaderOne = new URLClassLoader(classpathReferences, null);
Class klassOne = klassLoaderOne.loadClass(TestExtension.class.getName());
ClassLoader klassLoaderTwo = new URLClassLoader(classpathReferences, null);
Class klassTwo = klassLoaderTwo.loadClass(TestExtension.class.getName());

// create instances
Object instanceOne = extensionFactory.create(klassOne);
Object instanceTwo = extensionFactory.create(klassTwo);

// assert that instances not same
assertNotSame(instanceOne, instanceTwo);
}

private URL[] getClasspathReferences() throws MalformedURLException {
String classpathProperty = System.getProperty("java.class.path");

String[] classpaths = classpathProperty.split(":");
URL[] uris = new URL[classpaths.length];

for (int index = 0; index < classpaths.length; index++) {
uris[index] = new File(classpaths[index]).toURI().toURL();
}
return uris;
}

} }

Loading…
Cancel
Save