容器的基本用法
public class MyTestBean {
private String testStr = "testStr";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myTestBean" class="com.whyalwaysmea.MyTestBean" />
</beans>
public static void main(String[] args) {
//1.创建spring的ioc容器对象
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
//2.从ioc容器中获取bean实例
MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
String testStr = myTestBean.getTestStr();
System.out.println(testStr);
}
核心类介绍
DefaultListableBeanFacotry
XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取。
XmlBeanDefinitionReader
XML配置文件的读取是Spring中重要的功能,因为Spring的大部分功能都是以配置作为切入点的,那么我们可以从XmlBeanDefinitionReader中梳理一下资源文件读取。解析及注册的大致脉络。
- 通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转换为对应的Resource文件
- 通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件
- 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析
容器的基础XmlBeanFactory
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的各种服务来操作了,当我们有了Resource后就可以进行XmlBeanFactory的初始化了。
配置文件封装
Spring的配置文件读取是通过ClassPathResource进行封装的,如new ClassPathResource(“applicationContext.xml”),那么ClassPathResource完成了什么功能呢?
在Java中,将不同来源的资源抽象成URL,通过注册不同的handler来处理不同来源的资源的读取逻辑,一般handler的类型使用不同前缀来识别,如“file:”、”http:”,然而URL没有默认定义相对Classpath或ServletContext等资源的handler。
因为Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源:
// InputStreamSource封装任何能返回InputStream的类,比如File、
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
// 抽象了所有Spring内部使用到的底层资源
public interface Resource extends InputStreamSource {
// 判断资源存在性
boolean exists();
// 判断资源可读性
boolean isReadable();
// 判断资源是否处于打开状态
boolean isOpen();
// 资源类型转换
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
String getFilename();
String getDescription();
}
对不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)等
有了Resource接口便可以对所有资源文件进行统一处理。
当通过Resource相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了。
加载Bean
了解了Spring中将配置文件封装为Resource类型的实例方法后,就可以继续探寻XmlBeanFactory的初始化过程了。
public class XmlBeanFactory extends DefaultListableBeanFactory {
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);
}
}
this.reader.loadBeanDefinitions(resource);才是资源加载的真正实现。但是在调用此方法之前,会先调用构造方法,跟随父类构造方法会来到: AbstractAutowireCapableBeanFactory.java
public AbstractAutowireCapableBeanFactory() {
super();
this.ignoreDependencyInterface(BeanNameAware.class);
this.ignoreDependencyInterface(BeanFactoryAware.class);
this.ignoreDependencyInterface(BeanClassLoaderAware.class);
}
ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能。
当A中有属性B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B。但是某些情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口。Spring中是这样介绍的:自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFacotryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入
接下来我们顺着方法的调用进入
// XmlBeanDefinitionReader.java
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}
我们先梳理整个处理过程:
- 封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装
- 获取输入流。从Resource中获取对应的InputStream并构造InputSource
- 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions
接着进入到loadBeanDefinitions(EncodedResource encodedResource)
:
1. 首先对传入的resource参数做封装,目的是考虑到Resource可能存在编码要求的情况
2. 通过SAX读取XML文件的方式来这边InputSource对象
3. 将这边的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(InputSource inputSource, Resource resource)
doLoadBeanDefinitions(InputSource inputSource, Resource resource)
只要做三件事:
1. 获取对XML文件的验证模式
2. 加载XML文件,并得到对应的Document
3. 根据返回的Document注册Bean信息
XML的验证模式
XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD
DTD与XSD区别
DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则
以下是在Spring中使用DTD声明方式:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
...
</beans>
XML Schema语言就是XSD。文档设计者通过XML Schema指定一个XML文档所允许的结构和内容,并据此检查一个XML文档是否是有效的。XML Schema本身是一个XML文档
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
...
</beans>
xmlns声明名称空间; schemaLocation属性指定名称空间所对应的XML Schema文档的存储位置,它包含两部分,一部分是名称空间的URI,另一部分就是该名称空间所标识的XML Schema文件位置
Spring用来检测验证模式的办法就是判断是否包含了DOCTYPE,如果包含就是DTD,否则就是XSD
获取Document
Document的加载是委托了DocumentLoader去执行,真正调用的是DefaultDocumentLoader。主要通过SAX解析XML文档,并没有什么特殊的地方。
//
public interface DocumentLoader {
Document loadDocument(InputSource var1, EntityResolver var2, ErrorHandler var3, int var4, boolean var5) throws Exception;
}
有一个需要注意的是EntityResolver。
EntityResolver的作用是项目本身可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可,这样就避免了通过网络来寻找相应的声明。
解析及注册BeanDefinitions
在把文件转换为Document后,接下来就是提取及注册Bean
// XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
// 记录统计前BeanDefinition的加载个数
int countBefore = this.getRegistry().getBeanDefinitionCount();
// 加载及注册bean
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
// 记录本次加载的BeanDefinition个数
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
加载 及注册逻辑都交给了BeanDefinitionDocumentReader。BeanDefinitionDocumentReader是一个接口,实例化是通过createBeanDefinitionDocumentReader()完成
// DefaultBeanDefinitionDocumentReader.java
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
// 真正的解析
this.doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 处理profile属性
String profileSpec = root.getAttribute("profile");
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}
// 解析前处理,留着子类实现
this.preProcessXml(root);
this.parseBeanDefinitions(root, this.delegate);
// 解析后处理,留着子类实现
this.postProcessXml(root);
this.delegate = parent;
}
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)) {
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
因为在Spring的XML配置里面有两大类Bean声明,一个是默认的,如:<bean id="test" class="test.TestBean"/>
;另一类就是自定义的<tx:annotation-driven/>