Spring AOP Learning Notes 03: Acquisition Enhancer for Core Implementation of AOP

Above Describes how spring opens AOP, simply by registering the AnnotationAwareAspectJAutoProxyCreator class with the co...
1. Get Enhancers
2. Find matching enhancers
3. Summary

Above Describes how spring opens AOP, simply by registering the AnnotationAwareAspectJAutoProxyCreator class with the container, because this class eventually implements the BeanPostProcessor interface and completes the creation of the AOP proxy object in its postProcessAfterInitialization() method when the bean init method is executedAfter.The postProcessAfterInitialization() method is the focus of this article and the next one will be around it.

This method implements AbstractAutoProxyCreator in its parent class, so let's first look at its implementation:

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean != null) { // According to given bean Of class and name component key,Format: beanClassName_beanName Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.containsKey(cacheKey)) { // If it is suitable for being proxied, generate proxy classes in it return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // Do not process if already processed if (beanName != null && this.targetSourcedBeans.containsKey(beanName)) { return bean; } // Return directly without enhancement if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } // Designated bean Whether the class represents an infrastructure class, which should not be proxied, or is configured to specify bean Nor does it require automatic proxy if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // If it exists Advice Then create a proxy Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); // If you get enhancements, you need to create proxies for enhancements if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); // Create proxy Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }

In the above function, we can see the prototype of proxy creation, of course, before we really start, we need to go through a series of judgments, such as whether we have handled or whether we need to skip the bean s. The real proxy creation starts with getAdvicesAndAdvisorsForBean.
Creating an agent consists of two main steps:

  • Get the enhancement method or enhancer;
  • Proxy creation based on enhancements acquired;

This may seem like a simple two-step process, but each step has a lot of complex logic.This article first looks at the implementation logic for obtaining enhancements.

protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) { List advisors = findEligibleAdvisors(beanClass, beanName); if (advisors.isEmpty()) { return DO_NOT_PROXY; } return advisors.toArray(); } protected List<Advisor> findEligibleAdvisors(Class beanClass, String beanName) { List<Advisor> candidateAdvisors = findCandidateAdvisors(); List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); extendAdvisors(eligibleAdvisors); if (!eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); } return eligibleAdvisors; }

The acquisition of an enhancement method for a bean also consists of two steps, acquiring all enhancements and finding enhancements applicable to the bean and applying them in all enhancements, which are done by findCandidateAdvisors() and findAdvisorsThatCanApply().Return DO_here if no corresponding enhancer can be foundNOT_PROXY, also known as null.

1. Get Enhancers

What we are analyzing here is the annotated AOP. The implementation of findCandidateAdvisors is actually accomplished by the AnnotationAwareAspectJAutoProxyCreator class. Continue tracking:

protected List<Advisor> findCandidateAdvisors() { // Configure using annotations AOP Time does not throw away pairs XML Configuration support, where the parent method is called to load the AOP statement List<Advisor> advisors = super.findCandidateAdvisors(); // Build Advisors for all AspectJ aspects in the bean factory. advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); return advisors; }

AnnotationAwareAspectJAutoProxyCreator inherits AbstractAdvisorAutoProxyCreator indirectly. In addition to retaining the enhancements defined in the parent class's acquisition profile, AnnotationAwareAspectJAutoProxyCreator adds annotation enhancements for acquiring beans, which are implemented in part byThis.aspectJAdvisorsBuilder.buildAspectJAdvisors() to complete.

This is where you can actually try to imagine your own ideas and see if your implementation differs from Spring.We can try to achieve this in our own mind first to see if there is any idea.In fact, Spring has been implemented in four main steps:

  • Get all beanName s, and in this step all beans registered in the beanFactory are extracted;
  • Traverse through all beanName s and find classes that declare AspectJ annotations for further processing;
  • Extract enhancers for classes marked as AspectJ annotations;
  • Cache the extracted results;

Let's see how Spring can analyze all classes and extract Advisor:

public List<Advisor> buildAspectJAdvisors() { List<String> aspectNames = null; synchronized (this) { aspectNames = this.aspectBeanNames; if (aspectNames == null) { List<Advisor> advisors = new LinkedList<Advisor>(); aspectNames = new LinkedList<String>(); // Get All beanName String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false); // Traverse all beanName Find the corresponding enhancement method for (String beanName : beanNames) { // Illegal bean Skipped, rules are defined by subclasses, returned by default true if (!isEligibleBean(beanName)) { continue; } // Get the corresponding bean Type of Class beanType = this.beanFactory.getType(beanName); if (beanType == null) { continue; } // If the bean Upper Existence Aspect annotation if (this.advisorFactory.isAspect(beanType)) { aspectNames.add(beanName); AspectMetadata amd = new AspectMetadata(beanType, beanName); if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); // Resolution Enhancement Method List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory); if (this.beanFactory.isSingleton(beanName)) { this.advisorsCache.put(beanName, classAdvisors); } else { this.aspectFactoryCache.put(beanName, factory); } advisors.addAll(classAdvisors); } else { // Per target or per this. if (this.beanFactory.isSingleton(beanName)) { throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton"); } MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName); this.aspectFactoryCache.put(beanName, factory); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } } this.aspectBeanNames = aspectNames; return advisors; } } if (aspectNames.isEmpty()) { return Collections.EMPTY_LIST; } // Record Cache List<Advisor> advisors = new LinkedList<Advisor>(); for (String aspectName : aspectNames) { List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName); if (cachedAdvisors != null) { advisors.addAll(cachedAdvisors); } else { MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } return advisors; }

The extraction of Advisor has been completed here, and the most important and complex of the above steps is the acquisition of enhancers, which is delegated to the getAdvisors method.This.advisorFactory.getAdvisors(factory).(

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory maaif) { // Get tagged as AspectJ Class and its name, and verify final Class<?> aspectClass = maaif.getAspectMetadata().getAspectClass(); final String aspectName = maaif.getAspectMetadata().getAspectName(); validate(aspectClass); // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator // so that it will only instantiate once. final MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(maaif); final List<Advisor> advisors = new LinkedList<Advisor>(); for (Method method : getAdvisorMethods(aspectClass)) { // Get Enhancers Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName); if (advisor != null) { advisors.add(advisor); } } // If it's a per target aspect, emit the dummy instantiating aspect. if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory); advisors.add(0, instantiationAdvisor); } // Find introduction fields. for (Field field : aspectClass.getDeclaredFields()) { Advisor advisor = getDeclareParentsAdvisor(field); if (advisor != null) { advisors.add(advisor); } } return advisors; }

In the above function, the first step is to get the enhancers, including getting annotations and generating enhancements from them. Then, considering that the enhancements may be configured as delayed initialization in the configuration, you need to add synchronous instantiation enhancers first to ensure the instantiation before the enhancements are used. Finally, you get the DeclareParents annotations. This analysis is focused onGet the enhancer.
The acquisition logic for general enhancers is implemented in the getAdvisor method, which consists of acquiring annotations to the tangent points and generating enhancements based on annotation information.

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) { validate(aif.getAspectMetadata().getAspectClass()); // Acquisition of tangent information AspectJExpressionPointcut ajexp = getPointcut(candidateAdviceMethod, aif.getAspectMetadata().getAspectClass()); if (ajexp == null) { return null; } // Generating enhancers from tangent information return new InstantiationModelAwarePointcutAdvisorImpl( this, ajexp, aif, candidateAdviceMethod, declarationOrderInAspect, aspectName); }

1.1 Acquisition of tangent information

Getting the tangent information is getting the expression information for the specified annotation, such as @Before("test()").

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) { // Get annotations on Methods AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); if (aspectJAnnotation == null) { return null; } // Use AspectJExpressionPointcut Instance Encapsulation Gets Information AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]); // The expression in the extracted annotation, such as: // @Pointcut("execution(* *.*test*(..))")In execution(* *.*test*(..))") ajexp.setExpression(aspectJAnnotation.getPointcutExpression()); return ajexp; } protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) { // Setting sensitive annotation classes Class<? extends Annotation>[] classesToLookFor = new Class[] { Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class}; for (Class<? extends Annotation> c : classesToLookFor) { AspectJAnnotation foundAnnotation = findAnnotation(method, c); if (foundAnnotation != null) { return foundAnnotation; } } return null; } // Get the comment on the specified method and use it AspectJAnnotation encapsulation private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) { A result = AnnotationUtils.findAnnotation(method, toLookFor); if (result != null) { return new AspectJAnnotation<A>(result); } else { return null; } }

1.2 Generate enhancements based on tangent information

All enhancements are encapsulated uniformly by Advisor's implementation class InstantiationModelAwarePointcutAdvisorImpl.

public InstantiationModelAwarePointcutAdvisorImpl(AspectJAdvisorFactory af, AspectJExpressionPointcut ajexp, MetadataAwareAspectInstanceFactory aif, Method method, int declarationOrderInAspect, String aspectName) { this.declaredPointcut = ajexp; this.method = method; this.atAspectJAdvisorFactory = af; this.aspectInstanceFactory = aif; this.declarationOrder = declarationOrderInAspect; this.aspectName = aspectName; if (aif.getAspectMetadata().isLazilyInstantiated()) { // Static part of the pointcut is a lazy type. Pointcut preInstantiationPointcut = Pointcuts.union(aif.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut); // Make it dynamic: must mutate from pre-instantiation to post-instantiation state. // If it's not a dynamic pointcut, it may be optimized out // by the Spring AOP infrastructure after the first evaluation. this.pointcut = new PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aif); this.lazy = true; } else { // A singleton aspect. this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut); this.pointcut = declaredPointcut; this.lazy = false; } }

The encapsulation process simply encapsulates the information in the instance of the class, all the information is simply assigned values, and the initialization of the enhancer is completed during the initialization of the instance.Because different enhancements reflect different logic, such as the @Before("test()") and @After("test()") tags differ in the location of the enhancer enhancements, different enhancers are required to complete different logic, and the corresponding enhancers are initialized according to the information in the annotation in the instantiateAdvice function.

private Advice instantiateAdvice(AspectJExpressionPointcut pcut) { return this.atAspectJAdvisorFactory.getAdvice( this.method, pcut, this.aspectInstanceFactory, this.declarationOrder, this.aspectName); } public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut ajexp, MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) { Class<?> candidateAspectClass = aif.getAspectMetadata().getAspectClass(); validate(candidateAspectClass); AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); if (aspectJAnnotation == null) { return null; } // If we get here, we know we have an AspectJ method. // Check that it's an AspectJ-annotated class if (!isAspect(candidateAspectClass)) { throw new AopConfigException("Advice must be declared inside an aspect type: " + "Offending method '" + candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]"); } if (logger.isDebugEnabled()) { logger.debug("Found AspectJ method: " + candidateAdviceMethod); } AbstractAspectJAdvice springAdvice; // Encapsulate different enhancers according to different annotation types switch (aspectJAnnotation.getAnnotationType()) { case AtBefore: springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif); break; case AtAfter: springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif); break; case AtAfterReturning: springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif); AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation(); if (StringUtils.hasText(afterReturningAnnotation.returning())) { springAdvice.setReturningName(afterReturningAnnotation.returning()); } break; case AtAfterThrowing: springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif); AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation(); if (StringUtils.hasText(afterThrowingAnnotation.throwing())) { springAdvice.setThrowingName(afterThrowingAnnotation.throwing()); } break; case AtAround: springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif); break; case AtPointcut: if (logger.isDebugEnabled()) { logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); } return null; default: throw new UnsupportedOperationException( "Unsupported advice type on method " + candidateAdviceMethod); } // Now to configure the advice... springAdvice.setAspectName(aspectName); springAdvice.setDeclarationOrder(declarationOrderInAspect); String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod); if (argNames != null) { springAdvice.setArgumentNamesFromStringArray(argNames); } springAdvice.calculateArgumentBindings(); return springAdvice; }

As you can see above, Spring generates different enhancers based on different annotations, such as AtBefore corresponding AspectJMethodBeforeAdvice, which completes the logic of the enhancements in AspectJMethodBeforeAdvice.This article attempts to analyze several common enhancer implementations.

AspectJMethodBeforeAdvice

This is the pre-enhancer, which in Spring is encapsulated inside the MethodBeforeAdviceInterceptor class, which is an interceptor that is placed in the interceptor chain and used when creating proxy bean s:

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { private MethodBeforeAdvice advice; /** * Create a new MethodBeforeAdviceInterceptor for the given advice. * @param advice the MethodBeforeAdvice to wrap */ public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { Assert.notNull(advice, "Advice must not be null"); this.advice = advice; } public Object invoke(MethodInvocation mi) throws Throwable { this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() ); return mi.proceed(); } }

The property MethodBeforeAdvicce represents a pre-enhanced AspJMethodBeforeAdvice that tracks its before() method:

public void before(Method method, Object[] args, Object target) throws Throwable { invokeAdviceMethod(getJoinPointMatch(), null, null); } protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable { return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex)); } protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable { Object[] actualArgs = args; if (this.aspectJAdviceMethod.getParameterTypes().length == 0) { actualArgs = null; } try { ReflectionUtils.makeAccessible(this.aspectJAdviceMethod); // Activation Enhancement Method return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs); } catch (IllegalArgumentException ex) { throw new AopInvocationException("Mismatch on arguments to advice method [" + this.aspectJAdviceMethod + "]; pointcut expression [" + this.pointcut.getPointcutExpression() + "]", ex); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } }

The aspectJAdviceMethod in the invokeAdviceMethodWithGivenArgs method corresponds to the pre-enhancement method, where the call is implemented.

AspectJAfterAdvice

There is a slight discrepancy between the post-enhancement and the pre-enhancement.Looking back at the preceding enhancements mentioned above, the general structure is to place the MethodBeforeAdviceInterceptor in the interceptor chain and the AspectJMethodBeforeAdvice in the MethodBeforeAdviceInterceptor chain, which is called in series first when inovke is called.However, the latter enhancements are different, instead of providing intermediate classes such as MethodBeforeAdviceInterceptor, they implement the MethodInterceptor interface directly and use the intermediate AspectAfterAdvice in the interceptor chain.

public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice { public AspectJAfterAdvice(Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) { super(aspectJBeforeAdviceMethod, pointcut, aif); } public Object invoke(MethodInvocation mi) throws Throwable { try { return mi.proceed(); } finally { // Activation Enhancement Method invokeAdviceMethod(getJoinPointMatch(), null, null); } } }

2. Find matching enhancers

All enhancer acquisitions have been analyzed previously, but for all enhancements, they may not necessarily apply to the current Bean, but you should also select the appropriate enhancer, which is the one that satisfies the wildcard we configured.The implementation of this section is in findAdvisorsThatCanApply.

protected List<Advisor> findAdvisorsThatCanApply( List<Advisor> candidateAdvisors, Class beanClass, String beanName) { ProxyCreationContext.setCurrentProxiedBeanName(beanName); try { // Filter what is already available advisors return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); } finally { ProxyCreationContext.setCurrentProxiedBeanName(null); } } public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) { if (candidateAdvisors.isEmpty()) { return candidateAdvisors; } List<Advisor> eligibleAdvisors = new LinkedList<Advisor>(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); } } boolean hasIntroductions = !eligibleAdvisors.isEmpty(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor) { // already processed continue; } if (canApply(candidate, clazz, hasIntroductions)) { eligibleAdvisors.add(candidate); } } return eligibleAdvisors; }

The main function of the findAdvisorsThatCanApply function is to find all enhancers that are applicable to the current class, while the true match is actually implemented in canApply.

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { if (advisor instanceof IntroductionAdvisor) { return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); } else if (advisor instanceof PointcutAdvisor) { PointcutAdvisor pca = (PointcutAdvisor) advisor; return canApply(pca.getPointcut(), targetClass, hasIntroductions); } else { // It doesn't have a pointcut so we assume it applies. return true; } } public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) { Assert.notNull(pc, "Pointcut must not be null"); if (!pc.getClassFilter().matches(targetClass)) { return false; } MethodMatcher methodMatcher = pc.getMethodMatcher(); IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; if (methodMatcher instanceof IntroductionAwareMethodMatcher) { introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } Set<Class> classes = new LinkedHashSet<Class>(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); classes.add(targetClass); for (Class<?> clazz : classes) { Method[] methods = clazz.getMethods(); for (Method method : methods) { if ((introductionAwareMethodMatcher != null && introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) || methodMatcher.matches(method, targetClass)) { return true; } } } return false; }

The logic here is not very complex, it's just based on the classFilter encapsulated in advisor to determine whether a match corresponds to a class.

3. Summary

This paper mainly studies the acquisition of enhancement methods in the core implementation principle of Spring AOP, which consists of two steps:

  • Get all enhancements;
  • Find the enhancements that apply to bean s in all enhancements;

The following will focus on the creation of proxies, another part of the AOP core implementation principle.

15 June 2020, 22:49 | Views: 1345

Add new comment

For adding a comment, please log in
or create account

0 comments