美文网首页
【每天学点Spring】Spring @Aspect学习

【每天学点Spring】Spring @Aspect学习

作者: 伊丽莎白2015 | 来源:发表于2022-09-18 20:25 被阅读0次

    【官方文档】

    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
    具体如下: image.png

    比如单独用@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)

    【参考】

    相关文章

      网友评论

          本文标题:【每天学点Spring】Spring @Aspect学习

          本文链接:https://www.haomeiwen.com/subject/rwzeortx.html