首页 > 编程语言 >java动态代理技术

java动态代理技术

时间:2023-06-27 18:04:22浏览次数:34  
标签:java 对象 handler 代理 接口 InvocationHandler 动态 Subject


主要用来做方法的增强,让你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法),因为在 InvocationHandler的invoke方法中,你可以直接获取正在调用方法对应的 Method对象,具体应用的话,比如可以添加调用日志,做事务控制等。
还有一个有趣的作用是可以用作远程调用,比如现在有Java接口,这个接口的实现部署在其它服务器上,在编写客户端代码的时候,没办法直接调用接口方法,因为接口是不能直接生成对象的,这个时候就可以考虑代理模式(动态代理)了,通过 Proxy.newProxyInstance代理一个该接口对应的 InvocationHandler对象,然后在 InvocationHandler的invoke方法内封装通讯细节就可以了。具体的应用,最经典的当然是Java标准库的RMI,其它比如hessian,各种webservice框架中的远程调用,大致都是这么实现的。


Class 
  proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class }); 
 
 
 

    Foo f = (Foo) 
  proxyClass.getConstructor(new Class[] { InvocationHandler.class }) 
 
 
 

                                             . 
  newInstance(new Object[] { handler });


在java的动态代理机制中,有两个重要的类或接口,

一个是 InvocationHandler(Interface)、

另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:

InvocationHandler:


InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 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 invoke method of its invocation handler.


每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:



Object invoke(Object proxy, Method method, Object[] args) throws Throwable


我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?



Object invoke(Object proxy, Method method, Object[] args) throws Throwable proxy:  指代我通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行时i动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。 method:  指代的是我们所要调用真实对象的某个方法的Method对象 args:  指代的是调用真实对象某个方法时接受的参数 Object: 返回值object是调用了method方法后的返回值



如果不是很明白,等下通过一个实例会对这几个参数进行更深的讲解。

接下来我们来看看Proxy这个类:


Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.


Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:


public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException
 
  
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:


public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载,可以用handlre接口的classloader,也可以用接口的classloader: Subject.class.getClassLoader()
interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:

首先我们定义了一个Subject类型的接口,为其声明了两个方法:



public interface Subject
{
    public void rent();
    public void hello(String str);
}


接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:


public class RealSubject implements Subject
{
    @Override
    public void rent(){
        System.out.println("I want to rent my house");
    }
    
    @Override
    public void hello(String str){
        System.out.println("hello: " + str);
    }
}


下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

public class DynamicProxy implements InvocationHandler
{
    // 这个就是我们要代理的真实对象
    private Object subject;
    //    构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject){
        this.subject = subject;
    }
    
    @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable{
        //  在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");
        System.out.println("Method:" + method);
        //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
        //  在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");
        
        return null;
    }
}


最后,来看看我们的Client类:

public class Client
{
    public static void main(String[] args){
        //    我们要代理的真实对象
        Subject realSubject = new RealSubject();
        //   我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);
        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象,也可以用接口的classloader: Subject.class.getClassLoader()
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(
               handler.getClass().getClassLoader(), 
               realSubject.getClass().getInterfaces(), handler);
        
        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}


我们先来看看控制台的输出:




$Proxy0

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.rent()
I want to rent my house
after rent house

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
hello: world
after rent house


我们首先来看看 $Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?


Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);

可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

接着我们来看看这两句 

subject.rent();
subject.hello("world");

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行:


public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
        //  在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");
        System.out.println("Method:" + method);
        
        //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
        
        //  在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");
        
        return null;
    }

我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:



public abstract void com.xiaoluo.dynamicproxy.Subject.rent()
public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)



正好就是我们的Subject接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

这就是我们的java动态代理机制

标签:java,对象,handler,代理,接口,InvocationHandler,动态,Subject
From: https://blog.51cto.com/nethub/6564751

相关文章

  • Java NIO
    NIO主要有三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开、数据达到)。因此,单个线程可......
  • java JAXB 学习
    JAXB(JavaArchitectureforXMLBinding)是JDK的一部分,用于Object<->XML的转换(有点类似于.NET中的XML序列化)。1、创建XSD可以使用任何工具生成XSD工具,比如XMLSPY。eclipse也提供了相关的jaxb插件,File->New->XMLSchemaFile文件命名为order.xsd,eclipse中也提供了xsd可视化编......
  • 基于vue +Java+springboot+element-ui开发的智慧班牌系统源码
    电子班牌系统又称之为智慧班牌,是当前校园数字化信息化建设、文化建设的主流,是校园日常工作安排、校园信息发布、班级文化风采展示、课堂交流、家校互通的重要应用载体。在每个班级门口安装一台电子班牌终端,实现学校日常管理、校园信息化建设数据对接,为学生提供一个德智教育文化环境......
  • 庆军之blazor动态组件的研究与总结
    只上代码:rootcontrol.ControlParams=newDictionary<string,object>();rootcontrol.ControlParams["a"]="ssss";rootcontrol.Children.Add(newMControlParam(){ControlType=typeof(Layout),......
  • Java-基本语法回顾总结[13-24]
    (13)copyonwriteArrayList线程安全的arrayList,底层也是用数组实现的,主要集中在读与写操作上读:由于读写分别在老新数组上,因此,互相不干扰,也因此,读的性能不会受写的性能影响[适用于读多写少]写:写操作会生成新数组,在完成之前,其他线程无法进行写操作[上了锁,线程安全];在完成之前,读的......
  • CentOS7+java8+hadoop3.3.5环境搭建
    需要的配置文件centos7的镜像centos-7.9.2009-isos-x86_64安装包下载_开源镜像站-阿里云(aliyun.com)java8JavaDownloads|Oraclehadoop3.3.5Indexof/dist/hadoop/common/hadoop-3.3.5(apache.org)步骤首先第一步在本地下载好vmware和centos7的镜像 之后的......
  • Java基础 -Day04
    Java基础-Day04For循环循环结构的4个要素:①初始化条件②循环条件----->只能是Boolean类型③循环体④迭代条件循环结构for(①;②;④){③}执行过程:①->②->③->④->②->③->④->...->②/*输入两个正整数(m,n),求其最大公约数和最小公倍数*/impor......
  • Java四种引用类型
    强引用:在Java中最常用的就是强引用,把一个对象赋值给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远不会被用到,JVM也不会回收。因此强引用时造成Java内存泄漏的主要原因之一。软引用:需要用S......
  • ubuntu下java安装
    首先看自己的系统版本是什么,可以通过:uname-a的命令来查看,比如我的系统`22.04.1-Ubuntu XXX x86_64x86_64x86_64GNU/Linux`。去官网下载相关的包,地址:https://www.oracle.com/java/technologies/downloads/这里我们的是x86_64,选择x64CompressedArchive这个包下载。下载......
  • SAP ABAP 动态结构实现发送企业微信应用消息
    企业微信官方接口:应用支持推送文本、图片、视频、文件、图文等类型。请求方式:POST(HTTPS)请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN 大概思路:1.封装调用企业微信函数SE37:ZWECHAT_SEND_MESSAGE_MSGTYPE 注:   a.  ......