37 votes

Chargeur de service Java avec plusieurs Classloaders

Quelles sont les meilleures pratiques pour l'utilisation de ServiceLoader dans un Environnement avec plusieurs chargeurs de classes? La documentation recommande de créer et d'enregistrer une seule instance du service au moment de l'initialisation:

private static ServiceLoader<CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class);

Ce serait d'initialiser le ServiceLoader en utilisant le contexte actuel du chargeur de classe. Supposons maintenant que cet extrait est contenue dans une classe chargée partagé un chargeur de classe dans un conteneur web et plusieurs applications web souhaitez définir leurs propres implémentations de service. Ce ne serait pas obtenir ramassé dans le code ci-dessus, il pourrait même être possible que le chargeur est initialisé à l'aide de la première webapps contexte du chargeur de classe et de fournir la mauvaise mise en œuvre à d'autres utilisateurs.

Toujours la création d'une nouvelle ServiceLoader semble un gaspillage de performance sage, car il a d'énumérer et d'analyser les fichiers de service à chaque fois. Edit: Cela peut même être un gros problème de performance comme indiqué dans cette réponse au sujet de java XPath mise en œuvre.

Comment faire d'autres bibliothèques de gérer cela? Ils ne le cache de la mise en œuvre par des chargeurs de classes, font-ils d'analyse de leur configuration, à chaque fois, ou ont-ils simplement ignorer ce problème et ne fonctionne que pour un chargeur de classe?

67voto

David Blevins Points 10502

Personnellement, je n'aime pas l' ServiceLoader , en toutes circonstances. C'est lent et inutilement coûteux et il ya peu que vous pouvez faire pour l'optimiser.

Je trouve aussi que c'est un peu limité -- vous avez vraiment besoin de sortir de votre chemin si vous voulez faire plus de recherche par type seul.

xbean-finder ResourceFinder

  • ResourceFinder est lui-même un fichier java capable de remplacer le ServiceLoader d'utilisation. Copier/coller la réutilisation n'est pas un problème. C'est un fichier java et est l'ASL 2.0 licence et est disponible à partir de Apache.

Avant notre attention trop court, voici comment il est possible de remplacer une ServiceLoader

ResourceFinder finder = new ResourceFinder("META-INF/services/");
List<Class<? extends Plugin>> impls = finder.findAllImplementations(Plugin.class);

Cela permettra de trouver toutes les de la META-INF/services/org.acme.Plugin des implémentations dans votre classpath.

Remarque il ne fait pas instancier toutes les instances. De choisir celle(s) que vous voulez et vous êtes un newInstance() appel loin de disposer d'une instance.

Pourquoi est-ce agréable?

  • Comment est-il difficile d'appeler newInstance() avec une bonne gestion des exceptions? Pas dur.
  • Avoir la liberté d'instancier seulement ceux que vous voulez, c'est agréable.
  • Maintenant vous pouvez soutenir constructeur args!

Le rétrécissement de la zone de recherche

Si vous voulez juste vérifier les URLs, vous pouvez le faire facilement:

URL url = new File("some.jar").toURI().toURL();
ResourceFinder finder = new ResourceFinder("META-INF/services/", url);

Ici, seuls les "some.jar' objet d'une recherche sur tout l'utilisation de ce ResourceFinder instance.

Il y a aussi une classe utilitaire appelé UrlSet ce qui peut rendre la sélection des Url depuis le classpath très facile.

ClassLoader webAppClassLoader = Thread.currentThread().getContextClassLoader(); 
UrlSet urlSet = new UrlSet(webAppClassLoader);
urlSet = urlSet.exclude(webAppClassLoader.getParent());
urlSet = urlSet.matching(".*acme-.*.jar");

List<URL> urls = urlSet.getUrls();

Autre "service" styles

Disons que vous voulez appliquer l' ServiceLoader type de concept à la refonte de gestion des URL et trouver/chargement de la java.net.URLStreamHandler pour un protocole spécifique.

Voici comment vous pourriez disposition de les services dans votre classpath:

  • META-INF/java.net.URLStreamHandler/foo
  • META-INF/java.net.URLStreamHandler/bar
  • META-INF/java.net.URLStreamHandler/baz

foo est un fichier texte qui contient le nom de la mise en œuvre des services tout comme avant. Maintenant, dire que quelqu'un crée un foo://... d'URL. Nous pouvons trouver de la mise en œuvre pour que rapidement, par l'intermédiaire de:

ResourceFinder finder = new ResourceFinder("META-INF/");
Map<String, Class<? extends URLStreamHandler>> handlers = finder.mapAllImplementations(URLStreamHandler.class);
Class<? extends URLStreamHandler> fooHandler = handlers.get("foo");

Autre "service" styles 2

Supposons que vous vouliez mettre certaines informations de configuration dans votre fichier de service, de sorte qu'il contient plus que juste un nom de classe. Voici un autre style qui résout services à des fichiers de propriétés. Par convention, l'une des clés serait les noms de classe et les autres touches serait injectable propriétés.

Donc, ici, red est un fichier de propriétés

  • META-INF/org.acme.Plugin/red
  • META-INF/org.acme.Plugin/blue
  • META-INF/org.acme.Plugin/green

Vous pouvez regarder les choses de la même façon qu'avant.

ResourceFinder finder = new ResourceFinder("META-INF/");

Map<String,Properties> plugins = finder.mapAllProperties(Plugin.class.getName());
Properties redDefinition = plugins.get("red");

Voici comment vous pouvez utiliser ces propriétés avec xbean-reflect, une autre petite bibliothèque qui peut vous donner cadre-gratuit Cio. Vous donnez simplement le nom de la classe et quelques paires nom / valeur, et il permettra de construire et de les injecter.

ObjectRecipe recipe = new ObjectRecipe(redDefinition.remove("className").toString());
recipe.setAllProperties(redDefinition);

Plugin red = (Plugin) recipe.create();
red.start();

Voici à quoi cela peut ressembler "orthographié" en forme longue:

ObjectRecipe recipe = new ObjectRecipe("com.example.plugins.RedPlugin");
recipe.setProperty("myDateField","2011-08-29");
recipe.setProperty("myIntField","100");
recipe.setProperty("myBooleanField","true");
recipe.setProperty("myUrlField","http://www.stackoverflow.com");
Plugin red = (Plugin) recipe.create();
red.start();

L' xbean-reflect bibliothèque est une étape au-delà de l'intégré dans JavaBeans API, mais un peu mieux, sans vous obliger à aller tout le chemin vers un plein sur Cio cadre comme Guice ou au Printemps. Il prend en charge l'usine de méthodes et constructeur args et setter/champ d'injection.

Pourquoi le ServiceLoader si limitée?

Obsolète code dans la JVM des dommages-intérêts le langage Java lui-même. Beaucoup de choses sont taillées pour l'os avant d'être ajoutés à la JVM, parce que vous ne pouvez pas ajuster par la suite. L' ServiceLoader est un excellent exemple de cela. L'API est limitée et OpenJDK mise en œuvre est quelque part autour de 500 lignes, y compris javadoc.

Il n'y a rien de compliqué, il y et son remplacement est facile. Si cela ne fonctionne pas pour vous, ne l'utilisez pas.

Classpath portée

Api de côté, dans la pure pratique de limiter le champ de l'Url de recherche est la vraie solution à ce problème. App les Serveurs ont beaucoup d'Url par eux-mêmes, pas y compris les bocaux dans votre application. Tomcat 7 sur OSX, par exemple, a environ 40~ Url du StandardClassLoader seul (ce qui est le parent de tous les webapp chargeurs de classes).

Le plus grand de votre serveur d'application le plus à même une simple recherche prendra.

La mise en cache n'aide pas si vous l'intention de chercher plus d'une entrée. Ainsi, il peut ajouter un peu de mal fuites. Peut être un réel perdre-perdre du scénario.

Étroite de l'Url vers le 5 ou 12 qui compte vraiment pour vous et vous pouvez faire toutes sortes de services de chargement et de ne jamais l'avis de la frapper.

6voto

Peter Points 400

Avez-vous essayé d'utiliser la version à deux arguments afin que vous puissiez spécifier quel chargeur de classe utiliser? C.Ie, java.util.serviceLoader.load (Classe , ClassLoader)

4voto

Tassos Bassoukos Points 6872

Mu.

Dans un 1x WebContainer <-> Nx WebApplication système, le ServiceLoader instancié dans le WebContainer ne sera pas ramasser toutes les classes définies dans les Applications, seulement ceux dans le conteneur. Un ServiceLoader instancié dans une WebApplication permettra de détecter les classes définies dans la demande à ceux définis dans le conteneur.

Gardez à l'esprit d'Applications devront être séparés, sont conçues de cette manière, les choses vont se briser si vous essayez de contourner cela, et ils ne sont pas la méthode et système pour étendre le conteneur - si votre bibliothèque est un Pot simple, il suffit de déposer dans l'extension appropriée dossier du conteneur.

3voto

Clark Bao Points 1067

J'aime vraiment Neil réponse dans le lien que j'ai ajouté dans mon commentaire. En raison de j'ai la même experences dans mon dernier projet.

"Une autre chose à garder à l'esprit avec ServiceLoader est d'essayer de résumé le mécanisme de recherche. Le mécanisme de publication est très agréable et propre et déclarative. Mais la recherche (via java.util.ServiceLoader) est aussi laid que l'enfer, mis en œuvre comme un chemin de classe scanner qui rompt horriblement si vous placez le code dans n'importe quel environnement (comme OSGi ou Java EE) qui n'ont pas de visibilité à l'échelle mondiale. Si votre code est enchevêtrée avec cela, alors vous aurez du mal à le faire fonctionner sur OSGi plus tard. Mieux d'écrire une abstraction que l'on peut remplacer le moment venu."

J'ai rencontré ce problème dans OSGi environnement en fait c'est juste de l'éclipse dans notre projet. Mais heureusement, j'ai résolu en temps opportun. Ma solution est d'utiliser une classe à partir du plugin je veux charger ,et d'obtenir des chargeurs de classes à partir d'elle. Qui sera valide d'un correctif. Je n'ai pas utilisé la norme ServiceLoader, Mais mes processus est assez similaire, l'utilisation des propriétés pour définir les classes de plugin j'ai besoin de charger. Et je sais qu'il existe une autre façon de connaître chaque plugin du chargeur de classe. Mais au moins, je n'ai pas besoin de les utiliser.

Honnête, je n'aime pas les génériques utilisés dans ServiceLoader. Parce que c'limitée que l'on ServiceLoader ne peut gérer que des classes pour une interface. Bien est-il vraiment utile? Dans mon application, il ne vous force pas à cette restriction. Je viens d'utiliser une mise en œuvre de loader pour charger toutes les classes de plugin. Je ne vois pas la raison de l'utilisation de deux ou plus. En raison de la consommation peut savoir à partir des fichiers de configuration sur les relations entre les interfaces et la mise en œuvre.

1voto

Jörn Horstmann Points 18118

Cette question semble être plus compliqué que j'ai d'abord prévu. Comme je le vois c', il existe 3 stratégies possibles pour faire face à ServiceLoaders.

  1. Utilisation statique ServiceLoader instance et uniquement en charge le chargement de classes à partir de le même chargeur de classe comme l'un tient le ServiceLoader de référence. Cette serait de travailler quand

    • La configuration du service et la mise en commun des chargeurs de classes et tous les enfants les chargeurs de classe sont en utilisant le même de la mise en œuvre. L'exemple dans la documentation est orientée vers theis cas d'utilisation.

      Ou

    • La Configuration et la mise en œuvre sont mis dans chaque enfant du chargeur de classe et déployée le long de chaque webapp en WEB-INF/lib.

    Dans ce scénario, il n'est pas possible de déployer le service dans une commune du chargeur de classe et de laisser chaque webapp choisir son propre service de mise en œuvre.

  2. Initialiser le ServiceLoader à chaque accès, en passant le contexte du chargeur de classe de le thread courant en tant que second paramètre. Cette approche est prise à la JAXP et api JAXB, même s'ils utilisent leur propre FactoryFindermise en œuvre au lieu de ServiceLoader. Il est ainsi possible de regrouper un analyseur xml avec une webapp et qu'automatiquement obtenir ramassé par exemple, DocumentBuilderFactory#newInstance.

    Cette recherche a un impact sur les performances, mais dans le cas de l'analyse xml le temps de regarder jusqu'à la mise en œuvre est faible par rapport au temps nécessaire pour en fait analyser un document xml. Dans la bibliothèque, je suis prévoyant l'usine lui-même est assez simple pour la recherche de temps de dominer le rendement.

  3. En quelque sorte le cache de la mise en œuvre de la classe avec le contexte du chargeur de classe comme la clé. Je ne suis pas entièrement sûr si cela est possible dans tous les cas ci-dessus sans causer des fuites de mémoire.

En conclusion, je vais probablement être en ignorant ce problème et exiger que la bibliothèque est déployé à l'intérieur de chaque webapp, c'est à dire l'option 1b ci-dessus.

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