zoukankan      html  css  js  c++  java
  • Java中InvocationHandler接口中第一个参数proxy详解

    java动态代理机制中有两个重要的类和接口InvocationHandler(接口)和Proxy(类),这一个类Proxy和接口InvocationHandler是我们实现动态代理的核心;

    1.InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

    看下官方文档对InvocationHandler接口的描述:

         {@code InvocationHandler} is the interface implemented by
         the <i>invocation handler</i> of a proxy instance.
    
         <p>Each proxy instance has an associated invocation handler.
         When a method is invoked on a proxy instance, the method
         invocation is encoded and dispatched to the {@code invoke}
         method of its invocation handler.

    每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:

        /**
        * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
        * method:我们所要调用某个对象真实的方法的Method对象
        * args:指代代理对象方法传递的参数
        */
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    2.Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。
        public static Object newProxyInstance(ClassLoader loader, 
                                                Class<?>[] interfaces, 
                                                InvocationHandler h)

    Returns an instance of a proxy class for the specified interfaces
    that dispatches method invocations to the specified invocation
    handler. This method is equivalent to:

    这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:

    loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
    interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
    h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

    3动态代理中核心的两个接口和类上面已经介绍完了,接下来我们就用实例来讲解下具体的用法

    首先我们定义一个接口People

    package reflect;
    
    public interface People {
    
        public String work();
    }

    定义一个Teacher类,实现People接口,这个类是真实的对象

    package reflect;
    
    public class Teacher implements People{
    
        @Override
        public String work() {
            System.out.println("老师教书育人...");
            return "教书";
        }
    
    }

    现在我们要定义一个代理类的调用处理程序,每个代理类的调用处理程序都必须实现InvocationHandler接口,代理类如下:

    package reflect;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class WorkHandler implements InvocationHandler{
    
        //代理类中的真实对象  
        private Object obj;
    
        public WorkHandler() {
            // TODO Auto-generated constructor stub
        }
        //构造函数,给我们的真实对象赋值
        public WorkHandler(Object obj) {
            this.obj = obj;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //在真实的对象执行之前我们可以添加自己的操作
            System.out.println("before invoke。。。");
            Object invoke = method.invoke(obj, args);
            //在真实的对象执行之后我们可以添加自己的操作
            System.out.println("after invoke。。。");
            return invoke;
        }
    
    }

    上面的代理类的调用处理程序的invoke方法中的第一个参数proxy好像我们从来没有用过,而且关于这个参数的具体用法含义请参考我的另外一篇文章Java中InvocationHandler接口中第一个参数proxy详解

    接下来我们看下客户端类

    package reflect;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class Test {
    
        public static void main(String[] args) {
            //要代理的真实对象
            People people = new Teacher();
            //代理对象的调用处理程序,我们将要代理的真实对象传入代理对象的调用处理的构造函数中,最终代理对象的调用处理程序会调用真实对象的方法
            InvocationHandler handler = new WorkHandler(people);
            /**
             * 通过Proxy类的newProxyInstance方法创建代理对象,我们来看下方法中的参数
             * 第一个参数:people.getClass().getClassLoader(),使用handler对象的classloader对象来加载我们的代理对象
             * 第二个参数:people.getClass().getInterfaces(),这里为代理类提供的接口是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法
             * 第三个参数:handler,我们将代理对象关联到上面的InvocationHandler对象上
             */
            People proxy = (People)Proxy.newProxyInstance(handler.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);
            //System.out.println(proxy.toString());
            System.out.println(proxy.work());
        }
    }

    看下输出结果:

    before invoke。。。
    老师教书育人...
    after invoke。。。
    教书

    通过上面的讲解和示例动态代理的原理及使用方法,在Spring中的两大核心IOC和AOP中的AOP(面向切面编程)的思想就是动态代理,在代理类的前面和后面加上不同的切面组成面向切面编程。

    上面我们只讲解了Proxy中的newProxyInstance(生成代理类的方法),但是它还有其它的几个方法,我们下面就介绍一下:

    • getInvocationHandler:返回指定代理实例的调用处理程序
    • getProxyClass:给定类加载器和接口数组的代理类的java.lang.Class对象。
    • isProxyClass:当且仅当使用getProxyClass方法或newProxyInstance方法将指定的类动态生成为代理类时,才返回true。
    • newProxyInstance:返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

    补充:

    上一篇文章我们详细的讲解了创建代理类的调用处理程序(实现InvocationHandler接口的类),获得代理对象的Proxy类,但是就发现InvocationHandler中的invoke方法中的第一个参数proxy好像从来没有用过,所以就开始在网上查询proxy的用途,最后在国外的网站上找到了不错的讲解stackoverflow.com,下面就根据自己的学习心得,讲解一下proxy。

    1.讲解前我们先列一下我们要说明的问题

    • proxy代表什么意思
    • proxy参数怎么用及什么时候用
    • proxy参数运行时的类型是什么
    • 为什么不用this代替proxy

    2.proxy代表什么意思
    proxy是真实对象的真实代理对象,invoke方法可以返回调用代理对象方法的返回结果,也可以返回对象的真实代理对象(com.sun.proxy.$Proxy0)。

    3.proxy参数怎么用及什么时候用
    proxy参数是invoke方法的第一个参数,通常情况下我们都是返回真实对象方法的返回结果,但是我们也可以将proxy返回,proxy是真实对象的真实代理对象,我们可以通过这个返回对象对真实的对象做各种各样的操作。

    • 创建一个接口People,包含一个work方法,方法的返回对象是它本身
    package com.test.Application;
    
    public interface People {
    
        public People work(String workName);
        public String time();
    }
    • 创建一个接口People的实现类Student
    package com.test.Application;
    
    public class Student implements People{
    
        @Override
        public People work(String workName) {
            System.out.println("工作内容是"+workName);
            return this;
        }
        @Override
        public String time() {
            return "2018-06-12";
        }
    }
    • 创建一个代理类的调用处理程序WorkHandler
    package com.test.Application;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class WorkHandler implements InvocationHandler{
    
        private Object obj;
    
        public WorkHandler(Object obj) {
            this.obj = obj;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before 动态代理...");
            System.out.println(proxy.getClass().getName());
            System.out.println(this.obj.getClass().getName());
            if(method.getName().equals("work")) {
                method.invoke(this.obj, args);
                System.out.println("after 动态代理...");
                return proxy;
            } else {
                System.out.println("after 动态代理...");
                return method.invoke(this.obj, args);
            }
        }
    
    }

    我们可以看到上面的代理类调用处理程序打印了proxy参数对象,并且返回了proxy对象。

    • 客户端实例创建代理对象并输出结果
    package com.test.Application;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class Test {
    
        public static void main(String[] args) {
            People people = new Student();
            InvocationHandler handler = new WorkHandler(people);
    
            People proxy = (People)Proxy.newProxyInstance(people.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);
            People p = proxy.work("写代码").work("开会").work("上课");
    
            System.out.println("打印返回的对象");
            System.out.println(p.getClass());
    
            String time = proxy.time();
            System.out.println(time);
        }
    }

    运行结果:

    after 动态代理...
    before 动态代理...
    com.sun.proxy.$Proxy0
    com.test.Application.Student
    工作内容是上课
    after 动态代理...
    打印返回的对象
    class com.sun.proxy.$Proxy0
    
    class com.sun.proxy.$Proxy0
    before 动态代理...
    com.sun.proxy.$Proxy0
    com.test.Application.Student
    after 动态代理...
    2018-06-12

    我们可以看到WorkHandler代理调用处理程序打印proxy参数输出的结果是com.sun.proxy.$Proxy0,这也说明proxy参数是代理类的真实代理对象;Proxy类生成的代理对象可以调用work方法并且返回真实的代理对象,也可以通过反射来对真实的代理对象进行操作。

    4.proxy参数运行时的类型是什么
    上面我们已经打印出了proxy的类型是:com.sun.proxy.$Proxy0真实的代理对象

    5.为什么不用this替代
    因为this代表的是InvocationHandler接口实现类本身,并不是真实的代理对象。

  • 相关阅读:
    《你不知道的JavaScript》中卷 KYLE SIMPSON 著 单业 姜南 译
    输入框禁止输入emoji标签
    《你不知道的javascript上卷》读书笔记(kyle simpson 著,赵望野、梁译)
    Redis的介绍及使用实例.
    [Linux]Linux下redis的安装及配置.
    [Linux]Linux下安装和配置solr/tomcat/IK分词器 详细实例二.
    [Linux]Linux下安装和配置solr/tomcat/IK分词器 详细实例一.
    [Linux] linux下安装配置 zookeeper/redis/solr/tomcat/IK分词器 详细实例.
    ActiveMQ的介绍及使用实例.
    集群下session共享问题的解决方案.
  • 原文地址:https://www.cnblogs.com/cxhfuujust/p/12619027.html
Copyright © 2011-2022 走看看