2011-08-28 3 views
1

J'ai décidé d'implémenter le chargement dynamique des classes dans mon application web glassfish, afin de l'essayer et de supporter les petits plugins qui pourraient être chargés et exécutés par l'application web à runtime.comment créer une application web plugin chargée dynamiquement

I ajouté la classe suivante:

public class PluginManager { 

    private static final String dropBoxDir = "file:///path/to/dropbox/"; 
    private static final URLClassLoader dropBoxClassLoader; 
    static { 
     try { 
     URL dropBoxURL = new URL(dropBoxDir); 
     dropBoxClassLoader = URLClassLoader.newInstance(new URL[]{dropBoxURL}); 
     } 
     catch (MalformedURLException mue) { 
     throw new RuntimeException("MalformedURLException thrown during PluginManager initialization - the hardcoded URL " + dropBoxDir + " must be invalid.", mue); 
     } 
    } 

    //this method is called by a web service 
    public static void runPluginFromDropBox(String fullClassName) { 
     try { 
     //load the plugin class 
     Class<?> pluginClass = dropBoxClassLoader.loadClass(fullClassName); 
     //instantiate it 
     Runnable plugin = (Runnable)pluginClass.newInstance(); 
     //call its run() method 
     plugin.run(); 
     } 
     catch (ClassNotFoundException cnfe) { 
     throw new RuntimeException("The class file for " + fullClassName + " could not be located at the designated directory (" + dropBoxDir + "). Check that the specified class name is correct, and that its file is in the right location.", cnfe); 
     } 
     catch (InstantiationException ie) { 
     throw new RuntimeException("InstantiationException thrown when attempting to instantiate the plugin class " + fullClassName + " - make sure it is an instantiable class with a no-arg constructor.", ie); 
     } 
     catch (IllegalAccessException iae) { 
     throw new RuntimeException("IllegalAccessException thrown when attempting to instantiate the plugin class " + fullClassName + " - make sure the class and its no-arg constructor have public access.", iae); 
     } 
     catch (ClassCastException cce) { 
     throw new RuntimeException("Plugin instance could not be cast to Runnable - plugin classes must implement this interface.", cce); 
     } 
    } 
} 

Puis, dans un projet séparé, je créé un plugin de test:

public class TestPlugin implements Runnable { 
    @Override 
    public void run() { 
     System.out.println("plugin code executed"); 
    } 
} 

Je déployé l'application Web, puis compilé TestPlugin dans un fichier .class et déposé dans le dossier désigné. J'ai appelé un service Web qui atteint runPluginFromDropBox() avec le nom de la classe et a obtenu la sortie attendue.

Tout cela a fonctionné comme une preuve de concept, mais mon plugin est effectivement inutile à moins qu'il puisse être mis au courant des classes de mon application web. J'ai lu depuis que .war est conçu uniquement comme une application autonome, et non destiné à être sur les chemins de classes d'autres bibliothèques, ce qui n'est pas de bon augure pour ce petit projet parallèle.

J'ai jeté un coup d'oeil à cette discussion: Extending Java Web Applications with plugins et avoir l'impression de plonger dans un marais de défis de conception sans grande raison et devrait tourner autour. Cependant, ce post est un peu vieux et est spécifique à Tomcat, alors j'ai juste pensé que je demanderais s'il y a un moyen simple pour moi d'aborder cela sans un cadre de troisième partie élaboré.

Répondre

2

Les classes d'un fichier war sont chargées par un chargeur de classe spécifique, afin d'isoler la guerre des autres applications Web déployées sur le même serveur et de pouvoir annuler le déploiement de la guerre. Pour connaître les classes webapp, le classloader de votre plugin doit avoir comme parent le classloader webapp. Je ne suis pas conscient de tous les problèmes que vous pourriez avoir en utilisant un chargeur de classe supplémentaire, mais je soupçonne que vous pourriez avoir des fuites de mémoire et d'autres problèmes (valeurs statiques gardées en mémoire, etc.) lorsque le conteneur annulera et redéployera la webapp. Et vous pourriez aussi avoir des différences entre les conteneurs.

+0

Quand vous dites "plugin classloader", voulez-vous dire le 'URLClassLoader' que j'utilise pour charger le plugin? Ce code est dans l'application web si. Je me demande comment il serait difficile pour un plugin d'avoir la guerre sur son chemin de classe en quelque sorte. Par exemple, si l'application web a une classe 'Foo', mon plugin pourrait' importer mywebapp.Foo; 'et ensuite interagir avec' Foo' dans sa méthode 'run()' - ou suis-je naïvement hors piste? –

+0

Oui, je parle du chargeur de classe utilisé pour charger la classe plugin. Et ma réponse est que ce classloader devrait avoir le classloader de la guerre comme classloader parent. Regardez les constructeurs de URLClassLoader. Il y en a une qui prend comme argument un ClassLoader parent. Notez que l'importation est utilisée uniquement au moment de la compilation. Au moment de la compilation, il suffit de définir le classpath approprié pour avoir accès aux classes webapp. Au moment de l'exécution, vous avez besoin du classloader de la guerre en tant que chargeur de classe parent. –

+0

D'accord, je comprends ce que vous dites à propos du classloader parent. Cependant, je pense que c'est déjà le cas - à moins que je ne me trompe, l'appel 'newInstance()' utilise la valeur par défaut 'ClassLoader' comme parent, ce qui devrait être l'application web. Donc, je ne suis pas sûr de savoir pourquoi c'est pertinent à moins que je ne manque quelque chose. Je pense que ma question se résume à la prise de conscience au moment de la compilation de la bibliothèque de plug-ins des classes de l'application web. Ai-je manqué quelque chose de simple ici - puis-je juste mettre la guerre sur le classpath du plugin? –

Questions connexes