Injection de dépendances dans une WebApp embarquée dans Jetty

…ou comment se battre quelques heures avec les ClassLoaders…

Le contexte

J’aimerais bien pouvoir démarrer une WebApp depuis un Jetty embarqué dans mon application et rendre accessible des objets de mon application dans la WebApp.

Le problème

Mon application et ma WebApp ont deux ClassLoaders bien distincts = Problème de ‘ClassCastException’

L’explication de la solution

Pour ma part, l’application en question qui va démarrer la tambouille et partager des objets est le bus de service Petals. La WebApp, elle, est développée comme une WebApp classique, elle n’a pas de lien avec mon application sinon une interface, que je qualifierais, d’échange.

L’implémentation de cette interface est injectée par mon application dans le contexte de la WebApp. Les problèmes commencent ici. En fait le seul problème est un soucis de ClassLoader. En effet, dans la WebApp, le ClassLoader est créé avec le contenu de WEB-INF/lib/*, de WEB-INF/classes/* et du ClassLoader du container Web (on fait souvent le similaire pour expliquer le ClassLoader d’un composant JBI dans le container JBI Petals). Des ressources créées dans mon application ont (généralement mais pas toujours) un ClassLoader spécifique à l’application. Si je les injecte dans la WebApp via le ServletContext, que je les récupère et que j’essaie de les ‘caster’, je me retrouve avec un beau ClassCastException du style ‘foo.Bar cannot be cast to foo.Bar‘. Les personnes développant des applications standard, ne se soucient généralement pas des histoires de ClassLoader et peuvent donc rester perplexes devant un message de la sorte. Quand on développe un container de n’importe quel type, on se dit que la il y a un soucis de ClassLoader ie on essaye de ‘caster’ une ressource qui n’a pas le même ClassLoader que celui du Thread courant. Si on regarde plus finement en passant en mode debug ce qu’il se passe lorsque l’on fait quelque chose du style :

Bar bar = (Bar)o;

On s’aperçoit bien que la JVM va appeler la méthode loadClass(String className) du ClassLoader à un moment où à un autre et que là ca va poser problème. La solution à notre problème est simplement de créer notre propre ClassLoader et ‘d’overrider’ loadClass(String className) intelligemment pour pouvoir enfin accéder à nos objets dans notre WebApp.

Le code

Disons que j’ai une classe foo.BarImpl qui implémente foo.Bar. Je veux passer une instance de cette classe BarImpl à ma WebApp via le contexte de l’application Web (quelques commentaires dans le code suivant).

package foo.bar.myapp;

import java.io.File;
import java.io.IOException;

import org.mortbay.jetty.Server;
import org.mortbay.jetty.webapp.WebAppContext;

public class MyApp {

       public void startServer() Exception {

        File webapp = new File("mywebapp.war");

        if ((webapp == null) || !webapp.exists()) {
            throw new Exception("Can not find the Web application");
        }

        this.server = new Server(9999);
        WebAppContext context = new WebAppContext();
        context.setContextPath("/");
        context.setWar(webapp.getAbsolutePath());

        Foo foo = new FooImpl();
        try {
            // create classloader with current object classloader
            MyClassLoader classloader = new MyClassLoader(foo.getClass()
                    .getClassLoader(), context);
            context.setClassLoader(classloader);
        } catch (IOException e1) {
        }
// put the object in the Jetty context ie in the servlet context
        context.getServletContext().setAttribute("foo", foo);
        context.setAttribute("foo", foo);

        this.server.setHandler(context);
        try {
            this.server.start();
        } catch (Throwable e) {
        }
    }
}

La subtilité dans le code précédent est l’utilisation de ‘MyClassLoader‘. Ici ‘MyClassLoader‘ étend ‘org.mortbay.jetty.webapp.WebAppClassLoader‘ et redéfinit la méthode ‘loadClass(String name)‘ de la facon suivante:

   @Override
    public synchronized Class loadClass(String name) throws ClassNotFoundException {
        Class result = null;
        try {
            result = this.myAppClassLoader.loadClass(name);
        } catch (Exception e) {
        }
        if (result == null) {
            result = super.loadClass(name);
        }
        return result;
    }
<pre>

Où on a ‘myAppClassLoader‘ qui est le ClassLoader de mon application que j’ai passé lors de l’instanciation de mon ClassLoader (dans le listing 1 : foo.getClass().getClassLoader()).

De l’autre coté j’ai une Servlet qui peut maintenant récupérer l’objet stocké dans le contexte sous l’attribut nommé ‘foo‘ sans problème de ClassCastException, par exemple dans la méthode ‘init‘ de la Servlet :

    @Override
    public void init() throws ServletException {
        Object o = this.getServletContext().getAttribute("foo");
        if (o != null) {
            Foo foo = null;
            try {
                foo = (Foo) o;
            } catch (RuntimeException e1) {
            }
        } else {
        }
    }

Le mot de la fin

En me mettant d’accord sur le contract de l’objet d’échange, je peux développer une application Web indépendamment, travailler avec un mock en mode test et basculer finalement sur mon application le moment venu. Finit aussi les appels Web service ou JMX locaux pour accéder à l’application qui lance le container Jetty, c’est plus propre, plus rapide, bref c’est mieux et ca marche!

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s