【官方文档】
- Spring AOP相关文档:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj
1. 依赖
我使用的是Spring Boot 2.7.0,引入了:
- spring-boot-starter-web
- spring-boot-starter-aop
2. 简单介绍Aspect类
理论上来说,一个@Around注解,可以做完切面所有的定义。
在@Around
方法内:
- 在注解的value参数上定义切点,等同于
@Pointcut
。 - joinPoint.proceed()执行前的代码,等同于
@Before
。 - joinPoint.proceed()执行后的代码,等同于
@AfterReturning
。 - joinPoint.proceed()报错后,等同于
@AfterThrowing
。 - joinPoint.proceed() finally中的代码,等同于
@After
。
比如单独用@Pointcut + @Before来定义:
@Pointcut(value = "execution(* com.aspect.sample.cup.service.*.*(..))")
public void pointCut() {
}
@Before(value = ("pointCut()"))
public void before() {
log.info("@Before");
}
既然已经有了@Around
,再设计@Before
,@AfterReturning
,... 这样的设计细粒度小,更方便。
执行的顺序:
正常执行:【Before】-->【目标方法】-->【AfterReturning】-->【After】
目标方法执行报错:【Before】-->【目标方法】-->【AfterThrowing】-->【After】
3. 自定义一个annotation类以及Service
本章节自定义annotation类,然后在切点上定义,目标是所有被该annotation声明的方法都被include进来。
3.1 自定义annotation类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserUpdate {
FlushMode updateType() default FlushMode.ON_SAVE;
}
enum FlushMode类:
public enum FlushMode {
ON_SAVE, IMMEDIATELY
}
3.2 Service类
@Service
public class UserServiceImpl implements UserService {
@UserUpdate(updateType = FlushMode.IMMEDIATELY)
public UserDomain update(UserRes userRes, boolean batch) {
log.info("enter user service...");
return new UserDomain(1, "test");
}
}
4. Aspect类
4.1 声明Aspect类
用@Aspect
注解来定义切面类,@Component用来声明需要被Spring scan到。
@Aspect
@Component
public class UserUpdateAspect {
//
}
4.2 定义一个切点方法
@Around(value = "@annotation(UserUpdate)")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("enter around method...");
return joinPoint.proceed();
}
通过Controller --> 调用上述的userService.save(userRes, batch),日志:
enter around method...
enter user service...
5. 在切点方法中拿到目标方法的信息
5.1 joinPoint.getSignature()
拿到目标方法userSave.save(userRes, batch)上的注解@UserUpdate中的值:
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
UserUpdate userUpdate = method.getAnnotation(UserUpdate.class);
log.info("anotation: {}", userUpdate.updateType());
打印:
anotation: IMMEDIATELY
5.2 joinPoint.getArgs()
拿到目标方法userSave.save(userRes, batch)参数的值:
for (Object obj: joinPoint.getArgs()) {
log.info("joinPoint args: {}", obj);
}
打印:
joinPoint args: UserRes(id=1, name=test)
joinPoint args: false
5.3 joinPoint.getTarget().getClass()
取得目标class的类名:
String className = joinPoint.getTarget().getClass().getName();
log.info("class name: {}", className);
String classSimpleName = joinPoint.getTarget().getClass().getSimpleName();
log.info("class simple name: {}", classSimpleName);
class name: com.aspect.sample.service.impl.UserServiceImpl
class simple name: UserServiceImpl
5.4 取得执行的结果
Object returnObj = joinPoint.proceed();
log.info("return object: {}", returnObj);
也可以在@AfterReturnning中取得:
@AfterReturning(value="@annotation(UserUpdate)", returning="retVal")
public void afterReturning(Object retVal) {
log.info("return object: {}", retVal);
}
打印:
return object: UserDomain(id=1, name=test)
6. 切点的定义
上述两种方式都可以定义:
- @PointCut(value="表达式")
- @Around(value="表达式") 或@Around(value="pointCut方法")
对于比较复杂的切点,可以使用组合的方式:
如:@Around(value="表达式 || pointCut方法")的形式。
@Pointcut(value = "execution(* com.faj.aspect.sample.cup.service.*.*(..))")
public void pointCut() {
}
@Around(value = "@annotation(UserUpdate) || pointCut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
}
组合的条件可以有:
-
&&
:表示and -
||
:表示or -
!
:表示非
关于切点的表达式,更多的可以参考官网:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-examples
6.1 execution示例
参考:https://howtodoinjava.com/spring-aop/aspectj-pointcut-expressions/
No | 表达式 | 描述 |
---|---|---|
1 | 匹配com.abc.EmployeeManager类下所有的方法 | execution(* com.abc.EmployeeManager.*(..)) |
2 | 匹配和这个aspect同一个包下的EmployeeManager类下所有的方法 | execution(* EmployeeManager.*(..)) |
3 | 匹配同一个包下的EmployeeManager类的public方法 | execution(public * EmployeeManager.*(..)) |
4 | 匹配同一个包下的EmployeeManager类的public方法并且返回类型为EmployeeDTO | execution(public EmployeeDTO EmployeeManager.*(..)) |
5 | 匹配同一个包下的EmployeeManager类的所有public方法,返回类型为EmployeeDTO,并且第1个方法参数为EmployeeDTO | execution(public EmployeeDTO EmployeeManager.*(EmployeeDTO, ..)) |
6 | 在5的基础上,需要加上条件:方法参数为EmployeeDTO,Integer | execution(public EmployeeDTO EmployeeManager.*(EmployeeDTO, Integer)) |
6.2 match示例
No | 表达式 | 描述 |
---|---|---|
1 | 匹配在com.abc 包中的所有类的所有方法 | within(com.howtodoinjava.*) |
2 | 匹配在com.abc 包以及它的子包(sub-packages)下的所有类的所有方法 | within(com.howtodoinjava..*) |
3 | 匹配com.bbb.EmployeeManagerImpl这个类的所有方法 | within(com.howtodoinjava.EmployeeManagerImpl) |
4 | 匹配任何包下的EmployeeManagerImpl类的所有方法 | within(EmployeeManagerImpl) |
5 | 匹配任何包下的EmployeeManager接口的实现类EmployeeManagerImpl的所有方法(用+号表示实现类) | within(EmployeeManagerImpl+) |
6.3 bean示例
No | 表达式 | 描述 |
---|---|---|
1 | 匹配bean名字,以Manager结尾 | bean(*Manager) |
【参考】
网友评论