一、编写测试代码
1.1 新建一个Spring Boot项目,并引入@Aspect依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<scope>test</scope>
</dependency>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.alanchen</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2 demo代码
package com.alanchen.demo.aspect;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @author Alan Chen
* @description
* @date 2022-09-18
*/
@ComponentScan("com.alanchen.demo.aspect")
@EnableAspectJAutoProxy
public class AspectConfig {
}
package com.alanchen.demo.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* @author Alan Chen
* @description
* @date 2022-09-18
*/
@Aspect
@Component
public class OrderLogAspect {
@Before("execution(public void com.alanchen.demo.aspect.OrderService.order())")
public void beforeOrder(JoinPoint joinPoint){
System.out.println("执行下单接口前,打印的切面日志");
}
}
package com.alanchen.demo.aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
* @author Alan Chen
* @description
* @date 2022-09-18
*/
@Service
public class OrderService {
@Autowired
private UserService userService;
public void order(){
userService.userInfo();
System.out.println("正在下单");
}
}
package com.alanchen.demo.aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author Alan Chen
* @description
* @date 2022-09-18
*/
@Service
public class UserService {
public void userInfo(){
System.out.println("获取用户信息");
}
}
package com.alanchen.demo.aspect;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author Alan Chen
* @description
* @date 2022-09-18
*/
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspectConfig.class);
//拿到的是OrderService的代理对象
OrderService orderService = context.getBean(OrderService.class);
orderService.order();
System.out.println("下单接口执行完了");
}
}
1.3 项目截图
项目截图1.4 运行结果
执行下单接口前,打印的切面日志
获取用户信息
正在下单
下单接口执行完了
二、代码分析
2.1 Spring Bean 生命周期步骤伪代码
1、类
2、推断选择构造方法(默认调用类无参构造方法)
3、普通对象(new出来)
4、为对象的属性(加了注解的,如@Autowired)进行依赖注入
5、初始化前:判断方法上是否加了@PostConstruct注解
6、初始化:判断是否实现了InitializingBean接口,反射调用afterPropertiesSet方法
7、初始化后:进行AOP
8、代理对象
9、放入单例池Map
10、成为Bean
2.2 debug调试
我们在orderService.order();
和 System.out.println("下单接口执行完了");
处都打上断点,发现OrderService里的userService对象竟然为null,为什么order方法按预期执行成功了,并没有抛出空指针异常?
2.3 Spring AOP中动态代理的两种方式
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
-
JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
-
如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。Cglib是基于ASM实现的AOP。
由此可得,本本demo中,Spring会采用CGLIB的方式给OrderService生成代理类,证据如下:
CGLIB2.4 CGLIB实现代码方式
2.4.1 实现方式1 伪代码
public OrderServiceProxy extends OrderService{
@Override
public void order(){
// 执行@Before切面逻辑
//调用父类(被代理类)方法
super.order()
}
}
如果Spring采用的是这种代理实现方式,那么OrderService里的userService依然为null,order方法也不可能执行成功。
补充说明:这里代理对象OrderServiceProxy继承的是一个普通对象OrderService(不是Bean对象),所以OrderService里的属性为null,而且Spring Bean的生命周期中AOP是最后一步,AOP后没有依赖注入的步骤,所以代理对象OrderServiceProxy里的属性为null。
2.4.2 实现方式2 伪代码
public OrderServiceProxy extends OrderService{
// target里的userService里不为null
@Autowired
private OrderService target;
@Override
public void order(){
// 执行@Before切面逻辑
//调用target(被代理类)方法
target.order()
}
}
实现方式2的妙处在于在OrderServiceProxy类里引入了一个target,这个target是OrderService的一个普通对象(非AOP代理对象),这个普通对象已完成了Spring的依赖注入,所以这个对象里的userService是有值的。
我们再回到2.1中的Spring Bean生命周期来进行分析,放入Spring单例池的是OrderService AOP代理对象OrderServiceProxy,虽然OrderService普通对象(已完成依赖注入,依赖对象不为null)没有放入Spring单例池,但他被代理对象OrderServiceProxy引用,因此OrderService普通对象依然在JVM中,不会被销毁。
问题:为什么要用继承?
代理类OrderServiceProxy继承了OrderService后,用的时候就可以当成OrderService来用。
2.5 证实CGLIB采用的是实现方式2
我们在order()方法里打一个断点,继续来调试
断点1 断点2我们看第一个断点:
OrderService代理类中的userService依然是null,但有一个target,这个target是OrderService类型,地址是OrderService@2304
,target里的userService地址是UserService@2305
。
继续往下执行,我们来看第二个断点:
OrderService执行order()方法,this地址为OrderService@2304
,这个地址正是OrderServiceProxy代理类中的target对象,而且userService地址也刚好是UserService@2305
。
另外,我们可以在切面代码里,通过joinPoint可以取到被代理的类OrderService,如下:
@Aspect
@Component
public class OrderLogAspect {
@Before("execution(public void com.alanchen.demo.aspect.OrderService.order())")
public void beforeOrder(JoinPoint joinPoint){
//通过joinPoint可以取到被代理的类OrderService
OrderService orderService = (OrderService)joinPoint.getTarget();
System.out.println("执行下单接口前,打印的切面日志");
}
}
因此我们可以证实CGLIB采用的是实现方式2
网友评论