本次博主主要进行Spring AOP这里的解析,因为在工作中使用后,却不知道背后的实现原理并在使用的过程中发现了一些认知缺陷,所以决定写这么一篇文章以供大家参考参考,进入正题。
本次博主使用了@Aspect、@Around、@PointCut注解实现了一些小的需求,大家想必都用过,我就简单的举个例子吧。
ini
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注解源码分析加载与生效
www.processon.com/view/link/6...
我们开始走代码,我们直接走bean的创建开始,如果有小伙伴不知道整个bean创建流程的话,可以看一下博主以前的画 的草图脑补一下:
www.processon.com/view/link/5...
代码走起,任意的bean创建都可以,如果看不了静态代码,自行debug就可以了。\
scss
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方法解析注解流程。
kotlin
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 }
scss
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 }
kotlin
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 }
我们直接看第二步即可
kotlin
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 }
scss
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等注解里面,会有解析类去解析这个方法的。我们看看实例化后的后置处理器逻辑再
ini
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 }
kotlin
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 }
为了清晰逻辑,中间的环节代码就不看了,直接看一下返回的是啥。
arduino
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自动配置类了,为了防止大家看晕,博主也画了一张草图:
www.processon.com/view/link/6...
swift
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解析配置类时,就会解析该注解
less
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 }
scss
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 }
typescript
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类似
typescript
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 }
kotlin
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就全部讲解完毕了,里面为了减少文章篇幅,去掉了一些中间的跳转代码,具体可以看一下,博主发的草图,草图中所以的逻辑都很清晰,也贴了一些关键性的逻辑代码。希望大家可以在深入了解了解。