这是 java 编程思想 14 章类型信息,14.8 空对象小节的例子。空对象就是实现一个空的接口来代表 null。
//补充接口文件 import jaa.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 第一个参数到底该用哪个类的类加载器啊?
![]() | 1 BBCCBB 2019-09-01 22:16:53 +08:00 你想加载到哪个类加载器这个参数就是哪个, 不然要这个参数干嘛.. 如果你默认和加载你要代理的 interface 属于同一个 classloader,可以用你的 Interface.class.getClassLoader()作为参数, 一般都是这个. |
![]() | 2 amiwrong123 OP @BBCCBB 感觉还是没懂啊,可能我类加载器这块不怎么熟。 我就是觉得 Proxy.newProxyInstance 的第一个参数和第二个参数应该是有关系的,现在第一个参数是测试类的类加载类(它既没有实现 Null 接口,也没有实现 Robot 接口),第二个参数的两个元素是 Null 和 Robot 的类加载器。现在第一个参数和第二个参数根本没有关系。 感觉第一个参数起码也应该是 Robot.class.getClassLoader()啊 |
![]() | 3 KMpAn8Obw1QhPoEP 2019-09-02 00:59:19 +08:00 via Android 歪个楼 又是同款头像。。。。 |
4 ywcjxf1515 2019-09-02 03:16:42 +08:00 via iPad 这里你定义的那几个接口或者类的类加载器都是同一个类加载器,都是应用程序加载器(三级里最差的一级),你换成线程的类加载也是一样行的。 |
5 memedahui 2019-09-02 08:48:38 +08:00 都是大佬,我完全看不懂 |
![]() | 6 zpf124 2019-09-02 09:21:07 +08:00 ![]() 不是每个类都有自己独特的类加载器的. 不是说 NullRobot 的类加载器叫 NullRobotClassLoader, Null 的叫 NullClassloader. 这里他们用的应该都是 AppClassLoader. 我用做煨牛肉的做法(炖) 做了一条鱼有什么问题. 你非得说不对 必须是炖鱼的做法才能用来做鱼, 两者有区别吗? |
![]() | 7 Aresxue 2019-09-02 09:44:20 +08:00 ![]() 这个没有严格限定,在图中的 Null、Robot、NullRobot 都是开发自定义的接口或类,他们都是由 AppClassLoader 加载(Tomcat 比较特殊,有自定义的 WebClassLoader),所以实际上它们必然由同一个 ClassLoader 加载(如果你没有自定义 ClassLoader 并使用其加载) |
![]() | 8 amiwrong123 OP |
![]() | 9 amiwrong123 OP @Aresxue 好吧,大概懂啦。但有点好奇,这里它们三个虽然都是 AppClassLoader,但是都必须通过 类名.class.getClassLoader() 这种方式点点点,点出来啊。反正都是同一个,弄个更方便的形式岂不更好,比如直接静态变量: 某个系统类名.AppClassLoader |
10 DsuineGP 2019-09-02 10:20:35 +08:00 @amiwrong123 在你这个例子里面三个类的类加载器是同一个,但是在实际开发中有的时候需要自己实现类加载器,那么根据某个系统类名.AppClassLoader 获取的类加载器就跟实际的类加载器就不同了. |
![]() | 11 coolcfan 2019-09-02 10:54:01 +08:00 via Android @amiwrong123 假如你写运行在模块化系统里的程序,就需要注意了,比如 OSGi 的类加载器机制…… |
12 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。 |
13 crawl3r 2019-09-02 18:43:58 +08:00 ![]() 对了,之前写过一篇文章《跨 classloader 类型转换》( http://www.wisedream.net/2017/01/17/programming/type-cast-across-classloader/) 你可以参考下 |
14 SunnyGrocery 2019-10-28 21:17:52 +08:00 看 java 编程思想中产生的相同疑惑,看了帖子明白了很多,回头看下 p314-p315 的 Class 对象,有对 ClassLoader 的简单介绍 |
![]() | 15 xinlzju 2021-05-19 17:24:02 +08:00 跟楼主有同样的疑惑,看了帖子解决了我的问题,感谢 |