动态代理模式(Cglib实现)

使用过SpringAOP的人都知道, AOP的底层实现原理是使用了动态代理模式来实现面向切面编程。而SpringAOP使用的动态代理方式有JDK的动态代理,另一个则是Cglib的动态代理。本文先不讲AOP,主要讲Cglib的动态代理实现过程。

Cglib底层实现是使用了ASM操作字节码生成代理对象,由于这种技术十分底层,因此本文不打算研究,而且这些知识对于应用开发来说投入产出比不高,因此对于Cglib,个人主张只需要知道其大概实现过程,在日常开发中一旦遇到和Cglib相关的问题,可以能比较快速的解决方案。

首先还是归纳三要素:

  1. MethodInterceptor
  2. 继承
  3. 动态代理对象

首先还是先放测试用例再讲原理:
我们下面模仿Spring的AOP实现日志切面的功能来说说Cglib的动态代理。

@Slf4j
public class CglibTest {
    public static void main(String[] args) {
        // 1、创建对象
        StudentService studentService = BeanFactory.newInstance(StudentService.class);

        // 2、执行方法
        log.info("StudentService实际执行类: {}", studentService.toString());
        int learnResult = studentService.learn("Martin");
        log.info("learn方法返回参数: {}", learnResult);
        int sleepResult = studentService.sleep("Martin");
        log.info("sleep方法返回参数: {}", sleepResult);
        studentService.finalMethod();
    }
}

运行结果:

[INFO ] 2021-07-14 17:12:36.332 method:cn.gaaidou.dynamic.proxy.cglib.CglibTest.main(CglibTest.java:17): StudentService实际执行类: cn.gaaidou.dynamic.proxy.cglib.service.StudentService$$EnhancerByCGLIB$$73db738c@16c0663d
[INFO ] 2021-07-14 17:12:36.347 method:cn.gaaidou.dynamic.proxy.cglib.callback.SystemLogCallback.intercept(SystemLogCallback.java:24): 方法: cn.gaaidou.dynamic.proxy.cglib.service.StudentService.learn(String) 开始执行…
[INFO ] 2021-07-14 17:12:36.350 method:cn.gaaidou.dynamic.proxy.cglib.service.StudentService.learn(StudentService.java:22): Martin老师的课上得真好
[INFO ] 2021-07-14 17:12:36.350 method:cn.gaaidou.dynamic.proxy.cglib.callback.SystemLogCallback.intercept(SystemLogCallback.java:27): 方法: cn.gaaidou.dynamic.proxy.cglib.service.StudentService.learn(String) 执行完成, 耗时: 3 ms
[INFO ] 2021-07-14 17:12:36.352 method:cn.gaaidou.dynamic.proxy.cglib.CglibTest.main(CglibTest.java:19): learn方法返回参数: 1
[INFO ] 2021-07-14 17:12:36.353 method:cn.gaaidou.dynamic.proxy.cglib.service.StudentService.sleep(StudentService.java:37): Martin老师的课太无聊了我要睡觉了
[INFO ] 2021-07-14 17:12:36.353 method:cn.gaaidou.dynamic.proxy.cglib.CglibTest.main(CglibTest.java:21): sleep方法返回参数: 1
[INFO ] 2021-07-14 17:12:36.353 method:cn.gaaidou.dynamic.proxy.cglib.service.StudentService.finalMethod(StudentService.java:32): 本方法不会生成代理子类

下面按三要素

1.MethodInterceptor

要实现Cglib的动态代理,需要实现MethodInterceptor接口,这个接口其实和JDK的动态代理接口:

public interface InvocationHandler {

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

public interface MethodInterceptor extends Callback{
  
    public Object intercept(Object obj, Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;

}

两者比较方法参数都是大同小异。

我们下面实现MethodInterceptor实现日志AOP功能:

@Slf4j
public class SystemLogCallback implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        SystemLog systemLog = method.getAnnotation(SystemLog.class);
        // 如果配置日志注解, 打印方法调用时长
        if (null != systemLog) {
            String methodName = ClazzUtil.getMethodName(method);
            long start = System.currentTimeMillis();
            log.info("方法: {} 开始执行...", methodName);
            Object o1 = methodProxy.invokeSuper(o, objects);
            long end = System.currentTimeMillis();
            log.info("方法: {} 执行完成, 耗时: {} ms", methodName, end - start);
            return o1;
        } else {
            // 没有配置日志注解的, 不走动态代理方法
            return methodProxy.invokeSuper(o, objects);
        }
    }
}

2.继承

相对于Jdk的动态代理, Cglib的动态代理不同的地方是Cglib的动态代理的被代理对象不需要继承于接口,因此实际上在使用Cglib动态代理时,实际上我们可以理解为是为被代理类创建了一个子类,只是这个子类的创建过程是使用了ASM修改字节码来生成子类,并且创建这个子类对象,而这个子类对象就是动态代理对象。所以下面的被代理类中,final修饰的方法,是不会被子类继承,因此也不会有日志切面

@Slf4j
public class StudentService {


    /**
     * 普通service方法
     *
     * @param teacherName
     * @return
     */
    @SystemLog
    public int learn(String teacherName) {
        log.info("{}老师的课上得真好", teacherName);
        return 1;
    }

    /**
     * final修饰的方法
     * 虽然有SystemLog注解, 但因为子类不能继承, 因此也不会有动态代理效果
     */
    @SystemLog
    public final void finalMethod() {
        log.info("本方法不会生成代理子类");

    }

    public int sleep(String teacherName) {
        log.info("{}老师的课太无聊了我要睡觉了", teacherName);
        return 1;
    }
}

3. 动态代理对象

动态代理对象创建的过程。核心类是:Enhancer

@Slf4j
public class BeanFactory {

    static {
        // 生成代理类的class文件
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code");
    }

    /**
     * 根据class创建Bean
     * 1、如果该Bean有SystemLog注解, 则生成cglib代理对象
     * 2、如果该Bean没有SystemLog注解
     *
     * @param clazz
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T newInstance(Class<T> clazz) {
        // 1、判断是否存在SystemLog对象
        boolean isProxy = Arrays.stream(clazz.getMethods()).anyMatch(v -> null != v.getAnnotation(SystemLog.class));
        if (isProxy) {
            // 2、创建Cglib对象
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(new SystemLogCallback());
            return (T) enhancer.create();
        } else {
            // 3、生成原对象
            Optional<Constructor<?>> noArgsConstructor = Arrays.stream(clazz.getConstructors()).filter(v -> v.getParameterCount() == 0).findFirst();
            try {
                if (noArgsConstructor.isPresent()) {
                    return (T) noArgsConstructor.get().newInstance();
                } else {
                    return clazz.newInstance();
                }
            } catch (Exception e) {
                log.error("创建对象 {} 失败", clazz.getName());
                throw new RuntimeException("创建对象失败");
            }
        }
    }
}

完整代码在Gitee仓库上 维护(见Module: gaaidou-dynamic-proxy)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值