Spring Aop 内部方法调用拦截问题

Spring Aop 内部方法调用拦截问题

    • 问题描述
      • 通过spring aop实现方法调用日志记录
      • 上述实现存在的问题
    • 问题产生原因
    • 问题解决方法

问题描述

通过spring aop实现方法调用日志记录

  1. 首先定义日志记录注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)

public @interface LogAnnotation {
    String value();
}
  1. 定义增强类advisor,其实就类似于一个切面,由切点(poincut)与通知(advice)组成。
public class LogAnnotationAdvisor extends AbstractPointcutAdvisor {

    private Pointcut pointcut;

    private Advice advice;

    public void setPointcut(Pointcut pointcut){
        this.pointcut = pointcut;
    }

    public void setAdvice(Advice advice){
        this.advice = advice;
    }

    @Override
    public Pointcut getPointcut() {
        return pointcut;
    }

    @Override
    public Advice getAdvice() {
        return advice;
    }
}
  1. 定义方法拦截器,MethodInterceptor拓展了advice,所以该拦截器就类似于一个aop中的通知,可以在方法调用前后处理一些业务逻辑。
public class LogAnnotationInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("before method invoke "+System.currentTimeMillis());
        Object[] args = methodInvocation.getArguments();
        Object proceed = methodInvocation.proceed();
        LogAnnotation annotation = methodInvocation.getMethod().getAnnotation(LogAnnotation.class);
        System.out.println(annotation.value()+"=====");
        if(args!=null) {
            for (int i = 0; i < args.length; i++) {
                System.out.println(args[i].toString());
            }
        }
        System.out.println("after method invoke "+System.currentTimeMillis());
        return proceed;
    }
}

Spring Aop 内部方法调用拦截问题_第1张图片

  1. 配置增强类实例,装填切点与通知。切点使用注解方法形式。
@Configuration
public class LogAnnotationInterceptorConfig {
    @Bean
    public LogAnnotationAdvisor LogAdvisor(){
        LogAnnotationAdvisor advisor = new LogAnnotationAdvisor();

        AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(LogAnnotation.class);

        advisor.setPointcut(pointcut);
        advisor.setAdvice(new LogAnnotationInterceptor());
        return advisor;
    }

}
  1. 接下来就是业务代码
    controller类
@RestController
@RequestMapping("/test")
public class BaseController {
    @Autowired
    private BaseService baseService;

    @RequestMapping("/add")
    public String test(Integer a,Integer b){
        Integer result = baseService.add(a,b);
        return String.valueOf(result);
    }

}

Service 类

@Service
public class BaseService {

    @LogAnnotation(value = "BaseService add")
    public Integer add(Integer a,Integer b){
        hello();
        return a+b;
    }

    @LogAnnotation(value = "BaseService hello")
    public String hello(){
        return "hello world";
    }
}
  1. 这样我们在需要打印调用日志的方法上个加上@LogAnnotation便可进行日志记录,方便排查问题,链路追踪。

上述实现存在的问题

接下来启动工程,调用test方法时会进入我们定义的方法拦截器中,打印对应日志,而在BaseService的add方法中调用hello方法时却没有被拦截器拦截。
Spring Aop 内部方法调用拦截问题_第2张图片

问题产生原因

  1. BaseController调用BaseService的add方法时会被org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept拦截器拦截,然后调用cglib代理类的代理方法org.springframework.aop.framework.ReflectiveMethodInvocation#proceed。在该方法执行过程中会获取自定义的拦截器LogAnnotationInterceptor,然后执行我们植入的业务逻辑。但是在BaseService的add方法中调用hello方法时是直接通过this来调用的,并不会调用到对应的代理对象。所以不会打印对应的hello方法调用时的日志。类似spring的事物其实也存在这样的问题。大致原因如下图
    Spring Aop 内部方法调用拦截问题_第3张图片

问题解决方法

  1. 通过注入本类来进行方法调用
    获取代理类而不使用this来调用。在BaseService中注入本类,在运行时我们可以发现注入的是代理类,所以在add中调用hello时会在此执行切面逻辑。
    Spring Aop 内部方法调用拦截问题_第4张图片
    Spring Aop 内部方法调用拦截问题_第5张图片
  2. 上述方式是在运行时获取代理类,对于代码有很大的侵入性,不知道有没有更好的办法 0.0

你可能感兴趣的:(spring,java)