spring的主要特性是IOC,实现IOC的关键是bean,而更关键的是如何bean的管理容器,也就是BeanFactory,本文的目标是弄清楚BeanFactory具体是怎么样的存在。

先看下最简单的获取bean的案例,代码如下:

 public static void main(String[] args){
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-beans.xml"));
User user = (User) factory.getBean("user");
System.out.println(JSON.toJSON(user).toString());
}

首先是读取spring的配置文件,创建BeanFactory实例,如何直接从BeanFactory实例中获取指定名称的bean。接下来就从这几行简单的代码入手,分析下BeanFactory。

BeanFactory实例是通过XmlBeanFactory来创建的,很明显XmlBeanFactory是BeanFactory的子类,继承关系图如下:

那么先就从XmlBeanFactory的构造方法开始,源码如下:

 public class XmlBeanFactory extends DefaultListableBeanFactory {

     private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

     public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
} public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
} }

第5行的构造方法调用第9行的构造方法,首先是执行父类的构造方法,然后执行XmlBeanFefinitionReader的loadBeanFefinitions(resource)方法,这个方法显然就是加载bean配置文件的方法。接下来跟踪查看,源码如下:

     @Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}

通过EncodeResource来封装Resource,在调用重载的方法

 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
//校验Resource参数
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
} Set<EncodedResource> 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 {
//获取Resource的InputSteam流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//通过InputSteam构造InputSource
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//最终执行doLoadBeanDefinitions方法
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();
}
}
}

这次封装的EncodedResource作用主要是对XML配置文件的编码格式进行处理,然后最终执行了doLoadBeanDefinitions方法

     protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//通过InputSource和Resource得到一个Document对象,然后执行注册bean的方法
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}

该方法只有两行有效逻辑代码,首先是加载XML文件得到一个Document对象,然后根据Document对象来注册Bean信息,一步步来看,先看如何生成Document对象。

 private DocumentLoader documentLoader = new DefaultDocumentLoader();

 protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}

调用了DocumentLoader的实例来进行加载,DocumentLoader接口的loadDocument方法

 public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //这里显然是工厂模式+建造者模式,先创建DocumentBuilder工厂,如何得到DocumentBuilder,最终执行parse方法解析xml配置文件得到Document对象
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
 public Document parse(InputSource is) throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException(
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
"jaxp-null-input-source", null));
}
if (fSchemaValidator != null) {
if (fSchemaValidationManager != null) {
fSchemaValidationManager.reset();
fUnparsedEntityHandler.reset();
}
resetSchemaValidator();
}
//上面的代码都是校验,核心是下面的三行,通过DomParser来获取Document对象
domParser.parse(is);
Document doc = domParser.getDocument();
domParser.dropDocumentReferences();
return doc;
}

解析配置文件得到了Document对象,接下来就是通过Document来注册beanl了。回到XmlBeanDefinitionReader的RegisterBeanDefinitions方法

 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//创建BeanDefinitionDocumentReader实例
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//统计当前已经加载过的BeanDefinition个数
int countBefore = getRegistry().getBeanDefinitionCount();
//加载及注册bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//得到本次加载的BeanFinition个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}

核心是BeanDefinitionDocumentReader接口的registerBeanDefinitions方法,代码如下:

 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}

首先是获取Document的root,然后将root元素传递给下面的方法继续执行,这里的root元素就是配置文件中的<beans>标签

 protected void doRegisterBeanDefinitions(Element root) {
//专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) {
//处理<beans>标签中的profile属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
} preProcessXml(root);//代码为空(模板方法设计模式:提供给子类实现,如果需要在bean的解析前后做一些处理的话)
parseBeanDefinitions(root, this.delegate);//Bean的解析
postProcessXml(root);//代码为空 this.delegate = parent;
}

发现bean的注册方法应该是parseBeanDefinitions方法,继续往下

  protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
//获取<beans>标签的子节点也就是所有的<bean>标签
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(ele, delegate);
}
else {
//如果是自定义命名空间的bean
delegate.parseCustomElement(ele);
}
}
}
}
else {
//自定义命名空间的bean
delegate.parseCustomElement(root);
}
}

这里是逻辑很清楚,获取root下的子节点遍历,判断是否是默认命名空间的元素来分别处理。判断是否是默认命名空间的方式是取Node的namespanceUrl来和默认命名空间的地址进行比较,默认地址为:

public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

像<bean id="userService" class="com.test.UserService">这种写法就是默认标签,而如<tx:annotation-driven>这种就是自定义的写法。

总结:本文从BeanFactory的初始化开始,解析XML配置文件,生成Docuemnt对象,解析Document中的标签,按标签的类型来进行区分解析,而两种的解析方式截然不同,接下来就分别解析。

spring源码解析2--容器的基本实现的更多相关文章

  1. Spring源码解析-ioc容器的设计

    Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...

  2. Spring源码解析 – AnnotationConfigApplicationContext容器创建过程

    Spring在BeanFactory基础上提供了一些列具体容器的实现,其中AnnotationConfigApplicationContext是一个用来管理注解bean的容器,从AnnotationC ...

  3. spring源码解析——2容器的基本实现(第2版笔记)

    感觉第二版写的略潦草,就是在第一版的基础上加上了新的流行特性,比如idea,springboot,但是,潦草痕迹遍布字里行间. 虽然换成了idea,但是很多截图还是eclipse的,如果不是看了第一版 ...

  4. Spring源码解析-IOC容器的实现-ApplicationContext

    上面我们已经知道了IOC的建立的基本步骤了,我们就可以用编码的方式和IOC容器进行建立过程了.其实Spring已经为我们提供了很多实现,想必上面的简单扩展,如XMLBeanFacroty等.我们一般是 ...

  5. Spring源码解析-IOC容器的实现

    1.IOC容器是什么? IOC(Inversion of Control)控制反转:本来是由应用程序管理的对象之间的依赖关系,现在交给了容器管理,这就叫控制反转,即交给了IOC容器,Spring的IO ...

  6. Spring源码解析-Web容器启动过程

    Web容器启动过程,主要讲解Servlet和Spring容器结合的内容. 流程图如下: Web容器启动的Root Context是有ContextLoaderListener,一般使用spring,都 ...

  7. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegistry和SingletonBeanRegistry接口. 这边主要提供了 ...

  8. spring 源码解析

    1. [文件] spring源码.txt ~ 15B     下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB     下载( ...

  9. Spring源码解析系列汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...

  10. Spring源码解析之BeanFactoryPostProcessor(三)

    在上一章中笔者介绍了refresh()的<1>处是如何获取beanFactory对象,下面我们要来学习refresh()方法的<2>处是如何调用invokeBeanFactor ...

随机推荐

  1. Laravel安装redis扩展

    Laravel安装redis扩展 1.使用命令行,执行(当然要先安装composer) composer require predis/predis 2.执行完就安装好了,redis相关配置可以到.e ...

  2. react-native获取设备信息组件(react-native-device-info)

    转载链接:http://www.ncloud.hk/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/react-native-acquisition-device-infor ...

  3. Codechef August Challenge 2018 : Interactive Matrix

    传送门 首先整个矩阵可以被分为很多小矩阵,小矩阵内所有行的单调性是一样的,所有列的单调性是一样的. 考虑如何在这样一个小矩阵中找出答案.我的策略是每次取四个角中最大值和最小值的点,这样可以每次删掉一行 ...

  4. Linux 手册惯用的节名

    linux 手册将

  5. cadence分列元件原理图库的设计

  6. 配置 Tomcat 以服务方式自动运行

    接手一台用 Tomcat 跑着 Java Web 应用的 Linux CentOS 服务器,通过镜像的方式更换服务器后站点无法访问,发现是因为 Tomcat 不能在服务器启动后自动启动,于是基于 in ...

  7. [ipsec][strongswan] 使用wireshark查看strongswan ipsec esp ikev1 ikev2的加密内容

    一,编译,启用strongswan的save-keys plugin ./configure --prefix=/root/OUTPUT --exec-prefix=/root/OUTPUT --en ...

  8. shell脚本if语句的多种条件参数

    if语句有多种写法 [root@shell-yankerp sh]# [ -f file ] && echo "yes" || echo "no" ...

  9. Lambda Expression

    Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁.当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口.下面这个例子就是使用Lambda语法来代替匿名的内部类 ...

  10. 《Mysql 锁》

    一:什么是锁? - 锁是计算机协调多个进程或纯线程并发访问某一资源的机制. - 通俗的来说,锁是一种对资源的保护形式. 二:锁分类 - 表级锁 - 开销小,加锁快,没有死锁,锁定粒度大,发生锁冲突的概 ...