4

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

 8 months ago
source link: https://blog.51cto.com/StudiousXiaoYu/9192732
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

精选 原创

努力的小雨 2024-01-11 09:39:32 博主文章分类:经验分享 ©著作权

文章标签 List 缓存 sed 文章分类 Java 后端开发 yyds干货盘点 阅读数214

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

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

@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就可以了。

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方法解析注解流程。

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;
    }
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);
    }
//此方法分为两步
    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;
    }

我们直接看第二步即可

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;
    }
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等注解里面,会有解析类去解析这个方法的。我们看看实例化后的后置处理器逻辑再

@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;
    }
//调用此方法
    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;
    }

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

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

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解析配置类时,就会解析该注解

//这是校验配置类的时候解析的,路径-》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;
    }
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();
        }
        ......
    }
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类似

//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;
            .....
                }
            }
        }
//这里就像走我们的请求过滤器一样,每个拦截器都走一遍,最后都调用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就全部讲解完毕了,里面为了减少文章篇幅,去掉了一些中间的跳转代码,具体可以看一下,博主发的草图,草图中所以的逻辑都很清晰,也贴了一些关键性的逻辑代码。希望大家可以在深入了解了解。

  • 收藏
  • 评论
  • 分享
  • 举报

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK