Spring解析注册Bean:
近日,读取了Spring源码解析一书,现写博客记录一下收获。
BeanFactory bf = new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”));
MyTestBean bean = (MyTestBean) bf.getBean(“myTestBean”);
读取配置文件:beanFactoryTest.xml
根据配置文件中的Bean标签的name属性,找到myTestBean,并实例化。
1.读取和验证配置文件,放置内存中。(ConfigReader)
2.根据配置的内容,bean标签的class属性内容,进行反射实例化。(ReflectionUtil)
3.需要一个中间件,完成整个逻辑的串联。(App)
为了解决配置文件或者其他文件URL的识别,Spring对自己内部使用到的资源实现了自己的抽象(Resource接口来封装底层资源:File、URL、Classpath等),比如classpath:的解析。此外,还添加一些其它基本方法(判断资源是否存在exists();当前资源是否可读isReadable();是否已经打开isOpen()等方法,详情见Resource接口源码)。
对于不同的来源的资源文件都有对应的Resource接口实现:
常见用法(题外话)
Resource resource = new ClassPathResource(“beanFactoryTest.xml”);
InputStream inputStream = resource .getInputStream();
通过其实现类,获取到Resource对象,得到InputStream,从而作一系列的操作。当然,也可以通过Resource及其子类所实现的方法来为我们提供其他操作的便利。
当将配置文件封装到Resource对象后,就到配置文件的读取工作了,也就是代码中
BeanFactory bf = new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”));
的new XmlBeanFactory() 这一过程。
查看XmlBeanFactory 类的构造函数,发现其实际执行代码如下:
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource); (reader是XmlBeanDefinitionReader的对象)
我们先来了解一下super(parentBeanFactory); ,跟踪到类AbstractAutowrieCapableBeanFactory,所执行代码如下:
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
其中,ignoreDependencyInterface方法的主要功能就是忽略给定接口的自动装配功能。也就是说有两个嵌套的Bean,A中有B,在默认情况下,Spring在获取A时,若B还未初始化,就会自动初始化B。但是如果B实现了(BeanNameAware、BeanFactoryAware、BeanClassLoaderAware)接口,就会忽略B。
具体处理流程如下
先对参数Resource使用EncodedResource类进行封装。
获取输入流。从Resource中获取对应的inputStream来构造InputSource。
通过构造的InputSource和Resource实例继续调用doLoadBeanDefinitions方法。
XML文件的验证模式
XML文件的验证模式有两种:XSD和DTD,两者区别在于XML文件头部定义和校验文档(DTD文件或XSD文件):
xsi:schemaLocation=”http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans.xsd” ……
getValidationModeForResource方法解读
在获取验证模式后,就到Document对象的获取。在XmlBeanFactoryReader类中,委托了DefaultDocumentLoader类去执行。
其中,值得注意的是,传进来的EntityResolver对象,回过头看XmlBeanDefinitionReader这一类中的getEntityResolver方法。
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;
}
SAX解析XML时,SAX会首先读取XML文档的声明,根据声明寻找DTD(或XSD)定义,以便对文档进行验证。默认寻找规则,是通过声明的URI地址来下载相应的验证文件,进行认证。下载文件是一个不可控的过程,当网络中断或不可用时,就会报错。
EntityResolver的作用就是提供一个寻找DTD(或XSD)声明的方法,由程序来实现寻找DTD(或XSD)声明的过程。比如,我们将验证文件放在项目某处,在实现时直接将此文档读取返回给SAX,即可避免了通过网络来寻找相应的声明。
查看entityResolver接口方法声明
InputSource resolveEntity (publicId, systemId);
解析验证模式为XSD配置文件:
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”> ……
读取到参数如下:
publicId: null
systemId: http://www.Springframework.org/schema/beans/Spring-beans.xsd
解析验证模式为DTD配置文件:
DOCTYPE beans PUBLIC “-//Spring//DTD BEAN 2.0//EN” http://www.Springframework.org/dtd/Spring-beans-2.0.dtd>
……
读取到参数如下
publicId: -//Spring//DTD BEAN 2.0//EN
systemId: http://www.Springframework.org/dtd/Spring-beans-2.0.dtd
相关具体实现,Spring中使用DelegatingEntityResolver类作为EntityResolver的实现类,代码如下:
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
由此代码可发现,Spring对于不同的验证模式,使用不通的解析器解析。简单描述下其具体执行过程,比如加载DTD类型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/Spring.schemas文件中找到systemid所对应的XSD文件并加载。
当把文件转换为Document后,就到解析注册BeanDefinitions的工作了。回到之前的方法registerBeanDefinitions(),其主要工作执行如下:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//设置环境变量
documentReader.setEnvironment(getEnvironment());
//记录未加载前,已有BeanDefinition的个数
int countBefore = getRegistry().getBeanDefinitionCount();
//加载、注册bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//返回本次加载的BeanDefinition的个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
不难看出,加载、注册的核心,委托了BeanDefinitionDocumentReader接口(其具体实现类DefaultBeanDefinitionDocumentReader)来处理。首先进入的是registerBeanDefinitions方法,其具体代码如下:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
该方法主要提取Element root,交给doRegisterBeanDefinitions方法来处理,具体代码如下:
protected void doRegisterBeanDefinitions(Element root) {
//处理profile属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(this.readerContext, root, parent);
preProcessXml(root);
//开始解析处理
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
其主要工作,先对bean标签的profile属性进行处理,之后开始解析工作。这里,可以留意一下preProcessXml()和postProcessXml()两个方法,这两个方法并没有内容,主要便于使用者的拓展,用于Bean解析前处理和Bean解析后处理的操作,通过继承该类来重写这两个方法即可。
一般程序都有了多套配置文件来适用于各种使用环境,而这种配置可通过profile属性来解决。具体使用流程,在配置文件上定义多个
……
……
在配置文件上定义完成后,还需在web.xml声明使用哪一个环境。
这也就对应了前面代码的环境变量设置setEnvironment(getEnvironment());,若beans定义了profile属性就会到环境变量中寻找。
解析完profile属性后,就到XML文件的读取,也就是parseBeanDefinitions(root, this delegate)代码的执行,具体代码如下:
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)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
这段代码不难理解,无非就是节点属于默认命名空间就调用parseDefaultElement()默认方法来解析,否则,就调用parseCustomElement()方法来解析。而对于默认和自定义的区分,就在于命名空间的对比,若和http://www.Springframework.org/schema/beans一致则为默认,否则,就是自定义标签。