熟练掌握spring框架第一篇

前言

熟练掌握这个词相信很多同行在写简历的时候都用到过。熟练掌握这四个字是根据每个人水平不一样,理解也不一样。比如一个刚毕业的大学生,他可能也会在简历里面写熟练掌握spring框架,但实际上他并没有看过spring源码,也没有太多实践。可能只是看了几本书,使用spring框架写了一个毕业设计,然后就说自己熟练掌握spring框架了。再比如一个有二三年工作经验的求职者,也在简历里面说自己熟练掌握spring框架,他的理由是他看过spring 的一些关键性代码了,也在实际工作中使用spring开发了工程项目。甚至自己根据一些业务需要,写了几个不错的针对spring框架的扩展。再比如一个工作了四五年的程序员,他在自己简历上写熟练掌握spring框架。因为他把spring和spring boot源码大部分都看过,工作中解决了不少涉及原理性的问题。对spring框架的整个架构图,基本了然于胸。另外总结出了非常多的spring相关的最佳实践。再比如一个工作了七八年的程序员,他们对spring的理解可能又不太一样。其实我想说的是,水平和经验决定了一个人的见解。每个人都应该虚心学习丰富和巩固自己的知识体系,拓宽知识的广度,加强知识的深度。这样才会在有限的时间里面,成长成参天大树。好了,废话不多说,今天的主题是熟练掌握spring框架。那么我就从n个问题着手。说一下工作了n年的我对熟练掌握的见解吧。

BeanFactory和FactoryBean的区别

首先说下BeanFactory他是springIOC容器的顶层接口。负责管理和生产Bean。他的默认实现是:DefaultListableBeanFactory。在spring boot项目启动时,执行createApplicationContext()后返回的实例类型是AnnotationConfigApplicationContext,可以看下该类的层次结构图。发现他也是BeanFactory接口的实现类。代码是在ApplicationContext的抽象类AbstractApplicationContext中,可以看出所有BeanFactory的实现还是调用了GenericApplicationContext的成员DefaultListableBeanFactory beanFactory 的具体实现。所以Bean管理的核心代码自然就是DefaultListableBeanFactory

image-20210504100446316

查看它的类层次结构图发现,这个类主要扮演两个角色,第一个就是bean工厂,第二个就是BeanDefinition的注册中心。bean工厂提供了对单例bean注册中心(DefaultSingletonBeanRegistry),FactoryBean注册中心(FactoryBeanRegistrySupport)的支持。首先我们看下他是如何实现bean工厂的。

注册单例bean

使用@Component注解定义一个UserService

image-20210504105106608

断点设置在DefaultSingletonBeanRegistry单例bean注册中心的addSingleton中。

看下调用栈:

image-20210504110105721
  1. 发生在refreshContext阶段
  2. 对单例模式bean进行初始化
  3. 通过getBean触发实例化userService ,getBean进而调用doGetBean

doGetBean方法可以稍微展开下

它调用了DefaultSingletonBeanRegistrygetSingleton 其中第二个参数就是ObjectFactory,创建bean的工厂ObjectFactory 实际调用的是AbstractAutowireCapableBeanFactorycreateBean方法,进而调用它的doCreateBean,接着调用createBeanInstance方法,进而调用instantiateBean方法,使用SimpleInstantiationStrategy 策略类(实际使用的java反射技术动态创建对象)创建实例。

image-20210504114833569

获取单例bean

还是刚才那个例子。在ApplicationRunner的run方法调用getBean方法。

image-20210504121414084

doGetBean方法里会首先检查单利缓存里面是否有,如果有的话,直接返回。

BeanDefinition注册

我们再来看下BeanDefinition注册中心是如何实现的。
我们仍然是在DefaultListableBeanFactoryregisterBeanDefinition设置断点,看下调用栈。

image-20210504165833089

  1. 同样发生在refreshContext阶段
  2. 执行所有的BeanFactoryPostProcessor
  3. 执行所有的BeanDefinitionRegistryPostProcessor,此处默认就一个名为:ConfigurationClassPostProcessor
  4. 此方法主要工作就是首先调用ConfigurationClassUtils.checkConfigurationClassCandidate找到所有ConfigurationClass的候选人,然后使用ConfigurationClassParser解析每个ConfigurationClass
  5. 如果是ComponentScansConfigurationClass,就调用ClassPathBeanDefinitionScannerdoScan进行扫描。
  6. 将扫描的BeanDefinition 添加到DefaultListableBeanFactorybeanDefinitionMap里。至此完成BeanDefinition的注册。

FactoryBean

说了这么多,相信读者对spring框架的BeanFactory有了一个比较全面的了解了。下面聊聊FactoryBeanFactoryBean是spring 容器里的一种特殊的bean

该接口的实现以BeanFactory为工厂。假设某个Bean实现了该接口,它用作生产对象的工厂。而不是像普通的Bean那样直接暴露自己。通常使用getObject方法暴露beanFactoryBean支持单例和原型,并且可以可以按需延迟或者在启动的时候创建对象。这个接口在spring框架内部大量使用,比如AOPorg.springframework.aop.framework.ProxyFactoryBean jpaorg.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean,然而在实际业务代码里面并不常见。

来源java doc

下面就以JpaRepositoryFactoryBean详细介绍下FactoryBean是如何工作的。

读者可以翻看下之前的一篇文章【JpaRepositoryBean创建流程分析】此处对于repository bean的初始化就不详细介绍了。我们还是在ApplicationRunner的run方法里调用ApplicationContextgetBean获取一个UserRepository,如果是第一次获取,流程是这样的。

  1. 调用BeanFactorygetBean方法。
  2. 实际逻辑在AbstractBeanFactorydoGetBean方法里。
  3. 根据beanName userRepositorySingletonBeanRegistry里找到相应的JpaRepositoryFactoryBean
  4. 然后根据在FactoryBeanRegistry 的缓存里(factoryBeanObjectCache)根据beanName查找userRepository,由于是第一次,所以找不到。
  5. 调用FactoryBeanRegistrySupportgetObjectFromFactoryBean方法。该方法有个同步代码块,目的是保证并发情况下,创建的对象仍然是单例的。
  6. 同步代码块里回去调用JpaRepositoryFactoryBeangetObject方法获取之前就已经创建好的repository,然后加入到factoryBeanObjectCache中去并返回相应的bean

小结

通过源码学习,我们发现BeanFactoryFactoryBean是两个完全不同的概念,但是他们的代码又是紧密关联的。FactoryBeanBeanFactory里的一种特殊bean,因为他本身也是一个工厂,可以生产自己的Bean ,有个特殊的地方需要我们注意一下。如果传入的beanName是以&为前缀的话。会调用BeanFactoryUtilstransformedBeanName方法,去掉前缀,然后在Singleton注册中心获取相应的bean。如果找不到的话会有很长的一段代码进行处理。这里就不做深入探讨了,感兴趣的读者可以研究下。

BeanPostProcessor和BeanFactoryPostProcessor

两个类都以PostProcessor结尾,中文名为后置处理器,意思就在Bean创建或者BeanFactory创建之后进行的操作。spring核心代码里面PostProcessor随处可见。

首先我们来看下BeanFactoryPostProcessor,来张UML图。这个图很简单,BeanFactoryPostProcessor定义了一个方法。参数就是刚刚创建的bean工厂。而BeanDefinitionRegistryPostProcessor定义的方法,参数就是刚刚创建好的BeanDefinition注册中心。

image-20210504172750351

我们以一个最具有代表性的类ConfigurationClassPostProcessor 看下它是何时调用的,它做了什么。我们将断点设置在它的postProcessBeanFactorypostProcessBeanDefinitionRegistry方法里。发现发生在spring启动的refreshContext阶段,此时Bean工厂已经创建了。先调用所有的BeanDefinitionRegistryPostProcessor再调用所有BeanFactoryPostProcessor。详见PostProcessorRegistrationDelegateinvokeBeanFactoryPostProcessorsConfigurationClassPostProcessor两次都会被调到。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);
    //核心逻辑在processConfigBeanDefinition()方法中,用来处理BeanDefinition的注册
        processConfigBeanDefinitions(registry);
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        int factoryId = System.identityHashCode(beanFactory);
        if (this.factoriesPostProcessed.contains(factoryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + beanFactory);
        }
        this.factoriesPostProcessed.add(factoryId);
        if (!this.registriesPostProcessed.contains(factoryId)) {
            // BeanDefinitionRegistryPostProcessor hook apparently not supported...
            // Simply call processConfigurationClasses lazily at this point then.
            processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
        }

    // 对加了@Configuration注解的配置类进行Cglib增强代理
        enhanceConfigurationClasses(beanFactory);
    // 添加一个BeanPostProcessor后置处理器
        beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

ConfigurationClassPostProcessor无疑是spring中最重要也是最复杂的后置处理器了,此处就不详细展开了。再来看下BeanPostProcessor的UML图。

image-20210505112459583

这两个方法调用的地方都是在AbstractAutowireCapableBeanFactoryinitializeBean中。

image-20210505112427790

看个简单的例子吧,上面学习ConfigurationClassPostProcessor时,我们发现它添加了一个bean后置处理器ImportAwareBeanPostProcessor,下面是它的postProcessBeforeInitialization方法

//如果bean实现了ImportAware接口,就调用一下它的setImportMetadata方法,通过这种方式,可以在程序中拿到注解的元数据。
public Object postProcessBeforeInitialization(Object bean, String beanName) {
            if (bean instanceof ImportAware) {
                ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
                AnnotationMetadata importingClass = ir.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
                if (importingClass != null) {
                    ((ImportAware) bean).setImportMetadata(importingClass);
                }
            }
            return bean;
}

上面是spring内置的一个处理器。实际工作中,我们也会经常自定义后置处理器。一个常见的场景就是对某些类进行动态增强。

未完待续,更多内容请关注【熟练掌握spring框架】第二篇

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

推荐阅读更多精彩内容