浅谈Spring中JDK动态代理和CGLIB动态代理

什么是代理模式

​ 代理模式(Proxy Pattern)给某一个对象提供一个代理,并且由代理对象控制原对象得引用,代理对象在客户端和目标对象之间起到中间作用。

​ 代理模式是常用得结构设计模式之一,当直接访问某些对象存在问题时候可以通过一个代理对象间接访问,为了保证客户端使用透明性,所访问得真实对象需要实现相同得接口。

代理模式可以分为静态代理和动态代理两种类型,而动态代理中又分为JDK动态代理和CGLIB代理两种。我们今天主要讲动态代理。

动态代理定义及应用

​ 动态代理:是使用反射和字节码的技术,在运行期创建指定接口或类的子类,以及其实例对象的技术,通过这个技术可以无侵入的为代码进行增强。

​ 动态代理应用非 常的广泛,在各种开源的框架中都能看到他们的身影,比如spring中的aop使用动态代理增强,mybatis中使用动态代理生成mapper。

JDK实现的动态代理

解析

利用拦截器(必须实现InvocationHandler接口)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理

jdk实现的动态代理由两个重要的成员组成,分别是ProxyInvocationHandler

  • Proxy: 是所有动态代理的父类,它提供了一个静态方法来创建动态代理的class对象和实例

  • InvocationHandler: 每个动态代理实例都有一个关联的InvocationHandler,在代理实例上调用方法是,方法调用将被转发到InvocationHandler的invoke方法

1、代码实现

IStudentService接口


/**
 * @Author charles.yao
 * @Description 抽象角色,一般都是用接口,或者抽象类解决。
 * @Date 2022/10/21 15:32
 */
public interface IStudentService {
    /**
     * 增加
     */
    public void addStudent();

    /**
     * 删除
     */
    public void deleteStudent();

    /**
     * 更新
     */
    public void updateStudent();

    /**
     * 查询
     */
    public void selectStudent();
}

实现类StudentServiceImpl


/**
 * @Author charles.yao
 * @Description 学生信息service
 * @Date 2022/10/21 15:32
 */
@Slf4j
@Service
public class StudentServiceImpl implements IStudentService {
    @Override
    public void addStudent() {
        log.info("执行了addStudent");
    }

    @Override
    public void deleteStudent() {
        log.info("执行了deleteStudent");
    }

    @Override
    public void updateStudent() {
        log.info("执行了updateStudent");

    }

    @Override
    public void selectStudent() {
        log.info("执行了selectStudent");
    }
}

ProxyInvocationHandler代理类,实现InvocationHandler接口重写invoke方法


/**
 * @Author charles.yao
 * @Description 实现InvocationHandler接口重写invoke方法, 自动生成一个代理类
 * @Date 2022/10/21 15:35
 */
@Slf4j
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理得目标接口
    private Object target;

    public ProxyInvocationHandler() {
    }

    public ProxyInvocationHandler(Object target) {
        this.target = target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }
    /**
     * 生成需要得动态代理类
     *
     * @return
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

    /**
     * 处理代理的实例,并返回结果
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("方法:{}被执行", method.getName());
        //TODO 执行方法前需要增强得部分

        log.info("执行方法前,需要对学生信息进行校验>>>>>>");
        Object invoke = method.invoke(target, args);
        log.info("执行方法结束>>>>>>");
        return invoke;
    }
}

测试方法


/**
 * @Author charles.yao
 * @Description jdk动态代理测试
 * @Date 2022/10/21 15:44
 */
public class JdkProxyTest {
    public static void main(String[] args) {
        //真实类
        StudentServiceImpl studentService = new StudentServiceImpl();

        //代理角色,不存在的,
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        //设置要代理得对象
        proxyInvocationHandler.setTarget(studentService);
        //动态生成代理类
        IStudentService iStudentService = (IStudentService) proxyInvocationHandler.getProxy();

        iStudentService.addStudent();
    }

测试结果

16:02:38.053 [main] INFO com.test.ProxyInvocationHandler - 方法:addStudent被执行
16:02:38.064 [main] INFO com.test.ProxyInvocationHandler - 执行方法前,需要对学生信息进行校验>>>>>>
16:02:38.064 [main] INFO com.test.StudentServiceImpl - 执行了addStudent
16:02:38.066 [main] INFO com.test.ProxyInvocationHandler - 执行方法结束>>>>>>

CGlib动态代理

1、解析

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承的方式实现代理(最后这部分我们深思一下,它可能有哪些局限,final方法是不能够被重写,所以它不能增强被final修饰的方法,这个等下我们来验证)

CGLIB的实现也有两个重要的成员组成,EnhancerMethodInterceptor,其实这两个的使用和jdk实现的动态代理的ProxyInvocationHandler非常相似

  • Enhancer: 来指定要代理的目标对象,实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象、对这个对象所有的非final方法的调用都会转发给MethodInterceptor
  • MethodInterceptor: 动态代理对象的方法调用都会转发到intercept方法进行增强

2、代码示例

CGlib不像是JDK动态代理,CGlib需要导入Jar包,那么我用SpringBoot直接导入依赖

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>

StudentServiceImpl得类


/**
 * @Author charles.yao
 * @Description 学生信息service
 * @Date 2022/10/21 15:32
 */
@Slf4j
@Service
public class StudentServiceImpl implements IStudentService {
    @Override
    public void addStudent() {
        log.info("执行了addStudent");
    }

    @Override
    public void deleteStudent() {
        log.info("执行了deleteStudent");
    }

    @Override
    public void updateStudent() {
        log.info("执行了updateStudent");

    }

    @Override
    public void selectStudent() {
        log.info("执行了selectStudent");
    }
}

代理类StudentServiceCGlib


/**
 * @Author charles.yao
 * @Description CGlib 实现servce
 * @Date 2022/10/21 16:21
 */
@Slf4j
public class StudentServiceCGlib implements MethodInterceptor {
    //需要代理得类
    private Object target;

    public StudentServiceCGlib() {
    }

    public StudentServiceCGlib(Object target) {
        this.target = target;
    }

    /**
     * 返回一个代理对象,是target对象得代理对象     *
     * 利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来进行代理
     *
     * @return
     */
    public Object getProxyInstance() {
        //创建一个cglib工具类
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建子类对象,就是代理对象
        return enhancer.create();


    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        log.info("开始对代理对象增强啦>>>>>>>>>>");
        //TODO 添加业务增强代码
        Object result = methodProxy.invokeSuper(o, objects);
        log.info("对代理对象增强结束啦>>>>>>>>>>");
        return result;
    }
}

测试类CGlibProxyTest

/**
 * @Author charles.yao
 * @Description CGlib 动态搭理测试类
 * @Date 2022/10/21 16:28
 */
public class CGlibProxyTest {
    public static void main(String[] args) {

        StudentServiceCGlib studentServiceCGlib = new StudentServiceCGlib(new StudentServiceImpl());
        //利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来进行代理
        StudentServiceImpl proxyInstance = (StudentServiceImpl) studentServiceCGlib.getProxyInstance();

        proxyInstance.addStudent();

        proxyInstance.selectStudent();
    }
}

执行结果:

16:29:50.953 [main] INFO com.server.test.StudentServiceCGlib - 开始对代理对象增强啦>>>>>>>>>>
16:29:50.985 [main] INFO com.server.test.StudentServiceImpl - 执行了addStudent
16:29:50.985 [main] INFO com.server.test.StudentServiceCGlib - 对代理对象增强结束啦>>>>>>>>>>
16:29:50.985 [main] INFO com.server.test.StudentServiceCGlib - 开始对代理对象增强啦>>>>>>>>>>
16:29:50.985 [main] INFO com.server.test.StudentServiceImpl - 执行了selectStudent
16:29:50.985 [main] INFO com.server.test.StudentServiceCGlib - 对代理对象增强结束啦>>>>>>>>>>

最后总结

在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,JDK代理效率高于CGLIB代理。

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理
2、如果目标对象实现了接口,也可以强制使用CGLIB
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换


关注我的微信公众号
​​​​在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CharlesYooSky/article/details/127449424