Java 动态代理 Proxy.newProxyInstance 第一个参数到底该用哪个类的类加载器啊? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX    Java

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

  •  
  •   amiwrong123 2019-09-01 21:30:35 +08:00 6463 次点击
    这是一个创建于 2236 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这是 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 第一个参数到底该用哪个类的类加载器啊?

    15 条回复    2021-05-19 17:24:02 +08:00
    BBCCBB
        1
    BBCCBB  
       2019-09-01 22:16:53 +08:00
    你想加载到哪个类加载器这个参数就是哪个, 不然要这个参数干嘛..

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

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

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


    我用做煨牛肉的做法(炖) 做了一条鱼有什么问题. 你非得说不对 必须是炖鱼的做法才能用来做鱼, 两者有区别吗?
    Aresxue
        7
    Aresxue  
       2019-09-02 09:44:20 +08:00   1
    这个没有严格限定,在图中的 Null、Robot、NullRobot 都是开发自定义的接口或类,他们都是由 AppClassLoader 加载(Tomcat 比较特殊,有自定义的 WebClassLoader),所以实际上它们必然由同一个 ClassLoader 加载(如果你没有自定义 ClassLoader 并使用其加载)
    amiwrong123
        8
    amiwrong123  
    OP
       2019-09-02 10:02:50 +08:00
    @BBCCBB
    @ywcjxf1515
    @zpf124
    大概懂啦。发现了类加载器这块完全是我的知识盲区,发帖之前没去百度一下是我的错。而且发现了一个听起来很酷炫的名词“双亲委派”,等会去看看博客。
    amiwrong123
        9
    amiwrong123  
    OP
       2019-09-02 10:08:27 +08:00
    @Aresxue
    好吧,大概懂啦。但有点好奇,这里它们三个虽然都是 AppClassLoader,但是都必须通过 类名.class.getClassLoader() 这种方式点点点,点出来啊。反正都是同一个,弄个更方便的形式岂不更好,比如直接静态变量: 某个系统类名.AppClassLoader
    DsuineGP
        10
    DsuineGP  
       2019-09-02 10:20:35 +08:00
    @amiwrong123 在你这个例子里面三个类的类加载器是同一个,但是在实际开发中有的时候需要自己实现类加载器,那么根据某个系统类名.AppClassLoader 获取的类加载器就跟实际的类加载器就不同了.
    coolcfan
        11
    coolcfan  
       2019-09-02 10:54:01 +08:00 via Android
    @amiwrong123 假如你写运行在模块化系统里的程序,就需要注意了,比如 OSGi 的类加载器机制……
    crawl3r
        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。
    crawl3r
        13
    crawl3r  
       2019-09-02 18:43:58 +08:00   1
    对了,之前写过一篇文章《跨 classloader 类型转换》( http://www.wisedream.net/2017/01/17/programming/type-cast-across-classloader/) 你可以参考下
    SunnyGrocery
        14
    SunnyGrocery  
       2019-10-28 21:17:52 +08:00
    看 java 编程思想中产生的相同疑惑,看了帖子明白了很多,回头看下 p314-p315 的 Class 对象,有对 ClassLoader 的简单介绍
    xinlzju
        15
    xinlzju  
       2021-05-19 17:24:02 +08:00
    跟楼主有同样的疑惑,看了帖子解决了我的问题,感谢
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5460 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 06:40 PVG 14:40 LAX 23:40 JFK 02:40
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86