【问题标题】:cast across classloader?跨类加载器转换?
【发布时间】:2010-04-07 10:57:49
【问题描述】:

我该怎么做:

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
  }
}

我需要让 JVM 将 cl 中的 Foo 实例视为执行代码的类加载器中的 Foo 实例。

我见过这些方法,但都对我没有好处(上面的例子是一个玩具例子):

  1. 由作为调用代码和创建的类加载器的父类的类加载器加载类(或单独的接口)
  2. 序列化和反序列化对象。

【问题讨论】:

    标签: java classloader


    【解决方案1】:

    也许使用接口和java.lang.reflect.Proxy 的东西会适合您的喜好。使用InvocationHandler 查找并调用目标类的相关方法。 (请注意,如果您这样做,您拥有的任何移动代码安全性都会被击穿。)

    【讨论】:

      【解决方案2】:

      如果需要强制转换的类实现了 Serializable 则:

      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;
      }
      

      用法:

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

      【讨论】:

      • 我只想指出,使用序列化相当于在对象之间深度复制字段。但是,它不会“复制”方法体。所以主类加载器加载的 MyObj (A) 和从自定义加载器检索到的 MyObj (B) 是不同的,只是大多数字段内容匹配。
      • 它在我部署动态 Web 服务时帮我解决了很多问题...感谢@wit
      【解决方案3】:

      我最近两天一直在努力解决这个确切的问题,我终于通过使用 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[] {});
      }
      

      希望这对某人有所帮助。

      【讨论】:

      • 我们如何在没有反思的情况下做到这一点?如果我们的类加载器加载的每一个对象的每一个方法都需要通过反射来调用,那不会严重拖垮整个程序吗?
      【解决方案4】:

      不能在不同的 classLoader 中强制转换。

      你有这个与 Gson 的解决方法,例如将 Object 转换为 YourObject(Object 是 YourObject 类,但在其他 classLoader 中):

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

      我使用此解决方法是因为我在 WebApp(在 Tomcat 上)中编译任何 Java 代码。此解决方法在生产中运行。

      【讨论】:

      • 太棒了!这可能是一个 hack,但它为我解决了一个非常烦人的部署问题。
      【解决方案5】:

      不可能。类标识由完全限定名和类加载器组成。

      将对象转换为由不同类加载器加载的具有相同名称的类与尝试将String 转换为Integer 没有什么不同,因为尽管这些类具有相同的名称,但实际上可能完全不同。

      【讨论】:

      • 那么如果我们连这些新实例都不能转换,我们如何利用它们呢?如果我们能做的只有Object obj = cl.loadClass("Foo").newInstance();,那我们怎么调用Foo的新实例的方法呢?
      • @Pacerier:好吧,你可以使用反射。但更实际的情况是让类扩展类或从委托层次结构中的父类加载器实现接口,这些接口也可用于其余代码。但是在示例代码中,没有父类加载器(构造函数的第二个参数为null)...
      • 第二个选项的性能会比使用反射快吗?还是它在内部使用反射是真的吗?
      • @Pacerier:第二个选项的工作方式与来自同一个类加载器的类完全相同,因此它比使用反射更快;但无论如何,随着 JVM 的每个新版本,这种差距一直在缩小。
      【解决方案6】:

      这是一个旧帖子,因为我想做几乎相同的事情,但它的一个更简单的版本......

      如果 Foo 和加载的类(在我的例子中来自另一个包中的 .java 类文件)扩展同一个类,例如 AbstractTestClass,它实际上可以工作。

      代码片段:

      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();
      }
      

      这样我可以将加载的类插入ArrayList&lt;AbstractTestClass&gt;

      【讨论】:

      • 您从同一个类加载器加载“测试”。此外,即使您指定另一个,它也需要是当前类加载器的子类,否则您将无法强制转换为 AbstractTestClass
      • @IttayD 你是对的,它需要扩展父类(问题中的Foo)。难道不是问题的重点:“将 cl 中的 Foo 实例视为 Foo 的实例”?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-02
      • 1970-01-01
      • 2012-02-05
      相关资源
      最近更新 更多