zoukankan      html  css  js  c++  java
  • Java动态代理

    一、概述

    1. 什么是代理

    我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:

    • 优点一:可以隐藏委托类的实现;

    • 优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

    2. 静态代理

    若代理类在程序运行前就已经存在,那么这种代理方式被成为静态代理,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下,静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。下面我们用Vendor类代表生产厂家,BusinessAgent类代表微商代理,来介绍下静态代理的简单实现,委托类和代理类都实现了Sell接口,Sell接口的定义如下:

    public interface Sell {

        void sell();

        void ad();

    }

    Vendor类的定义如下:

    public class Vendor implements Sell {

        public void sell() {

            System.out.println("In sell method");

        }

        public void ad() {

            System,out.println("ad method")

        }

    }

    代理类BusinessAgent的定义如下:

    public class BusinessAgent implements Sell {

        private Vendor mVendor;

        public BusinessAgent(Vendor vendor) {

            mVendor = vendor;

        }

        public void sell() { mVendor.sell(); }

        public void ad() { mVendor.ad(); }

    }

    从BusinessAgent类的定义我们可以了解到,静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。

    下面我们考虑一下这个需求:给Vendor类增加一个过滤功能,只卖货给大学生。通过静态代理,我们无需修改Vendor类的代码就可以实现,只需在BusinessAgent类中的sell方法中添加一个判断即可如下所示:

    public class BusinessAgent implements Sell {

        ...

        public void sell() {

            if (isCollegeStudent()) {

                vendor.sell();

            }

        }

        ...

    }

    这对应着我们上面提到的使用代理的第二个优点:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。静态代理的局限在于运行前必须编写好代理类,下面我们重点来介绍下运行时生成代理类的动态代理方式。

    二、动态代理

    1. 什么是动态代理

    代理类在程序运行时创建的代理方式被成为动态代理。也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。这么说比较抽象,下面我们结合一个实例来介绍一下动态代理的这个优势是怎么体现的。

    现在,假设我们要实现这样一个需求:在执行委托类中的方法之前输出“before”,在执行完毕后输出“after”。我们还是以上面例子中的Vendor类作为委托类,BusinessAgent类作为代理类来进行介绍。首先我们来使用静态代理来实现这一需求,相关代码如下:

    public class BusinessAgent implements Sell {

        private Vendor mVendor;

        public BusinessAgent(Vendor vendor) {

            this.mVendor = vendor;

        }

        public void sell() {

            System.out.println("before");

            mVendor.sell();

            System.out.println("after");

        }

        public void ad() {

            System.out.println("before");

            mVendor.ad();

            System.out.println("after");

        }

    }

    从以上代码中我们可以了解到,通过静态代理实现我们的需求需要我们在每个方法中都添加相应的逻辑,这里只存在两个方法所以工作量还不算大,假如Sell接口中包含上百个方法呢?这时候使用静态代理就会编写许多冗余代码。通过使用动态代理,我们可以做一个“统一指示”,从而对所有代理类的方法进行统一处理,而不用逐一修改每个方法。下面我们来具体介绍下如何使用动态代理方式实现我们的需求。

    2. 使用动态代理

    (1)InvocationHandler接口

    在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口,这个接口的定义如下:

    public interface InvocationHandler {

        Object invoke(Object proxy, Method method, Object[] args);

    }

    从InvocationHandler这个名称我们就可以知道,实现了这个接口的中介类用做“调用处理器”。当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。因此我们只需在中介类的invoke方法实现中输出“before”,然后调用委托类的invoke方法,再输出“after”。下面我们来一步一步具体实现它。

    (2)委托类的定义

    动态代理方式下,要求委托类必须实现某个接口,这里我们实现的是Sell接口。委托类Vendor类的定义如下:

    public class Vendor implements Sell {

        public void sell() {

            System.out.println("In sell method");

        }

        public void ad() {

            System,out.println("ad method")

        }

    }

    (3)中介类

    上面我们提到过,中介类必须实现InvocationHandler接口,作为调用处理器”拦截“对代理类方法的调用。中介类的定义如下:

    public class DynamicProxy implements InvocationHandler {

        private Object obj; //obj为委托类对象;

        public DynamicProxy(Object obj) {

            this.obj = obj;

        }

        @Override

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

            System.out.println("before");

            Object result = method.invoke(obj, args);

            System.out.println("after");

            return result;

        }

    }

    从以上代码中我们可以看到,中介类持有一个委托类对象引用,在invoke方法中调用了委托类对象的相应方法(第11行),看到这里是不是觉得似曾相识?通过聚合方式持有委托类对象引用,把外部对invoke的调用最终都转为对委托类对象的调用。这不就是我们上面介绍的静态代理的一种实现方式吗?实际上,中介类与委托类构成了静态代理关系,在这个关系中,中介类是代理类,委托类就是委托类;

    代理类与中介类也构成一个静态代理关系,在这个关系中,中介类是委托类,代理类是代理类。也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。下面我们来介绍一下如何”指示“以动态生成代理类。

    (4)动态生成代理类

    动态生成代理类的相关代码如下:

    public class Main {

        public static void main(String[] args) {

            //创建中介类实例

            DynamicProxy  inter = new DynamicProxy(new Vendor());

            //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件

            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 

            //获取代理类实例sell

            Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(),

            new Class[] {Sell.class}, inter));

            //通过代理类对象调用代理类方法,实际上会转到invoke方法调用

            sell.sell();

            sell.ad();

        }

    }

    在以上代码中,我们调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。这个方法的声明如下:

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

    方法的三个参数含义分别如下:

    • loader:定义了代理类的ClassLoder;

    • interfaces:代理类实现的接口列表

    • h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例

    上面我们已经简单提到过动态代理的原理,这里再简单的总结下:首先通过newProxyInstance方法获取代理类实例,而后我们便可以通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。

    下面来具体介绍

    Java动态代理的两种实现方法

     AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。


    那么动态代理是如何实现将切面逻辑(advise)织入到目标类方法中去的呢?下面我们就来详细介绍并实现AOP中用到的两种动态代理。

    AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。。

    1、定义接口和实现

    1     package com.meituan.hyt.test3.service;  
    2       
    3       
    4     public interface UserService {  
    5         public String getName(int id);  
    6       
    7         public Integer getAge(int id);  
    8     }  
     1     package com.meituan.hyt.test3.service.impl;  
     2       
     3     import com.meituan.hyt.test3.service.UserService;  
     4       
     5       
     6     public class UserServiceImpl implements UserService {  
     7         @Override  
     8         public String getName(int id) {  
     9             System.out.println("------getName------");  
    10             return "Tom";  
    11         }  
    12       
    13         @Override  
    14         public Integer getAge(int id) {  
    15             System.out.println("------getAge------");  
    16             return 10;  
    17         }  
    18     }  
    19 
    20 
    21 2、jdk动态代理实现
    23 
    24     package com.meituan.hyt.test3.jdk;  
    25       
    26     import java.lang.reflect.InvocationHandler;  
    27     import java.lang.reflect.Method;  
    28       
    29       
    30     public class MyInvocationHandler implements InvocationHandler {  
    31         private Object target;  
    32       
    33         MyInvocationHandler() {  
    34             super();  
    35         }  
    36       
    37         MyInvocationHandler(Object target) {  
    38             super();  
    39             this.target = target;  
    40         }  
    41       
    42         @Override  
    43         public Object invoke(Object o, Method method, Object[] args) throws Throwable {  
    44             if("getName".equals(method.getName())){  
    45                 System.out.println("++++++before " + method.getName() + "++++++");  
    46                 Object result = method.invoke(target, args);  
    47                 System.out.println("++++++after " + method.getName() + "++++++");  
    48                 return result;  
    49             }else{  
    50                 Object result = method.invoke(target, args);  
    51                 return result;  
    52             }  
    53       
    54         }  
    55     }  
    56 
    57 
    59 
    60     package com.meituan.hyt.test3.jdk;  
    61       
    62     import com.meituan.hyt.test3.service.UserService;  
    63     import com.meituan.hyt.test3.service.impl.UserServiceImpl;  
    64       
    65     import java.lang.reflect.InvocationHandler;  
    66     import java.lang.reflect.Proxy;  
    67       
    68       
    69     public class Main1 {  
    70         public static void main(String[] args) {  
    71             UserService userService = new UserServiceImpl();  
    72             InvocationHandler invocationHandler = new MyInvocationHandler(userService);  
    73             UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),  
    74                     userService.getClass().getInterfaces(), invocationHandler);  
    75             System.out.println(userServiceProxy.getName(1));  
    76             System.out.println(userServiceProxy.getAge(1));  
    77         }  
    78     }  

    运行结果

    1 ++++++before getName++++++
    2 ------getName------
    3 ++++++after getName++++++
    4 Tom
    5 ------getAge------

    3、cglib动态代理实现

    Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:

    CGLIB的核心类:
        net.sf.cglib.proxy.Enhancer – 主要的增强类
        net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
        net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
        Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。

    net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
    public Object intercept(Object object, java.lang.reflect.Method method,
    Object[] args, MethodProxy proxy) throws Throwable;

    第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快。

     1     package com.meituan.hyt.test3.cglib;  
     2       
     3       
     4     import net.sf.cglib.proxy.MethodInterceptor;  
     5     import net.sf.cglib.proxy.MethodProxy;  
     6       
     7     import java.lang.reflect.Method;  
     8       
     9       
    10     public class CglibProxy implements MethodInterceptor {  
    11         @Override  
    12         public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {  
    13             System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");  
    14             System.out.println(method.getName());  
    15             Object o1 = methodProxy.invokeSuper(o, args);  
    16             System.out.println("++++++before " + methodProxy.getSuperName() + "++++++");  
    17             return o1;  
    18         }  
    19     }  
     1     package com.meituan.hyt.test3.cglib;  
     2       
     3     import com.meituan.hyt.test3.service.UserService;  
     4     import com.meituan.hyt.test3.service.impl.UserServiceImpl;  
     5     import net.sf.cglib.proxy.Enhancer;  
     6       
     7       
     8       
     9     public class Main2 {  
    10         public static void main(String[] args) {  
    11             CglibProxy cglibProxy = new CglibProxy();  
    12       
    13             Enhancer enhancer = new Enhancer();  
    14             enhancer.setSuperclass(UserServiceImpl.class);  
    15             enhancer.setCallback(cglibProxy);  
    16       
    17             UserService o = (UserService)enhancer.create();  
    18             o.getName(1);  
    19             o.getAge(1);  
    20         }  
    21     }  

    运行结果:

    1  ++++++before CGLIB$getName$0++++++
    2 getName
    3 ------getName------
    4 ++++++before CGLIB$getName$0++++++
    5 ++++++before CGLIB$getAge$1++++++
    6 getAge
    7 ------getAge------
    8 ++++++before CGLIB$getAge$1++++++
  • 相关阅读:
    Fiddler系列教程3:使用Fiddler录制Jmeter性能测试脚本
    PySide6读取EXCLE文档
    C#实现操作DOS命令的方法
    在PyCharm中调用xlrd模块出现 ModuleNotFoundError: No module named 'xlrd' ,但在sublime却可以正常
    pip安装时出现错误:File "D:Python39Scriptspip.exe\__main__.py", line 4, in <module> ModuleNotFoundError: No module named 'pip'
    /ect/fstab与/etc/mtab的区别
    虚拟机中Ubuntu系统修改分辨率
    关于报错Could not load file or assembly的问题
    关于WinCC归档应该知道的事
    vue + element 表单的简单公用组件,表格的集成写法
  • 原文地址:https://www.cnblogs.com/hzzjj/p/6626931.html
Copyright © 2011-2022 走看看