作者:董子龙
前言
记得那是2022年秋天的第一场雨,比2021年来的稍晚一些,在那个秋雨朦胧的下午,正在工位上奋笔疾书的我突然听到了前面波哥对着手机听筒说出来的"温柔"的话语:说说你了解的spring-aop。话音刚落,aop这三个字便犹如一把利剑一样狠狠的扎到了我的心上,让我的脑海中顿时浮现了当年刚刚毕业被面试官"蹂躏"的凄惨画面。历经多年,直至现在,虽然日常工作中经常使用aop做一些业务功能的开发,但是如果让我解释"面向切面"这四个字的意思,我还是会"十脸懵逼",哈哈。那么今天的文章,作为字节码增强技术系列承上启下的第二篇,就让我们以aop为马,去追逐字节码的星光。
一、SpringAop与Cglib
1.1、aop重要概念
1.2、实现原理解析
Spring AOP的实现原理是基于动态织入的动态代理技术,而动态代理技术又分为Java JDK动态代理和CGLIB动态代理。具体使用哪一种需要根据 AopProxyFactory 接口的 createProxy 方法中的 AdvisedSupport 中的参数进行确定,默认情况下如果目标类是接口,则使用 jdk 动态代理技术,如果是非接口类,则使用 cglib 来生成代理。具体的源码就不给大家一一贴出来了,大家可以去网上搜索一些资料进行深入了解。这里只给大家贴一张生成代理对象的图:
1.2.1、jdk动态代理
spring-aop中jdk代理的实现和我们平时自己实现动态代理开发是一致的,所以此处不做详细的介绍,只是简要给大家说明一下。使用做jdk动态代理时,代理类要实现InvocationHandler接口,目标类要实现接口,因为JDK提供的Proxy类将通过目标对象的类加载器ClassLoader和Interface,以及句柄(Callback)创建与目标类拥有相同接口的代理对象proxy,该代理对象将拥有目标类接口中的所有方法,同时代理类必须实现一个类似回调函数的InvocationHandler接口并重写该接口中的invoke方法,当调用proxy的每个方法(如案例中的proxy#execute())时,invoke方法将被调用,利用该特性,可以在invoke方法中对目标对象方法执行的前后动态添加其他外围业务操作,此时无需触及目标对象的任何代码,也就实现了外围业务的操作与目标对象完全解耦合的目的。当然缺点也很明显需要拥有接口,这也就有了后来的CGLIB动态代理了。
1.2.2、cglib动态代理
spring-aop中cglib代理的实现给大家贴一下源码的路径org.springframework.aop.framework.CglibAopProxy、org.springframework.aop.framework.CglibAopProxy#getProxy(java.lang.ClassLoader),感兴趣的话大家可以自己去看一下源码,接下来我会举一个具体的示例,来看一下如何使用cglib创建代理对象。(注:cglibgithub地址:https://github.com/cglib/cglib)
1.2.2.1、引入jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
1.2.2.2、示例代码
/**
* 创建目标对象
*/
public class Target {
public void execute(){
System.out.println("执行Target的execute方法...");
}
}
/**
* 创建cglib代理类
*/
public class CGLibProxy implements MethodInterceptor {
/**
* 被代理的目标类
*/
private Target target;
public CGLibProxy(Target target) {
super();
this.target = target;
}
/**
* 创建代理对象
* @return
*/
public Target createProxy(){
// 使用CGLIB生成代理:
// 1.声明增强类实例,用于生产代理类
Enhancer enhancer = new Enhancer();
// 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
enhancer.setSuperclass(target.getClass());
// 3.//设置回调函数,即一个方法拦截
enhancer.setCallback(this);
// 4.创建代理:
return (Target) enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 指定要执行被代理的方法
if("execute".equals(method.getName())) {
//调用前执行方法
System.out.println("调用前执行方法");
//调用目标对象的方法(执行A对象即被代理对象的execute方法)
Object result = methodProxy.invokeSuper(proxy, args);
//记录日志数据(动态添加其他要执行业务)
System.out.println("调用前执行方法");
return result;
}
//如果不需要增强直接执行原方法
return methodProxy.invokeSuper(proxy, args);
}
}
/**
* 创建测试类
*/
public class CglibTest {
public static void main(String[] args) {
// 将cglib生成的代理类写入到磁盘
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:\test\test");
CGLibProxy cgLibProxy = new CGLibProxy(new Target());
Target proxy = cgLibProxy.createProxy();
proxy.execute();
}
}
执行结果
从代码看被代理的类无需接口即可实现动态代理,而CGLibProxy代理类需要实现一个方法拦截器接口MethodInterceptor并重写intercept方法,类似JDK动态代理的InvocationHandler接口,也是理解为回调函数,同理每次调用代理对象的方法时,in