Spring启动说明,包括解析、加载,Aop处理,事务处理
前言
Spring
最关键的部分,也就是解析并加载Bean
的处理了,本篇文章以个人的角度进行说明Spring
的Bean
都是如何处理的。期间有些部分会进行跳过,可能自己还没理解到,也或者可能觉得不太重要吧。
本篇文章以解析Xml
的方式加载Spring
的Bean
为前提,基于ClassPathXmlApplicationContext
类开始进行介绍解析。现在最流行的通过注解的方式,暂时本篇文章不介绍,后续有机会再进行介绍。
1. 加载xml
配置文件
1
|
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
|
通过ClassPathXmlApplicationContext
加载xml
时,一般主要以这个构造函数为入口。主要关注setConfigLocations
和refresh
这2个方法。
setConfigLocations
方法主要是根据传入的地址,进行解析,例如如果传入的地址中存在${}
这种占位符的,spring
会根据PropertySourcesPlaceholderConfigurer
所配置的.properties
文件进行替换,解析出实际的文件路径。至于PropertySourcesPlaceholderConfigurer
的解析,不属于关键部分,这里不介绍。
refresh
方法就是执行具体的Spring
的Bean
的解析与加载,后面要着重介绍。
2. AbstractApplicationContext
中的refresh
方法
1
|
|
3. 解析xml
文件
obtainFreshBeanFactory()
->refreshBeanFactory()
->loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
->loadBeanDefinitions(XmlBeanDefinitionReader reader)
->loadBeanDefinitions(Resource... resources)
->loadBeanDefinitions(Resource resource)
->loadBeanDefinitions(EncodedResource encodedResource)
->doLoadBeanDefinitions(InputSource inputSource, Resource resource)
->registerBeanDefinitions(Document doc, Resource resource)
->registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
->doRegisterBeanDefinitions(Element root)
->parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
通过以上方法调用,最终会进入到DefaultBeanDefinitionDocumentReader#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
方法中,该方法才是真正开始解析xml
文件的方法。
1
|
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
|
该方法会根据xml
中标签对应的namespace
是否是spring
的namespace
来做处理,如果是spring
自己的namespace
,会调用parseDefaultElement
,否则调用parseCustomElement
。
这里也不通过代码的方式展现逻辑,毕竟这篇文章是为了面试准备的,面试时可不可能打开代码进行走查。
在parseDefaultElement
中会根据标签名做对应的解析,主要是四类标签,分别是import
、bean
、beans
、alias
。其中针对于bean
的解析才是最关键的。
具体解析的逻辑,其实就是读取bean
标签下的子标签和属性,其最终都会记录在BeanDefinitionHolder
中,而BeanDefinitionHolder
其实就3
个字段,beanName
是bean
的名字,alias
是bean
的别名,beanDefinition
是bean
解析出来参数主要存放的地方。其实BeanDefinition
是一个接口,定义了一些方法,而具体参数的内容是放在AbstractBeanDefinition
中的。
这里就假设Bean
加载的工作处理完成了。其实的代码逻辑其实挺复杂的。
3.1 非spring
的namespace
这里介绍下如何使用非spring
的namespace
。Spring
最强大的一点就在于其扩展性非常的好,namespace
就是其中之一。如果想实现自己的namespace
,则需要写一个实现NameSpaceHandler
的类,个性化对xml
中特殊Bean
的解析;然后还要定义DTD
或者XSD
文件用于spring
能够识别个性化的标签,后续介绍Aop
的时候再详细介绍。
4. Bean
实例化
finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)
->preInstantiateSingletons()
在DefaultListableBeanFactory#preInstantiateSingletons()
已经开始准备做实例化的工作了
4.1 构造RootBeanDefinition
首先是构建RootBeanDefinition
,由于Spring
中的Bean
可能会有父子的依赖关系,所以会把上面构造出来的BeanDefinition
进行父子结构构造。如果一个BeanDefinition
没有父Bean
,则自己就是RootBeanDefinition
;如果一个BeanDefinition
有父Bean
,则会进行递归构造RootBeanDefinition
,毕竟父Bean
也有可能还有父的。
4.2 FactoryBean
的使用
FactoryBean
是用于定义复杂Bean
参数的接口,该接口中定义了3
个接口方法。
方法名 | 作用 |
---|---|
T getObject() |
Bean 对象构建的核心逻辑 |
Class<?> getObjectType() |
该Bean 对象的Class 类型 |
boolean isSingleton() |
该Bean 是否是单例的 |
在preInstantiateSingletons
中,会针对于FactoryBean
类型的Bean
进行特殊化处理,这里不进行介绍。
4.3 Bean
真正实例化
preInstantiateSingletons()
->getBean(String name)
->doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly)
在这个方法中进行Bean
真正实例化的操作。Spring
在启动的时候,会自动把所有单例且非延迟加载的Bean
放置在Spring
容器中,这个加载过程其实就是调用一次getBean
方法,感觉有点low
,可实际就是这样的,只不过单例的Bean
会做缓存,这样getBean
的时候如果首次,就会进行实例化,而非首次的话,就从缓存中去取的。
4.4 循环依赖
循环依赖的解决方案:三层缓存singletonObjects
、earlySingletonObjects
、singletonFactories
。其中singletonObjects
是存放已经完全解析完毕的Bean
,而earlySingletonObjects
则是存放提前暴露的未加载完毕的Bean
(主要是指当前Bean
的Class
已经创建,可还未填充属性),而singletonFactories
则是存放未加载完毕Bean
的ObjectFactory
。当Spring
加载Bean
时发现其下有需要依赖的其他Bean
时,先判断在singletonObjects
是否存在,如果不存在再earlySingletonObjects
去找,如果还找不到就到singletonFactories
,一般此时能找到了的。找到后,把Bean
从singletonFactories
移除,移入earlySingletonObjects
中,尽量把Bean
慢慢移置在singletonObjects
中。
5. Aop
实现
上面说了Spring
可以通过自定义namespace
来进行扩展标签定义,这里的Aop
就是这么实现的。在spring-aop
工程下,有一个spring-aop.xsd
文件用于定义xml
文件中个性化参数要怎么写,还有一个AopNamespaceHandler
类,间接实现了NamespaceHandler
接口。
5.1 NamespaceHandler
接口介绍
NameSpaceHandler
接口下有3
个抽象方法,init
一般用于定义不同的标签对应的解析器,parse
用于解析xml
中的标签,decorate
则可以根据不同的xml
节点,例如Node
或者Attr
,进行特殊的个性化处理。
5.2 AopNamespaceHandler
介绍
AopNamespaceHandler
的init
方法下,注册了3
个解析器
处理的标签 | 对应解析器 | 说明 |
---|---|---|
config |
ConfigBeanDefinitionParser |
用于解析xml 文件中Aop 相关配置 |
aspectj-autoproxy |
AspectJAutoProxyBeanDefinitionParser |
用于配置Aop 开关,并可以通过注解方式实现Aop |
scoped-proxy |
ScopedProxyBeanDefinitionDecorator |
解析代理是基于cglib 的还是基于jdk 实现的 |
这里介绍一下ConfigBeanDefinitionParser
解析器,该解析器是用于解析xml
中相关参数的,下面还会介绍Spring
的事务,介绍事务时再介绍通过注解方式实现逻辑。
5.2 ConfigBeanDefinitionParser
ConfigBeanDefinitionParser
中的parse
方法中,第一步就是注册一个AspectJAwareAdvisorAutoProxyCreator
这个Bean
,这个Bean
间接实现了BeanPostProcessor
接口,也就说明其中肯定也实现了postProcessBeforeInitialization
和postProcessAfterInitialization
方法,而aop
主要是实现postProcessAfterInitialization
方法
在AbstractAutoProxyCreator
(AspectJAwareAdvisorAutoProxyCreator
的父类)类中实现了postProcessAfterInitialization
方法,在该方法中,首先校验当前Bean
是否满足Aop
的Adviser
,如果满足则创建代理。这样在后续Bean
的方法被调用的时候,其实是走的代理
自定义的Aop
,是通过AspectJAwareAdvisorAutoProxyCreator
来进行处理的,算是通过BeanPostProcessor
间接处理
6. Spring
事务实现
Spring
事务其实也是通过namespace
进行扩展的
在spring-tx
工程下,有spring-tx.xsd
描述文件,在工程中有TxNamespaceHandler
类来间接实现NamespaceHandler
接口
6.1 TxNamespaceHandler
介绍
TxNamespaceHandler
的init
方法注册了3
个解析器
处理的标签 | 对应解析器 | 说明 |
---|---|---|
advice |
TxAdviceBeanDefinitionParser |
解析xml 中关于事务相关的配置 |
annotation-driven |
AnnotationDrivenBeanDefinitionParser |
注解方式配置事务的开关,并通过注解方式实现事务 |
jta-transaction-manager |
JtaTransactionManagerBeanDefinitionParser |
暂时没有使用过 |
这里介绍AnnotationDrivenBeanDefinitionParser
解析器,该解析器是通过注解的方式处理Spring
事务,跟AopNamespaceHandler
分开介绍不同的解析器处理方式。
6.2 InfrastructureAdvisorAutoProxyCreator
代理创建器
在AnnotationDrivenBeanDefinitionParser
中首先注册了一个InfrastructureAdvisorAutoProxyCreator
,该代理创建器其实并不属于事务特有的,该创建器只是把一些基础设施类忽略创建代理而已,例如Advice
、Pointcut
、Advisor
、AopInfrastructureBean
这些接口的实现类,是不进行代理处理的,这个类跟事务其实没有特别大的关系。
6.3 BeanFactoryTransactionAttributeSourceAdvisor
增强器
Spring
事务依赖于Aop
的实现,在Aop
中会注册所有Advisor
的实现类为增强器,根据Advisor
指定的方式来创建代理,而BeanFactoryTransactionAttributeSourceAdvisor
就是做这个事情的。在AnnotationDrivenBeanDefinitionParser
解析器中,除了注册了InfrastructureAdvisorAutoProxyCreator
之外,还注册BeanFactoryTransactionAttributeSourceAdvisor
增强器,该增强器中包含了另外注册的2个类,分别是AnnotationTransactionAttributeSource
和TransactionInterceptor
。
拦截器 | 作用 |
---|---|
AnnotationTransactionAttributeSource |
用于解析@Transactional 注解的 |
TransactionInterceptor |
事务处理的具体流程 |
6.4 TransactionInterceptor
事务拦截器
Spring
会首先记录注解信息到TransactionAttribute
中,然后创建TransactionInfo
,而TransactionInfo
包含TransactionAttribute
,并且把当前事务的信息、例如事务传播方式、事务超时校验、事务隔离级别等等的信息进行校验,根据不同的传播方式,TransactionInfo
还有可能对挂起的事务进行处理。在真正的业务处理中,其实就是进行的try-catch-finally
操作,当业务逻辑正常流转时,会调用commit
操作,而抛异常时会进行rollback
操作。其实这里所谓commit
和rollback
操作,也还是有额外处理的。例如rollback
操作,在默认情况下,事务仅针对RuntimeException
才会进行事务回滚,如果抛出的Exception
,则还是会进行commit
。例如commit
操作时,会去校验事务的传播方式,如果是嵌套事务,就不会进行commit
操作,而是会回到上层事务,根据上层事务是否抛异常来决定是否回滚。所以事务的具体操作,还是要根据不同的事务传播方式来决定的。