Spring启动说明,包括解析、加载,Aop处理,事务处理

前言

Spring最关键的部分,也就是解析并加载Bean的处理了,本篇文章以个人的角度进行说明SpringBean都是如何处理的。期间有些部分会进行跳过,可能自己还没理解到,也或者可能觉得不太重要吧。
本篇文章以解析Xml的方式加载SpringBean为前提,基于ClassPathXmlApplicationContext类开始进行介绍解析。现在最流行的通过注解的方式,暂时本篇文章不介绍,后续有机会再进行介绍。

1. 加载xml配置文件

1
2
3
4
5
6
7
8
9
10
11
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}

通过ClassPathXmlApplicationContext加载xml时,一般主要以这个构造函数为入口。主要关注setConfigLocationsrefresh这2个方法。

setConfigLocations方法主要是根据传入的地址,进行解析,例如如果传入的地址中存在${}这种占位符的,spring会根据PropertySourcesPlaceholderConfigurer所配置的.properties文件进行替换,解析出实际的文件路径。至于PropertySourcesPlaceholderConfigurer的解析,不属于关键部分,这里不介绍。

refresh方法就是执行具体的SpringBean的解析与加载,后面要着重介绍。

2. AbstractApplicationContext中的refresh方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 启动准备,记录时间,校验必传参数等等
prepareRefresh();

// 后续着重介绍地方,创建BeanFactory,加载Xml配置文件
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 对BeanFactory进行参数填充
// 设置需要参数注入的类
// 注入特殊对象
prepareBeanFactory(beanFactory);

try {
// 注册BeanFactoryPostProcessor,是针对于BeanFactory的拦截器
postProcessBeanFactory(beanFactory);

// 执行BeanFactoryPostProcessor对应的拦截器
invokeBeanFactoryPostProcessors(beanFactory);

// 注册BeanPostProcessor,是针对于Bean的拦截器
registerBeanPostProcessors(beanFactory);

// 初始化MessageSource,用于国际化
initMessageSource();

// 初始化Spring事件广播器
initApplicationEventMulticaster();

// 初始化其他需要处理的事情,由子类实现
onRefresh();

// 注册Spring事件监听器
registerListeners();

// 后续着重介绍,用于Bean实例化的地方
finishBeanFactoryInitialization(beanFactory);

// Spring容器初始化完成,发送事件等等
finishRefresh();
}

catch (BeansException ex) {
// 初始化失败需要清理Spring容器中的Bean等等各种操作
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}

 

该方法会根据xml中标签对应的namespace是否是springnamespace来做处理,如果是spring自己的namespace,会调用parseDefaultElement,否则调用parseCustomElement
这里也不通过代码的方式展现逻辑,毕竟这篇文章是为了面试准备的,面试时可不可能打开代码进行走查。
parseDefaultElement中会根据标签名做对应的解析,主要是四类标签,分别是importbeanbeansalias。其中针对于bean的解析才是最关键的。
具体解析的逻辑,其实就是读取bean标签下的子标签和属性,其最终都会记录在BeanDefinitionHolder中,而BeanDefinitionHolder其实就3个字段,beanNamebean的名字,aliasbean的别名,beanDefinitionbean解析出来参数主要存放的地方。其实BeanDefinition是一个接口,定义了一些方法,而具体参数的内容是放在AbstractBeanDefinition中的。
这里就假设Bean加载的工作处理完成了。其实的代码逻辑其实挺复杂的。

3.1 非springnamespace

这里介绍下如何使用非springnamespaceSpring最强大的一点就在于其扩展性非常的好,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 循环依赖

循环依赖的解决方案:三层缓存singletonObjectsearlySingletonObjectssingletonFactories。其中singletonObjects是存放已经完全解析完毕的Bean,而earlySingletonObjects则是存放提前暴露的未加载完毕的Bean(主要是指当前BeanClass已经创建,可还未填充属性),而singletonFactories则是存放未加载完毕BeanObjectFactory。当Spring加载Bean时发现其下有需要依赖的其他Bean时,先判断在singletonObjects是否存在,如果不存在再earlySingletonObjects去找,如果还找不到就到singletonFactories,一般此时能找到了的。找到后,把BeansingletonFactories移除,移入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介绍

AopNamespaceHandlerinit方法下,注册了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接口,也就说明其中肯定也实现了postProcessBeforeInitializationpostProcessAfterInitialization方法,而aop主要是实现postProcessAfterInitialization方法
AbstractAutoProxyCreatorAspectJAwareAdvisorAutoProxyCreator的父类)类中实现了postProcessAfterInitialization方法,在该方法中,首先校验当前Bean是否满足AopAdviser,如果满足则创建代理。这样在后续Bean的方法被调用的时候,其实是走的代理
自定义的Aop,是通过AspectJAwareAdvisorAutoProxyCreator来进行处理的,算是通过BeanPostProcessor间接处理

6. Spring事务实现

Spring事务其实也是通过namespace进行扩展的
spring-tx工程下,有spring-tx.xsd描述文件,在工程中有TxNamespaceHandler类来间接实现NamespaceHandler接口

6.1 TxNamespaceHandler介绍

TxNamespaceHandlerinit方法注册了3个解析器

处理的标签对应解析器说明
advice TxAdviceBeanDefinitionParser 解析xml中关于事务相关的配置
annotation-driven AnnotationDrivenBeanDefinitionParser 注解方式配置事务的开关,并通过注解方式实现事务
jta-transaction-manager JtaTransactionManagerBeanDefinitionParser 暂时没有使用过

这里介绍AnnotationDrivenBeanDefinitionParser解析器,该解析器是通过注解的方式处理Spring事务,跟AopNamespaceHandler分开介绍不同的解析器处理方式。

6.2 InfrastructureAdvisorAutoProxyCreator代理创建器

AnnotationDrivenBeanDefinitionParser中首先注册了一个InfrastructureAdvisorAutoProxyCreator,该代理创建器其实并不属于事务特有的,该创建器只是把一些基础设施类忽略创建代理而已,例如AdvicePointcutAdvisorAopInfrastructureBean这些接口的实现类,是不进行代理处理的,这个类跟事务其实没有特别大的关系。

6.3 BeanFactoryTransactionAttributeSourceAdvisor增强器

Spring事务依赖于Aop的实现,在Aop中会注册所有Advisor的实现类为增强器,根据Advisor指定的方式来创建代理,而BeanFactoryTransactionAttributeSourceAdvisor就是做这个事情的。在AnnotationDrivenBeanDefinitionParser解析器中,除了注册了InfrastructureAdvisorAutoProxyCreator之外,还注册BeanFactoryTransactionAttributeSourceAdvisor增强器,该增强器中包含了另外注册的2个类,分别是AnnotationTransactionAttributeSourceTransactionInterceptor

拦截器作用
AnnotationTransactionAttributeSource 用于解析@Transactional注解的
TransactionInterceptor 事务处理的具体流程

6.4 TransactionInterceptor事务拦截器

Spring会首先记录注解信息到TransactionAttribute中,然后创建TransactionInfo,而TransactionInfo包含TransactionAttribute,并且把当前事务的信息、例如事务传播方式、事务超时校验、事务隔离级别等等的信息进行校验,根据不同的传播方式,TransactionInfo还有可能对挂起的事务进行处理。在真正的业务处理中,其实就是进行的try-catch-finally操作,当业务逻辑正常流转时,会调用commit操作,而抛异常时会进行rollback操作。其实这里所谓commitrollback操作,也还是有额外处理的。例如rollback操作,在默认情况下,事务仅针对RuntimeException才会进行事务回滚,如果抛出的Exception,则还是会进行commit。例如commit操作时,会去校验事务的传播方式,如果是嵌套事务,就不会进行commit操作,而是会回到上层事务,根据上层事务是否抛异常来决定是否回滚。所以事务的具体操作,还是要根据不同的事务传播方式来决定的。

当前开发中涉及到的设计模式(新)
posted @ 2019-07-31 16:03  qxwang  阅读(67)  评论(0)    收藏  举报