本次博主主要進行Spring AOP這裡的解析,因為在工作中使用後,卻不知道背後的實現原理並在使用的過程中發現了一些認知缺陷,所以決定寫這麼一篇文章以供大家參考參考,進入正題。

  本次博主使用了@Aspect、@Around、@PointCut註解實現了一些小的需求,大家想必都用過,我就簡單的舉個例子吧。

  1 @Aspect
2 @Component
3 public class CrmCacheAspect {
4
5 @Autowired
6 StringRedisTemplate stringRedisTemplate;
7
8 private ConcurrentHashMap<String, ICacheResultParser> parserMap = new ConcurrentHashMap();
9
10 private ConcurrentHashMap<String, IKeyGenerator> generatorMap = new ConcurrentHashMap();
11
12 private ConcurrentHashMap<String,Boolean> keyMap = new ConcurrentHashMap<>();
13 @Pointcut("@annotation(com.bjh.hms.crm.annotation.CrmCache)")
14 public void pointCut(){}
15
16 @Around("pointCut() && @annotation(crmCache)")
17 public Object joinPoint(ProceedingJoinPoint joinPoint, CrmCache crmCache) throws InstantiationException, IllegalAccessException {
18 String value = "";
19 String key = "";
20 Object result = "";
21 try {
22 key = getKey(crmCache,joinPoint);
23 value = stringRedisTemplate.opsForValue().get(key);
24 } catch (Exception e) {
25 XxlJobHelper.log("獲取快取{}失敗:{}",crmCache.key(),e);
26 } finally {
27 if (StringUtils.isBlank(value)) {
28 value = synchronizeCache(key, joinPoint, crmCache);
29 }
30 result = getResult(crmCache, value, joinPoint);
31 }
32 return result;
33 }
34
35 private Object getResult(CrmCache crmCache,
36 String value,
37 ProceedingJoinPoint joinPoint) throws InstantiationException, IllegalAccessException {
38 if (value == null) {
39 return null;
40 }
41 String name = crmCache.parser().getName();
42 ICacheResultParser iCacheResultParser;
43 if (parserMap.containsKey(name)) {
44 iCacheResultParser = parserMap.get(name);
45 } else {
46 iCacheResultParser = crmCache.parser().newInstance();
47 parserMap.put(name,iCacheResultParser);
48 }
49 MethodSignature signature = (MethodSignature) joinPoint.getSignature();
50 Class returnType = signature.getReturnType();
51 Object parse = iCacheResultParser.parse(value, returnType);
52 return parse;
53 }
54
55 /**
56 * Title: 解決redis併發穿透
57 * @author 2021/8/13 17:15
58 * @return java.lang.String
59 */
60 private String synchronizeCache(String key,
61 ProceedingJoinPoint joinPoint,
62 CrmCache crmCache) {
63 String value = "";
64 //暫停100-200ms,執行緒順序執行
65 try {
66 Thread.sleep((int)(Math.random()*(200 - 100 + 1) + 100));
67 } catch (InterruptedException e) {
68 XxlJobHelper.log("synchronizeCache error {}", ExceptionUtil.stacktraceToString(e));
69 }
70 while (StringUtils.isBlank(value = stringRedisTemplate.opsForValue().get(key))
71 && (keyMap.get(key) == null || keyMap.get(key))){
72 //防止重複呼叫
73 if (keyMap.get(key) == null || !keyMap.get(key)) {
74 keyMap.put(key,true);
75 Object proceed = null;
76 try {
77 proceed = joinPoint.proceed();
78 } catch (Throwable e) {
79 XxlJobHelper.log("處理失敗:{}",ExceptionUtil.stacktraceToString(e));
80 }
81 putValueByRedis(key,proceed,crmCache);
82 keyMap.put(key,false);
83 }
84 }
85 keyMap.remove(key);
86 return value;
87 }
88
89 private void putValueByRedis(String key, Object value, CrmCache crmCache) {
90 if (value == null) {
91 return;
92 }
93 if (value instanceof String) {
94 stringRedisTemplate.opsForValue().set(key, value.toString());
95 } else {
96 String jsonString = JSONObject.toJSONString(value);
97 stringRedisTemplate.opsForValue().set(key,jsonString);
98 }
99 //-1代表不過期
100 if (crmCache.expire() != -1) {
101 stringRedisTemplate.expire(key, crmCache.expire(), TimeUnit.MINUTES);
102 }
103 }
104
105 private String getKey(CrmCache crmCache, ProceedingJoinPoint joinPoint) throws InstantiationException, IllegalAccessException {
106 MethodSignature signature = (MethodSignature) joinPoint.getSignature();
107 Method method = signature.getMethod();
108 Object[] args = joinPoint.getArgs();
109 String iKeyGeneratorName = crmCache.generator().getName();
110 String key = crmCache.key();
111 IKeyGenerator iKeyGenerator = null;
112 if (generatorMap.containsKey(iKeyGeneratorName)) {
113 iKeyGenerator = generatorMap.get(iKeyGeneratorName);
114 } else {
115 iKeyGenerator = crmCache.generator().newInstance();
116 generatorMap.put(iKeyGeneratorName,iKeyGenerator);
117 }
118 return iKeyGenerator.generate(key,method,args);
119 }
120
121 }

  本例子主要是對結果與請求進行解析快取,spring其實有自帶的,但是不可以使用快取時間,有快取時間又需要引入其他依賴包,公司內部私服又是內網訪問的,所以就自寫了一個簡單的註解實現了快取有限時間功能。這不是重點,我們來分析一下註解是如何載入進來的,又是如何被spring走進來解析的吧。

  講解之前,博主還是一如既往的為大家畫了幾張草圖,以便大家防止看程式碼看暈,先來第一張:aspect註解原始碼分析載入與生效

  https://www.processon.com/view/link/6134aae163768906a2203894

  我們開始走程式碼,我們直接走bean的建立開始,如果有小夥伴不知道整個bean建立流程的話,可以看一下博主以前的畫 的草圖腦補一下:

  https://www.processon.com/view/link/5f704050f346fb166d0f3e3c

  程式碼走起,任意的bean建立都可以,如果看不了靜態程式碼,自行debug就可以了。

 1     protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
2 Object bean = null;
3 if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
4 // 確定是否有aspect註解
5 if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
6 Class<?> targetType = determineTargetType(beanName, mbd);
7 if (targetType != null) {
8 //解析註解
9 bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
10 if (bean != null) {
11 //此處會給有自定義註解的bean建立代理類返回
12 bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
13 }
14 }
15 }
16 mbd.beforeInstantiationResolved = (bean != null);
17 }
18 return bean;
19 }

  我們分析一下hasInstantiationAwareBeanPostProcessors方法,看看是如何走進來的,從方法名字可以看出,是否有InstantiationAwareBeanPostProcessors後置處理器,那我們本身並沒有去填加這個類,那怎麼就有了呢,原因就在我們引入aop包依賴後,有一個預設的自動配置AopAutoConfiguration,EnableAspectJAutoProxy註解中間引入了一個AspectJAutoProxyRegistrar類,實現這個registerBeanDefinitions方法後,引入了一個AnnotationAwareAspectJAutoProxyCreator類,這個類就是AspectJAutoProxyRegistrar的實現類,所以hasInstantiationAwareBeanPostProcessors方法走通了。

  再看一下applyBeanPostProcessorsBeforeInstantiation方法解析註解流程。

 1 public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
2 Object cacheKey = getCacheKey(beanClass, beanName);
3
4 if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
5 if (this.advisedBeans.containsKey(cacheKey)) {
6 return null;
7 }//我們主要分析一下shouldSkip方法
8 if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
9 this.advisedBeans.put(cacheKey, Boolean.FALSE);
10 return null;
11 }
12 }
13
14 // Create proxy here if we have a custom TargetSource.
15 // Suppresses unnecessary default instantiation of the target bean:
16 // The TargetSource will handle target instances in a custom fashion.
17 TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
18 if (targetSource != null) {
19 if (StringUtils.hasLength(beanName)) {
20 this.targetSourcedBeans.add(beanName);
21 }
22 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
23 Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
24 this.proxyTypes.put(cacheKey, proxy.getClass());
25 return proxy;
26 }
27
28 return null;
29 }
 1     protected boolean shouldSkip(Class<?> beanClass, String beanName) {
2 // TODO: Consider optimization by caching the list of the aspect names
3 //主要這裡獲取了註解
4 List<Advisor> candidateAdvisors = findCandidateAdvisors();
5 for (Advisor advisor : candidateAdvisors) {
6 if (advisor instanceof AspectJPointcutAdvisor &&
7 ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
8 return true;
9 }
10 }
11 return super.shouldSkip(beanClass, beanName);
12 }
 1     //此方法分為兩步
2 protected List<Advisor> findCandidateAdvisors() {
3 // Add all the Spring advisors found according to superclass rules.
4 //第一步從bean工廠中找到所有Advisor的實現類
5 List<Advisor> advisors = super.findCandidateAdvisors();
6 // Build Advisors for all AspectJ aspects in the bean factory.
7 if (this.aspectJAdvisorsBuilder != null) {
8 //主要是第二步:從bean工廠中找到所有帶有@aspect註解的類
9 advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
10 }
11 return advisors;
12 }

  我們直接看第二步即可

 1     public List<Advisor> buildAspectJAdvisors() {
2 List<String> aspectNames = this.aspectBeanNames;
3
4 if (aspectNames == null) {
5 synchronized (this) {
6 aspectNames = this.aspectBeanNames;
7 if (aspectNames == null) {
8 List<Advisor> advisors = new ArrayList<>();
9 aspectNames = new ArrayList<>();
10 //獲取所有類
11 String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
12 this.beanFactory, Object.class, true, false);
13 for (String beanName : beanNames) {
14 if (!isEligibleBean(beanName)) {
15 continue;
16 }
17 // We must be careful not to instantiate beans eagerly as in this case they
18 // would be cached by the Spring container but would not have been weaved.
19 Class<?> beanType = this.beanFactory.getType(beanName);
20 if (beanType == null) {
21 continue;
22 }
23 //改類是否是我們寫的aspect註解類
24 if (this.advisorFactory.isAspect(beanType)) {
25 aspectNames.add(beanName);
26 AspectMetadata amd = new AspectMetadata(beanType, beanName);
27 if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
28 MetadataAwareAspectInstanceFactory factory =
29 new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
30 //開始在這裡解析
31 List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
32 if (this.beanFactory.isSingleton(beanName)) {
33 this.advisorsCache.put(beanName, classAdvisors);
34 }
35 else {
36 this.aspectFactoryCache.put(beanName, factory);
37 }
38 advisors.addAll(classAdvisors);
39 .......
40 return advisors;
41 }
 1     public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
2 //獲取我們的註解類
3 Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
4 //獲取名稱
5 String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
6 validate(aspectClass);
7
8 // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
9 // so that it will only instantiate once.
10 MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
11 new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
12
13 List<Advisor> advisors = new ArrayList<>();
14 //這裡迴圈獲取我們類的方法,找到除Pointcut註解外的註解方法
15 for (Method method : getAdvisorMethods(aspectClass)) {
16 //解析方法,如果找到Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class註解,則返回InstantiationModelAwarePointcutAdvisorImpl生成類
17 Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
18 if (advisor != null) {
19 advisors.add(advisor);
20 }
21 }
22
23 .......
24
25 return advisors;
26 }

  自此,我們的註解就解析完成了,不過心細的同學發現了,直解析了除Pointcut註解外的註解,Pointcut直接沒有解析啊,這個註解一般我們都配置在了Around等註解裡面,會有解析類去解析這個方法的。我們看看例項化後的後置處理器邏輯再

 1     @Override
2 public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
3 throws BeansException {
4
5 Object result = existingBean;
6 //遍歷所有後置處理器,但是我們只看AbstractAutoProxyCreator類的
7 for (BeanPostProcessor processor : getBeanPostProcessors()) {
8 Object current = processor.postProcessAfterInitialization(result, beanName);
9 if (current == null) {
10 return result;
11 }
12 result = current;
13 }
14 return result;
15 }
 1     //呼叫此方法
2 protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
3 if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
4 return bean;
5 }
6 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
7 return bean;
8 }
9 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
10 this.advisedBeans.put(cacheKey, Boolean.FALSE);
11 return bean;
12 }
13
14 // Create proxy if we have advice.
15 //是否有註解
16 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
17 if (specificInterceptors != DO_NOT_PROXY) {
18 this.advisedBeans.put(cacheKey, Boolean.TRUE);
19 //有則建立代理類返回
20 Object proxy = createProxy(
21 bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
22 this.proxyTypes.put(cacheKey, proxy.getClass());
23 return proxy;
24 }
25
26 this.advisedBeans.put(cacheKey, Boolean.FALSE);
27 return bean;
28 }

  為了清晰邏輯,中間的環節程式碼就不看了,直接看一下返回的是啥。

 1     public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
2 //config.isProxyTargetClass()這個預設時true,為什麼走cglib代理呢?
3 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
4 Class<?> targetClass = config.getTargetClass();
5 if (targetClass == null) {
6 throw new AopConfigException("TargetSource cannot determine target class: " +
7 "Either an interface or a target is required for proxy creation.");
8 }
9 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
10 return new JdkDynamicAopProxy(config);
11 }
12 return new ObjenesisCglibAopProxy(config);
13 }
14 else {
15 return new JdkDynamicAopProxy(config);
16 }
17 }

  為什麼spring預設走cglib代理呢?我們大家可能還知道一個註解是@EnableAspectJAutoProxy,其實這個才是控制的開關。如果我們寫成false的話是走jdk代理的,但是為什麼我們自己的配置類配置EnableAspectJAutoProxy註解了也是無效的呢?這時候就要看一下AopAutoConfiguration自動配置類了,為了防止大家看暈,博主也畫了一張草圖:

  https://www.processon.com/view/link/6134bef3e401fd1fb6a91dc6

 1 public class AopAutoConfiguration {
2 //jdk和cglib都有註解,但是預設只有一個生效了,就是CglibAutoProxyConfiguration,因為ConditionalOnProperty註解說明了一起
3 @Configuration
4 @EnableAspectJAutoProxy(proxyTargetClass = false)
5 @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
6 public static class JdkDynamicAutoProxyConfiguration {
7
8 }
9
10 @Configuration
11 @EnableAspectJAutoProxy(proxyTargetClass = true)
12 @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
13 public static class CglibAutoProxyConfiguration {
14
15 }
16
17 }

  當我們不去在配置檔案中明確標明spring.aop.proxy-target-class屬性時,只有就是CglibAutoProxyConfiguration是生效的,怕有些小夥伴不知道ConditionalOnProperty註解的作用,博主就簡單帶帶大家看一下,熟悉同學可以自行略過,在spring解析配置類時,就會解析該註解

 1     //這是校驗配置類的時候解析的,路徑-》org.springframework.context.annotation.ConditionEvaluator#shouldSkip
2 public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
3 //由於jdk和cglib類都有Conditional的子註解,所以都通過了
4 if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
5 return false;
6 }
7
8 if (phase == null) {
9 if (metadata instanceof AnnotationMetadata &&
10 ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
11 return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
12 }
13 return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
14 }
15
16 List<Condition> conditions = new ArrayList<>();
17 //找到ConditionalOnProperty註解
18 for (String[] conditionClasses : getConditionClasses(metadata)) {
19 for (String conditionClass : conditionClasses) {
20 Condition condition = getCondition(conditionClass, this.context.getClassLoader());
21 conditions.add(condition);
22 }
23 }
24
25 AnnotationAwareOrderComparator.sort(conditions);
26
27 for (Condition condition : conditions) {
28 ConfigurationPhase requiredPhase = null;
29 if (condition instanceof ConfigurationCondition) {
30 requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
31 }
32 //開始檢驗是否匹配
33 if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
34 return true;
35 }
36 }
37
38 return false;
39 }
 1     public final boolean matches(ConditionContext context,
2 AnnotatedTypeMetadata metadata) {
3 String classOrMethodName = getClassOrMethodName(metadata);
4 try {
5 //走這裡檢視OnPropertyCondition匹配即可
6 ConditionOutcome outcome = getMatchOutcome(context, metadata);
7 logOutcome(classOrMethodName, outcome);
8 recordEvaluation(context, classOrMethodName, outcome);
9 return outcome.isMatch();
10 }
11 ......
12 }
 1         private void collectProperties(PropertyResolver resolver, List<String> missing,
2 List<String> nonMatching) {
3 for (String name : this.names) {
4 String key = this.prefix + name;
5 if (resolver.containsProperty(key)) {
6 if (!isMatch(resolver.getProperty(key), this.havingValue)) {
7 nonMatching.add(name);
8 }
9 }
10 else {
11 //直接檢視關鍵程式碼,如果配置檔案中沒有該屬性,檢視是否註解中寫了matchIfMissing屬性,而我們的cglib是true,所以,不會missing,而是裝配起來了,所以預設走cglib代理
12 if (!this.matchIfMissing) {
13 missing.add(name);
14 }
15 }
16 }
17 }

  現在我們的註解不僅載入完了,而且被註解表明的也生成了代理類,我們看看切面註解是如何生效的,我們就以cglib舉例了,jdk類似

 1 //CglibAopProxy
2 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
3 Object oldProxy = null;
4 boolean setProxyContext = false;
5 Object target = null;
6 TargetSource targetSource = this.advised.getTargetSource();
7 try {
8 .....
9 //獲取是否有攔截鏈,並不是我們的請求攔截器,這裡把切面認為是一種攔截器了
10 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
11 Object retVal;
12 // Check whether we only have one InvokerInterceptor: that is,
13 // no real advice, but just reflective invocation of the target.
14 if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
15 // We can skip creating a MethodInvocation: just invoke the target directly.
16 // Note that the final invoker must be an InvokerInterceptor, so we know
17 // it does nothing but a reflective operation on the target, and no hot
18 // swapping or fancy proxying.
19 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
20 retVal = methodProxy.invoke(target, argsToUse);
21 }
22 else {
23 // We need to create a method invocation...
24 //主要就是走後面的.proceed()方法
25 retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
26 }
27 retVal = processReturnType(proxy, target, method, retVal);
28 return retVal;
29 .....
30 }
31 }
32 }
 1     //這裡就像走我們的請求過濾器一樣,每個攔截器都走一遍,最後都呼叫proceed()再回到這個方法,直到++this.currentInterceptorIndex到頭終止
2 public Object proceed() throws Throwable {
3 // We start with an index of -1 and increment early.
4 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
5 return invokeJoinpoint();
6 }
7
8 Object interceptorOrInterceptionAdvice =
9 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
10 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
11 // Evaluate dynamic method matcher here: static part will already have
12 // been evaluated and found to match.
13 InterceptorAndDynamicMethodMatcher dm =
14 (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
15 if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
16 //別的我們不看,就看我們自己定義的@around
17 return dm.interceptor.invoke(this);
18 }
19 else {
20 // Dynamic matching failed.
21 // Skip this interceptor and invoke the next in the chain.
22 return proceed();
23 }
24 }
25 else {
26 // It's an interceptor, so we just invoke it: The pointcut will have
27 // been evaluated statically before this object was constructed.
28 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
29 }
30 }

  呼叫反射的時候,就會發現我們的around中才會去解析pointcut方法,因為我們在around註解裡面寫了,具體設這個類PointcutParser#parsePointcutExpression去進行解析的,將pointcut的表示式放入到around中作為引數傳遞。

  對此,Spring AOP就全部講解完畢了,裡面為了減少文章篇幅,去掉了一些中間的跳轉程式碼,具體可以看一下,博主發的草圖,草圖中所以的邏輯都很清晰,也貼了一些關鍵性的邏輯程式碼。希望大家可以在深入瞭解瞭解。