zoukankan      html  css  js  c++  java
  • [转载] java的动态代理机制详解

    转载自http://www.cnblogs.com/xiaoluo501395377/p/3383130.html

    代理模式 
    代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 
    按照代理的创建时期,代理类可以分为两种。 
    静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 
    动态代理:在程序运行时,运用反射机制动态创建而成。 

    首先看一下静态代理: 
    1、Count.java 

    1. package net.battier.dao;  
    2.   
    3. /** 
    4.  * 定义一个账户接口 
    5.  *  
    6.  * @author Administrator 
    7.  *  
    8.  */  
    9. public interface Count {  
    10.     // 查看账户方法  
    11.     public void queryCount();  
    12.   
    13.     // 修改账户方法  
    14.     public void updateCount();  
    15.   
    16. }  


    2、CountImpl.java 

    1. package net.battier.dao.impl;  
    2.   
    3. import net.battier.dao.Count;  
    4.   
    5. /** 
    6.  * 委托类(包含业务逻辑) 
    7.  *  
    8.  * @author Administrator 
    9.  *  
    10.  */  
    11. public class CountImpl implements Count {  
    12.   
    13.     @Override  
    14.     public void queryCount() {  
    15.         System.out.println("查看账户方法...");  
    16.   
    17.     }  
    18.   
    19.     @Override  
    20.     public void updateCount() {  
    21.         System.out.println("修改账户方法...");  
    22.   
    23.     }  
    24.   
    25. }  
    26.   
    27. 、CountProxy.java  
    28. package net.battier.dao.impl;  
    29.   
    30. import net.battier.dao.Count;  
    31.   
    32. /** 
    33.  * 这是一个代理类(增强CountImpl实现类) 
    34.  *  
    35.  * @author Administrator 
    36.  *  
    37.  */  
    38. public class CountProxy implements Count {  
    39.     private CountImpl countImpl;  
    40.   
    41.     /** 
    42.      * 覆盖默认构造器 
    43.      *  
    44.      * @param countImpl 
    45.      */  
    46.     public CountProxy(CountImpl countImpl) {  
    47.         this.countImpl = countImpl;  
    48.     }  
    49.   
    50.     @Override  
    51.     public void queryCount() {  
    52.         System.out.println("事务处理之前");  
    53.         // 调用委托类的方法;  
    54.         countImpl.queryCount();  
    55.         System.out.println("事务处理之后");  
    56.     }  
    57.   
    58.     @Override  
    59.     public void updateCount() {  
    60.         System.out.println("事务处理之前");  
    61.         // 调用委托类的方法;  
    62.         countImpl.updateCount();  
    63.         System.out.println("事务处理之后");  
    64.   
    65.     }  
    66.   
    67. }  

    3、TestCount.java 

    1. package net.battier.test;  
    2.   
    3. import net.battier.dao.impl.CountImpl;  
    4. import net.battier.dao.impl.CountProxy;  
    5.   
    6. /** 
    7.  *测试Count类 
    8.  *  
    9.  * @author Administrator 
    10.  *  
    11.  */  
    12. public class TestCount {  
    13.     public static void main(String[] args) {  
    14.         CountImpl countImpl = new CountImpl();  
    15.         CountProxy countProxy = new CountProxy(countImpl);  
    16.         countProxy.updateCount();  
    17.         countProxy.queryCount();  
    18.   
    19.     }  
    20. }  

    观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。 

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。

    在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:  指代我们所代理的那个真实对象
    method:  指代的是我们所要调用真实对象的某个方法的Method对象
    args:  指代的是调用真实对象某个方法时接受的参数

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

    接下来我们来看看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对象来对生成的代理对象进行加载
    
    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对象来加载我们的代理对象
             * 第二个参数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中的动态代理机制,这个知识点非常非常的重要,包括我们Spring的AOP其就是通过动态代理的机制实现的,所以我们必须要好好的理解动态代理的机制。

    总结

    一个典型的动态代理创建对象过程可分为以下四个步骤:
    1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);
    2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
    Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
    3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
    Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
    4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
    Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
    为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
    生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))

    美中不足

    诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。

  • 相关阅读:
    十三、Sleuth分布式请求链路追踪
    十二、SpringCloud Stream消息驱动
    十一、SpringCloud Bus 消息总线
    Linux命令(权限管理)
    Linux命令(文件管理)
    Linux的文件和目录
    Linux介绍及安装
    Docker
    Nginx
    13、SpringBoot整合Mybatis-Plus
  • 原文地址:https://www.cnblogs.com/scott19820130/p/4641300.html
Copyright © 2011-2022 走看看