设为首页 加入收藏

TOP

接口方法上的注解无法被 @Aspect 声明的切面拦截的原因分析(一)
2018-05-21 15:48:41 】 浏览:1680
Tags:接口 方法 注解 无法 @Aspect 声明 拦截 原因分析

前言

在Spring中使用MyBatis的Mapper接口自动生成时,用一个自定义的注解标记在Mapper接口的方法中,再利用@Aspect定义一个切面,拦截这个注解以记录日志或者执行时长。但是惊奇的发现这样做之后,在Spring Boot 1.X(Spring Framework 4.x)中,并不能生效,而在Spring Boot 2.X(Spring Framework 5.X)中却能生效。

这究竟是为什么呢?Spring做了哪些更新产生了这样的变化?此文将带领你探索这个秘密。

案例

核心代码

@SpringBootApplication
public class Starter {
  public static void main(String[] args) {
    SpringApplication.run(DynamicApplication.class, args);
  }
}

@Service
public class DemoService {

    @Autowired
    DemoMapper demoMapper;

    public List<Map<String, Object>> selectAll() {
        return demoMapper.selectAll();
    }
}

/**
 * mapper类
 */
@Mapper
public interface DemoMapper {

  @Select("SELECT * FROM demo")
  @Demo
  List<Map<String, Object>> selectAll();

}

/**
 * 切入的注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Demo {
  String value() default "";
}

/**
 * aspect切面,用于测试是否成功切入
 */
@Aspect
@Order(-10)
@Component
public class DemoAspect {

  @Before("@annotation(demo)")
  public void beforeDemo(JoinPoint point, Demo demo) {
    System.out.println("before demo");
  }

  @AfterDemo("@annotation(demo)")
  public void afterDemo(JoinPoint point, Demo demo) {
    System.out.println("after demo");
  }

}

测试类

@RunWith(SpringRunner.class) 
@SpringBootTest(classes = Starter.class)
public class BaseTest {

    @Autowired
    DemoService demoService;

    @Test
    public void testDemo() {
        demoService.selectAll();
    }

}

在Spring Boot 1.X中,@Aspect里的两个println都没有正常打印,而在Spring Boot 2.X中,都打印了出来。

调试研究

已知@Aspect注解声明的拦截器,会自动切入符合其拦截条件的Bean。这个功能是通过@EnableAspectJAutoProxy注解来启用和配置的(默认是启用的,通过AopAutoConfiguration),由@EnableAspectJAutoProxy中的@Import(AspectJAutoProxyRegistrar.class)可知,@Aspect相关注解自动切入的依赖是AnnotationAwareAspectJAutoProxyCreator这个BeanPostProcessor。在这个类的postProcessAfterInitialization方法中打上条件断点:beanName.equals(“demoMapper”)

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
	if (bean != null) {
	    // 缓存中尝试获取,没有则尝试包装
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (!this.earlyProxyReferences.contains(cacheKey)) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

在wrapIfNecessary方法中,有自动包装Proxy的逻辑:

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 如果是声明的需要原始Bean,则直接返回
	if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}
	// 如果不需要代理,则直接返回
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}
	// 如果是Proxy的基础组件如Advice、Pointcut、Advisor、AopInfrastructureBean则跳过
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

	// Create proxy if we have advice.
	// 根据相关条件,查找interceptor,包括@Aspect生成的相关Interceptor。
	// 这里是问题的关键点,Spring Boot 1.X中这里返回为空,而Spring Boot 2.X中,则不是空
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	if (specificInterceptors != DO_NOT_PROXY) {
	    /
首页 上一页 1 2 3 4 5 6 下一页 尾页 1/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇用 Maven 实现一个 protobuf 的 J.. 下一篇直播一次问题排查过程

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目