本文主要讲解spring源码分析需要的一些前置知识,如果已经掌握可以进行跳过。
通过类图结构可以发现
ApplicaionContext
是一个接口,并且它是BeanFactory
的子接口。
通过下面这张功能对比图可以发现,BeanFactory提供了一个抽象的配置和对象的管理机制,ApplicationContext是BeanFactory的子接口,ApplicationContext包含BeanFactory的所有功能,并且还扩展了好多特性。主要扩展了如下功能:
- AOP的支持(AnnotationAwareAspectJAutoProxyCreator 作用于 Bean 的初始化之后)
- 配置元信息(BeanDefinition、Environment、注解等)
- 资源管理(Resource抽象)
- 事件驱动机制(ApplicationEvent、ApplicationListener)
- 消息与国际化(LocaleResolver)
- Environment抽象
在类上标注
@Configuration
注解表示这个类是一个配置类,在方法上标注@Bean
注解,方法返回一个Person类型的对象,这个是向IOC容器注册一个类型为Person,Id为person的bean。方法的返回值代表注册的类型,方法名代表Bean的id,也可以直接在注解中指定bean名称。
组件注册注解,在类上标注
@Component
注解,代表该类会被注册到IOC容器中作为一个bean
,如果没有指定bean的名字,它的默认规则是"类名的首字母小写",@Component
还有很多衍生注解,spring在迎合MVC三层架构,额外提供了三个注解:@Controller
、@Service
、@Repository
,分别代表表现层、业务层、持久层。这三个注解的作用与@Component
完全一致,底层也是@Component
。
如果像上面只声明了组件,那么在启动IOC容器的时候,是感知不到有
@Component
存在的,会报错NoSuchBeanDefinitionException
,所以spring给我们提供了@ComponentScan
,来提供组件扫描,可以在配置类上标注一个@ComponentScan
,并制定要扫描的路径,它就可以扫描注定路径包及其子包下的所有@Component
组件。如果不指定扫描路径,会默认扫描本类及所在包及子包下的所有@Component
组件
set方式注入Bean属性
构造器方式注入bean属性
注解注入
@Autowired
:可以在Bean的属性/setter方法上标注@Autowired
注解,IOC容器会按照属性对应的类型从容器着对应类型的Bean赋值到对应属性上实现自动注入。
- 如果注入的Bean不存在,可以在
@Autowired
注解上加一个属性:required=false。- 如果有多个相同类型的Bean,但是beanName不一样,可以使用
@Qualifier
注定注入Bean的名称。其实不使用Qualifier注解,可以直接修改属性名为你要注入的beanName也行。- 多个相同类型Bean的全部注入,可以通过使用List>xxx把一组相同类型的注入进来。
@Autowired
注入逻辑:
先拿属性对应的类型,去 IOC 容器中找 Bean ,如果找到了一个,直接返回;如果找到多个类型一样的 Bean , 把属性名拿过去,跟这些 Bean 的 id 逐个对比,如果有一个相同的,直接返回;如果没有任何相同的 id 与要注入的属性名相同,则会抛出 NoUniqueBeanDefinitionException 异常。
注解注入:
JSR250-@Resource
:@resource
也是用来属性注入的注解,它与@Autowired
的不同之处在于:@Autowired
是按照类型注入,@Resource
是直接按照属性名/Bean的名称注入的。其实这个注解就相当于标注@Autowired
和@Qualifier
。
依赖注入的注入方式
不同注解注入对比
常见依赖注入问题:
- 依赖注入的目的和优点?
首先,依赖注入作为 IOC 的实现方式之一,目的就是解耦,我们不再需要直接去 new 那些依赖的类对象(直接依赖会导致对象的创建机制、初始化过程难以统一控制);而且,如果组件存在多级依赖,依赖注入可以将这些依赖的关系简化,开发者只需要定义好谁依赖谁即可。除此之外,依赖注入的另一个特点是依赖对象的可配置:通过 xml 或者注解声明,可以指定和调整组件注入的对象,借助 Java 的多态特性,可以不需要大批量的修改就完成依赖注入的对象替换(面向接口编程与依赖注入配合近乎完美)。- 谁把什么注入给谁了?
由于组件与组件之间的依赖只剩下成员属性 + 依赖注入的注解,而注入的注解又被 SpringFramework 支持,所以这个问题也好回答:IOC 容器把需要依赖的对象注入给待注入的组件。- 使用setter注入还是构造器注入?
SpringFramework 4.0.2 及之前是推荐 setter 注入,理由是一个 Bean 有多个依赖时,构造器的参数列表会很长;而且如果 Bean 中依赖的属性不都是必需的话,注入会变得更麻烦;
4.0.3 及以后官方推荐构造器注入,理由是构造器注入的依赖是不可变的、完全初始化好的,且可以保证不为 null ;
当然 4.0.3 及以后的官方文档中也说了,如果真的出现构造器参数列表过长的情况,可能是这个 Bean 承担的责任太多,应该考虑组件的责任拆解。
回调注入的根接口是一个叫Aware的接口,它是一个空接口,底下有一系列的子接口
常用的回调接口:
使用示例:这里演示最常用的
ApplicationContextAware
接口回调的使用示例
这里我们实现了ApplicationContextAware
接口,在这个bean
初始化完成后,Spring
就回把ApplicationContext
传给他,我们就可以干一些事情,比如下面的打印beanName
。
之前我们创建的Bean都是普通Bean,这里重点说一下
FactoryBean
的使用和场景。
场景:Bean的创建需要指定一些策略,或者依赖特殊的场景来分别创建,或者一个对象的创建过程过于复杂,这个时候就可以借助
FactoryBean
来使用工厂方法创建对象。就比如Mybatis
就是借助FactoryBean
来创建SqlSessionFactory
。是什么:从下图看到,
FactoryBean
本身是一个接口,它本身是一个创建对象的工厂,如果Bean实现了FactoryBean
接口,则它本身不会在实际的业务逻辑中起作用,而是由创建的对象起作用。
FactoryBean
使用:
例:下面这个Demo是小孩要买玩具,由一个玩具生产工厂来给这个小孩造玩具。这里就可以自动注入小孩,然后根据小孩喜欢什么玩具来生产什么玩具出来。
FactoryBean
常见问题:
- 如何取出
FactoryBean
本体:如果直接根据bean的id去容器中获取,那么取出来的是成产出来的bean,取FactoryBean
的方式需要在Bean的id前面加"&"
符号
BeanFactory
与FactoryBean
的区别:
BeanFactory
:SpringFramework 中实现 IOC 的最底层容器(此处的回答可以从两种角度出发:从类的继承结构上看,它是最顶级的接口,也就是最顶层的容器实现;从类的组合结构上看,它则是最深层次的容器,ApplicationContext 在最底层组合了 BeanFactory )
FactoryBean
:创建对象的工厂 Bean ,可以使用它来直接创建一些初始化流程比较复杂的对象
回想
Servlet
规范,Servlet
提供了两个方法init
和destory
,用来初始化和销毁Servlet,是留给用户的回调函数,它是由父类/接口定义好,由第三方框架或容器来调用。我们来看下spring提供的初始化和销毁的回调方法。
InitializingBean
&DisposableBean
(这里不讲xml对应的init-method和destory-method)
这里实现这两个接口,然后在初始化和销毁的回调方法中做我们自己的逻辑处理。
JSR250规范提供的
@PostConstruct
和@PreDestory
注解,可以直接标注在Bean
的方法上,然后在初始化后和销毁前会进行回调。
上面两种声明周期同时存在顺序:顺序为
@PostConstruct → InitializingBean → init-method
,这里没有提到init-method
,因为现在很少使用xml
方式来进行bean
注册了。
观察者模式,也称为发布订阅模式,观察者模式关注的点是某一个对象被修改/做出某些反应/发布一个信息等会自动通知他的对象(订阅者)
spring中的事件驱动核心分为四个主体:事件源、事件、广播器、监听器
- 事件源:发布事件的对象
- 事件:事件源发布的信息/做出的动作
- 广播器:事件真正广播给监听器的对象【及ApplicationContext】
ApplicationContext接口实现ApplicationEventPublisher
接口,具备事件广播器的发布事件的能力
ApplicationEventMulticaster
组合了所有监听器,具备事件广播器的广播事件的能力- 监听器:监听的对象
spring内部提供了很多事件,如
ContextRefreshedEvent
等,我们不使用这些,来自定义事件开发,这里我们自定义一个事件继承ApplicationEvent
这里我们编写了一个监听器,实现了
ApplicationListener
,并定义我们要监听RegisterSuccessEvent
事件。
这里也可以使用
@EvnetListener
定义监听器,在方法的参数上表示你要监听什么事件,这里可以使用@Order
注解调整监听器的触发顺序,标注这个注解默认是Integer.MAX_VALUE,代表最靠后,越小越靠前。
使用
ApplicationEventPublisher
发布事件,这里我们实现ApplicationEventPublisherAware
接口帮我们自动注入ApplicationEventPublisher,然后我们就可以在业务逻辑中通过ApplicationEventPublisher来广播事件。
- 什么是模块?
通常模块可以理解为一个一个可以分解、组合、更换的独立单元,模块与模块之间可能存在一定的依赖,模块的内部通常是高内聚的,一个功能可以看成一个模块,一个组件也可以看成一个模块。- 什么是模块装配?
模块是功能单元,那么模块装配就是把一个模块需要的核心功能组件都装配好- spring中的模块装配?
在springCloud中我们集成一个组件,都是通过@Enablexxx注解,我们来剖析一下这种注解到底做了什么使用(其实就是自定义注解+@Import导入组件)
场景模拟:现在有一个酒馆,酒馆里有吧台,调酒师、服务员和老板,现在我们要装配这酒馆需要的全部组件,通过一个注解,把这些组件全部填充到酒馆中。
这里先解析一下上面的@Import注解,这个是最核心的注解,通过注释我们可以知道他可以导入配置类、ImportSelector的实现类、ImportBeanDefinitionRegistrar的实现类或者普通类。
这里导入配置类和普通类就不说了,重点看一下ImportBeanDefinitionRegistrar和ImportSelector的装配方式。
- ImportSelector:这里实现了他的selectImports方法,返回的是String数组,他需要你返回一组全限定类名,然后把这组类注册到Spring容器中。
- ImportBeanDefinitionRegistrar:这里实现了registerBeanDefinitions,是手动构造BeanDefinition然后注册到容器中(BeanDefinition后续会说,是bean的元数据信息)
@Profile实现环境装配,profile提供了一种基于环境的配置:根据当前项目的运行时环境不同,可以动态的注册当前运行环境匹配的组件。
我们给BartenderConfiguration上添加profile,也就是注册调酒师的配置上只有当前环境处于城市才生效,默认的profile是default。
我们可以在spring启动的时候添加vmoptions设置启动的profile为city
由于Profile控制的面太大,是整个项目的运行环境,无法根据bean维度的因素决定是否装配,这个时候就可以使用@Conditional注解了。
被标注@Conditional的bean要注册到IOC容器时,必须全部满足@Conditional上指定的所有条件才可以,这里在注解中需要传入一个Condition接口的实现类数组,还需要编写条件匹配类做匹配依据。
这里编写了一个条件匹配规则类,用来判断IOC容器中是否包含Boss对象。
然后就可以把这个规则类放入Conditional,来判断装配的时候如果存在boss类才进行装配。
spring也帮我们封装好了一些通用的Conditional扩展注解,使我们不用自己编写规则类。
如下所示:
- @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
- @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean
- @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。
- @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
- @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
- @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
- @ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
- @ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
- @ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
- @ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化
- @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
- @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
- @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
- @ConditionalOnExpression:基于SpEL表达式的条件判断。
- @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
- @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
- @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
- @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
BeanDefinition是一种配置元信息,描述了Bean的定义信息,整体包含以下几个部分:
- Bean 的类信息 - 全限定类名 ( beanClassName )
- Bean 的属性 - 作用域 ( scope ) 、是否默认 Bean ( primary ) 、描述信息 ( description ) 等
- Bean 的行为特征 - 是否延迟加载 ( lazy ) 、是否自动注入 ( autowireCandidate ) 、初始化 / 销毁方法 ( initMethod / destroyMethod ) 等
- Bean 与其他 Bean 的关系 - 父 Bean 名 ( parentName ) 、依赖的 Bean ( dependsOn ) 等
- Bean 的配置属性 - 构造器参数 ( constructorArgumentValues ) 、属性变量值 ( propertyValues ) 等
如果只是仅仅需要实例化一个bean那么只需要Class就行了,但是Class无法完成bean的抽象,如果上面的一些特性,bean的作用域,是否是懒加载的,注入模型等等,这些是class无法表示的,所以BeanDefinitiion就是存储这些和bean相关的元信息的。
作为Bean Definition的抽象实现,他内部定义好了一些属性,具体如下
// bean的全限定类名
private volatile Object beanClass;
// 默认的作用域为单实例
private String scope = SCOPE_DEFAULT;
// 默认bean都不是抽象的
private boolean abstractFlag = false;
// 是否延迟初始化
private Boolean lazyInit;
// 自动注入模式(默认不自动注入)
private int autowireMode = AUTOWIRE_NO;
// 是否参与IOC容器的自动注入(设置为false则它不会注入到其他bean,但其他bean可以注入到它本身)
// 可以这样理解:设置为false后,你们不要来找我,但我可以去找你们
private boolean autowireCandidate = true;
// 同类型的首选bean
private boolean primary = false;
// bean的构造器参数和参数值列表
private ConstructorArgumentValues constructorArgumentValues;
// bean的属性和属性值集合
private MutablePropertyValues propertyValues;
// bean的初始化方法
private String initMethodName;
// bean的销毁方法
private String destroyMethodName;
// bean的资源来源
private Resource resource;
- 通过 xml 加载的 BeanDefinition ,它的读取工具是 XmlBeanDefinitionReader ,它会解析 xml 配置文件,最终来到 DefaultBeanDefinitionDocumentReader 的 doRegisterBeanDefinitions 方法,根据 xml 配置文件中的 bean 定义构造 BeanDefinition ,最底层创建 BeanDefinition 的位置在 org.springframework.beans.factory.support.BeanDefinitionReaderUtils#createBeanDefinition 。
- 通过模式注解 + 组件扫描的方式构造的 BeanDefinition ,它的扫描工具是 ClassPathBeanDefinitionScanner ,它会扫描指定包路径下包含特定模式注解的类,核心工作方法是 doScan 方法,它会调用到父类 ClassPathScanningCandidateComponentProvider 的 findCandidateComponents 方法,创建 ScannedGenericBeanDefinition 并返回。
- 通过配置类 + @Bean 注解的方式构造的 BeanDefinition 最复杂,它涉及到配置类的解析。配置类的解析要追踪到 ConfigurationClassPostProcessor 的 processConfigBeanDefinitions 方法,它会处理配置类,并交给 ConfigurationClassParser 来解析配置类,取出所有标注了 @Bean 的方法。随后,这些方法又被 ConfigurationClassBeanDefinitionReader 解析,最终在底层创建 ConfigurationClassBeanDefinition 并返回。
像我们平时先编写Class再new出对象一样,spring需要对其中的bean进行定义抽取,只有抽取成统一类型/格式的模型,才能在后续的bean对象管理时进行统一管理,也或者对特殊的bean进行特殊化的处理,所以需要BeanDifinition这个抽象化的模型。
- BeanDefinitionRegistry中存放了所有BeanDefinition
- BeanDefinitionRegistry中维护了BeanDefinition,包含了Definition的增、删、改等API
这里注册的流程是实现ImportBeanDefinitionRegistrar,这样可以在方法上拿到BeanDefinitionRegistry,然后就调用registerBeanDefinition注册BeanDefinition到容器中
这里移除的流程是需要实现BeanFactoryPostProcessor,并重写postProcessBeanFactory方法,这里Bean的后置处理器后续会说,下面就是移除调用容器中所有性别为male的Person。
- BeanPostProcessor是一个容器扩展点,是一个回调机制的扩展点,核心工作就是在bean的初始化前后做一些额外的处理(预初始化bean的属性值、注入特定的依赖、生成代理对象等)
- BeanPostProcessor的执行可以指定先后顺序(可以配置多个BeanPostProcessor,摒并且通过实现Ordered接口或者@Order注解配置执行顺序)
- BeanPostProcessor在IOC容器间互不影响(不同IOC容器的BeanPostProcessor不会互相起作用)
BeanPostProcessor是一个接口,定义了两个方法,postProcessBeforeInitialization 方法会在任何 bean 的初始化回调(例如 InitializingBean 的 afterPropertiesSet 或自定义 init-method )之前执行,而 postProcessAfterInitialization 方法会在任何 bean 的初始化回调(例如 `InitializingBean 的 afterPropertiesSet 或自定义 init-method )之后。
Bean的初始化阶段的全流程:
BeanPostProcessor#postProcessBeforeInitialization → @PostConstruct → InitializingBean → init-method → BeanPostProcessor#postProcessAfterInitialization
InstantiationAwareBeanPostProcessor
postProcessBeforeInstantiation
方法:在Bean的实例化之前执行postProcessAfterInstantiation
方法:在Bean的实例化之后处理postProcessProperties
:在属性赋值之前触发,PropertyValues是一组field-value的键值对,可以参与属性的赋值postProcessPropertyValues
:已经废弃,被postProcessProperties方法代替
SmartInstantiationAwareBeanPostProcessor
继承了上面的InstantiationAwareBeanPostProcessor
,又额外扩展了3个方法
predictBeanType
:预测bean的类型(不能预测时返回null)- d
etermineCandidateConstructors
:根据 bean 对应 Class 中的构造器定义,决定使用哪个构造器进行对象实例化,这个方法很重要,如果 bean 没有声明任何构造器,则此处会拿到默认的无参构造器;如果声明了多个构造器,则该处会根据 IOC 容器中的 bean 和指定的策略,选择最适合的构造器getEarlyBeanReference
:提早暴露出 bean 的对象引用(用以解决spring的循环依赖)
DestructionAwareBeanPostProcessor
(bean的销毁前拦截处理),bean销毁的回调方法
MergeDefinitionPostProcessor
postProcessMergedBeanDefinition
方法:发生在bean的实例化之后,自动注入之前,这个是为了在属性赋值和自动注入之前,把要注入的属性都收集好,才能顺序进行注入的逻辑。
在spring容器中有一个非常重要的MergeDefinitionPostProcessor
的实现就是AutowiredAnnotationBeanPostProcessor
,他负责给bean实现注解的自动注入,注入的依据就是postProcessMergedBeanDefinition
后整理的标记。
他操作的是bean的配置元信息,也就是
BeanDefinition
,可以在bean实例的初始化之前修改beanDefinition
信息。
这里可以在
postProcessBeanFactory
方法中,通过beanFactory
拿到所有的beanDefinition
,然后获取到指定bean的Definition
,然后修改定义信息,下图是增加属性.
BeanPostProcessor
与BeanFactoryPostProcessor
对比
这个可以允许在
BeanFactoryPostProcessor
之前注册其他的BeanDefinition
,所以执行时机比BeanFactoryPostProcessor
更早,一般用于修改,删除,增加BeanDefinition
。