@aspect注解背后的奥秘--上(代码片段)

热爱编程的大忽悠 热爱编程的大忽悠     2023-02-22     576

关键词:

@Aspect注解背后的奥秘--上


引言

Spring为我们提供了简单易用的声明式AOP实现方案,我们只需要通过@Aspect注解标注一个切面类,并通过@Around,@Before等注解标注切面类中相关增强方法,注解内部标注切入范围,即可一键完成切入,程序员只需要关注切入后的拦截逻辑实现即可,下面给出的是一个简单的实现案例:

@Aspect
@Component
public class RateLimiterAspect 
    @Pointcut("")
    public void pointCut() 

    
    
    @Before("@annotation(limiter)")
    public void doBefore(JoinPoint point, Limiter limiter)
        Method method = getMethod(point);
        System.out.println("before拦截:  方法名为"+method.getName());
    

    @After("@annotation(limiter)")
    public void doAfter(JoinPoint point, Limiter limiter)
        Method method = getMethod(point);
        System.out.println("after拦截:  方法名为"+method.getName());
    

    @Around(value = "@annotation(limiter)")
    public Object doAround(ProceedingJoinPoint point, Limiter limiter) throws Throwable
        Method method = getMethod(point);
        System.out.println("around拦截:  方法名为"+method.getName());
        return point.proceed();
    

    @AfterReturning(value = "@annotation(limiter)",returning = "res")
    public void doReturning(JoinPoint point,Limiter limiter,Object res)
    
        Method method = getMethod(point);
        System.out.println("returning拦截:  方法名为"+method.getName());
    

    @AfterThrowing(value = "@annotation(limiter)",throwing = "ex")
    public void doThrowing(JoinPoint point,Limiter limiter,Exception ex)
    
        Method method = getMethod(point);
        System.out.println("throwing拦截:  方法名为"+method.getName());
    

    private Method getMethod(JoinPoint point) 
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        return method;
    

aop一键式开启的背后,究竟发生了什么呢? 本文将带领大家一步步探究这背后的奥秘。


aop的原始时代

在spring aop最初诞生时,还不支持自动化的aop实现,如果我们想要对某个bean进行代理,需要手动操作,例如:

public class ProxyTest 
    @Test
    public void test()
        noAspectJProxy();
        AspectJProxy();
    

    private void AspectJProxy() 
        AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory();
        aspectJProxyFactory.setTarget(new A());
        aspectJProxyFactory.addAspect(RateLimiterAspect.class);
        A proxyA = (A) aspectJProxyFactory.getProxy();
        proxyA.say();
    

    private void noAspectJProxy() 
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(new A());
        //强制采用cglib代理,这里不设置也是采用cglib进行代理,因为A没有实现任何接口
        proxyFactory.setProxyTargetClass(true);
        proxyFactory.addAdvice(new MethodInterceptor() 
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable 
                System.out.println("拦截器执行");
                return invocation.proceed();
            
        );
        A proxyA = (A) proxyFactory.getProxy();
        proxyA.say();
    


    public static class A 
        @Limiter
        public void say()
            System.out.println("A说你好!");
        
    


ProxyFactory和AspectJProxyFactory都是手动创建代理类的工厂实现,区别在于ProxyFactory添加的都是普通的advisor实现,内部已经提供了pointCut和Advice的实现,无需特别处理; 而AspectJProxyFactory添加的是切面类,负责将切面类进行解析,然后将其中的增强方法包装为一个个advisor,因此多了解析和适配的步骤。


ProxyFactory实现思路

由于Spring AOP模块涉及内容较多,所以受限于篇幅限制,本文只会简要提一下相关类起到的作用,具体源码大家可以自行查看,或者通过阅读我的Spring源码专栏中AOP源码解读部分进行学习。

  • ProxyConfig类提供相关属性控制代理的具体过程,如: proxyTargetClass选项强制设置采用cglib进行代理
  • AdvisedTagetClassAware两个接口提供了对目标对象和应用到目标对象上的advisor进行管理的相关接口
  • AdvisedSupport负责管理目标对象相关信息,如包装了目标对象的TargetSource和目前对象需要实现的接口数组interfaces;advisor的管理,其中一个核心是利用AdvisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法获取能够切入某个方法的advisor执行链。
  • ProxyCreatorSupport类的核心功能体现在create上,主要负责利用内部的AopProxyFactory创建代理对象
  • ProxyFactory负责创建代理对象的工厂类,核心通过getProxy方法得到一个代理对象,利用的是ProxyCreatorSupport提供的createAopProxy方法,相关代理信息的保存由AdvisedSupport负责完成。

AopProxyFactory是根据AdvisedSupport提供的代理配置信息创建AopProxy对象的工厂类。

AopProxy提供的getProxy方法负责创建真正的代理对象,而AopProxyFactory根据AdvisedSupport提供的代理配置信息决定选择何种方式生成代理对象,是jdk还是cglib,然后分别创建对应类型的AopProxy返回。

TargetSource包装了目标对象,默认使用的是SingletonTargetSource,每次获取目标对象时,只是简单返回内部封装的目标对象。


AspectJProxyFactory的实现思路


AspectJProxyFactory相比ProxyFactory多出了将切面类进行解析,并将内部的增强方法全部适配为一个个advisor的过程,这个过程体现在addAspectJ方法中:

    //添加的是标注了@AspectJ注解的切面类
	public void addAspect(Object aspectInstance) 
	    //获取切面类的Class
		Class<?> aspectClass = aspectInstance.getClass();
		//切面类的类名
		String aspectName = aspectClass.getName();
		//解析切面类形成切面类的元数据信息---如果传入的不是标注了@AspectJ注解的切面类,解析过程中会抛出异常
		AspectMetadata am = createAspectMetadata(aspectClass, aspectName);
		//有关AspectJ的知识,通常切面类默认都是SINGLETON单例实现
		if (am.getAjType().getPerClause().getKind() != PerClauseKind.SINGLETON) 
			throw new IllegalArgumentException(
					"Aspect class [" + aspectClass.getName() + "] does not define a singleton aspect");
		
		//该方法是解析切面类,并筛选advisor的核心方法
		addAdvisorsFromAspectInstanceFactory(
		     //负责实例化单例切面类的工厂--但是由于单例的切面类是通过构造函数传入的
		     //因此可以把工厂看做是保存切面类单例实例对象和切面类元数据的工厂类
				new SingletonMetadataAwareAspectInstanceFactory(aspectInstance, aspectName));
	

addAdvisorsFromAspectInstanceFactory方法负责根据工厂类中保存的切面元数据信息解析并转换得到一批advisors,再利用advisor内部的poincut进行筛选,留下那些能够切入目标对象的advisors:

	private void addAdvisorsFromAspectInstanceFactory(MetadataAwareAspectInstanceFactory instanceFactory) 
	    //切面工厂负责根据instanceFactory内部的存储的切面元数据信息解析并转换得到一批advisor
		List<Advisor> advisors = this.aspectFactory.getAdvisors(instanceFactory);
		//得到目标对象类型--如果先addAspect,再设置目标对象,此时就会因为目标对象还未被及时设置而抛出异常
		Class<?> targetClass = getTargetClass();
		Assert.state(targetClass != null, "Unresolvable target class");
		//利用AopUtils判断哪些advisor能够切入当前目标对象,
		advisors = AopUtils.findAdvisorsThatCanApply(advisors, targetClass);
		//添加ExposeInvocationInterceptor到已有增强链头部
		AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(advisors);
		//对advisor进行排序
		AnnotationAwareOrderComparator.sort(advisors);
		addAdvisors(advisors);
	

1.切面元数据解析过程


根据切面元数据解析并转换得到一批advisors的工作是通过ReflectiveAspectJAdvisorFactory完成的:

	public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) 
	    //拿到切面类型和切面名
		Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
		String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
		//主要校验切面类上是否存在@AspectJ注解
		validate(aspectClass);
        //AspectInstanceFactory工厂作用是创建一个切面类实现,他有很多实现,例如我们上面看到的SingletonMetadataAwareAspectInstanceFactory
        //但是有一些实现类,可能每次调用getAspectInstance方法都会重新创建一个切面实例
        //因此LazySingleton的希望能够实现懒加载并且保证切面类的单例性--源码比较简单,大家可以自行阅读
		MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
				new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
        
		List<Advisor> advisors = new ArrayList<>();
		//getAdvisorMethods获取当前切面类,父类,接口中所有方法,除了标注了@PointCut注解的方法
		for (Method method : getAdvisorMethods(aspectClass)) 
		    //判断当前方法上是否存@Around等注解,如果存在则将方法转换为一个advisor
			Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
			//返回的advisor为null,说明当前方法上不存在相关注解
			if (advisor != null) 
				advisors.add(advisor);
			
		
		...
		// 处理切面类字段上存在的@DeclaredParent注解--用的比较少,这里直接跳过
		for (Field field : aspectClass.getDeclaredFields()) ...
		//返回解析切面类获得的一批advisors
		return advisors;
	

判断方法是否为增强方法,如果是则转换为advisor:

	public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
			int declarationOrderInAspect, String aspectName) 
	    ...		
	    //如果方法上存在@Before,@After相关注解,则提取注解中的切入表达式,包装为一个PointCut返回
		AspectJExpressionPointcut expressionPointcut = getPointcut(
				candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
		//返回空,说明当前方法不是增强方法
		if (expressionPointcut == null) 
			return null;
		
        //将当前增强方法封装为一个InstantiationModelAwarePointcutAdvisorImpl返回
        //该advisor内部持有增强方法,拥有切面实例的aspectInstanceFactory
        //还有上面得到的AspectJExpressionPointcut 
		return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
				this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
	    
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) 
		AspectJAnnotation<?> aspectJAnnotation =
		        //寻找@Before,@After相关注解
				AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
		//不存在相关注解
		if (aspectJAnnotation == null) 
			return null;
		
        //存在则将注解中的切点表达式封装为一个AspectJExpressionPointcut
        //该AspectJExpressionPointcut的classFilter和methodMatcher方法都是根据切点表达式进行过滤的
		AspectJExpressionPointcut ajexp =
				new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
		ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
		if (this.beanFactory != null) 
			ajexp.setBeanFactory(this.beanFactory);
		
		//返回pointCut
		return ajexp;
	

2.InstantiationModelAwarePointcutAdvisorImpl获取advice

advisor是由pointcut和advice组成的,InstantiationModelAwarePointcutAdvisorImpl获取pointcut时,直接返回设置进去的AspectJExpressionPointcut 即可。

但是获取advice时,该怎么处理呢?

	public synchronized Advice getAdvice() 
	    //第一次获取advice时为空,先实例化
		if (this.instantiatedAdvice == null) 
			this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
		
		return this.instantiatedAdvice;
	

	private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) 
	    //利用aspectJAdvisorFactory工厂根据增强方法上的注解创建不同类型的advice实现
		Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut,
				this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
		return (advice != null ? advice : EMPTY_ADVICE);
	

aspectJAdvisorFactory工厂提供的主要功能接口如下:

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
			MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) 
		Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
		...
		AspectJAnnotation<?> aspectJAnnotation =
				AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
		...
		AbstractAspectJAdvice springAdvice;
		//根据注解类型的不同创建不同类型的advice实现
		switch (aspectJAnnotation.getAnnotationType()) 
			case AtPointcut:
				if (logger.isDebugEnabled()) 
					logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
				
				return null;
			case AtAround:
				springAdvice = new AspectJAroundAdvice(
						candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
				break;
			case AtBefore:
				springAdvice = new AspectJMethodBeforeAdvice(
						candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
				break;
			case AtAfter:
				springAdvice = new AspectJAfterAdvice(
						candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
				break;
			case AtAfterReturning:
				springAdvice = new AspectJAfterReturningAdvice(
						candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
				AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
				//注解中指定了接收方法返回值的方法参数名字
				if (StringUtils.hasText(afterReturningAnnotation.returning())) 
					springAdvice.setReturningName(afterReturningAnnotation.returning());
				
				break;
			case AtAfterThrowing:
				springAdvice = new AspectJAfterThrowingAdvice(
						candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
				AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
				//注解中指定了接收异常参数的方法参数名字
				if (StringUtils.hasText(afterThrowingAnnotation.throwing())) 
					springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
				
				break;
			default:
				throw new UnsupportedOperationException(
						"Unsupported advice type on method: " + candidateAdviceMethod);
		

		springAdvice.setAspectName(aspectName);
		springAdvice.setDeclarationOrder(declarationOrder);
		//参数绑定过程,如果以后遇到了参数绑定问题,可以debug此部分源码寻找原因
		String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
		if (argNames != null) 
			springAdvice.setArgumentNamesFromStringArray(argNames);
		
		springAdvice.calculateArgumentBindings();
		//返回最终生成的advice
		return springAdvice;
	

3.AopUtils筛选能够应用到当前目标对象上的advisors

这里我将AopUtils的findAdvisorsThatCanApply核心流程保留,去掉不常用的分支情况和代码:

	public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) 
		if (candidateAdvisors.isEmpty()) 
			return candidateAdvisors;
		springaop中@aspect拦截介绍(二)

参考技术A本章介绍的@Aspect拦截用的注解方式,对于想要实现多数据源切换,在指定类或方法上只需加上一个注解便可以实现入参出参日志打印的小伙伴,要认真看那一大堆有关SpringAOP支持的AspectJ切入点指示符的注释了。使用@Po... 查看详情

手把手教你解决循环依赖,一步一步地来窥探出三级缓存的奥秘(代码片段)

先不去管Spring中的循环依赖,我们先实现一个自定义注解,来模拟@Autowired的功能。一、自定义注解模拟@Autowired自定义Load注解,被该注解标识的字段,将会进行自动注入/***@authorqcy*@create2021/10/0213:31:20*/... 查看详情

“idea无法解析@aspect注解”的解决方法(代码片段)

...没有引入以下两个jar包:解决方法:1.在官网下载AspectJ相关jar包链接:https://www.eclipse.org/aspectj/downloads.php.2.安装jar包后,将文章开头两个jar包复制到项目的lib中3.在专业版IDEA中开发AspectJ,需要确保下述插件被激活&#... 查看详情

“idea无法解析@aspect注解”的解决方法(代码片段)

...没有引入以下两个jar包:解决方法:1.在官网下载AspectJ相关jar包链接:https://www.eclipse.org/aspectj/downloads.php.2.安装jar包后,将文章开头两个jar包复制到项目的lib中3.在专业版IDEA中开发AspectJ,需要确保下述插件被激活&#... 查看详情

aop记录日志(代码片段)

...public@interfaceRptLogStringvalue()default"";2.aop相关的配置类@Slf4j@Aspect@ComponentpublicclassRptOperateLogAspect@Autowired@SuppressWarnings("all")privateRptOperateLogMapperrptOperateLogMapper;@AutowiredprivateUserServiceuserService;//定义切点@Pointcut//在注解的位置切入代码@Poi... 查看详情

java使用注解加aspect实现拦截方法打印方法日志(代码片段)

    为了熟悉AOP中的ASPECTJ的使用,为了方便我们在写数据搬运型代码时候观测的方便,自己学习并定义了一组切面方法和注解,来实现这个功能,啥都不说了,先上代码:首先是注解定义:importjava.la... 查看详情

SwiftUI 中某些环境自动更新背后的奥秘

】SwiftUI中某些环境自动更新背后的奥秘【英文标题】:TheMysterybehindautoupdateofsomeEnvironmentinSwiftUI【发布时间】:2021-03-2917:23:50【问题描述】:我可以使用环境修改器轻松更改视图级别中名为backgroundColor的自定义环境!像这样下来... 查看详情

springaop官方文档学习笔记之基于注解的springaop(代码片段)

1.@Aspect注解(1)@Aspect注解用于声明一个切面类,我们可在该类中来自定义切面,早在Spring之前,AspectJ框架中就已经存在了这么一个注解,而Spring为了提供统一的注解风格,因此采用了和AspectJ框架相同的注解方式,这便是@Aspect注... 查看详情

注解+aspect省时省力的管理好接口日志(代码片段)

背景无论是对外提供的RPC接口,还是项目内的普通方法,我们都会有需要打印方法入参、出参的需求,方便在遇到问题时通过查看日志快速定位,我们也会需要对方法的执行时间进行打印方便分析和调优。比较笨... 查看详情

aop面向切面编程androidstudio使用aspectj监控方法运行(定义连接点注解|定义aspect切面|定义切入点|逐个处理切入点的各个连接点)(代码片段)

文章目录一、定义JoinPoint连接点注解二、定义Aspect切面1、定义Aspect切面2、定义Aspect切面3、逐个处理切入点的各个连接点4、完整Aspect切面代码三、使用AspectJ埋点并监控方法性能一、定义JoinPoint连接点注解要监控哪些方法,首先要... 查看详情

注解+aspect省时省力的管理好接口日志(代码片段)

...etentionPolicy.RUNTIME)@Documentedpublic@interfaceRunTimeAnnotationAspect我们使用Aspect进行切面开发首先引用对应的pom依赖<dependency><groupId>org.aspectj</groupId><artifactId 查看详情

java使用注解加aspect实现拦截方法打印方法日志(代码片段)

    为了熟悉AOP中的ASPECTJ的使用,为了方便我们在写数据搬运型代码时候观测的方便,自己学习并定义了一组切面方法和注解,来实现这个功能,啥都不说了,先上代码:首先是注解定义:importjava.la... 查看详情

工作中实现记录接口调用的注解例子(代码片段)

...义好了,我们就需要编写一个能处理上面注解的处理类@Aspect@ComponentpublicclassRequestLogAspectpublicfinalstaticLoggerlogger=LoggerFactory.getLogger(RequestLogAspect.class);@Pointcut("@annotation(com.***.***.***.service.aspect.RequestLogAnnotation)")//上面注解的包路径pub... 查看详情

reentrantlock源码探究探究公平锁与非公平锁背后的奥秘(代码片段)

本文目录前言ReentrantLock定义锁的可重入性什么是AQS公平、非公平锁区别一(lock方法)核心AQS解读AQS(tryAcquire)尝试去竞争锁AQS(addWaiter)维护双向链表AQS(acquireQueued)休眠第二个节点后的所有节... 查看详情

srping全注解开发---aop模块(代码片段)

AOP模块回顾五个通知注解使用演示1.切面类(@Aspect)业务逻辑类在配置类中将切面类和业务逻辑类都加入到容器中切记给配置类上加上@EnableAspectJAutoProxy,开启基于注解的aop模式测试注意:spring中很多Enablexxx都是... 查看详情

图解|从武侠角度探究stl排序算法的奥秘(代码片段)

...悍的性能和效率是STL的更让人惊艳的地方。STL极致表现的背后是大牛们炉火纯青的编程技艺和追求极 查看详情

关于数据库时区,这么多奥秘你都知道么?(代码片段)

...介绍。本文分享自华为云社区《人人都知道的时区,背后竟然有这么多奥秘》,原文作者:leapdb。背景介绍时区同其它工业标准一样,标准化过程也是一个复杂而漫长的过程。GaussDB(DWS)作为一款面向全球用户的高... 查看详情

xwindow的奥秘(代码片段)

欢迎关注大数据和人工智能技术文章发布的微信公众号:清研学堂,在这里你可以学到夜白(作者笔名)精心整理的笔记,让我们每天进步一点点,让优秀成为一种习惯!阅读目录了解自己机器上的XWindow理解display和虚拟控制台... 查看详情