首先来回顾一下简单的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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://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.xsd
http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
http\://www.springframework.org/schema/util/spring-util-4.0.xsd=org/springframework/beans/factory/xml/spring-util-4.0.xsd
http\://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