Java 动态代理 Proxy.newProxyInstance 第一个参数到底该用哪个类的类加载器啊?

2019-09-01 21:30:35 +08:00
 amiwrong123

这是 java 编程思想 14 章类型信息,14.8 空对象小节的例子。空对象就是实现一个空的接口来代表 null。

//补充接口文件
import java.util.*;
import net.mindview.util.*;

interface Operation {
    String description();
    void command();
}

public interface Robot {
  String name();
  String model();
  List<Operation> operations();
  class Test {
    public static void test(Robot r) {
      if(r instanceof Null)
        System.out.println("[Null Robot]");
      System.out.println("Robot name: " + r.name());
      System.out.println("Robot model: " + r.model());
      for(Operation operation : r.operations()) {
        System.out.println(operation.description());
        operation.command();
      }
    }
  }
}


//空接口
public interface Null {}



//测试类
import java.lang.reflect.*;
import java.util.*;
import net.mindview.util.*;

class NullRobotProxyHandler implements InvocationHandler {
  private String nullName;
  private Robot proxied = new NRobot();
  NullRobotProxyHandler(Class<? extends Robot> type) {
    nullName = type.getSimpleName() + " NullRobot";
  }
  private class NRobot implements Null, Robot {
    public String name() { return nullName; }
    public String model() { return nullName; }
    public List<Operation> operations() {
      return Collections.emptyList();
    }
  }	
  public Object
  invoke(Object proxy, Method method, Object[] args)
  throws Throwable {
    return method.invoke(proxied, args);
  }
}

public class NullRobot {
  public static Robot newNullRobot(Class<? extends Robot> type) {
    return (Robot)Proxy.newProxyInstance(
      NullRobot.class.getClassLoader(),//这里为什么用这个类加载器啊?
      new Class[]{ Null.class, Robot.class },
      new NullRobotProxyHandler(type));
  }	
  public static void main(String[] args) {
    Robot[] bots = {
      new SnowRemovalRobot("SnowBee"),
      newNullRobot(SnowRemovalRobot.class)
    };
    for(Robot bot : bots)
      Robot.Test.test(bot);
  }
}

这个例子倒是懂了,但 Proxy.newProxyInstance 第一个参数我就不懂了。按照正常的例子来说,第一个参数应该是实际调用类的类加载器,或者是某个 interface.class,但是这里它却用得 NullRobot 的类加载器,这个 NullRobot 在我眼里就是一个测试用的类啊,怎么用它的类加载器还能执行成功呢。(在本地执行过,能成功)

Proxy.newProxyInstance 第一个参数到底该用哪个类的类加载器啊?

5773 次点击
所在节点    Java
15 条回复
BBCCBB
2019-09-01 22:16:53 +08:00
你想加载到哪个类加载器这个参数就是哪个, 不然要这个参数干嘛..

如果你默认和加载你要代理的 interface 属于同一个 classloader,可以用你的 Interface.class.getClassLoader()作为参数, 一般都是这个.
amiwrong123
2019-09-01 22:29:04 +08:00
@BBCCBB
感觉还是没懂啊,可能我类加载器这块不怎么熟。

我就是觉得 Proxy.newProxyInstance 的第一个参数和第二个参数应该是有关系的,现在第一个参数是测试类的类加载类(它既没有实现 Null 接口,也没有实现 Robot 接口),第二个参数的两个元素是 Null 和 Robot 的类加载器。现在第一个参数和第二个参数根本没有关系。

感觉第一个参数起码也应该是 Robot.class.getClassLoader()啊
enchilada2020
2019-09-02 00:59:19 +08:00
歪个楼 又是同款头像。。。。
ywcjxf1515
2019-09-02 03:16:42 +08:00
这里你定义的那几个接口或者类的类加载器都是同一个类加载器,都是应用程序加载器(三级里最差的一级),你换成线程的类加载也是一样行的。
memedahui
2019-09-02 08:48:38 +08:00
都是大佬,我完全看不懂
zpf124
2019-09-02 09:21:07 +08:00
不是每个类都有自己独特的类加载器的.
不是说 NullRobot 的类加载器叫 NullRobotClassLoader, Null 的叫 NullClassloader. 这里他们用的应该都是 AppClassLoader.


我用做煨牛肉的做法(炖) 做了一条鱼有什么问题. 你非得说不对 必须是炖鱼的做法才能用来做鱼, 两者有区别吗?
Aresxue
2019-09-02 09:44:20 +08:00
这个没有严格限定,在图中的 Null、Robot、NullRobot 都是开发自定义的接口或类,他们都是由 AppClassLoader 加载(Tomcat 比较特殊,有自定义的 WebClassLoader),所以实际上它们必然由同一个 ClassLoader 加载(如果你没有自定义 ClassLoader 并使用其加载)
amiwrong123
2019-09-02 10:02:50 +08:00
@BBCCBB
@ywcjxf1515
@zpf124
大概懂啦。发现了类加载器这块完全是我的知识盲区,发帖之前没去百度一下是我的错。而且发现了一个听起来很酷炫的名词“双亲委派”,等会去看看博客。
amiwrong123
2019-09-02 10:08:27 +08:00
@Aresxue
好吧,大概懂啦。但有点好奇,这里它们三个虽然都是 AppClassLoader,但是都必须通过 类名.class.getClassLoader() 这种方式点点点,点出来啊。反正都是同一个,弄个更方便的形式岂不更好,比如直接静态变量: 某个系统类名.AppClassLoader
DsuineGP
2019-09-02 10:20:35 +08:00
@amiwrong123 在你这个例子里面三个类的类加载器是同一个,但是在实际开发中有的时候需要自己实现类加载器,那么根据某个系统类名.AppClassLoader 获取的类加载器就跟实际的类加载器就不同了.
coolcfan
2019-09-02 10:54:01 +08:00
@amiwrong123 假如你写运行在模块化系统里的程序,就需要注意了,比如 OSGi 的类加载器机制……
crawl3r
2019-09-02 18:40:02 +08:00
看下源码
`
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();
// Android-changed: sm is always null
// final SecurityManager sm = System.getSecurityManager();
// if (sm != null) {
// checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
// }

/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);

/*
* Invoke its constructor with the designated invocation handler.
*/
try {
// Android-changed: sm is always null
// if (sm != null) {
// checkNewProxyPermission(Reflection.getCallerClass(), cl);
// }

final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
// Android-changed: Removed AccessController.doPrivileged
cons.setAccessible(true);
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}

`
注意这行代码*Class<?> cl = getProxyClass0(loader, intfs);*
通过 loader 加载或生成某个 proxy 类,也就是说 jvm 创建的 proxy 类挂到了这个 classloader 上。对于你这个例子没法说。我给你讲个实际的例子。
对于安卓应用是通过 DexClassLoader 加载的,而 xposed 模块是通过 PathClassloader 加载的,它们是同级的类加载器。如果想在 xposed 模块中调用应用里的某个方法,如` void download(String url, ICallback)`.
我们可以用反射创建 ICallback 的动态代理。在调用这个方法的时候它是运行在应用内的,也就是说对于安卓应用来说它是不知道有个 PathClassloader 的,所以创建的 ICallback 动态代理必须能够通过它自己的类加载器加载到,否则就是 ClassNotFound。
crawl3r
2019-09-02 18:43:58 +08:00
对了,之前写过一篇文章《跨 classloader 类型转换》( http://www.wisedream.net/2017/01/17/programming/type-cast-across-classloader/) 你可以参考下
SunnyGrocery
2019-10-28 21:17:52 +08:00
看 java 编程思想中产生的相同疑惑,看了帖子明白了很多,回头看下 p314-p315 的 Class 对象,有对 ClassLoader 的简单介绍
xinlzju
2021-05-19 17:24:02 +08:00
跟楼主有同样的疑惑,看了帖子解决了我的问题,感谢

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/596995

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX