Spring源码解析-容器的基本实现
来源:互联网 发布:java打印直角三角形 编辑:程序博客网 时间:2024/06/01 11:47
首先来回顾一下简单的bean获取。
1、bean类
public class MyTestBean { private String name = "whz"; public String getName() { return name; } public void setName(String name) { this.name = name; }}
2、配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/taskhttp://www.springframework.org/schema/task/spring-task.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="myTestBean" class="bean.MyTestBean"></bean></beans>
3、测试程序
public class BeanFactoryTest { @Test public void testSimpleLoad(){ BeanFactory bf =new XmlBeanFactory(new ClassPathResource(("beanFactoryTest.xml"))); MyTestBean myTestBean = (MyTestBean)bf.getBean("myTestBean"); System.out.println(myTestBean.getName()); }}
4、结果
whz
通过初步猜想,我们认为是先加载xml配置文件,获取bean中的类名,然后通过反射机制进行实例化。
现在开始分析源码;
①配置文件封装:
首先通过new ClassPathResource((“beanFactoryTest.xml”)对配置文件进行封装,ClassPathResource继承的类实现了Resource接口,可以返回文件输入流,也就是getInputStream()方法。
我们进入XmlBeanFactory的构造方法:
private final XmlBeanDefinitionReader reader; public XmlBeanFactory(Resource resource) throws BeansException { this(resource, (BeanFactory)null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader = new XmlBeanDefinitionReader(this); this.reader.loadBeanDefinitions(resource); }
这个工厂实例化了一个XmlBeanDefinitionReader类,然后这个类有加载了资源文件,也就是这个配置文件。进入loadBeanDefinitions()方法,
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return this.loadBeanDefinitions(new EncodedResource(resource)); }
首先看见对resource用EncodedResource进行封装。这个类是用于对资源文件的编码进行处理。
接着进入loadBeanDefinitions方法
InputStream inputStream = encodedResource.getResource().getInputStream();
我们可以看到这么一段代码,可见先是获取配置文件的输入流,
try { //将输入流封装成InputSource,这个类往往用来读取xml文件 InputSource inputSource = new InputSource(inputStream); if(encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); }
进入核心部分doLoadBeanDefinitions()方法,我删除了一些异常处理,留下关键代码,可见通过doLoadDocument加载Doucment对象,学过XML读取的都知道这是xml文件中的一种对象,跟html也有相似之处。然后根据这个Document对象注册Bean信息。这个代码是spring4的代码,可能与spring3版本有点不一样,但是大体过程是一样的。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { Document doc = this.doLoadDocument(inputSource, resource); return this.registerBeanDefinitions(doc, resource); }
进入加载doLoadDocument方法。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware()); }
我们能看见有这么一段代码:this.getValidationModeForResource(resource),这段代码是获取xml 的验证模式。了解XML文件的应该知道XML的验证模式,一般有DTD和XSD这两种验证模式,一般会在XML文件的头部加上验证模式的声明,虽然IDE会报错,但是我们在读取xml时还是要验证xml 的格式规范。DTD是一种XML约束模式语言,XSD描述了XML文档的结构。在这对这两种模式就不详细解释了。
首先我们要从xml文件中获取验证模式
protected int getValidationModeForResource(Resource resource) { int validationModeToUse = this.getValidationMode(); //如果手动制定了验证模式则使用指定的验证模式 if(validationModeToUse != 1) { return validationModeToUse; } else { //否则就自动检测验证模式 int detectedMode = this.detectValidationMode(resource); return detectedMode != 1?detectedMode:3; } }
进入detectValidationMode()方法,删除了一些抛出异常
rotected int detectValidationMode(Resource resource) { if(resource.isOpen()) { } else { InputStream inputStream; inputStream = resource.getInputStream(); this.validationModeDetector.detectValidationMode(inputStream); }
继续进入detectValidationMode方法
public int detectValidationMode(InputStream inputStream) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); byte var4; try { boolean isDtdValidated = false; while(true) { String content; //如果是空行就略过 if((content = reader.readLine()) != null) { content = this.consumeCommentTokens(content); //如果是注释也略过 if(this.inComment || !StringUtils.hasText(content)) { continue; } //如果有DOCTYPE标记就说明是DTD模式 if(this.hasDoctype(content)) { isDtdValidated = true; } else if(!this.hasOpeningTag(content)) { continue; } } int var5 = isDtdValidated?2:3; return var5; } } catch (CharConversionException var9) { var4 = 1; } finally { reader.close(); } return var4; }
获取了验证模式后,接下来我们要获取Document对象
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware()); }
进入这个loadDocument方法
发现是接口,然后我们的找到实体类:
private DocumentLoader documentLoader = new DefaultDocumentLoader();
进入这个类,找到loadDocument方法
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //首先创建document工厂 DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware); if(logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } 然后创建documentBuilder DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }
然后用这个builder来解析输入流,这是通过SAX来解析XML文档,在这其中还传入了一个参数entityResolver,我们知道XML文档解析需要获取DTD约束文档或者XSD的结构文档,来对XML文件进行验证,首先获取这个EntityResolver
protected EntityResolver getEntityResolver() { if(this.entityResolver == null) { ResourceLoader resourceLoader = this.getResourceLoader(); if(resourceLoader != null) { this.entityResolver = new ResourceEntityResolver(resourceLoader); } else { this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader()); } } return this.entityResolver; }
spring采用DelegatingEntityResolver为EntityResolver实现类,
从代码中看出,不同的验证模式会采用不同的解析器。
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { if(systemId != null) { if(systemId.endsWith(".dtd")) { return this.dtdResolver.resolveEntity(publicId, systemId); } if(systemId.endsWith(".xsd")) { return this.schemaResolver.resolveEntity(publicId, systemId); } } return null; }
SAX首先会读取xml上的声明
例如:xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
因为从网络上获取DTD声明或者XSD非常不可靠,所以SAX
调用EntityResolver的resolveEntity方法(参数为获取的声明),来从本地获取约束文件,加载DTD类型的BeansDtdResolver的resolveEntity方法是直接截取systemId最后的xx.dtd,然后去当前路径下查找,而加载XSD类型的PluggableSchemaResolver,是默认到META-INF/Spring.schemas文件中找到systemId对应的XSD文件并加载。
我们文件中的一部分,可以看书每个声明对应了不同的xsd文件位置。
http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsdhttp\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsdhttp\://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsdhttp\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd
获取到document对象后,
解析及注册BeanDefinitions,接下来是解析及注册BeanDefinitions
Document doc = this.doLoadDocument(inputSource, resource); return this.registerBeanDefinitions(doc, resource);
进入registerBeanDefinitions方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //创建BeanDefinitionDocumentReader对象 BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader(); //设置环境变量 documentReader.setEnvironment(this.getEnvironment()); //记录统计前beanDefition的个数 int countBefore = this.getRegistry().getBeanDefinitionCount(); //加载(或者说是解析)及注册bean documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource)); //记录本次加载的个数 return this.getRegistry().getBeanDefinitionCount() - countBefore; }
BeanDefinitionDocumentReader是个接口,真正的类型是DefaultBeanDefinitionDocumentReade,打开这个类,找到registerBeanDefinitions这个方法。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; this.logger.debug("Loading bean definitions"); //提取root元素 Element root = doc.getDocumentElement(); this.doRegisterBeanDefinitions(root); }
进入doRegisterBeanDefinitions查看
protected void doRegisterBeanDefinitions(Element root) { //处理profile属性 String profileSpec = root.getAttribute("profile"); if(StringUtils.hasText(profileSpec)) { Assert.state(this.environment != null, "Environment must be set for evaluating profiles"); String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; "); if(!this.environment.acceptsProfiles(specifiedProfiles)) { return; } } //专门处理解析 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = this.createDelegate(this.readerContext, root, parent); //解析前处理,留给子类实现 this.preProcessXml(root); //解析 this.parseBeanDefinitions(root, this.delegate); 解析后处理,留给子类实现 this.postProcessXml(root); this.delegate = parent; }
profile属性我们不常用,
我们可以再beans标签中加入profile属性
例如<beans profile="aaa"></beans><beans profile="bbb"></beans>
集成到web环境中时在web.xml中加入以下代码
<context-param> <param-name>Spring.profiles.active</param-name> <param-value>aaa</param-value></context-param>
这样我们可以采用两套spirng配置
接着我们来说preProcessXml和postProcessXml方法,这两个方法是空方法,如果我们需要对root进行处理,我们可以用子类继承这个类,然后重写该方法,这种设计模式叫做模板方法。
接着我们进入parseBeanDefinitions方法;该方法是对xml进行读取对bean进行处理。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //对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)) { //对默认标签处理 this.parseDefaultElement(ele, delegate); } else { //对自定义标签处理 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
总结
这次主要探究了spring在出bean处理之前的动作,接下来回顾一下步骤。
1、封装配置文件资源。
2、创建XmlBeanDefinitionReader读取器,接下来用读取器处理资源文件。
3、从配置文件资源中获取文件输入流。
4、获取验证模式,创建可以找到约束文档的EntityResolver
5、进行解析xml文件,获取Document对象,然后解析bean并注册BeanDefitions
- Spring源码解析-容器的基本实现
- 《Spring源码深度解析》阅读笔记3-容器的基本实现之容器的基础XmlBeanFactory
- 《Spring源码深度解析》阅读笔记2-容器的基本实现之Spring的结构组成
- Spring 源码深入解析之bean容器的基本实现(一)
- Spring 源码深入解析之bean容器的基本实现(二)
- 02.Spring IOC源码深度解析之容器的基本实现
- Spring源码深度解析(二)容器的基本用法
- 《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现
- spring源码附录(3)容器的基本实现
- Spring源码分析----IOC容器的实现(IoC容器的初始化过程(定位、载入解析、注册))
- 《Spring源码深度解析》阅读笔记4-容器的基本实现之获取XML的验证模式、获取Document及解析及注册BeanDefinitons
- Spring源码解析之IoC容器系列的设计实现(IoC容器系列概况)
- 创建ApplicationContext与BeanFactory时的区别-Spring源码学习之容器的基本实现
- Spring 容器的基本实现流程
- spring源码初步学习-容器(BeanFactory)基本实现
- spring源码解读(1)-容器基本实现
- 【Spring源码--IOC容器的实现】-- 综述
- 【Spring源码--IOC容器的实现】-- 综述
- Ubuntu系统搭建bind DNS服务器
- 回到顶部(两种)
- [BZOJ]3125: CITY 插头DP
- emWin 2天速成实例教程004_软件定时器(Timer)和位图片动画
- Revit二次开发——怎么读取cad里的单独的一条线
- Spring源码解析-容器的基本实现
- linux 下 github 学习指导 (本地git)
- IntelliJ IDEA + 破解(2015以前)+ 快捷键
- MySQL的字符集设定
- QTcpSocket内存泄漏
- 8月
- 编写量化策略需要注意的几个细节问题
- android 5大布局详解(初)
- catVSdog完整源码【转】