2010-04-07 8 views
17

Comment puis-je faire ceci:cast à travers classloader?

class Foo { 
    public static Foo get() throws Exception { 
    ClassLoader cl = new URLClassLoader(new URL[]{"foo.jar"}, null); // Foo.class is in foo.jar 
    return (Foo)cl.loadClass("Foo").newInstance(); // fails on class cast 
    } 
} 

Ce que j'ai besoin est pour la machine virtuelle Java pour examiner l'instance Foo de cl comme si elle est une instance de Foo du classloader du code d'exécution.

J'ai vu ces approches, aucun d'entre eux bon pour moi (l'exemple ci-dessus est un exemple de jouet):

  1. charge la classe (ou une interface distincte) par un chargeur de classe qui est un parent de à la fois le code appelant et le chargeur de classe créé
  2. Sérialiser et désérialiser l'objet.

Répondre

20

Impossible. L'identité de classe comprend le nom complet et le chargeur de classe.

Le lancement d'un objet dans une classe portant le même nom et chargé par différents chargeurs de classe n'est pas différent d'essayer de transformer un String en Integer, car ces classes peuvent être complètement différentes bien qu'elles portent le même nom.

+1

Donc, si nous ne pouvons même pas jeter ces nouveaux cas, comment pouvons-nous les utiliser? Si tout ce que nous pouvons faire est 'Object obj = cl.loadClass (" Foo "). NewInstance();', alors comment appelons-nous les méthodes de la nouvelle instance de Foo? – Pacerier

+1

@Pacerier: Eh bien, vous pourriez utiliser la réflexion. Mais un cas plus pratique consiste à faire en sorte que les classes étendent des classes ou implémentent des interfaces à partir d'un chargeur de classe parent dans la hiérarchie de délégation qui sont également disponibles pour le reste du code. Mais dans l'exemple de code, il n'y a pas de chargeur de classe parent (le second paramètre du constructeur est nul) ... –

+0

La performance de la seconde option sera-t-elle plus rapide que l'utilisation de la réflexion? Ou est-ce vrai qu'il utilise la réflexion de toute façon? – Pacerier

3

Peut-être que quelque chose utilisant des interfaces et java.lang.reflect.Proxy conviendrait à vos préférences. En utilisant un InvocationHandler qui trouve et invoque la méthode appropriée sur la classe cible. (Note, toute sécurité du code mobile vous sera tourné par si vous faites cela.)

7

Je viens de passer les deux derniers jours aux prises avec ce problème exact et je finalement obtenu contourner le problème en utilisant la réflexion java:

// 'source' is from another classloader 
final Object source = events[0].getSource(); 

if (source.getClass().getName().equals("org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptThread")) { 

    // I cannot cast to 'org.eclipse.wst.jsdt.debug.internal.core.model.JavaScriptThread' 
    // so I invoke the method 'terminate()' manually 
    Method method = source.getClass().getMethod("terminate", new Class[] {}); 
    method.invoke(source, new Object[] {}); 
} 

Espérons que cela aide quelqu'un.

+1

Comment pouvons-nous faire cela sans réfléchir? Si chaque méthode de chaque objet chargé par notre chargeur de classe doit être appelée par réflexion, cela ne réduirait-il pas le programme entier? – Pacerier

0

C'est un ancien poste où je suis arrivé parce que je voulais faire à peu près la même chose, mais une version plus simple de celui-ci ...

Il fonctionne en fait, si les deux Foo et la classe chargée (dans mon cas de un fichier .java classfile dans un autre paquet) étend la même classe, disons, AbstractTestClass.

Pièces de code:

public AbstractTestClass load(String toLoad) { 
    try{ 
     Class test = Class.forName("testsFolder.testLoadable"); 
     Constructor ctorlist[] = test.getDeclaredConstructors(); 
     for(Constructor aConstructor : ctorlist){ 
      if(...){// find the good constructor 
       Object loadedTest = aConstructor.newInstance(new Object[]{/*params*/}); 
       return (AbstractTestClass) test; 
      } 
     } 
    }catch(...){} 
    return new defaultTestClass(); 
} 

De cette façon, je peux insérer la classe chargée dans un ArrayList<AbstractTestClass>.

+0

vous chargez 'test' à partir du même chargeur de classe. En outre, même si vous en spécifiez un autre, il devra être un enfant du chargeur de classe en cours ou vous ne pourriez pas convertir en AbstractTestClass – IttayD

+0

@IttayD vous avez raison, il doit étendre la classe parent (Foo in la question). N'était-ce pas le but de la question: "considérer l'instance de Foo à partir de cl comme s'il s'agissait d'une instance de Foo"? – Aname

1

Impossible de diffuser dans un classLoader différent.

Vous avez cette solution avec Gson, par exemple jeté objet à YourObject (Object est une classe YourObject mais dans d'autres classLoader):

Object o = ... 
Gson gson = new Gson(); 
YourObject yo = gson.fromJson(gson.toJson(o), YourObject.class); 

J'utilise cette solution parce que je compile un code java dans un WebApp (sur Tomcat). Cette solution de contournement s'exécute en production.

3

Si la classe qui implémente besoin d'être jeté Serializable alors:

private <T> T castObj(Object o) throws IOException, ClassNotFoundException { 
    if (o != null) { 
     ByteArrayOutputStream baous = new ByteArrayOutputStream(); 
     { 
      ObjectOutputStream oos = new ObjectOutputStream(baous); 
      try { 
       oos.writeObject(o); 
      } finally { 
       try { 
        oos.close(); 
       } catch (Exception e) { 
       } 
      } 
     } 

     byte[] bb = baous.toByteArray(); 
     if (bb != null && bb.length > 0) { 
      ByteArrayInputStream bais = new ByteArrayInputStream(bb); 
      ObjectInputStream ois = new ObjectInputStream(bais); 
      T res = (T) ois.readObject(); 
      return res; 
     } 
    } 
    return null; 
} 

utilisation:

Object o1; // MyObj from different class loader 
MyObj o2 = castObj(o1); 
+0

Je veux juste souligner que l'utilisation de la sérialisation est équivalente à la copie en profondeur des champs entre les objets. Cependant, il ne "copie" pas les corps de méthode. Ainsi, MyObj (A) chargé par le chargeur de classe principal et MyObj (B) récupéré à partir du chargeur personnalisé sont différents et la plupart des contenus de champs correspondent. – Vortex