Spring 一开始最强大的就是 IOC / AOP 两大核心功能,我们今天一起来学习一下 Spring AOP 常见注解和执行顺序。
Spring Aop 的常用注解
首先我们一起来回顾一下 Spring Aop 中常用的几个注解:
- @Before 前置通知:目标方法之前执行
- @After 后置通知:目标方法之后执行(始终执行)
- @AfterReturning 返回之后通知:执行方法结束之前执行(异常不执行)
- @AfterThrowing 异常通知:出香异常后执行
- @Around 环绕通知:环绕目标方法执行
常见问题
1、你肯定知道 Spring , 那说说 Aop 的去全部通知顺序, Spring Boot 或者 Spring Boot 2 对 aop 的执行顺序影响?
2、说说你在 AOP 中遇到的那些坑?
示例代码
下面我们先快速构建一个 spring aop 的 demo 程序来一起讨论 spring aop 中的一些细节。
配置文件
为了方便我直接使用 spring-boot 进行快速的项目搭建,大家可以使用 idea 的spring-boot 项目快速创建功能,或者去 start.spring.io
上面去快速创建spring-boot 应用。(因为本人经常手动去网上贴一些依赖导致,依赖冲突服务启动失败等一些问题)。
plugins {
id 'org.springframework.boot' version '2.6.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group 'io.zhengsh'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
# 其实这里也可以不增加 web 配置,为了试验简单,大家请忽略
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-aop'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
接口类
首先我们需要定义一个接口。我们这里可以再来回顾一下 JDK 的默认代理实现的选择:
如果目标对象实现了接口,则默认采用JDK动态代理
如果目标对象没有实现接口,则采用进行动态代理
如果目标对象实现了接口,且强制Cglib,则使用cglib代理
这块的逻辑在 DefaultAopProxyFactory
大家有兴趣可以去看看。
public interface CalcService {
public int div(int x, int y);
}
实现类
这里我门就简单一点做一个除法操作,可以模拟正常也可以很容易的模拟错误。
@Service
public class CalcServiceImpl implements CalcService {
@Override
public int div(int x, int y) {
int result = x / y;
System.out.println("====> CalcServiceImpl 被调用了,我们的计算结果是:" + result);
return result;
}
}
aop 拦截器
申明一个拦截器我们要为当前对象增加 @Aspect
和 @Component
,笔者之前也是才踩过这样的坑,只加了一个。
其实这块我刚开始也不是很理解,但是我看了 Aspect
注解的定义我就清楚了
这里面根本就没有 Bean 的定义。所以我们还是乖乖的加上两个注解。还有就是如果当测试的时候需要开启Aop 的支持为配置类上增加 @EnableAspectJAutoProxy
注解。
其实 Aop 使用就三个步骤:
-
定义 Aspect 定义切面
-
定义 Pointcut 就是定义我们切入点
-
定义具体的通知,比如: @After, @Before 等。
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* io.zhengsh.spring.service.impl..*.*(..))")
public void divPointCut() {
}
@Before("divPointCut()")
public void beforeNotify() {
System.out.println("----===>> @Before 我是前置通知");
}
@After("divPointCut")
public void afterNotify() {
System.out.println("----===>> @After 我是后置通知");
}
@AfterReturning("divPointCut")
public void afterReturningNotify() {
System.out.println("----===>> @AfterReturning 我是前置通知");
}
@AfterThrowing("divPointCut")
public void afterThrowingNotify() {
System.out.println("----===>> @AfterThrowing 我是异常通知");
}
@Around("divPointCut")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object retVal;
System.out.println("----===>> @Around 环绕通知之前 AAA");
retVal = proceedingJoinPoint.proceed();
System.out.println("----===>> @Around 环绕通知之后 BBB");
return retVal;
}
}
测试类
其实我这个测试类,虽然用了 @Test
注解,但是我这个类更加像一个 main
方法把:如下所示:
public class DemoServiceTest {
@Configuration
@EnableAspectJAutoProxy
@Import(MyAspect.class, CalcServiceImpl.class)
public static class TestConfig{
}
@Test
void div() {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(TestConfig.class);
CalcService calcService = applicationContext.getBean(CalcService.class);
System.out
.println("spring 版本" + SpringVersion.getVersion() + "\t springboot 版本" + SpringBootVersion.getVersion());
int retVal = calcService.div(1, 1);
assertEquals(1, retVal);
}
}
执行结论
结果记录:spring 4.x, spring-boot 1.5.9
无法现在依赖,所以无法试验
我直接说一下结论: Spring 4 中环绕通知是在最里面执行的
结果记录:spring 版本5.3.15 springboot 版本2.6.
作者:心城以北
链接:https://juejin.cn/post/7062506923194581029
来源:稀土掘金
评论区