首先了解一下spring的启动细节:
1、 创建maven工程,pom文件中加入对spring的依赖和log4j的支持:
2、建立两个测试类,其中service中包含对dao的引用,然后定义一个Test类进行测试用:
package com.yushh.test.chapter42;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class DaoFoo {
protected final Log log = LogFactory.getLog(DaoFoo.class);
private String daoName;
private String daoPartern;
public String getDaoName() {
return daoName;
}
public void setDaoName(String daoName) {
this.daoName = daoName;
}
public String getDaoPartern() {
return daoPartern;
}
public void setDaoPartern(String daoPartern) {
this.daoPartern = daoPartern;
}
@Override
public String toString() {
return "DaoFoo [daoName=" + daoName + ", daoPartern=" + daoPartern
+ "]";
}
public void doSomething(){
log.info("DaoFoo method doSomething()!");
}
}
package com.yushh.test.chapter42;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;
public class ServiceFoo {
protected final Log log = LogFactory.getLog(ServiceFoo.class);
private String name;
private String user;
private DaoFoo dao;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
@Override
public String toString() {
return "ServiceFoo [name=" + name + ", user=" + user + "]";
}
public void doSomething(){
log.info("ServiceFoo's method doSomething()!");
}
public void setDao(DaoFoo dao) {
this.dao = dao;
}
}
package com.yushh.test.chapter42;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
ServiceFoo service = context.getBean("serviceFoo", ServiceFoo.class);
service.doSomething();
}
}
3、 配置文件中配置dao和service:
<?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-3.0.xsd">
<bean id="daoFoo" class="com.yushh.test.chapter42.DaoFoo">
</bean>
</beans>
Service.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-3.0.xsd">
<!-- services -->
<bean id="serviceFoo"
class="com.yushh.test.chapter42.ServiceFoo">
<property name="dao" ref="daoFoo"/>
</bean>
</beans>
Log4J.properties:
log4j.rootCategory=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE}%5p%t%c{2}:%L-%m%n
log4j.category.org.springframework.beans.factory=DEBUG
运行结果如下:
11:18:46,280 INFO main support.ClassPathXmlApplicationContext:495 - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@9f2a0b: startup date [Tue Mar 27 11:18:46 CST 2012]; root of context hierarchy
11:18:46,312 INFO main xml.XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [services.xml]
11:18:46,327 DEBUG main xml.DefaultDocumentLoader:72 - Using JAXP provider [com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl]
11:18:46,358 DEBUG main xml.PluggableSchemaResolver:140 - Loading schema mappings from [META-INF/spring.schemas]
11:18:46,358 DEBUG main xml.PluggableSchemaResolver:146 - Loaded schema mappings: {http://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd, http://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd, http://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd, http://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.1.xsd, http://www.springframework.org/schema/aop/spring-aop-3.0.xsd=org/springframework/aop/config/spring-aop-3.0.xsd, http://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd, http://www.springframework.org/schema/aop/spring-aop-2.0.xsd=org/springframework/aop/config/spring-aop-2.0.xsd, http://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd, http://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd, http://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd, http://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd, http://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd, http://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop-3.1.xsd, http://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd, http://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd, http://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd, http://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd, http://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd, http://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd, http://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd, http://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd, http://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd, http://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd, http://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd, http://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.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/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd, http://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd, http://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.1.xsd, http://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd, http://www.springframework.org/schema/aop/spring-aop-2.5.xsd=org/springframework/aop/config/spring-aop-2.5.xsd, http://www.springframework.org/schema/aop/spring-aop-3.1.xsd=org/springframework/aop/config/spring-aop-3.1.xsd, http://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd, http://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd, http://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd, http://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd, http://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd, http://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd, http://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd}
11:18:46,358 DEBUG main xml.PluggableSchemaResolver:118 - Found XML schema [http://www.springframework.org/schema/beans/spring-beans-3.0.xsd] in classpath: org/springframework/beans/factory/xml/spring-beans-3.0.xsd
11:18:46,390 DEBUG main xml.DefaultBeanDefinitionDocumentReader:108 - Loading bean definitions
11:18:46,405 DEBUG main xml.XmlBeanDefinitionReader:216 - Loaded 1 bean definitions from location pattern [services.xml]
11:18:46,405 INFO main xml.XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [daos.xml]
11:18:46,405 DEBUG main xml.DefaultDocumentLoader:72 - Using JAXP provider [com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl]
11:18:46,405 DEBUG main xml.PluggableSchemaResolver:118 - Found XML schema [http://www.springframework.org/schema/beans/spring-beans-3.0.xsd] in classpath: org/springframework/beans/factory/xml/spring-beans-3.0.xsd
11:18:46,421 DEBUG main xml.DefaultBeanDefinitionDocumentReader:108 - Loading bean definitions
11:18:46,421 DEBUG main xml.XmlBeanDefinitionReader:216 - Loaded 1 bean definitions from location pattern [daos.xml]
11:18:46,436 INFO main support.DefaultListableBeanFactory:557 - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1081d2e: defining beans [serviceFoo,daoFoo]; root of factory hierarchy
11:18:46,436 DEBUG main support.DefaultListableBeanFactory:217 - Creating shared instance of singleton bean 'serviceFoo'
11:18:46,436 DEBUG main support.DefaultListableBeanFactory:430 - Creating instance of bean 'serviceFoo'
11:18:46,436 DEBUG main support.DefaultListableBeanFactory:504 - Eagerly caching bean 'serviceFoo' to allow for resolving potential circular references
11:18:46,452 DEBUG main support.DefaultListableBeanFactory:217 - Creating shared instance of singleton bean 'daoFoo'
11:18:46,452 DEBUG main support.DefaultListableBeanFactory:430 - Creating instance of bean 'daoFoo'
11:18:46,452 DEBUG main support.DefaultListableBeanFactory:504 - Eagerly caching bean 'daoFoo' to allow for resolving potential circular references
11:18:46,452 DEBUG main support.DefaultListableBeanFactory:458 - Finished creating instance of bean 'daoFoo'
11:18:46,468 DEBUG main support.DefaultListableBeanFactory:458 - Finished creating instance of bean 'serviceFoo'
11:18:46,468 DEBUG main support.DefaultListableBeanFactory:245 - Returning cached instance of singleton bean 'daoFoo'
11:18:46,483 DEBUG main support.DefaultListableBeanFactory:245 - Returning cached instance of singleton bean 'lifecycleProcessor'
11:18:46,483 DEBUG main support.DefaultListableBeanFactory:245 - Returning cached instance of singleton bean 'serviceFoo'
11:18:46,483 INFO main chapter42.ServiceFoo:46 - ServiceFoo's method doSomething()!
可见我们成功调用了ServiceFoo的doSomething方法,下面我们看一下spring在这期间究竟做了什么:
1、刷新org.springframework.context.support.ClassPathXmlApplicationContext对象,因为我们用的是ClassPathXmlApplicationContext这种方式创建ApplicationContext,所以spring启动后先调用这个类进行初始化。
那么 ClassPathXmlApplicationContext 这个类干了些什么工作呢,我们打开源码:public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
可见,这个类就是根据location指定的位置读取xml文件,真正的读取方法是它的父类AbstractXmlApplicationContext中定义的,代码如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
传入的参数就是DefaultListableBeanFactory,这应该是spring的默认beanFactory,然后初始化一个
XmlBeanDefinitionReader
用来读取
xml
,首先设置了一下
XmlBeanDefinitionReader
它要做的一件事就是先解析出
DefaultListableBeanFactory
中的资源列表,然后一个一个读取:
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
counter += loadBeanDefinitions(resource);
}
return counter;
}
loadBeanDefinitions首先指定Resource和Encoding方式,并把它们放在EncodedResource对象中,接着调用读取的方法:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
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();
}
}
}
接下来我们断点调试一下这个方法,看看其中有什么奥秘,第一行首先读入的是class path resource [services.xml]这个配置,resourcesCurrentlyBeingLoaded这个是个ThreadLocal<Set<EncodedResource>>,也就是是个线程绑定的集合,集合中防入了EncodedResource对象,这个我们在上文中也提到过就是编码和Resource的封装对象,然后是这个集合的初始化工作,这个不用解释了,
InputStream inputStream = encodedResource.getResource().getInputStream();
执行到这个代码时就开始读取services.xml这个文件了。
doLoadBeanDefinitions
这个方法时实际解析
bean
定义的方法,我们看一下这个方法:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
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);
}
}
documentLoader.loadDocument方法读取了services.xml的内容并得到Document对象,当然了还有些附件操作,如格式验证,命名空间验证等。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(this.getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
这个方法根据刚才生成的Document对象解析bean的定义,registerBeanDefinitions除了Document之外另一个参数是ReaderContext,这个我的理解是一个和命令空间有关的XmlReaderContext对象,也就是说可能不同的命名空间下有同名的xmlReader对象,这个就是区分的作用。
说了这么多总要开始解析了吧,protected void doRegisterBeanDefinitions(Element root) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "environment property must not be null");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!this.environment.acceptsProfiles(specifiedProfiles)) {
return;
}
}
// any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createHelper(readerContext, root, parent);
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
这个也是一个很重要的方法,其中BeanDefinitionParserDelegate对象是个bean定义的代理类,
preProcessXml(root);和postProcessXml(root);
目前是这个接口中声明的方法,没有具体实现,作用就是可以在解析xml为bean之前和之后进行一些回调操作。
parseBeanDefinitions解析终于开始了:
parseDefaultElement 进行默认的解析:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
定义了四种解析的方式:解析import标签、beans标签、bean标签和alias标签。其中 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry());
这个方法时核心方法,就是把解析到的bean注册到容器中:
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
registry.registerAlias(beanName, aliase);
}
}
}
beanName:得到serviceFoo
definitionHolder:保留了bean的属性信息:
Bean definition withname 'serviceFoo' and aliases []: Generic bean: class[com.yushh.test.chapter42.ServiceFoo]; scope=; abstract=false; lazyInit=false;autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false;factoryBeanName=null; factoryMethodName=null; initMethodName=null;destroyMethodName=null; defined in class path resource [services.xml]
至此完成了对一个bean的解析。
下面我们总结一下: spring 通过 ClassPathXmlApplicationContext 类的 loadBeanDefinitions 完成对 bean 的解析。后者通过调用 XmlBeanDefinitionReader 的 doLoadBeanDefinitions 生成要解析的 Document 对象,然后调用 BeanDefinitionDocumentReader 的实现类 DefaultBeanDefinitionDocumentReader 的 doRegisterBeanDefinitions 方法进行真正的解析,最后把解析到的 bean 通过 BeanDefinitionReaderUtils 注册到容器中。