使用过SpringAOP的人都知道, AOP的底层实现原理是使用了动态代理模式来实现面向切面编程。而SpringAOP使用的动态代理方式有JDK的动态代理,另一个则是Cglib的动态代理。本文先不讲AOP,主要讲Cglib的动态代理实现过程。
Cglib底层实现是使用了ASM操作字节码生成代理对象,由于这种技术十分底层,因此本文不打算研究,而且这些知识对于应用开发来说投入产出比不高,因此对于Cglib,个人主张只需要知道其大概实现过程,在日常开发中一旦遇到和Cglib相关的问题,可以能比较快速的解决方案。
首先还是归纳三要素:
- MethodInterceptor
- 继承
- 动态代理对象
首先还是先放测试用例再讲原理:
我们下面模仿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)。