Java动态代理

什么是动态代理

动态代理就是在 程序运行期 ,创建目标对象的 代理对象 ,对 目标对象 中的 方法 进行 功能性增强 的一种技术

核心概念:
动态代理能够让真实对象专注于自己的核心功能,让代理对象拦截客户对真实对象的访问,另外可以在不修改方法源码的情况下,增强被代理对象的方法的功能。

我们在做项目的时候把日志和异常统一处理就是利用了动态代理,让我们能够关注核心业务。

举个例子,小明(真实角色)的主要业务是唱歌,在还没火的时候自己跑东跑西去街头、酒吧等地方唱歌,在这期间需要自己负责找合适的场地,以及和酒吧老板谈工资等业务,突然有一天他火了,很多人要请他唱歌,所有的事亲历亲为他根本忙不过来,这个时候经纪人(代理角色)出现了,客户们要请小明唱歌,不能直接找到小明了,而是需要和经纪人谈,经纪人在商业等方面的理解比小明强(对代理对象的方法增强)。这样小明就可以专注于自己的唱歌业务了,经纪人的存在就是拦截了客户对小明(真实对象)的访问。

动态代理的常用方式

  1. JDK动态代理:JDK自带的动态代理功能(基于接口的动态代理),它的实现前提是现有类必须拥有一个接口,因为它是通过 对现有类接口的实现 来完成的。
  2. CGLIB代理:一个开源工具包(基于类的动态代理),它的实现是通过 继承现有类 ,然后 重写现有类的方法 实现的。

JDK动态代理

在java的 java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

代码实现

我们以上述小明故事的场景,代理对象代理了小明的谈演出费的方法,看看具体怎么实现

  1. 首先定义一个接口,这个接口是一个公共接口,这个接口有一个talk()方法
public interface Person {
    //谈薪
    public void talk();
}
  1. 创建真实对象,实现上述接口,那么代理对象需要代理的就是这个接口
public class XiaoMing implements Person{
    @Override
    public void talk(){
        System.out.println("小明成功收取唱歌费");
    }
}
  1. 创建BrokerInvocationHandler类,这个类实现了 InvocationHandler 接口,我们通过这个类来动态生成代理类,InvocationHandler中有一个 invoke 方法,所有执行代理对象的方法都会被替换成执行invoke方法。然后通过反射在invoke方法中执行代理类的方法。在代理过程中,在执行代理类的方法前或者后可以执行自己的操作,这就是spring aop的主要原理。
//用这个类自动生成代理类(这个并不是代理类,我们通过这个生成代理类)
public class BrokerInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Person person;
 
    public void setPerson(Person person) {
        this.person = person;
    }
    //生成得到代理类 固定的代码
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),person.getClass().getInterfaces(),
                this);
    }
    /**
     *处理代理实例并返回结果
     * @param proxy:代表动态代理对象
     * @param method:代表正在执行的方法
     * @param args:代表调用目标方法时传入的实参
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理过程中插入其他操作 对方法的增强
        System.out.println("客户和经纪人交流");
        //动态代理本质就是使用反射机制!!如下
        Object result = method.invoke(person,args);
        return result;
    }
}
  1. 创建客户对象,通过BrokerInvocationHandler生成代理对象,客户通过访问代理对象实现谈薪
public class TestMain{
    public static void main(String[] args) {
        //真实角色
        XiaoMing xiaoming = new XiaoMing();
        //代理角色:现在没有 先得到BrokerInvocationHandler对象
        BrokerInvocationHandler bid = new BrokerInvocationHandler();
        //通过调用程序处理角色来处理我们要调用的接口对象(知道我们要调用的是实现这个接口的哪个对象)
        bid.setPerson(xiaoming);
        //得到代理对象
        Person proxy = (Person) bid.getProxy();//这里的proxy是动态生成的,我们并未写!!
        proxy.talk();
    }
}
  1. 输出结果如下
客户和经纪人交流
小明成功收取唱歌费

小结

可以看到,代理对象成功代理了小明的talk()方法;

JDK动态代理首先需要创建一个interface然后一个class实现这个interface,然后对这个class进行代理,这个class必须实现至少一个接口,否则不能进行代理。

JDK动态代理实现步骤总结

  1. 实现 InvocationHandler 接口,将我们具体地增强逻辑代码写在 invoke() 方法中
  2. 使用 Proxy 类的 newProxyInstance() 方法去创建一个代理类的实例对象.
  3. 使用这个代理类对象

CGLIB动态代理

JDK动态代理需要被代理类实现接口,如果被代理类没有实现接口,就只能使用CGLib了。

这种代理方式就叫做CGlib代理。

CGlib代理也叫作子类代理,他是通过在内存中构建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,然后加入自己需要的操作。

因为使用的是继承的方式,所以不能代理final 类。

CGLIB是一个功能强大,高性能的 代码生成包 (CGLIB就是用来生成代码的,它也是在内存中动态的去生成的)

代码实现

同样以上述小明故事的场景,代理对象代理了小明的谈演出费的方法,看看具体怎么实现

  1. CGLIB是第三方提供的包,所以我们需要在maven工程中引入对应jar包
<!--CGLIB-->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2.新建被代理类,此时并不需要再新建接口,因为我们现在是要使用CGLIB进行代理

public class XiaoMing {
    public void talk(){
        System.out.println("小明成功收取唱歌费");
    }
}
  1. 继续创建BrokerInvocationHandler类,这个类实现了 MethodInterceptor 接口,我们通过这个类来动态生成代理类,此时,获得的代理类就是目标对象所属类的子类。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class BrokerInvocationHandler implements MethodInterceptor {
    //被代理类
    private XiaoMing xiaoMing;

    public void setXiaoMing(XiaoMing xiaoMing) {
        this.xiaoMing = xiaoMing;
    }

    //生成得到代理类 固定的代码
    public Object getProxy(){
        // 1. 创建Enhancer类对象,它类似于JDK动态代理中的Proxy类,该类就是用来获取代理对象的
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类的字节码对象。为啥子要这样做呢?因为使用CGLIB生成的代理类是属于目标类的子类的,也就是说代理类是要继承自目标类的
        enhancer.setSuperclass(xiaoMing.getClass());
        // 3. 设置回调函数
        enhancer.setCallback(this);
        // 4. 创建代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //代理过程中插入其他操作 对方法的增强
        System.out.println("客户和经纪人交流");
        Object invoke = method.invoke(xiaoMing, objects);
        return invoke;
    }

}
```  

4. 创建测试类测试
```java
public class TestMain {
    public static void main(String[] args) {
        //真实角色
        XiaoMing xiaoMing = new XiaoMing();
        //代理角色:现在没有 先得到BrokerInvocationHandler对象
        BrokerInvocationHandler brokerInvocationHandler =new BrokerInvocationHandler();
        brokerInvocationHandler.setXiaoMing(xiaoMing);
        //得到代理对象
        XiaoMing proxy = (XiaoMing) brokerInvocationHandler.getProxy();
        proxy.talk();
    }
}
  1. 输出结果如下
客户和经纪人交流
小明成功收取唱歌费

小结

可以看到,代理对象同样成功代理了小明的talk()方法;

CGLIB动态代理不需要创建一个interface,代理的class不需要实现接口。

CGLIB动态代理实现步骤总结

  1. 实现 MethodInterceptor 接口,将我们具体地增强逻辑代码写在 intercept() 方法中
  2. 使用 Enhancer.create() 方法去创建一个代理类的实例对象.
  3. 使用这个代理类对象

实现原理、优缺点、两者区别

JDKCGLIB
实现原理JDK动态代理是由Java内部的反射机制来实现的,它是JDK原生就支持的一种代理方式,它的实现原理,就是通过让target类和代理类实现同一接口,代理类持有target对象,来达到方法拦截的作用,这样通过接口的方式有两个弊端,一个是必须保证target类有接口,第二个是如果想要对target类的方法进行代理拦截,那么就要保证这些方法都要在接口中声明,实现上略微有点限制。CGLIB动态代理是通过继承来实现的,底层则是借助asm(Java 字节码操控框架)来实现的(采用字节码的方式,给A类创建一个子类B,子类B使用方法拦截的技术拦截所有父类的方法调用)。
优缺点JDK动态代理有一定的局限性,只能基于接口。CGLIB这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。但无法处理final的情况,因为它通过继承实现。
两者区别JDK动态代理只能对实现了接口的类生成代理,而不能针对类。CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,但不能代理final修饰的类。

参考链接

  1. https://blog.csdn.net/weixin_61543601/article/details/124656001