1. 前言
本文围绕AOP进行讲解,AOP可以做什么,涉及到了哪些注解,以及各个注解运行的时机,以及@Around相较于其它注解有什么不同,并且如果要执行目标方法需要怎么做
2. 什么是AOP
Spring的AOP(面向切面编程)是Spring框架的一个重要特性,它允许开发人员在应用程序中通过定义切面来实现横切关注点的功能,如日志记录、性能监控、事务管理等。AOP通过将这些关注点从业务逻辑中抽离出来,使得代码更加模块化、可维护和可重用。
SpringAOP就是批量对Spring容器中bean的方法做增强,并且这种增强不会与原来方法中的代码耦合
3. AOP快速入门
目标:要求service包下所有的类中的方法调用前输出: “方法被调用了”
在学AOP前,大家可能会在每个方法内添加一个输出语句. 但如果类很多,类中的方法也很多,添加起来也很麻烦,而且如果后续要进行修改,也很麻烦
- 首先要引入相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.29</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
- 把相关bean放到Spring容器中
可以使用注解@ComponentScan(basePackages = "com.example")
,也可以在xml配置文件中,使用<context:component-scanbase-package="com.example"></context:component-scan>
因为我的代码结构是这样的,所以是com.example.
- 实现AOP
实现AOP可以使用注解,也可以使用xml配置文件. 因为是入门,所以先认识一下注解实现AOP的方式
①开启AOP注解支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
②创建切换类
其实就是普通的类,加上@Component
和x @Aspect
这两个注解而已
使用使用@Pointcut注解来指定要被强的方法
使用@Before注解来给我们的增湿代码所在的方法进行标识,并且指定了增强代码是在被增强方法执行之前执行
的。
示例:
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void point(){
}
@Before("point()")
public void methodBefore(){
System.out.println("方法被调用了");
}
}
@Pointcut("execution(* com.example.service.*.*(..))")
这段代码:是指 对com.example的service包下类的所有方法进行增强
@Before("point()")
public void methodBefore(){
System.out.println("方法被调用了");
}
@Before("point()")
是指选中point()这个切点表达式的方法进行增强,增强的内容就是方法中的代码
UserService:
@Service
public class UserService {
public void update(){
System.out.println("执行了UserService的update方法");
}
}
准备工作完成,进行测试.
可以看到在执行UserService的update()方法前,输出了"方法被调用了"
4. AOP的核心概念
- Joinpoint (连接点): 所谓连接点是指那些可以被增强到的点。在spring中,这些点指的是方法,因为spring
只支持方法类型的连接点 - ⭐Pointcut (切入点) : 所谓切入点是指被增强的连接点(方法)
- Advice (通知/ 增强) : 所谓通知是指具体增强的代码
- Target (目标对象): 被增强的对象就是目标对象
- Aspect (切面) : 是切入点和通知(引介) 的结合
- Proxy (代理) : 一个类被 AOP 增强后,就产生一个结果代理类
5. 切点表达式
切点表达式用来表示要对哪些方法进行增强
写法: execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略,大部分情况下可以省略
- 返回值类型、包名、类名、方法名可以使用星号
*
代表任意包 - 名与类名之间一个点
.
代表当前包下的类,两个点..
表示当前包及其子包下的类 - 参数列表可以使用两个点
..
表示任意个数,任意类型的参数列表
如快速入门中的切点表达式:
execution(* com.example.service.*.*(..))
该切点表达式就是 对com.example的service包下类的所有方法进行增强,
6. 切点函数
我们也可以在要增强的方法上加上注解。然后使用@annotation来表示对加了什么注解的方法进行增强。
示例:
首先自定义一个注解,在创建类时选择Annotation
public @interface MyComment{
}
注意此时是不能直接用,我们需要添加几个注解
不知道添加什么也很好办,可以直接写一个注解,点击看源码
- @Retention(RetentionPolicy.RUNTIME): 表示注解可以保持到什么时期,RUNTIME就是运行时
- @Target({ElementType.METHOD}): 表示此注解可以添加到哪些东西方法,METHOD就是方法
直接将注解添加到我们自定义的注解上即可
使用自定义注解,直接在相应的方法中添加即可:
此时的切点就不能像之前那样写了,需要使用@annotation
注解,并加上自定义注解的全类名
此时运行代码同样可以看到userService中的方法被增强了.
其实这种方式的AOP增强比使用切点表达式灵活多了.
7. 通知
SpingAOP的通知共有五种:
- @Before: 前置通知在方法执行前执行
- @AfterReturning: 返回后通知,在目标方法执行后执行,如果出现异常不会执行
- @After: 后置通知,在目标方法返回结果之后执行,无论是否出现异常都会执行
- @AfterThrowing: 异常通知,在目标方法抛出异常后执行
- @Around: 环绕通知,围绕着方法执行
@Before,@AfterReturning和@After方法使用起来很简单,只需要知道加了这些注解的方法是在什么时候增强的即可
示例:
@Component
@Aspect
public class MyAspect {
@Pointcut("@annotation(com.example.aspect.MyComment)")
public void point(){
}
@Before("point()")
public void methodBefore(){
System.out.println("Before");
}
@AfterReturning("point()")
public void methodAfterReturning(){
System.out.println("AfterReturning");
}
@After("point()")
public void methodAfter(){
System.out.println("After");
}
}
@Service
public class UserService {
@MyComment
public void update(){
System.out.println("执行了UserService的update方法");
}
}
注意:@AfterReturning 如果方法中异常不会执行❗
@AfterThrowing恰恰相反,只有出现异常才会执行
在切面类中增加 @AfterThrowing注解的方法
@AfterThrowing("point()")
public void methodAfterThrowing(){
System.out.println("AfterThrowing");
}
让需要增加的方法报错
@Service
public class UserService {
@MyComment
public void update(){
System.out.println("执行了UserService的update方法");
System.out.println(1/0);
}
}
执行结果:
可以看到没有执行@AfterReturning相应的方法,而是执行了@AfterThrowing相应的方法
以上注解都比较简单,只需要知道他们运行的时机即可,重中之重还是@Around
切面类:
@Component
@Aspect
public class MyAspect {
@Pointcut("@annotation(com.example.aspect.MyComment)")
public void point(){
}
@Around("point()")
public void methodAround(){
System.out.println("Around");
}
}
切点
@Service
public class UserService {
@MyComment
public void update(){
System.out.println("执行了UserService的update方法");
}
}
运行结果:
虽然执行了@Around相应的方法,但是结果中并没有UserService中的对应的输出语句,这是为什么? 这就是@Around的奇妙之处了
如果想要目标方法执行,需要添加一个ProceedingJoinPoint
类型的参数,同时调用里面的proceed()
方法:
@Around("point()")
public void methodAround(ProceedingJoinPoint joinPoint){
System.out.println("Around");
try {
joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
此时就可以正常执行目标方法了
但@Around的用处远不止这些,它可以完成其它4个注解的作用
只需要添加@Around注解的相应方法这么改就可以了.
@Around("point()")
public void methodAround(ProceedingJoinPoint joinPoint){
System.out.println("方法执行前");
try {
joinPoint.proceed();
System.out.println("方法执行后");
} catch (Throwable e) {
System.out.println("方法出现异常");
throw new RuntimeException(e);
}finally {
System.out.println("finally进行增强");
}
}
8. 总结
Spring的AOP基于代理模式实现,它使用代理对象(Proxy)来包装目标对象(Target),从而实现在目标对象的方法执行前、执行后或抛出异常时插入额外的逻辑。可以通过使用注解或配置文件来定义切面和切点,从而将横切关注点应用到目标对象的方法中。
Spring的AOP提供了一系列通知(Advice)类型,如前置通知(Before)、后置通知(After)、环绕通知(Around)、异常通知(AfterThrowing)和最终通知(AfterReturning),可以根据需要选择合适的通知类型来实现特定的横切关注点功能。