Spring Boot实践——Spring AOP实现之动态代理
Spring AOP 介绍
AOP 的介绍可以查看 Spring Boot 实践——AOP 实现
与 AspectJ 的静态代理不同,Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理。JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是InvocationHandler
接口和Proxy
类。
如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,是利用 asm 开源包,可以在运行时动态的生成某个类的子类。注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为final
,那么它是无法使用 CGLIB 做动态代理的。
这里有注意的几点如下:
- 从 Spring 3.2 以后不再将 CGLIB 放在项目的 classpath 下,而是将 CGLIB 类打包放在 spring-core 下面的 org.springframework 中。这个就意味着基于 CGLIB 的动态代理与 JDK 的动态代理在支持“just works”就一样了。
- 在 Spring 4.0 中,因为 CGLIB 代理实例是通过 Objenesis 创建的,所以代理对象的构造器不再有两次调用。
- 在 Spring Boot 2.0 中,Spring Boot 现在默认使用 CGLIB 动态代理 (基于类的动态代理), 包括 AOP. 如果需要基于接口的动态代理 (JDK 基于接口的动态代理) , 需要设置 spring.aop.proxy-target-class 属性为 false。
实现方式
一、验证 Spring AOP 动态代理
博主使用的是 Spring Boot 2.0 版本
1、JDK 动态代理
定义接口
public interface Person {String say(String name);</span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> eat(String food);
}
实现类
@Component public class Chinese implements Person { private Logger logger = LoggerFactory.getLogger(Person.class);</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Chinese() { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(); logger.info(</span>"Chinese ==> Chinese method : 正在生成一个Chinese实例"<span style="color: rgba(0, 0, 0, 1)">); } @Override @PersonAnnotation(name</span>="Chinese"<span style="color: rgba(0, 0, 0, 1)">)<span style="color: rgba(255, 0, 0, 1)">//该注解是用来定义切点 </span></span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String say(String name) { logger.info(</span>"Chinese ==> say method : say {}"<span style="color: rgba(0, 0, 0, 1)">, name); </span><span style="color: rgba(0, 0, 255, 1)">return</span> name + " hello, JDK implement AOP"<span style="color: rgba(0, 0, 0, 1)">; } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> eat(String food) { logger.info(</span>"Chinese ==> eat method : eat {}"<span style="color: rgba(0, 0, 0, 1)">, food); }
}
定义 Aspect
@Aspect @Component public class PersonAspect { private Logger logger = LoggerFactory.getLogger(PersonAspect.class);@Pointcut(</span>"@annotation(com.only.mate.springboot.annotation.PersonAnnotation)"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> pointCut(){ } @Before(</span>"pointCut()"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> before(JoinPoint joinPoint) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Throwable { logger.info(</span>"PersonAspect ==> before method : {}"<span style="color: rgba(0, 0, 0, 1)">, joinPoint.getClass()); } @After(</span>"pointCut()"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> after(JoinPoint joinPoint){ logger.info(</span>"PersonAspect ==> after method : {}"<span style="color: rgba(0, 0, 0, 1)">, joinPoint.getClass()); }
}
2、CGLIB 动态代理
定义一个类
@Component public class American { private Logger logger = LoggerFactory.getLogger(American.class);</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> American() { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">(); logger.info(</span>"American ==> American method : 正在生成一个American实例"<span style="color: rgba(0, 0, 0, 1)">); } @PersonAnnotation(name</span>="American"<span style="color: rgba(0, 0, 0, 1)">)<span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">//该注解是用来定义切点</span></span> </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String say(String name) { logger.info(</span>"American ==> say method : say {}"<span style="color: rgba(0, 0, 0, 1)">, name); </span><span style="color: rgba(0, 0, 255, 1)">return</span> name + " hello, CGLIB implement AOP"<span style="color: rgba(0, 0, 0, 1)">; } </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> eat(String food) { logger.info(</span>"American ==> eat method : eat {}"<span style="color: rgba(0, 0, 0, 1)">, food); }
}
3、配置
<!-- 自动扫描使用了 aspectj 注解的类 --> <aop:aspectj-autoproxy/>
或
@Configuration @ComponentScan("com.only.mate.springboot.aop") @EnableAspectJAutoProxy//开启 AspectJ 注解 public class CustomAopConfigurer { }
4、运行
@Controller @RequestMapping(value="/aop") public class AopImplementController { private Logger logger = LoggerFactory.getLogger(AopImplementController.class);@Autowired </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Person chinese; @Autowired </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> American american; @ResponseBody @RequestMapping(value</span>="/talk"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String talk() { chinese.say(</span>"中国人说汉语"<span style="color: rgba(0, 0, 0, 1)">); american.say(</span>"American say english"<span style="color: rgba(0, 0, 0, 1)">); logger.info(</span>"AopImplementController ==> talk method : {}"<span style="color: rgba(0, 0, 0, 1)">, chinese.getClass()); logger.info(</span>"AopImplementController ==> talk method : {}"<span style="color: rgba(0, 0, 0, 1)">, american.getClass()); </span><span style="color: rgba(0, 0, 255, 1)">return</span> "success"<span style="color: rgba(0, 0, 0, 1)">; }
}
5、效果图
问题出现了,按照之前的说法,Chinese 应该是用 JDK 动态代理,American 使用 CGLIB 动态代理才对。
由于博主使用的是Spring Boot 2.0 ,在 Spring Boot 2.0 中,Spring Boot 现在默认使用 CGLIB 动态代理 (基于类的动态代理), 包括 AOP。 如果需要基于接口的动态代理 (JDK 基于接口的动态代理) ,需要设置 spring.aop.proxy-target-class 属性为 false。
因此在 application.properties 加上配置 spring.aop.proxy-target-class=false。重启访问
如图:
这样才达到了预期的目的
通过网上查找:
- 想要强制使用 CGLIB,那么就设置
<aop:config>
下面的proxy-target-class
属性为true
<aop:config proxy-target-class="true"> <!-- other beans defined here... --> </aop:config>
此处没有验证,仅供参考。
- 要是使用 @AspectJ 强制使用 CGLIB 的话,可以配置
<aop:aspectj-autoproxy>
下的proxy-target-class
属性为true
<aop:aspectj-autoproxy proxy-target-class="true"/>
此处博主用 Spring Boot 2.0,加以上配置无效,还是使用默认的 CGLIB 动态代理。失效的具体原因有待查验!
- 要是使用 @AspectJ 强制使用 CGLIB 的话,向
@EnableAspectJAutoProxy
注解中添加属性proxyTargetClass = true
此处博主用 Spring Boot 2.0,加以上配置无效,还是使用默认的 CGLIB 动态代理。失效的具体原因有待查验!
二、Spring AOP 动态代理实现
1、JDK 动态代理
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/**
- @Description: Spring AOP 之 JDK 动态代理实现
- @ClassName: JDKDynamicSubject
- @author OnlyMate
- @Date 2018 年 9 月 11 日 下午 4:47:53
*/
public class JDKDynamicObject implements InvocationHandler {
private Logger logger = LoggerFactory.getLogger(JDKDynamicObject.class);</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Object target; </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> JDKDynamicObject() { } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * @Description: 绑定对象,并生成代理对象 * @Title: bind * </span><span style="color: rgba(128, 128, 128, 1)">@author</span><span style="color: rgba(0, 128, 0, 1)"> OnlyMate * @Date 2018年9月11日 下午4:48:31 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> target * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Object bind(Object target) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.target =<span style="color: rgba(0, 0, 0, 1)"> target; </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 取得代理对象</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> Object invoke(Object proxy, Method method, Object[] args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Throwable { logger.info(</span>"JDKDynamicObject ==> invoke method : {},{},{}"<span style="color: rgba(0, 0, 0, 1)">, proxy.getClass(), method.getName(), args.toString()); method.invoke(target, args); </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; }
}
2、CGLIB 动态代理
import java.lang.reflect.Method;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;/**
- @Description: Spring AOP 之 CGLIB 动态代理实现
- @ClassName: CGLIBDynamicObject
- @author OnlyMate
- @Date 2018 年 9 月 11 日 下午 4:57:30
*/
public class CGLIBDynamicObject implements InvocationHandler {
private Logger logger = LoggerFactory.getLogger(CGLIBDynamicObject.class);</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Object target; </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> CGLIBDynamicObject() { } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * @Description: 绑定对象,并生成代理对象 * @Title: bind * </span><span style="color: rgba(128, 128, 128, 1)">@author</span><span style="color: rgba(0, 128, 0, 1)"> OnlyMate * @Date 2018年9月11日 下午4:48:31 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> target * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Object bind(Object target) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.target =<span style="color: rgba(0, 0, 0, 1)"> target; Enhancer enhancer </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Enhancer(); enhancer.setSuperclass(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.target.getClass()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 回调方法</span> enhancer.setCallback(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建代理对象</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> enhancer.create(); } @Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> Object invoke(Object arg0, Method arg1, Object[] arg2) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Throwable { logger.info(</span>"CGLIBDynamicObject ==> invoke method : {},{},{}"<span style="color: rgba(0, 0, 0, 1)">, arg0.getClass(), arg1.getName(), arg2.toString()); arg1.invoke(target, arg2); </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; }
}
3、运行
@Controller @RequestMapping(value="/aop") public class AopImplementController { private Logger logger = LoggerFactory.getLogger(AopImplementController.class);@Autowired </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Person chinese; @Autowired </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> American american; @ResponseBody @RequestMapping(value</span>="/talk"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String talk() {
// chinese.say("中国人说汉语");
// american.say("American say english");
//
// logger.info("AopImplementController ==> talk method : {}", chinese.getClass());
// logger.info("AopImplementController ==> talk method : {}", american.getClass());</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">自定义JDK动态代理
// Chinese chinese1 = new Chinese();
// InvocationHandler dsc = new JDKDynamicObject(chinese1);
// Person person = (Person) Proxy.newProxyInstance(chinese1.getClass().getClassLoader(), chinese1.getClass().getInterfaces(), dsc);
// person.say("中国人说汉语");
// logger.info("AopImplementController ==> talk method : JDKDynamicObject {}", person.getClass());
JDKDynamicObject dsc1 = new JDKDynamicObject();
Person person1 = (Person)dsc1.bind(new Chinese());
person1.say("中国人说汉语");
logger.info("AopImplementController ==> talk method : JDKDynamicObject {}", person1.getClass());</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">自定义CGLIB动态代理</span> CGLIBDynamicObject dsm = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CGLIBDynamicObject(); American american1 </span>= (American) dsm.bind(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> American()); american1.say(</span>"American say english"<span style="color: rgba(0, 0, 0, 1)">); logger.info(</span>"AopImplementController ==> talk method : CGLIBDynamicObject {}"<span style="color: rgba(0, 0, 0, 1)">, american1.getClass()); </span><span style="color: rgba(0, 0, 255, 1)">return</span> "success"<span style="color: rgba(0, 0, 0, 1)">; }
}
4、效果图
简单的实现了 Spring AOP 的 JDK 动态代理和 CGLIB 动态代理。