源码解析Spring AOP的加载与生效

2024-06-08 15:08:04 浏览数 (1)

本次博主主要进行Spring AOP这里的解析,因为在工作中使用后,却不知道背后的实现原理并在使用的过程中发现了一些认知缺陷,所以决定写这么一篇文章以供大家参考参考,进入正题。

  本次博主使用了@Aspect、@Around、@PointCut注解实现了一些小的需求,大家想必都用过,我就简单的举个例子吧。

代码语言:java复制
@Aspect
@Component
public class CrmCacheAspect {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    private ConcurrentHashMap<String, ICacheResultParser> parserMap = new ConcurrentHashMap();

    private ConcurrentHashMap<String, IKeyGenerator> generatorMap = new ConcurrentHashMap();

    private ConcurrentHashMap<String,Boolean> keyMap = new ConcurrentHashMap<>();
    @Pointcut("@annotation(com.bjh.hms.crm.annotation.CrmCache)")
    public void pointCut(){}

    @Around("pointCut() && @annotation(crmCache)")
    public Object joinPoint(ProceedingJoinPoint joinPoint, CrmCache crmCache) throws InstantiationException, IllegalAccessException {
        String value = "";
        String key = "";
        Object result = "";
        try {
            key = getKey(crmCache,joinPoint);
            value = stringRedisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            XxlJobHelper.log("获取缓存{}失败:{}",crmCache.key(),e);
        } finally {
            if (StringUtils.isBlank(value)) {
                value = synchronizeCache(key, joinPoint, crmCache);
            }
            result = getResult(crmCache, value, joinPoint);
        }
        return result;
    }

    private Object getResult(CrmCache crmCache,
                             String value,
                             ProceedingJoinPoint joinPoint) throws InstantiationException, IllegalAccessException {
        if (value == null) {
            return null;
        }
        String name = crmCache.parser().getName();
        ICacheResultParser iCacheResultParser;
        if (parserMap.containsKey(name)) {
            iCacheResultParser = parserMap.get(name);
        } else {
            iCacheResultParser = crmCache.parser().newInstance();
            parserMap.put(name,iCacheResultParser);
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class returnType = signature.getReturnType();
        Object parse = iCacheResultParser.parse(value, returnType);
        return parse;
    }

    /**
     * Title: 解决redis并发穿透
     * @author 2021/8/13 17:15
     * @return java.lang.String
     */
    private String synchronizeCache(String key,
                                    ProceedingJoinPoint joinPoint,
                                    CrmCache crmCache) {
        String value = "";
        //暂停100-200ms,线程顺序执行
        try {
            Thread.sleep((int)(Math.random()*(200 - 100   1)   100));
        } catch (InterruptedException e) {
            XxlJobHelper.log("synchronizeCache error {}", ExceptionUtil.stacktraceToString(e));
        }
        while (StringUtils.isBlank(value = stringRedisTemplate.opsForValue().get(key))
        && (keyMap.get(key) == null || keyMap.get(key))){
            //防止重复调用
            if (keyMap.get(key) == null || !keyMap.get(key)) {
                keyMap.put(key,true);
                Object proceed = null;
                try {
                    proceed = joinPoint.proceed();
                } catch (Throwable e) {
                    XxlJobHelper.log("处理失败:{}",ExceptionUtil.stacktraceToString(e));
                }
                putValueByRedis(key,proceed,crmCache);
                keyMap.put(key,false);
            }
        }
        keyMap.remove(key);
        return value;
    }

    private void putValueByRedis(String key, Object value, CrmCache crmCache) {
        if (value == null) {
            return;
        }
        if (value instanceof String) {
            stringRedisTemplate.opsForValue().set(key, value.toString());
        } else {
            String jsonString = JSONObject.toJSONString(value);
            stringRedisTemplate.opsForValue().set(key,jsonString);
        }
        //-1代表不过期
        if (crmCache.expire() != -1) {
            stringRedisTemplate.expire(key, crmCache.expire(), TimeUnit.MINUTES);
        }
    }

    private String getKey(CrmCache crmCache, ProceedingJoinPoint joinPoint) throws InstantiationException, IllegalAccessException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        String iKeyGeneratorName = crmCache.generator().getName();
        String key = crmCache.key();
        IKeyGenerator iKeyGenerator = null;
        if (generatorMap.containsKey(iKeyGeneratorName)) {
            iKeyGenerator = generatorMap.get(iKeyGeneratorName);
        } else {
            iKeyGenerator = crmCache.generator().newInstance();
            generatorMap.put(iKeyGeneratorName,iKeyGenerator);
        }
        return iKeyGenerator.generate(key,method,args);
    }

}

本例子主要是对结果与请求进行解析缓存,spring其实有自带的,但是不可以使用缓存时间,有缓存时间又需要引入其他依赖包,公司内部私服又是内网访问的,所以就自写了一个简单的注解实现了缓存有限时间功能。这不是重点,我们来分析一下注解是如何加载进来的,又是如何被spring走进来解析的吧。

  讲解之前,博主还是一如既往的为大家画了几张草图,以便大家防止看代码看晕,先来第一张:aspect注解源码分析加载与生效

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

  我们开始走代码,我们直接走bean的创建开始,如果有小伙伴不知道整个bean创建流程的话,可以看一下博主以前的画 的草图脑补一下:

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

  代码走起,任意的bean创建都可以,如果看不了静态代码,自行debug就可以了。

代码语言:java复制
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
        Object bean = null;
        if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
            // 确定是否有aspect注解
            if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
                Class<?> targetType = determineTargetType(beanName, mbd);
                if (targetType != null) {
                    //解析注解
                    bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
                    if (bean != null) {
                        //此处会给有自定义注解的bean创建代理类返回
                        bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
                    }
                }
            }
            mbd.beforeInstantiationResolved = (bean != null);
        }
        return bean;
    }

我们分析一下hasInstantiationAwareBeanPostProcessors方法,看看是如何走进来的,从方法名字可以看出,是否有InstantiationAwareBeanPostProcessors后置处理器,那我们本身并没有去填加这个类,那怎么就有了呢,原因就在我们引入aop包依赖后,有一个默认的自动配置AopAutoConfiguration,EnableAspectJAutoProxy注解中间引入了一个AspectJAutoProxyRegistrar类,实现这个registerBeanDefinitions方法后,引入了一个AnnotationAwareAspectJAutoProxyCreator类,这个类就是AspectJAutoProxyRegistrar的实现类,所以hasInstantiationAwareBeanPostProcessors方法走通了。

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

代码语言:java复制
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        Object cacheKey = getCacheKey(beanClass, beanName);

        if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }//我们主要分析一下shouldSkip方法
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }

        // Create proxy here if we have a custom TargetSource.
        // Suppresses unnecessary default instantiation of the target bean:
        // The TargetSource will handle target instances in a custom fashion.
        TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            if (StringUtils.hasLength(beanName)) {
                this.targetSourcedBeans.add(beanName);
            }
            Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }

        return null;
    }
代码语言:java复制
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        // TODO: Consider optimization by caching the list of the aspect names
        //主要这里获取了注解
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        for (Advisor advisor : candidateAdvisors) {
            if (advisor instanceof AspectJPointcutAdvisor &&
                    ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
                return true;
            }
        }
        return super.shouldSkip(beanClass, beanName);
    }
代码语言:java复制
//此方法分为两步
    protected List<Advisor> findCandidateAdvisors() {
        // Add all the Spring advisors found according to superclass rules.
        //第一步从bean工厂中找到所有Advisor的实现类
        List<Advisor> advisors = super.findCandidateAdvisors();
        // Build Advisors for all AspectJ aspects in the bean factory.
        if (this.aspectJAdvisorsBuilder != null) {
        //主要是第二步:从bean工厂中找到所有带有@aspect注解的类
            advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
        }
        return advisors;
    }

我们直接看第二步即可

代码语言:java复制
public List<Advisor> buildAspectJAdvisors() {
        List<String> aspectNames = this.aspectBeanNames;

        if (aspectNames == null) {
            synchronized (this) {
                aspectNames = this.aspectBeanNames;
                if (aspectNames == null) {
                    List<Advisor> advisors = new ArrayList<>();
                    aspectNames = new ArrayList<>();
                    //获取所有类
                    String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                            this.beanFactory, Object.class, true, false);
                    for (String beanName : beanNames) {
                        if (!isEligibleBean(beanName)) {
                            continue;
                        }
                        // We must be careful not to instantiate beans eagerly as in this case they
                        // would be cached by the Spring container but would not have been weaved.
                        Class<?> beanType = this.beanFactory.getType(beanName);
                        if (beanType == null) {
                            continue;
                        }
                        //改类是否是我们写的aspect注解类
                        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);
                                //开始在这里解析
                                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);
        .......
        return advisors;
    }
代码语言:java复制
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    //获取我们的注解类
        Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
        //获取名称
        String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
        validate(aspectClass);

        // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
        // so that it will only instantiate once.
        MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
                new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

        List<Advisor> advisors = new ArrayList<>();
        //这里循环获取我们类的方法,找到除Pointcut注解外的注解方法
        for (Method method : getAdvisorMethods(aspectClass)) {
        //解析方法,如果找到Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class注解,则返回InstantiationModelAwarePointcutAdvisorImpl生成类
            Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }

        .......

        return advisors;
    }

自此,我们的注解就解析完成了,不过心细的同学发现了,直解析了除Pointcut注解外的注解,Pointcut直接没有解析啊,这个注解一般我们都配置在了Around等注解里面,会有解析类去解析这个方法的。我们看看实例化后的后置处理器逻辑再

代码语言:java复制
@Override
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {

        Object result = existingBean;
        //遍历所有后置处理器,但是我们只看AbstractAutoProxyCreator类的
        for (BeanPostProcessor processor : getBeanPostProcessors()) {
            Object current = processor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }
代码语言:java复制
//调用此方法
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }

        // Create proxy if we have advice.
        //是否有注解
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //有则创建代理类返回
            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;
    }

为了清晰逻辑,中间的环节代码就不看了,直接看一下返回的是啥。

代码语言:java复制
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    //config.isProxyTargetClass()这个默认时true,为什么走cglib代理呢?
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: "  
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

为什么spring默认走cglib代理呢?我们大家可能还知道一个注解是@EnableAspectJAutoProxy,其实这个才是控制的开关。如果我们写成false的话是走jdk代理的,但是为什么我们自己的配置类配置EnableAspectJAutoProxy注解了也是无效的呢?这时候就要看一下AopAutoConfiguration自动配置类了,为了防止大家看晕,博主也画了一张草图:

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

代码语言:java复制
public class AopAutoConfiguration {
    //jdk和cglib都有注解,但是默认只有一个生效了,就是CglibAutoProxyConfiguration,因为ConditionalOnProperty注解说明了一起
    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
    public static class JdkDynamicAutoProxyConfiguration {

    }

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
    public static class CglibAutoProxyConfiguration {

    }

}

当我们不去在配置文件中明确标明spring.aop.proxy-target-class属性时,只有就是CglibAutoProxyConfiguration是生效的,怕有些小伙伴不知道ConditionalOnProperty注解的作用,博主就简单带带大家看一下,熟悉同学可以自行略过,在spring解析配置类时,就会解析该注解

代码语言:java复制
//这是校验配置类的时候解析的,路径-》org.springframework.context.annotation.ConditionEvaluator#shouldSkip
    public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    //由于jdk和cglib类都有Conditional的子注解,所以都通过了
        if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
            return false;
        }

        if (phase == null) {
            if (metadata instanceof AnnotationMetadata &&
                    ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
            }
            return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
        }

        List<Condition> conditions = new ArrayList<>();
        //找到ConditionalOnProperty注解
        for (String[] conditionClasses : getConditionClasses(metadata)) {
            for (String conditionClass : conditionClasses) {
                Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                conditions.add(condition);
            }
        }

        AnnotationAwareOrderComparator.sort(conditions);

        for (Condition condition : conditions) {
            ConfigurationPhase requiredPhase = null;
            if (condition instanceof ConfigurationCondition) {
                requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
            }
            //开始检验是否匹配
            if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
                return true;
            }
        }

        return false;
    }
代码语言:java复制
public final boolean matches(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        String classOrMethodName = getClassOrMethodName(metadata);
        try {
        //走这里查看OnPropertyCondition匹配即可
            ConditionOutcome outcome = getMatchOutcome(context, metadata);
            logOutcome(classOrMethodName, outcome);
            recordEvaluation(context, classOrMethodName, outcome);
            return outcome.isMatch();
        }
        ......
    }
代码语言:java复制
private void collectProperties(PropertyResolver resolver, List<String> missing,
                List<String> nonMatching) {
            for (String name : this.names) {
                String key = this.prefix   name;
                if (resolver.containsProperty(key)) {
                    if (!isMatch(resolver.getProperty(key), this.havingValue)) {
                        nonMatching.add(name);
                    }
                }
                else {
                //直接查看关键代码,如果配置文件中没有该属性,查看是否注解中写了matchIfMissing属性,而我们的cglib是true,所以,不会missing,而是装配起来了,所以默认走cglib代理
                    if (!this.matchIfMissing) {
                        missing.add(name);
                    }
                }
            }
        }

现在我们的注解不仅加载完了,而且被注解表明的也生成了代理类,我们看看切面注解是如何生效的,我们就以cglib举例了,jdk类似

代码语言:java复制
//CglibAopProxy
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object oldProxy = null;
            boolean setProxyContext = false;
            Object target = null;
            TargetSource targetSource = this.advised.getTargetSource();
            try {
                .....
                //获取是否有拦截链,并不是我们的请求拦截器,这里把切面认为是一种拦截器了
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                Object retVal;
                // Check whether we only have one InvokerInterceptor: that is,
                // no real advice, but just reflective invocation of the target.
                if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
                    // We can skip creating a MethodInvocation: just invoke the target directly.
                    // Note that the final invoker must be an InvokerInterceptor, so we know
                    // it does nothing but a reflective operation on the target, and no hot
                    // swapping or fancy proxying.
                    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                    retVal = methodProxy.invoke(target, argsToUse);
                }
                else {
                    // We need to create a method invocation...
                    //主要就是走后面的.proceed()方法
                    retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
                }
                retVal = processReturnType(proxy, target, method, retVal);
                return retVal;
            .....
                }
            }
        }
代码语言:java复制
//这里就像走我们的请求过滤器一样,每个拦截器都走一遍,最后都调用proceed()再回到这个方法,直到  this.currentInterceptorIndex到头终止
    public Object proceed() throws Throwable {
        //    We start with an index of -1 and increment early.
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }

        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(  this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            // Evaluate dynamic method matcher here: static part will already have
            // been evaluated and found to match.
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            //别的我们不看,就看我们自己定义的@around
                return dm.interceptor.invoke(this);
            }
            else {
                // Dynamic matching failed.
                // Skip this interceptor and invoke the next in the chain.
                return proceed();
            }
        }
        else {
            // It's an interceptor, so we just invoke it: The pointcut will have
            // been evaluated statically before this object was constructed.
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

调用反射的时候,就会发现我们的around中才会去解析pointcut方法,因为我们在around注解里面写了,具体设这个类PointcutParser#parsePointcutExpression去进行解析的,将pointcut的表达式放入到around中作为参数传递。

  对此,Spring AOP就全部讲解完毕了,里面为了减少文章篇幅,去掉了一些中间的跳转代码,具体可以看一下,博主发的草图,草图中所以的逻辑都很清晰,也贴了一些关键性的逻辑代码。希望大家可以在深入了解了解。

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

0 人点赞