IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Java Discussion :

ClassLoader & Sérialisation


Sujet :

Langage Java

  1. #1
    Membre du Club
    Homme Profil pro
    Directeur technique
    Inscrit en
    Novembre 2013
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Directeur technique
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Novembre 2013
    Messages : 7
    Par défaut ClassLoader & Sérialisation
    Bonjour tout le monde,

    Je conçois une application modulaire. J'utilise le ServiceLoader pour charger mes plugins et tout fonctionne correctement.
    Mon problème est le suivant:

    A l'intérieur de l'un de mes plugins, je souhaite charger un fichier de configuration qui est un BEAN serialisé avec XStream ou SnakeYAML.

    J'obtiens un joli "ClassNotFoundException" lorsque mon plugin essaie de déserialisé le fichier (chargé dans l'application principale).
    J'ai validé le fonctionnement par des tests unitaires dans le plugin.

    Je pense que c'est un problème de classLoader même si je ne comprends pas pourquoi..

    Quelqu'un pourrait-il m'éclairer ?

    Merci d'avance.

  2. #2
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    Quelle classe? Où est-elle? Comment tu fais tes plugins? Comment tu crée tes Classloader? Comment tu appelle XStream? On peux avoir la trace compète de ton exception?

  3. #3
    Membre du Club
    Homme Profil pro
    Directeur technique
    Inscrit en
    Novembre 2013
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Directeur technique
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Novembre 2013
    Messages : 7
    Par défaut
    Bonjour tchize_,

    merci de jeter un oeil à mon problème

    Voici la classe de l'objet serialsé

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
     
            public class MongoClusterConfigBean {
     
    	/**
             * Number of maximum connections by entryPoint
             */
    	private int maxPoolSize;
     
    	/**
             * Socket timeout in ms
             */
    	private int socketTimeoutMs;
     
    	/**
             * The database name to connect to
             */
    	private String database;
     
     
     
    	public MongoClusterConfigBean() {
     
    	}
     
    	public int getMaxPoolSize() {
    		return maxPoolSize;
    	}
     
    	public void setMaxPoolSize(int maxPoolSize) {
    		this.maxPoolSize = maxPoolSize;
    	}
     
    	public int getSocketTimeoutMs() {
    		return socketTimeoutMs;
    	}
     
    	public void setSocketTimeoutMs(int socketTimeoutMs) {
    		this.socketTimeoutMs = socketTimeoutMs;
    	}
     
    	public String getDatabase() {
    		return database;
    	}
     
    	public void setDatabase(String database) {
    		this.database = database;
    	}
     
    }
    La serialisation fonctionne comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
              public T loadConfig(File fileToLoad) throws FileNotFoundException {
    		InputStream is = new BufferedInputStream(new FileInputStream(fileToLoad));
    		Object o = this.serializer.unSerialize(is);
    		T object = this.t.cast(o);  // ERREUR de CAST ICI
    		logger.trace("unserialize from yaml file \"" + fileToLoad.getAbsolutePath() + "\" object<" + this.t.getCanonicalName() + ">");
    		return object;
    	}
    C'est ici qu'est générée l'erreur suivante:

    Exception in thread "main" java.lang.ClassCastException: com.tmp.core.engine.mongo.connection.MongoClusterConfigBean cannot be cast to com.tmp.core.engine.mongo.connection.MongoClusterConfigBean
    at com.tmp.core.engine.mongo.facade.MongoDALFactory.init(MongoDALFactory.java:70)
    at com.tmp.core.engine.storage.StorageLoader.load(StorageLoader.java:75)
    at com.tmp.core.engine.bootstrap.AppBootstrap.initiateContainer(AppBootstrap.java:62)
    at com.tmp.core.engine.bootstrap.AppBootstrap.<init>(AppBootstrap.java:24)


    Ce qui est vraiment bizarre c'est que mon objet est chargée, déserialisé correctement, mais ça crash au cast (avec XStream, avec SnakeYAML, j'ai un ClassNotFoundException)

    grace à cet objet :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
     
    	private XStream stream;
     
            public XMLSerializer() {
    		this.xstream = new XStream();
    	}
     
    	@Override
    	public void serialize(Object o, OutputStream stream) {
    		// TODO Auto-generated method stub
    		this.xstream.toXML(o, stream);
    	}
    	@Override
    	public Object unSerialize(InputStream inputStream) {
    		// TODO Auto-generated method stub
    		return this.xstream.fromXML(inputStream);
    	}
    Mon plugin est chargé de la façon suivante
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     
    public IDALFactory load() {
    		if(this.plugin.exists()) {
    			try {
    				URL[] url = new URL[]{this.plugin.toURI().toURL()};
    				URLClassLoader ucl = new URLClassLoader(url);
     
    				ServiceLoader<IDALFactory> serviceLoader = ServiceLoader.load(IDALFactory.class, ucl);
    				Iterator<IDALFactory> iterator = serviceLoader.iterator();
    				if(iterator.hasNext()) {
    					IDALFactory dalfactory = iterator.next();
    					dalfactory.init(this);
    					return dalfactory;
    				}
    				else {
    					logger.error("Impossible to load \"" + this.plugin.getName() + "\"");
    				}
    			} catch (MalformedURLException e) {
    				// TODO Auto-generated catch block
    				logger.error(e.getLocalizedMessage());
    			}
    		}
    		else {
    			logger.error("Storage plugin not found: " + this.plugin.getAbsolutePath());
    		}
    		return null;
    	}
    Désolé pour le bourrage de crane avec tout ce code

  4. #4
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    Faudrait voir ce qui se passe dans dans com.tmp.core.engine.mongo.facade.MongoDALFactory.init(MongoDALFactory.java:70)

    Mais pour le problème là (can not be cast), ça veux dire que ta classe MongoClusterConfigBean est présente dans deux classloader, et que tu deserialize en utilisant un classloader, mais que tu essaie de caster en utilisant un autre classloader, donc une autre version de la classe -> l'erreur

    Maintenant, je ne comprend toujours pas

    -> comment tu gère les classloader de tes plugins (tu n'a pas répondu)
    -> Comme tu crée tes deserializer
    -> Où se trouvent, dans ta hierarchie de classloader les classes qui posent problème

  5. #5
    Membre du Club
    Homme Profil pro
    Directeur technique
    Inscrit en
    Novembre 2013
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Directeur technique
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Novembre 2013
    Messages : 7
    Par défaut
    Je vais tenter de répondre à tes questions,

    Pour les classLoaders qui gèrent les plugins, pour le moment, je n'ai qu'un plugin.
    Je crée donc un classLoader pour ce plugin à partir de ma main application.
    Un serviceLoader associé à ce nouveau classLoader s'occupe de m'instancier mon plugin.
    (cf le dernier bloc de code de mon message précédent)
    J'imagine que c'est ici que je fais quelque chose de travers.

    Mon deserializer est une instance de XStream créée depuis mon plugin (avec son classLoader ?)

    Pour la hiérarchie des classLoader, je sais pas comment te répondre..

  6. #6
    Membre du Club
    Homme Profil pro
    Directeur technique
    Inscrit en
    Novembre 2013
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Directeur technique
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Novembre 2013
    Messages : 7
    Par défaut
    J'ai modifié la façon dont je charge mon plugin comme suit:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     
    try {
    				URL[] url = new URL[]{this.plugin.toURI().toURL()};
    				URLClassLoader ucl = new URLClassLoader(url);
     
                                    // Ces trois lignes suivantes ajoutées me permettent d'avoir le meme classLoader dans mon plugin et dans la main App, mais est ce une bonne chose ?
    				ClassLoader originalClassLoader = ClassLoader.getSystemClassLoader();
    				ClassLoader ncl = new JarSeekingURLClassLoader(this.plugin, originalClassLoader);
    				Thread.currentThread().setContextClassLoader(ncl);
     
    				ServiceLoader<IDALFactory> serviceLoader = ServiceLoader.load(IDALFactory.class, ucl);
    				Iterator<IDALFactory> iterator = serviceLoader.iterator();
     
    				logger.warn(Thread.currentThread().getContextClassLoader());
     
    				if(iterator.hasNext()) {
    					IDALFactory dalfactory = iterator.next();
    					dalfactory.init(this);
    					return dalfactory;
    				}
    				else {
    					logger.error("Impossible to load \"" + this.plugin.getName() + "\"");
    				}
    			} catch (MalformedURLException e) {
    				// TODO Auto-generated catch block
    				logger.error(e.getLocalizedMessage());
    			}
    Et je log l'id de l'object classLoader dans mon plugin (à l'instanciation avant chargement de sa config -> qui plante) et l'id du classLoader de l'application main

    ils sont identiques:

    15:03:55.976 [main] WARN com.tmp.core.engine.storage.StorageLoader - com.tmp.core.engine.storage.JarSeekingURLClassLoader@1f1740f
    15:03:55.981 [main] WARN com.tmp.core.engine.mongo.facade.MongoDALFactory - com.tmp.core.engine.storage.JarSeekingURLClassLoader@1f1740f


    je comprends plus trop du coup..

  7. #7
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    Citation Envoyé par dterranova Voir le message
    Je crée donc un classLoader pour ce plugin à partir de ma main application.
    Un serviceLoader associé à ce nouveau classLoader s'occupe de m'instancier mon plugin.
    (cf le dernier bloc de code de mon message précédent)
    Désolé, je n'avais pas vu ce bout de code. Visiblement, tu ne charge qu'un jar
    J'imagine que c'est ici que je fais quelque chose de travers.
    Citation Envoyé par dterranova Voir le message
    Mon deserializer est une instance de XStream créée depuis mon plugin (avec son classLoader ?)
    Ben c'est à toi de nous le dire, on a pas ton code pour le moment qui fait ça Il y a plein de manière de créer un deserializer. Par défaut, il utilisera le ContextClassLoader je pense qui, a moins que tu aie fait quelque chose de particulier, n'est pas le classloader de ton plugin.


    Donc pour refenir à ton message (Class cast exception), tu as un classe Machin chargée depuis le plugin, probablement présente dans le jar du plugin, mais aussi une classe Machin présente dans ton application principale. XStream utilise la définition présente dans ton application principale, alors que quand tu fait le cast juste après, tu utilise la définition du plugin => clash.

    Vérifie que
    1) ta classe ne serait pas présente dans 2 jar
    2) les classes du plugin n'ont pas été chargée aussi par l'application principale. C'est une erreur commune d'avoir un dossier contenant tous les jar de l'application, puis d'essayer de recharger un de ces jar une deuxième fois avec un URLClassLoader comme plugin => ça deviens le bordel.
    Citation Envoyé par dterranova Voir le message
    Pour la hiérarchie des classLoader, je sais pas comment te répondre..
    C'est bon, j'avais pas vu le bout de code. Mais ce serait bien de te faire une liste de quel jar se trouve à quel niveau. Si tu n'arrive pas a avoir une idée claire là dessus, tu va devant de grosses emmerdes

  8. #8
    Membre du Club
    Homme Profil pro
    Directeur technique
    Inscrit en
    Novembre 2013
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Directeur technique
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Novembre 2013
    Messages : 7
    Par défaut
    Mon XStream est construit comme suit depuis le plugin
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
            public XMLSerializer() {
    		this.xstream = new XStream();
    	}
    Son classLoader est le même que celui de l'app main et que celui du plugin
    (je me retrouve avec un seul et unique classLoader)

    Mais la serialisation ne semble plus être le vraie problème, je m'explique:
    je mets un point d'arrêt après la deserialisation et avant le cast de cet objet et je me retrouve bien avec mon object dans l'inspecteur. c'est au moment du cast que ca déraille..

    Ma classe com.tmp.core.engine.mongo.connection.MongoClusterConfigBean n'est présente que dans le plugin, pas dans l'app main/principale.

    Avec un unique classLoader et la présence uniquement dans mon plugin comment pourrais je avoir plusieurs fois la classe chargée dans mon class loader ?

    Tu me dis de vérifier si ma classe n'est pas présente dans 2 jars, et elle ne l'est pas.
    Par contre la classe qui déserialize est partagée entre l'app main et le plugin par une dépendance maven. c'est peut être ca le soucis ?

    Sinon, mon jar/plugin est dans un dossier ou il est seul, donc les autres libs ne sont pas chargées une seconde fois.

  9. #9
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    Citation Envoyé par dterranova Voir le message
    Son classLoader est le même que celui de l'app main et que celui du plugin
    (je me retrouve avec un seul et unique classLoader)
    C'est soit l'un, soit l'autre, mais pas les deux, puisque tu crée spécifiquement un URLClassloader pour charger le plugin.



    c'est au moment du cast que ca déraille..
    Oui, et je t'ai expliqué pourquoi: tu as deux classes avec le même non, même package, mais dont le class loader est différent. Il y a celle utilisée par le main, et que, a priori, XStream a utilisé pour désérialiser, et celle utilisée par ton plugin, qui est utilisée pour le cast.

    Ma classe com.tmp.core.engine.mongo.connection.MongoClusterConfigBean n'est présente que dans le plugin, pas dans l'app main/principale.
    Ce n'est pas ce que dit ton message d'erreur.

    Avec un unique classLoader
    Le code que tu as montré jusque là indique bien que tu crée un deuxième classeLoader, celui de ton plugin, en plus du classloader de ton application, géré par la jvm
    et la présence uniquement dans mon plugin comment pourrais je avoir plusieurs fois la classe chargée dans mon class loader ?

    Tu me dis de vérifier si ma classe n'est pas présente dans 2 jars, et elle ne l'est pas.
    Un fois dans le classloader main, une fois dans le classloader du plugin. Si elle n'est pas présente dans deux jar, c'est que tu as ajouté le jar du plugin que classpath de ton application. Du coup la jvm y charge des classe et, à part, ton URLClassloader du plugin aussi:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    Mainclassloader(jvm)
     |  * tonAppli.jar
     |  * lib/xstream.jar
     |  * lib/trucmuch.jar
     |  * lib/tonplugin.jar
     |
     URLClassloader(plugin)
         lib/tonplugin.jar
    Le plus simple pour vérifier, c'est de faire une Class.forName("com.tmp.core.engine.mongo.connection.MongoClusterConfigBean") dans ton main().

    Par contre la classe qui déserialize est partagée entre l'app main et le plugin par une dépendance maven. c'est peut être ca le soucis ?
    Ca ne devrait pas, je pense plutot l'inverse: ton application principale a une dépendance vers le plugin et charge aussi ses classes. A noter que tu devrais construire ton XStream en lui passant une ClassLoaderReference, qu'il sache quelle classloader utiliser.


    Pour avancer, tu pourrais précéder ta ligne (ou équivalent)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    MongoClusterConfigBean  config =  machin.loadConfig(fileToLoad);
    par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    System.out.println(MongoClusterConfigBean.class.getName()+ "-> "+MongoClusterConfigBean.class.getClassloader());
    ainsi que mettre juste après

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    	Object o = this.serializer.unSerialize(is);
    par


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    System.out.println(o.getClass().getName()+ "-> "+o.getClass().getClassloader())

  10. #10
    Membre du Club
    Homme Profil pro
    Directeur technique
    Inscrit en
    Novembre 2013
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Directeur technique
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Novembre 2013
    Messages : 7
    Par défaut
    J'ai logé comme tu le préconises, avant et après la deserialization

    com.tmp.core.engine.mongo.connection.MongoClusterConfigBean-> null
    com.tmp.core.engine.mongo.connection.MongoClusterConfigBean-> com.tmp.core.engine.storage.JarSeekingURLClassLoader@7731547f
    Le classLoader de com.tmp.core.engine.mongo.connection.MongoClusterConfigBean est null

    Mon idée de proposer le système de loadConfig en dépendance était de fournir ce service à tous les plugins.

  11. #11
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    Le null, c'est le classloader par défaut de la jvm, c'est à dire celui qui charge ton main(). Donc tu as bien la classe
    com.tmp.core.engine.mongo.connection.MongoClusterConfigBean dans deux classloader, ce qu'il faut résoudre. Si j'ai bien compris, elle ne devrait exister que dans le plugin. Il faut donc que tu regarde comment ça se fait que l'application principale le voie aussi. C'est probablement que tu as ajouté le plugin au classpath de ton application explicitement ou alors qu'un de tes jars de l'application principale le référence via une entrée class-path: dans son MANIFEST.MF

  12. #12
    Membre du Club
    Homme Profil pro
    Directeur technique
    Inscrit en
    Novembre 2013
    Messages
    7
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Directeur technique
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Novembre 2013
    Messages : 7
    Par défaut
    Ton dernier message m'a bien aidé.
    J'ai repris l'ensemble du code à tête reposée. Le problème venait du fait que j'utilisais une librairie homeMade pour deserialiser. Dans le code de celle-ci j'initialisais bien le classLoader de XStream, mais avec le classLoader du jar en question. J'avais aussi fait des bricoles qui me semblaient me faire avancer dans le bon sens mais qui effectivement fichaient le bazar dans les classLoaders.

    Merci beaucoup pour ton aide et ta patience

    Bonne continuation

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [Serializable][image] Sérialiser une image
    Par Galima dans le forum Graphisme
    Réponses: 7
    Dernier message: 01/07/2004, 18h12
  2. Sérialisation avec sockets
    Par sebi77 dans le forum Entrée/Sortie
    Réponses: 4
    Dernier message: 03/05/2004, 20h24
  3. SGBD ou sérialisation
    Par tiboleo dans le forum Décisions SGBD
    Réponses: 3
    Dernier message: 07/10/2003, 16h18

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo