上篇讲到SpringAOP的一些用法以及概念,这里我们单独讲一下AOP中的“织入”。
我们知道,SpringAOP是基于动态代理实现的技术,而织入则是一个生成动态代理对象并且将切面和目标对象方法编织成为约定流程的过程。
对于通知,上篇文章中都是采用接口+实现类的方式,这也是Spring推荐的方式,但并不是强制的,动态代理的实现方式也是有很多种的,目前主流的就是JDK、CGLIB、Javassist、ASM等。对于Spring来说,它采用了JDK、CGLIB动态代理方式。
首先我们来看看JDK、CGLIB动态代理的区别。
JDK、CGLIB动态代理的区别
JDK动态代理
JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
JDK创建代理对象效率较高,执行效率较低;
JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;
JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:
1.实现InvocationHandler接口,重写invoke()
2.使用Proxy.newProxyInstance()产生代理对象
3.被代理的对象必须要实现接口
CGLIB动态代理
CGLIB代理使用字节码处理框架asm,对代理对象类的class文件加载进来,通过修改字节码生成子类。
CGLIB创建代理对象效率较低,执行效率高。
CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,因为是继承机制,不能代理final修饰的类。
CGLib 必须依赖于CGLib的类库,需要满足以下要求:
1.实现MethodInterceptor接口,重写intercept()
2.使用Enhancer对象.create()产生代理对象
参考:https://blog.csdn.net/qq_59527118/article/details/127386305
Spring Boot动态代理验证
Spring Boot会自己判断使用哪种代理,如果被代理类有接口,就采用JDK动态代理,否则就采用CGLIB动态代理。
然而,在Spring Boot2.X版本中,Spring Boot不管被代理类是否有实现接口,都会默认采用CGLIB动态代理,如果要让其使用JDK动态代理,则需要在application.properties文件中添加以下配置:
spring.aop.proxy-target-class=false
下面让我们一起验证以下。
首先我们创建两个service类,一个实现了接口,一个没有实现接口,代码如下:
@Service
@Slf4j
public class UserServiceImpl implements IUserService{
@Override
public void printUser(User user) {
if(null == user){
throw new IllegalArgumentException();
}
log.info("用户信息如下:{}", user.toString());
}
}
@Service
@Slf4j
public class UserServiceNoImpl{
public void printUser(User user) {
if(null == user){
throw new IllegalArgumentException();
}
log.info("用户信息如下:{}", user.toString());
}
}
然后将这两个类的printUser都创建切点以及通知,再创建测试controller:
@RestController
@Slf4j
public class UserController {
@Autowired
private IUserService userService;
@Autowired
private UserServiceNoImpl userServiceNo;
@GetMapping("/getUser")
public User getUser(){
User user = new User()
.setId(111)
.setName("admin")
.setNote("aop测试")
;
// 实现了接口
userService.printUser(user); // 断点处
// 没有实现接口
userServiceNo.printUser(user);
try {
userService.printUser(null);
}catch (Exception e){
log.warn("获取用户信息异常:");
}
return user;
}
此时application.properties文件中不添加“spring.aop.proxy-target-class=false”,最后启动应用,访问地址,结果如下:
可以看到,此时UserServiceImpl 类有实现接口,但是Spring Boot仍然使用了CGLIB代理。
下面让我们在application.properties文件中添加“spring.aop.proxy-target-class=false”,再次启动应用,访问地址,结果如下:
可以看到,UserServiceImpl 类有实现接口,Spring Boot使用了JDK代理。
好了,今天就先到这里了,眼过千遍不如手过一遍,赶紧去自己尝试一下吧。。。。拜拜