28 votes

Vous voulez exécuter la bibliothèque non threadsafe en parallèle - peut-il être fait en utilisant plusieurs chargeurs de classe?

Je travaille sur un projet où l'on utilise une bibliothèque qui n'est pas garanti thread-safe (et n'est-ce pas) et mono-thread en Java 8 flux de scénario, qui fonctionne comme prévu.

Nous aimerions utiliser les ruisseaux parallèles pour obtenir les branches basses de l'évolutivité de fruits.

Malheureusement cette cause, la bibliothèque de l'échec, probablement parce que l'une instance interfère avec des variables partagées avec l'autre instance, donc nous avons besoin d'isolement.

Je considérais en utilisant un chargeur de classe pour chaque instance (éventuellement fil local) qui, à ma connaissance, devrait signifier qu'à toutes fins utiles que j'ai l'isolement nécessaire, mais je suis pas familier avec délibérément la construction de chargeurs de classes à cette fin.

Est-ce la bonne solution? Comment dois-je faire pour avoir une bonne qualité de la production?


Edit: on m'a demandé des renseignements supplémentaires au sujet de la situation de déclenchement de la question, afin de mieux le comprendre. La question est encore à propos de la situation générale, ne fixant pas la bibliothèque.

J'ai le plein contrôle sur l'objet créé par la bibliothèque (qui est https://github.com/veraPDF/) comme tirés par

<dependency>
    <groupId>org.verapdf</groupId>
    <artifactId>validation-model</artifactId>
    <version>1.1.6</version>
</dependency>

en utilisant le référentiel d'artefacts.

<repositories>
    <repository>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
        <id>vera-dev</id>
        <name>Vera development</name>
        <url>http://artifactory.openpreservation.org/artifactory/vera-dev</url>
    </repository>
</repositories>

Pour l'instant, il est impossible de durcir la bibliothèque.


EDIT: on m'a demandé de montrer le code. Notre adaptateur de base est à peu près:

public class VeraPDFValidator implements Function<InputStream, byte[]> {
    private String flavorId;
    private Boolean prettyXml;

    public VeraPDFValidator(String flavorId, Boolean prettyXml) {
        this.flavorId = flavorId;
        this.prettyXml = prettyXml;
        VeraGreenfieldFoundryProvider.initialise();
    }

    @Override
    public byte[] apply(InputStream inputStream) {
        try {
            return apply0(inputStream);
        } catch (RuntimeException e) {
            throw e;
        } catch (ModelParsingException | ValidationException | JAXBException | EncryptedPdfException e) {
            throw new RuntimeException("invoking VeraPDF validation", e);
        }
    }

    private byte[] apply0(InputStream inputStream) throws ModelParsingException, ValidationException, JAXBException, EncryptedPdfException {
        PDFAFlavour flavour = PDFAFlavour.byFlavourId(flavorId);
        PDFAValidator validator = Foundries.defaultInstance().createValidator(flavour, false);
        PDFAParser loader = Foundries.defaultInstance().createParser(inputStream, flavour);
        ValidationResult result = validator.validate(loader);

        // do in-memory generation of XML byte array - as we need to pass it to Fedora we need it to fit in memory anyway.

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        XmlSerialiser.toXml(result, baos, prettyXml, false);
        final byte[] byteArray = baos.toByteArray();
        return byteArray;
    }
}

qui est une fonction des cartes à partir d'un InputStream (fournir un fichier PDF) d'un tableau d'octets (soit le rapport XML de sortie).

(Voir le code, j'ai remarqué qu'il y est un appel à l'initialisation dans le constructeur, qui peut être le coupable ici, dans mon cas particulier. Je voudrais encore une solution au problème générique.

13voto

BGR Points 4330

Nous avons fait face à des défis similaires. Les questions de l'habitude de se de propriétés statiques qui est devenu contre son gré "partagée" entre les différents threads.

À l'aide de différents chargeurs de classe a travaillé pour nous tant que nous pouvions garantir que les propriétés statiques ont été effectivement mis sur les classes chargées par notre chargeur de classe. Java peut avoir un peu de classes qui fournissent des propriétés ou des méthodes qui ne sont pas isolés entre les threads, ou ne sont pas thread-safe ('System.setProperties() et Security.addProvider() sont OK - tout canonique de la documentation sur ce sujet est la bienvenue, en passant).

Potentiellement réalisable et rapide de la solution - qui au moins peut vous donner une chance de tester cette théorie pour votre bibliothèque - est d'utiliser un moteur de servlet comme Jetty ou Tomcat.

Construire quelques guerres qui contiennent votre bibliothèque et de démarrer le processus en parallèle (1 par la guerre).

Lors de l'exécution de code à l'intérieur d'une servlet fil, l' WebappClassLoaders de ces moteurs de tentative de chargement d'une des classes à partir du chargeur de classe parent en premier (le même que le moteur) et si il ne trouve pas la classe, les tentatives de le charger à partir de l'pots/classes emballés avec la guerre.

Avec embarcadère, vous pouvez par programme de déploiement à chaud des guerres au contexte de votre choix et alors, en théorie, à l'échelle le nombre de processeurs (les guerres) comme requis.

Nous avons mis en place notre propre chargeur de classe en étendant URLClassLoader et ont pris l'inspiration de la Jetée de la Webapp chargeur de classe. Il n'est pas aussi dur un emploi en tant que, comme il semble.

Notre chargeur de classe fait exactement le contraire: il tente de charger une classe de pots de locaux pour le "package" d'abord , puis essaie de les obtenir à partir du chargeur de classe parent. Ceci garantit qu'une bibliothèque accidentellement chargé par le chargeur de classe parent n'est jamais considéré comme (premier). Notre "paquet" est en fait un pot qui contient d'autres pots/bibliothèques avec un personnalisé du fichier de manifeste.

L'affichage de cette classe code de chargeur "comme" ne serait pas beaucoup de sens (et de créer quelques problèmes de droit d'auteur). Si vous voulez explorer cette voie plus loin, je peux essayer de venir avec un squelette.

Source de la Jetée WebappClassLoader

8voto

Nicolas Filotto Points 32004

La réponse dépend en réalité de ce que votre bibliothèque s'appuie sur:

  1. Si votre bibliothèque s'appuie sur au moins une bibliothèque native, à l'aide de ClassLoaders pour isoler votre bibliothèque de code ne va pas aider, car, selon la JNI de Spécification, il n'est pas permis de charger le même JNI bibliothèque native dans plus d'une classe loader tel que vous vous retrouvez avec un UnsatisfiedLinkError.
  2. Si vous avez de la bibliothèque s'appuie sur au moins une ressource externe qui n'est pas destiné à être partagé, comme par exemple, un fichier et qui est modifié par votre bibliothèque, vous pourriez vous retrouver avec un système complexe de bugs et/ou de la corruption de la ressource.

En supposant que vous n'êtes pas dans les cas énumérés ci-dessus, de manière générale, si une classe est connue en tant que non thread-safe et ne pas modifier les champs statiques, à l'aide d'une instance dédiée de cette classe par appel ou par fil est assez bon comme l'instance de classe n'est plus partagé.

Votre bibliothèque repose évidemment et modifie certains champs statiques qui ne sont pas destinés à être partagés, vous avez en effet besoin d'isoler les classes de votre bibliothèque dans un dédié ClassLoader et bien sûr, assurez-vous que votre fils ne partage pas le même ClassLoader.

Pour cela, vous pouvez créer simplement un URLClassLoader à laquelle vous souhaitez indiquer l'emplacement de votre bibliothèque URL (à l'aide d' URLClassLoader.newInstance(URL[] urls, ClassLoader parent)), puis par la réflexion, vous récupérerez la classe de votre bibliothèque correspondant au point d'entrée et d'appeler votre cible méthode. Pour éviter la construction d'un nouveau URLClassLoader lors de chaque appel, vous pourriez envisager de s'en remettre à un ThreadLocal pour stocker l' URLClassLoader ou Class ou Method exemple être utilisé pour un thread donné.


Donc, voici comment vous pourriez procéder:

Disons que le point d'entrée de ma bibliothèque, c'est la classe Foo qui ressemble à ceci:

package com.company;

public class Foo {

    // A static field in which we store the name of the current thread
    public static String threadName;

    public void execute() {
        // We print the value of the field before setting a value
        System.out.printf(
            "%s: The value before %s%n", Thread.currentThread().getName(), threadName
        );
        // We set a new value
        threadName = Thread.currentThread().getName();
        // We print the value of the field after setting a value
        System.out.printf(
            "%s: The value after %s%n", Thread.currentThread().getName(), threadName
        );
    }
}

Cette classe est clairement pas thread-safe et la méthode de execute modifie la valeur d'un champ statique qui n'est pas destiné à être modifié par les threads simultanés comme dans votre cas d'utilisation.

En supposant que pour le lancement de ma bibliothèque, j'ai tout simplement besoin de créer une instance d' Foo et invoquer la méthode execute. Je pourrais stocker le correspondant Method en ThreadLocal pour le récupérer par la réflexion qu'une seule fois par thread à l'aide de ThreadLocal.withInitial(Supplier<? extends S> supplier) de la prochaine:

private static final ThreadLocal<Method> TL = ThreadLocal.withInitial(
    () -> {
        try {
            // Create the instance of URLClassLoader using the context 
            // CL as parent CL to be able to retrieve the potential 
            // dependencies of your library assuming that they are
            // thread safe otherwise you will need to provide their 
            // URL to isolate them too
            URLClassLoader cl = URLClassLoader.newInstance(
                new URL[]{/* Here the URL of my library*/},
                Thread.currentThread().getContextClassLoader()
            );
            // Get by reflection the class Foo
            Class<?> myClass = cl.loadClass("com.company.Foo");
            // Get by reflection the method execute
            return myClass.getMethod("execute");
        } catch (Exception e) {
            // Here deal with the exceptions
            throw new IllegalStateException(e);
        }
    }
);

Et enfin, nous allons simuler un concurrent à l'exécution de ma bibliothèque:

// Launch 50 times concurrently my library
IntStream.rangeClosed(1, 50).parallel().forEach(
    i -> {
        try {
            // Get the method instance from the ThreadLocal
            Method myMethod = TL.get();
            // Create an instance of my class using the default constructor
            Object myInstance = myMethod.getDeclaringClass().newInstance();
            // Invoke the method
            myMethod.invoke(myInstance);
        } catch (Exception e) {
            // Here deal with the exceptions
            throw new IllegalStateException(e);
        }
    }
);

Vous obtiendrez une sortie du type suivant qui montre que nous n'avons pas de conflits entre les threads et les fils correctement la réutilisation de ses correspondants de classe/valeur du champ à partir d'un appel d' execute à un autre:

ForkJoinPool.commonPool-worker-7: The value before null
ForkJoinPool.commonPool-worker-7: The value after ForkJoinPool.commonPool-worker-7
ForkJoinPool.commonPool-worker-7: The value before ForkJoinPool.commonPool-worker-7
ForkJoinPool.commonPool-worker-7: The value after ForkJoinPool.commonPool-worker-7
main: The value before null
main: The value after main
main: The value before main
main: The value after main
...

Depuis cette approche permettra de créer un ClassLoader par fil, assurez-vous d'appliquer cette approche à l'aide d'un pool de threads avec un nombre fixe de threads et le nombre de threads doit être choisi à bon escient pour éviter de manquer de mémoire, car un ClassLoader n'est pas libre en terme de mémoire, de sorte que vous besoin de limiter le montant total des instances en fonction de votre taille de segment de mémoire.

Une fois que vous avez terminé avec votre bibliothèque, vous devriez nettoyage de l' ThreadLocal pour chaque thread de votre pool de threads pour empêcher les fuites de mémoire et de le faire ici est de savoir comment vous pourriez procéder:

// The size of your the thread pool
// Here as I used for my example the common pool, its size by default is
// Runtime.getRuntime().availableProcessors()
int poolSize = Runtime.getRuntime().availableProcessors();
// The cyclic barrier used to make sure that all the threads of the pool
// will execute the code that will cleanup the ThreadLocal
CyclicBarrier barrier = new CyclicBarrier(poolSize);
// Launch one cleanup task per thread in the pool
IntStream.rangeClosed(1, poolSize).parallel().forEach(
    i -> {
        try {
            // Wait for all other threads of the pool
            // This is needed to fill up the thread pool in order to make sure 
            // that all threads will execute the cleanup code
            barrier.await();
            // Close the URLClassLoader to prevent memory leaks
            ((URLClassLoader) TL.get().getDeclaringClass().getClassLoader()).close();
        } catch (Exception e) {
            // Here deal with the exceptions
            throw new IllegalStateException(e);
        } finally {
            // Remove the URLClassLoader instance for this thread
            TL.remove();
        }
    }
);

7voto

kriegaex Points 6365

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();
  }
}

3voto

raphw Points 6008

En isolant une bibliothèque sur un chargeur de classe par thread, vous pouvez garantir à toutes les classes de la simultanéité des propriétés comme vous le suggérez. La seule exception sont les bibliothèques qui, explicitement interagir avec la classe de bootstrap loader ou le système de chargeur de classe. Il est possible d'injecter des classes dans ces chargeurs de classe, soit par la réflexion ou l' Instrumentation API. Un exemple d'une telle fonctionnalité serait Mockito inline maquette maker qui n'a toutefois pas été victime d'une simultanéité des contraintes de ma connaissance.

Mise en œuvre d'un chargeur de classe avec ce comportement n'est pas trop difficile. La solution la plus simple serait d'inclure explicitement le nécessaire pots dans votre projet, par exemple en tant que ressource. De cette façon, vous pouvez utiliser un URLClassLoader pour le chargement de vos classes:

URL url = getClass().getClassLoader().getResource("validation-model-1.1.6.jar");
ClassLoader classLoader = new URLClassLoader(new URL[] {url}, null);

Par référencement null que le super chargeur de classe de l' URLClassLoader (deuxième argument), vous garantie qu'il n'y a pas de classes en dehors des classes de bootstrap. Notez que vous ne pouvez pas utiliser toutes les classes de cette classe créée chargeur de l'extérieur. Toutefois, si vous ajoutez un deuxième récipient contenant une classe qui déclenche votre logique, vous pouvez offrir un point d'entrée qui devient accessible sans réflexion:

class MyEntryPoint implements Callable<File> {
  @Override public File call() {
    // use library code.
  }
}

Il suffit d'ajouter cette classe à son propre pot et de le fournir en tant que deuxième élément de ce qui précède URL tableau. Notez que vous ne pouvez pas faire référence à un type de bibliothèque en tant que valeur de retour de ce type ne seront pas disponibles pour le consommateur qui vit à l'extérieur de la classe loader qui permet d'utiliser le point d'entrée.

En enveloppant le chargeur de classe de création en ThreadLocal, vous pouvez garantir la classe chargeurs uniqunes:

class Unique extends ThreadLocal<ClassLoader> implements Closable {
  @Override protected ClassLoader initialValue() {
    URL validation = Unique.class.getClassLoader()
                          .getResource("validation-model-1.1.6.jar");
    URL entry = Unique.class.getClassLoader()
                          .getResource("my-entry.jar");
    return new URLClassLoader(new URL[] {validation, entry}, null);
  }

  @Override public void close() throws IOException {
    get().close(); // If Java 7+, avoid handle leaks.
    set(null); // Make class loader eligable for GC.
  }

  public File doSomethingLibrary() throws Exception {
    Class<?> type = Class.forName("pkg.MyEntryPoint", false, get());
    return ((Callable<File>) type.newInstance()).call();
  }
}

Notez que la classe chargeurs sont des objets coûteux et doivent être déréférencés lorsque vous n'en avez plus besoin, même si un thread continue à vivre. Aussi, pour éviter de fichier fuites, vous devez fermer un URLClassLoader précédemment à un déréférencement.

Enfin, pour continuer à l'utiliser Maven est de la résolution des dépendances et afin de simplifier votre code, vous pouvez créer une salle de Maven module où vous définissez votre code de point d'entrée et de déclarer votre Maven dépendances de la bibliothèque. Sur l'emballage, l'utilisation de l'Maven ombre plugin pour créer un Uber pot qui comprend tout ce dont vous avez besoin. De cette façon, vous avez seulement besoin de fournir un seul bocal à votre URLClassLoader et n'ont pas besoin pour s'assurer que tous (transitif) les dépendances à la main.

3voto

efekctive Points 1187

Cette réponse est fondée sur l'original de mes "plugin" commentaire. Et on commence avec un chargeur de classe qui hérite de démarrage et les extensions de classe chargeurs seulement.

package safeLoaderPackage;

import java.net.URL;
import java.net.URLClassLoader;

public final class SafeClassLoader extends URLClassLoader{
    public SafeClassLoader(URL[] paths){
        super(paths, ClassLoader.getSystemClassLoader().getParent());
    }   
}

C'est la seule classe qui doit être incluse dans le manuel du chemin de classe. Cette url chargeur de classe hérite de l'objet parent de ClassLoader.getSystemClassLoader(). Il comprend le démarrage et les extensions de la classe loader. Il n'a aucune notion de la classe de chemin d'accès utilisé par l'utilisateur.

Prochaine

package safeLoaderClasses;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class SecureClassLoaderPlugin <R> {

    private URL[] paths;
    private Class[] args;
    private String method;
    private String unsafe;

    public void setMethodData(final String u, final URL[] p, String m, Class[] a){
        method = m;
        args = a;
        paths = p;
        unsafe = u;
    }

    public Collection<R> processUnsafe(Object[][] p){
        int i;
        BlockingQueue<Runnable> q;
        ArrayList<R> results = new ArrayList<R>();
        try{
            i = p.length;
            q = new ArrayBlockingQueue<Runnable>(i);
            ThreadPoolExecutor tpe = new ThreadPoolExecutor(i, i, 0, TimeUnit.NANOSECONDS, q);
            for(Object[] params : p)
                tpe.execute(new SafeRunnable<R>(unsafe, paths, method, args, params, results));
            while(tpe.getActiveCount() != 0){
                Thread.sleep(10);
            }
            for(R r: results){
                System.out.println(r);
            }
            tpe.shutdown();
        }
        catch(Throwable t){

        }
        finally{

        }
        return results;
    }
}

et

package safeLoaderClasses;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;

import safeLoaderInterface.SafeClassLoader;

class SafeRunnable <R> implements Runnable{
    final URL[] paths;
    final private String unsafe;
    final private String method;
    final private Class[] args;
    final private Object[] processUs;
    final ArrayList<R> result;

    SafeRunnable(String u, URL[] p, String m, Class[] a, Object[] params, ArrayList<R> r){
        unsafe = u;
        paths = p;
        method = m;
        args = a;
        processUs = params;
        result = r;
    }

    public void run() {
        Class clazz;
        Object instance;
        Method m;
        SafeClassLoader sl = null;

        try{
            sl = new SafeClassLoader(paths);
            System.out.println(sl);

            clazz = sl.loadClass(unsafe);
            m = clazz.getMethod(method, args);
            instance = clazz.newInstance();
            synchronized(result){
                result.add((R) m.invoke(instance, processUs));
            }
        }
        catch(Throwable t){
            t.printStackTrace();
        }
        finally{
            try {
                sl.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

sont le plugin jar. Pas de lambdas. Juste un pool de threads exécuteur testamentaire. Chaque thread ajoute à une liste de résultats après l'exécution.

Les génériques ont besoin de polissage, mais j'ai testé ces contre cette classe (qui réside dans un autre bocal)

package stackoverflow4;

public final class CrazyClass {

    static int i = 0;

    public int returnInt(){
        System.out.println(i);
        return 8/++i;
    }
}

Ce serait le moyen de se connecter à partir de son code. Le chemin d'accès à la classe de chargement doit être inclus, car il est perdu avec la getParent() appel

private void process(final String plugin, final String unsafe, final URL[] paths) throws Exception{
        Object[][] passUs = new Object[][] {{},{}, {},{}, {},{},{},{},{},{}};
        URL[] pathLoader = new URL[]{new File(new String(".../safeLoader.jar")).toURI().toURL(), 
                new File(new String(".../safeLoaderClasses.jar")).toURI().toURL()};
        //instantiate the loader
        SafeClassLoader sl = new SafeClassLoader(pathLoader); 
        System.out.println(sl);
        Class clazz = sl.loadClass("safeLoaderClasses.SecureClassLoaderPlugin");
        //Instance of the class that loads the unsafe jar and launches the thread pool executor
        Object o = clazz.newInstance(); 
        //Look up the method that set ups the unsafe library
        Method m = clazz.getMethod("setMethodData", 
                new Class[]{unsafe.getClass(), paths.getClass(), String.class, new Class[]{}.getClass()});
        //invoke it
        m.invoke(o, new Object[]{unsafe,paths,"returnInt", new Class[]{}});
        //Look up the method that invokes the library
        m = clazz.getMethod("processUnsafe", new Class[]{ passUs.getClass()});
        //invoke it
        o = m.invoke(o, passUs);
        //Close the loader
        sl.close();
    }

avec jusqu'à 30+ threads et il semble fonctionner. Le plugin utilise un autre chargeur de classe et de chacun des threads utilisent leur propre chargeur de classe. Après la sortie de la méthode, tout est gc ed.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X