J'ai trouvé la question intéressant et créé un petit outil pour vous:
https://github.com/kriegaex/ThreadSafeClassLoader
Il n'est actuellement pas disponible comme une version officielle sur Maven Central le moment, mais vous pouvez obtenir un instantané comme ceci:
<dependency>
<groupId>de.scrum-master</groupId>
<artifactId>threadsafe-classloader</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- (...) -->
<repositories>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>ossrh</id>
<name>Sonatype OSS Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
Classe ThreadSafeClassLoader
:
Il utilise JCL (Pot Class Loader) sous le capot, car il offre déjà de la classe de chargement, l'instanciation d'objets et de génération de proxy fonctionnalités abordées dans d'autres parties de ce fil. (Pourquoi réinventer la roue?) Ce que j'ai ajouté sur le dessus est une interface agréable pour exactement ce dont nous avons besoin ici:
package de.scrum_master.thread_safe;
import org.xeustechnologies.jcl.JarClassLoader;
import org.xeustechnologies.jcl.JclObjectFactory;
import org.xeustechnologies.jcl.JclUtils;
import org.xeustechnologies.jcl.proxy.CglibProxyProvider;
import org.xeustechnologies.jcl.proxy.ProxyProviderFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ThreadSafeClassLoader extends JarClassLoader {
private static final JclObjectFactory OBJECT_FACTORY = JclObjectFactory.getInstance();
static {
ProxyProviderFactory.setDefaultProxyProvider(new CglibProxyProvider());
}
private final List<Class> classes = new ArrayList<>();
public static ThreadLocal<ThreadSafeClassLoader> create(Class... classes) {
return ThreadLocal.withInitial(
() -> new ThreadSafeClassLoader(classes)
);
}
private ThreadSafeClassLoader(Class... classes) {
super();
this.classes.addAll(Arrays.asList(classes));
for (Class clazz : classes)
add(clazz.getProtectionDomain().getCodeSource().getLocation());
}
public <T> T newObject(ObjectConstructionRules rules) {
rules.validate(classes);
Class<T> castTo = rules.targetType;
return JclUtils.cast(createObject(rules), castTo, castTo.getClassLoader());
}
private Object createObject(ObjectConstructionRules rules) {
String className = rules.implementingType.getName();
String factoryMethod = rules.factoryMethod;
Object[] arguments = rules.arguments;
Class[] argumentTypes = rules.argumentTypes;
if (factoryMethod == null) {
if (argumentTypes == null)
return OBJECT_FACTORY.create(this, className, arguments);
else
return OBJECT_FACTORY.create(this, className, arguments, argumentTypes);
} else {
if (argumentTypes == null)
return OBJECT_FACTORY.create(this, className, factoryMethod, arguments);
else
return OBJECT_FACTORY.create(this, className, factoryMethod, arguments, argumentTypes);
}
}
public static class ObjectConstructionRules {
private Class targetType;
private Class implementingType;
private String factoryMethod;
private Object[] arguments;
private Class[] argumentTypes;
private ObjectConstructionRules(Class targetType) {
this.targetType = targetType;
}
public static ObjectConstructionRules forTargetType(Class targetType) {
return new ObjectConstructionRules(targetType);
}
public ObjectConstructionRules implementingType(Class implementingType) {
this.implementingType = implementingType;
return this;
}
public ObjectConstructionRules factoryMethod(String factoryMethod) {
this.factoryMethod = factoryMethod;
return this;
}
public ObjectConstructionRules arguments(Object... arguments) {
this.arguments = arguments;
return this;
}
public ObjectConstructionRules argumentTypes(Class... argumentTypes) {
this.argumentTypes = argumentTypes;
return this;
}
private void validate(List<Class> classes) {
if (implementingType == null)
implementingType = targetType;
if (!classes.contains(implementingType))
throw new IllegalArgumentException(
"Class " + implementingType.getName() + " is not protected by this thread-safe classloader"
);
}
}
}
J'ai testé mon concept avec plusieurs unité et l'intégration des tests, parmi eux, l'un montrant comment les reproduire et de les résoudre le veraPDF problème.
Maintenant, c'est ce que votre code ressemble lors de l'utilisation de mon chargeur de classe:
Classe VeraPDFValidator
:
Nous sommes simplement en ajoutant un static ThreadLocal<ThreadSafeClassLoader>
des membres de notre classe, en disant qu'il les classes et les bibliothèques à mettre dans le nouveau chargeur de classe (en mentionnant une classe par la bibliothèque est assez, par la suite mon outil identifie la bibliothèque automatiquement).
Puis via threadSafeClassLoader.get().newObject(forTargetType(VeraPDFValidatorHelper.class))
on instancie notre classe d'assistance à l'intérieur de la thread-safe chargeur de classe et de créer un objet proxy pour lui si on peut appeler ça de l'extérieur.
BTW, static boolean threadSafeMode
n'existe que pour basculer entre l'ancien (dangereux) et la nouvelle (thread-safe) de l'utilisation de veraPDF afin de rendre le problème de la traçabilité pour l'intégration négative de cas de test.
package de.scrum_master.app;
import de.scrum_master.thread_safe.ThreadSafeClassLoader;
import org.verapdf.core.*;
import org.verapdf.pdfa.*;
import javax.xml.bind.JAXBException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Function;
import static de.scrum_master.thread_safe.ThreadSafeClassLoader.ObjectConstructionRules.forTargetType;
public class VeraPDFValidator implements Function<InputStream, byte[]> {
public static boolean threadSafeMode = true;
private static ThreadLocal<ThreadSafeClassLoader> threadSafeClassLoader =
ThreadSafeClassLoader.create( // Add one class per artifact for thread-safe classloader:
VeraPDFValidatorHelper.class, // - our own helper class
PDFAParser.class, // - veraPDF core
VeraGreenfieldFoundryProvider.class // - veraPDF validation-model
);
private String flavorId;
private Boolean prettyXml;
public VeraPDFValidator(String flavorId, Boolean prettyXml)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
this.flavorId = flavorId;
this.prettyXml = prettyXml;
}
@Override
public byte[] apply(InputStream inputStream) {
try {
VeraPDFValidatorHelper validatorHelper = threadSafeMode
? threadSafeClassLoader.get().newObject(forTargetType(VeraPDFValidatorHelper.class))
: new VeraPDFValidatorHelper();
return validatorHelper.validatePDF(inputStream, flavorId, prettyXml);
} catch (ModelParsingException | ValidationException | JAXBException | EncryptedPdfException e) {
throw new RuntimeException("invoking veraPDF validation", e);
}
}
}
Classe VeraPDFValidatorHelper
:
Dans cette catégorie, nous isoler tous les accès à la brisure de la bibliothèque. Rien de spécial ici, juste le code copié à partir de l'OP de la question. Tout ce qui est fait ici qui se passe à l'intérieur de la thread-safe chargeur de classe.
package de.scrum_master.app;
import org.verapdf.core.*;
import org.verapdf.pdfa.*;
import org.verapdf.pdfa.flavours.PDFAFlavour;
import org.verapdf.pdfa.results.ValidationResult;
import javax.xml.bind.JAXBException;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
public class VeraPDFValidatorHelper {
public byte[] validatePDF(InputStream inputStream, String flavorId, Boolean prettyXml)
throws ModelParsingException, ValidationException, JAXBException, EncryptedPdfException
{
VeraGreenfieldFoundryProvider.initialise();
PDFAFlavour flavour = PDFAFlavour.byFlavourId(flavorId);
PDFAValidator validator = Foundries.defaultInstance().createValidator(flavour, false);
PDFAParser loader = Foundries.defaultInstance().createParser(inputStream, flavour);
ValidationResult result = validator.validate(loader);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XmlSerialiser.toXml(result, baos, prettyXml, false);
return baos.toByteArray();
}
}