spring 源码分析(四)core

Spring 源码分析

sschrodinger

2019/08/14


注解代理如何应用


bean 生命周期

bean 的生命周期如下:

image.png

详见 Spring 源码分析(二)
,其中,代理生成最主要的过程为第 9 步,即调用 beanPostProcessor
postProcessAfterInitialization() 方法。该方法接受一个未被包装的 bean,然后返回一个包装的 bean(即代理的 bean),并将包装的 bean 注入到 spring 上下文容器中

实际上,Spring 容器上下文初始化时,会将实现了 beanPostProcessor 的类提前初始化,并将其注入到 spring 容器中,接下来初始化其他 bean 时,就会根据注解判断是否 beanPostProcessor 满足当前 bean 的注解需求,如果满足,则需要对 bean 进行处理。

整个 Spring 容器初始化的过程如下(ApplicationContext 类 的 refresh 方法):

@Override
   public void refresh() throws BeansException, IllegalStateException {
       synchronized (this.startupShutdownMonitor) {
           //...

           // step 1. 获得所有的 bean 定义
           ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

           //...

           try {
               // ...

               // step 2. 将所有的 beanPostProcessor 注入到容器中
               registerBeanPostProcessors(beanFactory);

               // ... 
               
               // step 3. 初始化其他 bean
               finishBeanFactoryInitialization(beanFactory);

               //...
           }

           catch (BeansException ex) {
               // ...
           }

           finally {
               // ...
           }
       }
   }

我们重点关注上面的三步代码:

  1. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 该方法主要的目的就是获得所有 bean 的定义,封装在一个 beanDefinition 结构中,这个时候并不会初始化任何的 bean
  2. registerBeanPostProcessors(beanFactory); 这个函数就是加载所有的 beanPostProcessor 到容器中。
  3. finishBeanFactoryInitialization(beanFactory); 这一步是具体的初始化。

方法上的注解

对于 Aop 来说,可以将注解写在方法上,用于代理的生成,以 @Transactional 注解为例,声明方法如下:

@Service
public class ServiceImpl {
    
    @Transactional
    public void findById(long id) {}
    
}

对于方法上的注解,他的解析过程放在初始化的 step 3 中,我们接下来去分析 step 3。

我们知道,在初始化一个 bean 时,需要调用 beanPostProcessorpostProcessAfterInitialization() 方法进行包装,在 spring 中,默认使用 InfrastructureAdvisorAutoProxyCreator 这个 beanPostProcessor 处理内置的事务通知,所以我们直接看这个类的方法,如下:

// 该方法由 InfrastructureAdvisorAutoProxyCreator 的父类 AbstractAutoProxyCreator 提供
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

// 该方法由 InfrastructureAdvisorAutoProxyCreator 的父类 AbstractAutoProxyCreator 提供
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;
    }

    // step 1. 获得所有 Intercepter
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // step 2. 利用动态代理生成代理类
        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;
}

最重点的是 step 1,获得所有的 Intercepter,所谓的 Intercepter 在 JDK 动态代理中,既是实现了 InvocationHandler 的类,只有获得了这个,才能够进行动态代理。

通过研究 getAdvicesAndAdvisorsForBean,我们可以知道注解在什么时候有效,什么时候无效。

protected Object[] getAdvicesAndAdvisorsForBean(
        Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {

    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // step 1.
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // step 2.
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
    // ...
    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;
}
  • step 1 在容器中找到所有的 Advisor 对象,该对象一般持有了一个过滤器,用来过滤不需要代理的方法。
  • step 2 主要是对目标类的方法进行过滤,找到需要过滤的方法

最终,第二步会调用 AopUtilscanApply 方法,判断当前的目标类是否可以用 Advisor 代理。

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    // ...

    MethodMatcher methodMatcher = pc.getMethodMatcher();
    // ...

    Set<Class<?>> classes = new LinkedHashSet<>();
    if (!Proxy.isProxyClass(targetClass)) {
        classes.add(ClassUtils.getUserClass(targetClass));
    }
    classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

    for (Class<?> clazz : classes) {
        // step 1. 
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
        for (Method method : methods) {
            // step 2.
            if (introductionAwareMethodMatcher != null ?
                    introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
                    methodMatcher.matches(method, targetClass)) {
                return true;
            }
        }
    }

    return false;
}
  • step 1 获得该类及其父类的所有方法。
  • step 2 循环遍历所有的方法,查找是否有满足条件(有注解)的方法。

@Transactional 注解举例,这个注解所对应的 AdvisorTransactionAttributeSourcePointcut,即会调用 TransactionAttributeSourcePointcutmatches 方法。

依次进入
getTransactionAttribute -> computeTransactionAttribute() -> findTransactionAttribute(specificMethod) -> determineTransactionAttribute(method) -> parseTransactionAnnotation(element) -> findMergedAnnotationAttributes() -> searchWithFindSemantics()最终的注解是由 searchWithFindSemantics 函数完成的

searchWithFindSemantics 的参数形式如下:

private static <T> T searchWithFindSemantics(AnnotatedElement element,
            Set<Class<? extends Annotation>> annotationTypes, @Nullable String annotationName,
            @Nullable Class<? extends Annotation> containerType, Processor<T> processor)

参数解释

  • AnnotatedElement element:当前的 annotatedElement 对象,如 Method
  • Set<Class<? extends Annotation>> annotationTypes:需要查找的 annotation 注解对象集合

对于事务来说,他所需要查找的注解集合为 Transactionalelement 为对应方法。

searchWithFindSemantics 寻找策略

searchWithFindSemantics 函数提供的方法可以防止无止尽的循环寻找注解,使用一个 visit 列表,将访问过的 element 保存在该列表中,保证 element 不会被循环寻找。

searchWithFindSemantics 在四个地方查找是否有注解:

  • 当前 element 是否有注解(不考虑 @Inherited,即不考虑注解的继承)
  • 如果 element 是方法类型,即 element instanceof Method == true,在定义该 element 的接口中查找注解
  • 如果 element 是方法类型,从该类的直接父类开始,依次在父类中查找该方法的父方法,判断是否有该注解。
  • 如果 element 是类类型,那么会依次查找该类及其父类,看有没有注解。

note

  • 除了查找直接注解是否所需的注解,该算法还需要递归,注解的注解,即,如果该 element 有注解,但是没有直接注解,那么就需要循环注解,看该注解是否被所需的注解修饰。

==综上,使用方法上的注解,不管是在父类中、在接口方法中或者在注解的注解中都可以被找到并加载==

类上的注解

同样的,我们也可以把注解写在类或者接口的定义上,如下:

@Service
@Transactional
public class ServiceImpl {
}

@Transactional
public interface Service {
}

我们继续分析如何获得类上的注解定义。

回到 mputeTransactionAttribute 方法,如下:

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {

    // ...

    // First try is the method in the target class.
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if (txAttr != null) {
        return txAttr;
    }

    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
    }

    // ...

    return null;
}

在方法上获得 txAttr 失败时,会尝试在方法上找到 txAttr,该方法最终也会调用 searchWithFindSemantics 方法,在该类及其父类用依次查找是否有注解。

==综上,使用在注解上的注解,不管是在父类中、在接口方法中或者在注解的注解中都可以被找到并加载==。

==note==

  • ==不管是注解注解在什么位置,在 spring 中都可以使用代理==
  • ==注意注解的覆盖性:即方法上的注解可以覆盖类上的注解;子类的方法注解可以覆盖父类的方法注解;接口的方法注解可以覆盖父类的方法注解==
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,498评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,668评论 3 406
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,857评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,305评论 1 303
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,308评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,747评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,078评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,080评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,649评论 1 327
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,644评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,760评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,352评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,076评论 3 341
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,490评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,651评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,353评论 3 383
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,828评论 2 367

推荐阅读更多精彩内容