Spring5源码分析(2)-- Spring容器
Spring容器的基本实现
1. 容器的基本用法 首先看目录结构
文章图片
先来个示例:
public class UserBean { private String username = "lilei->>>>>>>>>>>>>>>>>>>>>>>>>";
public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
}
resource下的spring-config.xml文件
Test类:
/**
* @ClassName Test
* @Author qzx
* @Description TODO
* @Date 2019/12/19
* @Version 1.0
*/
public class Test { public static void main(String[] args) {
//由于XmlBeanFactory已经过时,这里用ClassPathXmlApplicationContext代替
//BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
BeanFactory bf = new ClassPathXmlApplicationContext("spring-config.xml");
UserBean userBean = (UserBean) bf.getBean("userBean");
String username = userBean.getUsername();
System.out.println(username);
}
}
我们执行Test类下面的main方法,执行结果如下:
控制台打印出了我们获取的username,那么这段代码具体是如何运行的呢?我们一步步来分析。
2. ClassPathXmlApplicationContext源码解析 我们debug程序并进入ClassPathXmlApplicationContext方法,可以看到如下代码:
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {super(parent);
setConfigLocations(configLocations);
if (refresh) {
//核心代码
refresh();
}
}
核心代码是refresh()方法,直接点击进入会进入到AbstractApplicationContext#refresh()方法中
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//创建DefaultListableBeanFactory并加载配置文件 转化为BeanDefinition
//核心方法
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// spring扩展的实现(容器级别) BeanFactoryPostProcessor 在实例化任何用户定义的bean之前 会首先调用BFPP的接口方法
// 常见的BFPP:PropertyPlaceholderConfigurer
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// spring可扩展的另一个实现:BeanPostProcessor 在调用beanClass实例化前后或者调用initMethod方法的前后会调用接口方法
// 较常见的硬编码的BPP:ApplicationContextAwareProcessor,ApplicationListenerDetecto
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//核心方法
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}catch (BeansException ex) {
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();
}
}
}
我们继续往下走,prepareRefresh(); 方法是刷新上下文并设置一些变量做准备工作,这里不深究,下面的一行代码
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
是加载配置文件的核心方法,我们深入研究,进入obtainFreshBeanFactory()方法内,我们最终可以到达AbstractRefreshableApplicationContext#refreshBeanFactory
方法:/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
//判断是否已经加载过beanFactory,如果已经加载过,则重新加载
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//实例化DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//开始解析配置文件
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
createBeanFactory()方法的作用主要就是实例化DefaultListableBeanFactory,在实例化过程中,有一段代码需要了解
/**
* Create a new AbstractAutowireCapableBeanFactory.
*/
public AbstractAutowireCapableBeanFactory() {
super();
//ignoreDependencyInterface 忽略了给定接口的自动装配功能
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
这段代码的作用是忽略指定接口的自动装配功能,目前这段代码为什么需要忽略我还没弄清楚,查阅网上相关资料,初步理解为spring注册bean时正常实例化的bean和定制化的bean要有所区分,而BeanNameAware等接口的作用就是获取bean的name,beanFactory的信息等来实现定制化的bean,所以spring正常实例化的bean就要忽略这些接口。如果理解不正确请指正。
又一资料《Spring源码深度解析》说:如果A中有属性B,那么Spirng在获取A的Bean的时候如果B的属性还没有被初始化,那么Spring会自动初始化B,但是如果B实现了BeanNameAware接口的话则不会被初始化。自动装配时忽略给定依赖的接口,典型的应用是通过其他方式解析Application上下文注册,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入
我们继续回到
refreshBeanFactory()
,customizeBeanFactory(beanFactory);
方法是设置beanFactory是否允许覆盖和循环引用的属性,这里还没弄清楚具体作用,先搁置。直接进入到
loadBeanDefinitions(beanFactory);
解析配置文件的方法:@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
这段代码中前面都是一些环境资源的配置,可以带过,重要的是
loadBeanDefinitions(beanDefinitionReader);
这个方法,执行代码最终会进入到AbstractBeanDefinitionReader#loadBeanDefinitions()
方法中public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//将String类型的地址封装到Resource数组中
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
由于我们从xml文档中获取出来的文件路径是String类型,这里将String类型的地址转换成Resource后放到Resource数组中以便后续使用,进入
loadBeanDefinitions(resources)
方法,看看执行了什么操作。一路走到
XmlBeanDefinitionReader#loadBeanDefinitions
后可以看到如下代码/**
* Load bean definitions from the specified XML file.
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
进入
XmlBeanDefinitionReader#loadBeanDefinitions
首先对Resource使用EncodedResource进行封装,这里有一个操作 new EncodedResource(resource)
这是对资源文件进行编码处理。继续进入方法内/**
* Load bean definitions from the specified XML file.
* @param encodedResource the resource descriptor for the XML file,
* allowing to specify an encoding to use for parsing the file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
//通过属性来记录已加载的资源
Set currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//从encodedResource中获取已经封装的Resource并在此从Resource中获取其中的inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//核心代码
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
这段代码主要目的是从Resource中获取InputStream并构造InputSource,构造成功够调用
doLoadBeanDefinitions
方法开始真正的逻辑核心部分/**
* @Author: qzx
* @Date: Created in 14:19 2019/12/23
* @Description: TODO 通过DocumentLoader对resource文件进行转换,将resource文件转换为document文件
* @Param: [inputSource, resource]
* @Return: int
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {try {
//加载xml文件并得到对应的Document
Document doc = doLoadDocument(inputSource, resource);
//根据返回的Doucment注册bean信息
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
...
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
方法
doLoadDocument(inputSource, resource);
将会调用本方法中的doLoadDocument
方法,protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
而在这个方法中的
getEntityResolver()
方法目的是返回EntityResolver参数,看一下方法内部/**
* Return the EntityResolver to use, building a default resolver
* if none specified.
*/
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
// Determine default EntityResolver to use.
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
介绍一下这个EntityResolver参数的作用:
在进行XML文件验证的时候,有两种验证方法–DTD&XSD,通常的我们在创建XML后是通过XML文件中的URL地址来从网络上下载相应的声明后进行验证,这样会导致一些网络的延迟,影响用户体验。
EntityResolver的作用就是提供一个如何寻找本地DTD&XSD声明文件的方法。我们将DTD&XSD声明文件下载到项目本地,直接从本地读取声明文件,就可以避免网络延迟,增加用户体验。
详细用法以后会有时间写一篇博客介绍吧。
this.documentLoader.loadDocument
调用的是DocumentLoader
接口中的方法,而进入DocumentLoader
接口的实现是在DefaultDocumentLoader
中/**
* @Author: qzx
* @Date: Created in 17:14 2019/12/23
* @Description: TODO
* @Param: [inputSource, entityResolver : 项目本身可以提供一个如何寻找DTD声明的方法,即由程序来寻找DTD声明的过程,避免了通过网络来寻找相应的声明
* , errorHandler, validationMode, namespaceAware]
* @Return: org.w3c.dom.Document
*/
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
Spring通过SAX解析XML大致都是一样的,先创建
DocumentBuilderFactory
,通过DocumentBuilderFactory
创建DocumentBuilder
,进而解析inputSource
来返回Document
,这里有时间再深究吧。在获取到Document信息后,我们进入到下一阶段–注册bean信息,也就是进入
XmlBeanDefinitionReader#doLoadBeanDefinitions.registerBeanDefinitions(doc, resource)
方法内部public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//在实例化BeanDefinitionReader时会将BeanDefinitionRegistry传入,默认使用继承自DefaultListableBeanFactory的子类
//记录统计前BeanDefinition的个数
int countBefore = getRegistry().getBeanDefinitionCount();
/** 加载及注册bean */
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//记录本次加载的BeanDefinition的个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
每一步的作用都做了注释,
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
是核心逻辑代码,进入查看@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//对xml进行解析,核心代码
doRegisterBeanDefinitions(doc.getDocumentElement());
}
我们到这一步后,算是已经完成了一大半了,之前所有的工作都是解析xml的准备工作,而这一步才是真正开始解析xml文件。进入方法内部查看。
/**
* Register each bean definition within the given root {@code } element.
*/
@SuppressWarnings("deprecation")// for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
// Any nested elements will cause recursion in this method. In
// order to propagate and preserve default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
//处理profile属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
//解析前处理,留给子类实现
// 代码是空的,它是面向继承而设计的,属于设计模式的模板方法模式,
// 如果继承自DefaultBeanDefinitionDocumentReader的子类需要在bean的解析前后做一些事情的话,只需要重写这个方法就行了
preProcessXml(root);
//解析并注册beanDefinitions
parseBeanDefinitions(root, this.delegate);
//解析后处理,留给子类实现
postProcessXml(root);
this.delegate = parent;
}
首先我们获取root后需要先处理profile属性,那么这个profile属性究竟是什么呢?
贴段代码就知道了
这么一看就很明白了,profile就是区分项目中的配置文件是开发环境还是生产环境。
接下来一大段就是处理profile属性的代码,可以不用看,继续向下,直接看
parseBeanDefinitions(注释里介绍了上下两个方法)
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
* @TODO 解析并注册BeanDefinitions
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对bean的处理,根据命名空间的不同调用不同的方法,默认命名空间"http://www.springframework.org/schema/beans"
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)) {
//对bean的处理,parseDefaultElement 解析import alias bean beans标签元素
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
//解析aop context tx 或者是用户自定义命名空间的元素
delegate.parseCustomElement(root);
}
}
/**
* @Author: qzx
* @Date: Created in 14:57 2019/12/24
* @Description: TODO
* @Param: [ele:即代表一个bean标签元素, delegate]
* @Return: void
*/
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//解析import标签
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//解析alias标签元素
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//解析bean标签元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//解析beans标签元素
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
我直接粘了两个方法,首先看第一个方法
parseBeanDefinitions
,由于Spring在XML配置类中有两大Bean的声明,分别是默认:
和
自定义:
<**:annotation-driven/>//支持注解
【Spring5源码分析(2)-- Spring容器】如果是默认配置,代码会执行
parseDefaultElement(ele, delegate);
方法对import alias bean beans
标签进行解析,就是我上面粘贴的第二个方法,逻辑很简单,一看就懂。如果是自定义配置,则会执行else中的方法。
那么默认标签和自定义标签具体是如何解析的呢?
转到默认标签解析-----
转到自定义标签解析-----
累了,有时间再写吧。。。。
未完待续…
推荐阅读
- 如何寻找情感问答App的分析切入点
- D13|D13 张贇 Banner分析
- 自媒体形势分析
- 2020-12(完成事项)
- Android事件传递源码分析
- Python数据分析(一)(Matplotlib使用)
- Quartz|Quartz 源码解析(四) —— QuartzScheduler和Listener事件监听
- 泽宇读书会——如何阅读一本书笔记
- Java内存泄漏分析系列之二(jstack生成的Thread|Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析)
- [源码解析]|[源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3)