V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  Java

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

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

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

    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()啊
    enchilada2020
        3
    enchilada2020  
       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   ·   实用小工具   ·   3530 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 00:55 · PVG 08:55 · LAX 16:55 · JFK 19:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.