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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

@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和目前对象需要实现的接口数组interfacesadvisor的管理其中一个核心是利用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;
		}
		List<Advisor> eligibleAdvisors = new ArrayList<>();
		...
		for (Advisor candidate : candidateAdvisors) {
			...
			if (canApply(candidate, clazz)) {
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}


	public static boolean canApply(Advisor advisor, Class<?> targetClass) {
		...
		if (advisor instanceof PointcutAdvisor) {
			PointcutAdvisor pca = (PointcutAdvisor) advisor;
			//利用pointcut进行过滤
			return canApply(pca.getPointcut(), targetClass,...);
		}
		else {
			// It doesn't have a pointcut so we assume it applies.
			return true;
		}
	}

	public static boolean canApply(Pointcut pc, Class<?> targetClass,...) {
		 //classFilter过滤成功了,才有机会继续往下判断
		if (!pc.getClassFilter().matches(targetClass)) {
			return false;
		}

		MethodMatcher methodMatcher = pc.getMethodMatcher();
		if (methodMatcher == MethodMatcher.TRUE) {
			return true;
		}
        
        //切面类中增强方法关联的pointcut底层使用的methodMatcher类型为IntroductionAwareMethodMatcher
        //切面类底层应用中和普通的MethodMatcher没啥区别,因为第三个参数通常都是为false
		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}
        //搜集当前目标对象实现的所有接口,如果当前目标对象还未被jdk代理,那么将目标对象自身的class也添加进去   
		Set<Class<?>> classes = new LinkedHashSet<>();
		if (!Proxy.isProxyClass(targetClass)) {
		    //getUserClass如果是cglib代理的情况,获取代理类的父类类型
			classes.add(ClassUtils.getUserClass(targetClass));
		}
		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
        //判断是否存在一个方法满足pointCut的matcher条件,如果满足说明当前advisor能够切入当前目标类那么返回true
		for (Class<?> clazz : classes) {
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
			for (Method method : methods) {
				if (introductionAwareMethodMatcher != null ?
				         //大部分情况下可以把introductionAwareMethodMatcher.matches看做与
				         //methodMatcher.matches等价
						introductionAwareMethodMatcher.matches(method, targetClass, false) :
						methodMatcher.matches(method, targetClass)) {
					return true;
				}
			}
		}

		return false;
	}

4.AspectJProxyUtils添加一个ExposeInvocationInterceptor到增强器链头部

AspectJProxyUtils调用makeAdvisorChainAspectJCapableIfNecessary添加一个ExposeInvocationInterceptor到增强器链头部:

	public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {
		if (!advisors.isEmpty()) {
			boolean foundAspectJAdvice = false;
			for (Advisor advisor : advisors) {
				if (isAspectJAdvice(advisor)) {
					foundAspectJAdvice = true;
					break;
				}
			}
			//当前增强器链中存在AspectJ类型的advice时才需要添加一个ExposeInvocationInterceptor到增强器链头部
			if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {
				advisors.add(0, ExposeInvocationInterceptor.ADVISOR);
				return true;
			}
		}
		return false;
	}

ExposeInvocationInterceptor有什么用?

public final class ExposeInvocationInterceptor implements MethodInterceptor, PriorityOrdered, Serializable {
	public static final ExposeInvocationInterceptor INSTANCE = new ExposeInvocationInterceptor();
	
	public static final Advisor ADVISOR = new DefaultPointcutAdvisor(INSTANCE) {
		@Override
		public String toString() {
			return ExposeInvocationInterceptor.class.getName() +".ADVISOR";
		}
	};

	private static final ThreadLocal<MethodInvocation> invocation =
			new NamedThreadLocal<>("Current AOP method invocation");
    //静态方法获取当前执行中的拦截器链
	public static MethodInvocation currentInvocation() throws IllegalStateException {
		MethodInvocation mi = invocation.get();
		if (mi == null) {
			throw new IllegalStateException(
					"No MethodInvocation found: Check that an AOP invocation is in progress and that the " +
					"ExposeInvocationInterceptor is upfront in the interceptor chain. Specifically, note that " +
					"advices with order HIGHEST_PRECEDENCE will execute before ExposeInvocationInterceptor! " +
					"In addition, ExposeInvocationInterceptor and ExposeInvocationInterceptor.currentInvocation() " +
					"must be invoked from the same thread.");
		}
		return mi;
	}
    
    //将当前拦截器链的设置到threadlocal中
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		MethodInvocation oldInvocation = invocation.get();
		invocation.set(mi);
		try {
			return mi.proceed();
		}
		finally {
			invocation.set(oldInvocation);
		}
	}
   //设置拦截器优先级为最高级别,确保排序式总是排在最前面
	@Override
	public int getOrder() {
		return PriorityOrdered.HIGHEST_PRECEDENCE + 1;
	}
}

之所以要添加ExposeInvocationInterceptor到增强器链头部,并且该拦截器被调用时,将当前拦截器链暴露到threadLocal中,是考虑到某个切面类中的增强方法想要获取到完整的执行器链上下文。

spring官方其实不推荐我们使用该拦截器链,完整理由如下:
在这里插入图片描述


5.对advisor进行排序

在这里插入图片描述
AspectJ切面类的增强方法会被包装为InstantiationModelAwarePointcutAdvisorImpl该增强器的getOrder方法返回值为:
在这里插入图片描述

切面类的优先级又如何获取呢
在这里插入图片描述
spring不支持给切面类中的增强方法上标注@Order注解指明执行顺序:
在这里插入图片描述
属于同一个切面类中的所有增强方法对应的advisor优先级都是一致的都和当前切面类优先级相同,切面类上可以标注@Order注解或者实现Order接口返回当前切面的排序值,否则默认为最小值。


小结

本文内容已经比较多了,大家先消化一些,下一篇中,我们将来到aop自动化时代,探究自动化背后的原理。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6