这是 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 第一个参数到底该用哪个类的类加载器啊?
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
enchilada2020 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 1
不是每个类都有自己独特的类加载器的.
不是说 NullRobot 的类加载器叫 NullRobotClassLoader, Null 的叫 NullClassloader. 这里他们用的应该都是 AppClassLoader. 我用做煨牛肉的做法(炖) 做了一条鱼有什么问题. 你非得说不对 必须是炖鱼的做法才能用来做鱼, 两者有区别吗? |
7
Aresxue 2019-09-02 09:44:20 +08:00 1
这个没有严格限定,在图中的 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 2
看下源码
` 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 1
对了,之前写过一篇文章《跨 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
跟楼主有同样的疑惑,看了帖子解决了我的问题,感谢
|