(精)Spring IOC核心源码学习III:bean标签和自定义标签实现原理
本文将解析spring bean定义标签和自定义标签的解析实现原理。
这里说的标签仅限于以xml作为bean定义描述符的spring容器,继承AbstractXmlApplicationContext的一些子 容器,如XmlApplicationContext、ClassPathXmlApplicationContext、 FileSystemXmlApplicationContext等。同时也仅限于描述schema作为标签定义的情况。
Springxml ioc容器常用标签和自定义标签
以Xml资源定义的容器配置是我们最常见的一种方式。
Spring容器需要解析xml的标签,并把xml里bean的定义转化为内部的结构BeanDifinition。
Spring的标签有很多种,其支持的常见的标签有:
标签 | 说明 | 例子 |
<bean> | 最常用的,定义一个普通bean。 |
Java代码
|
<tx> | 如<tx: advice>等,提供事务配置通用支持。 |
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="save*"/> <tx:method name="remove*"/> <tx:method name="*" read-only="true"/> </tx:attributes> </tx:advice> |
<aop> | <aop:config>,<aop: aspectj-autoproxy>等提供代理bean通用配置支持。 |
Java代码
|
<util> | 提供在容器内配置一些JDK自带的工具类、集合类和常量的支持。 |
Java代码
|
<p> | 属性的简单访问 |
Java代码
|
<lang> | <lang:groovy><lang:jruby>等,提供对动态脚本的支持。 |
Java代码
|
<jee> | <jee:jndi-lookup/>等,对一些javaEE规范的bean配置的简化,如jndi等。 |
Java代码
|
基本上每一种标签都是用来定义一类bean的(P标签除外)。以上都是spring自带的一些标签,当然spring也支持自定义标签。其实<tx><aop>这些也可以认为是自定义标签,不过是由spring扩展的而已。
其实所有的bean定义都可以用bean标签来实现定义的。而衍生这种自定义标签来定义bean有几个好处:
1.见名知意。
2.对于同一类的通用bean。封装不必要的配置,只给外部暴露一个简单易用的标签和一些需要配置的属性。很多时候对于一个框架通用的bean,我们不需要把bean的所有配置都暴露出来,甚至像类名、默认值等我们都想直接封装,这个时候就可以使用自定义标签了,如:<services:property-placeholder />可能这个标签就默认代表配置了一个支持property placeholder的通用bean,我们都不需要去知道配这样一个bean的类路径是什么。
可以说自定义标签是spring的xml容器的一个扩展点,本身spring自己的很多标签也是基于这个设计上面来构造出来的。
Spring 对于自定义(声明式)bean标签解析如何设计
Bean的定义方式有千千万万种,无论是何种标签,无论是何种资源定义,无论是何种容器,最终的bean定义内部表示都将转换为内部的唯一结构:BeanDefinition。外部的各种定义说白了就是为了方便配置。
Spring提供对其支持的标签解析的天然支持。所以只要按照spring的规范编写xml配置文件。所有的配置,在启动时都会正常的被解析成BeanDefinition。但是如果我们要实现一个自定义标签,则需要提供对自定义标签的全套支持。
我们知道要去完成一个自定义标签,需要完成的事情有:
1.编写自定义标签schema定义文件,放在某个classpath下。
2.在classpath的在META-INF下面增加spring.schemas配置文件,指定schema虚拟路径和实际xsd的映射。我们在xml里的都是虚拟路径,如:
- <?xmlversion="1.0"encoding="UTF-8"?>
- <beansxmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"
- xmlns:p="http://www.springframework.org/schema/p"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd
- ">
- <beanid="otherBean"class="com.test.OtherBean"scope="prototype"/>
- <beanid="myBean"class="com.test.MyBean"lazy-init="true"/>
- <beanid="singletonBean"class="com.test.SingletonBean"/>
- </beans>
头部的
- http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd
就是一个虚拟路径,其对应的真实路径在spring jar包里的META-INF/spring.schemas里面有映射到classpath定义:
- http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
3.增加一个NamespaceHandler和BeanDefinitionParser,用于解析自定义的标签,将自定义标签的bean解析成一个BeanDefinition返回。
4.在classpath的在META-INF下面增加spring.handlers配置文件,指定标签命名空间和handlers的映射。
为什么要做以上几个事情?我们来看看设计:
Spring对标签解析的设计的过程如下:
解释:
Step 1:将xml文件解析成Dom树。将xml文件解析成dom树的时候,需要xml标签定义schema来验证文件的语法结构。Spring约定将所有的shema的虚拟路径和真是文件路径映射定义在classpath的在META-INF/spring.schemas下面。在容器启动时Spring会扫描所有的META-INF/spring.schemas并将映射维护到一个map里。
如spring jar包里会有自带的标签的schemas映射,可以看一下部分配置:
- 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/aop/spring-aop-2.5.xsd=org/springframework/aop/config/spring-aop-2.5.xsd
- http\://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop-2.5.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-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
- http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.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/context/spring-context.xsd=org/springframework/context/config/spring-context-2.5.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/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
- ......
等号左边是虚拟路径,右边是真是路径(classpath下的)。
虚拟路径用在我们的bean定义配置文件里,如:
- <beansxmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"
- xmlns:p="http://www.springframework.org/schema/p"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd>
- <bean>
- </beans>
beans里面的
- http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd
就是个虚拟路径。
Step 2:将dom树解析成BeanDifinition。将定义bean的标签和xml定义解析成BeanDefinition的过程。如果是默认的bean标签,spring会直接进行解析。而如果不是默认的bean标签,包括自定义和spring扩展的<aop>、<p>、<util>等标签,则需要提供专门的xmlparser来处理。paorser由自己定义和编写,并通过handler注册到容器。Spring约定了META-INF/spring.handlers文件,在这里面定义了标签命名空间和handler的映射。容器起来的时候会加载handler,handler会向容器注册该命名空间下的标签和解析器。在解析的自定义标签的时候,spring会根据标签的命名空间和标签名找到一个解析器。由该解析器来完成对该标签内容的解析,并返回一个BeanDefinition。
以下是spring jar包自带的一些自定义标签扩展的spring.handlers文件,可以看到定义了aop\p等其扩展标签的handlers。
- http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
- http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
- http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
- http\://www.springframework.org/schema/jms=org.springframework.jms.config.JmsNamespaceHandler
- http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
- http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
- http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
- http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
META-INF/spring.handlers
看看UtilNamespaceHandler的代码实现
- publicvoidinit(){
- registerBeanDefinitionParser("constant",newConstantBeanDefinitionParser());
- registerBeanDefinitionParser("property-path",newPropertyPathBeanDefinitionParser());
- registerBeanDefinitionParser("list",newListBeanDefinitionParser());
- registerBeanDefinitionParser("set",newSetBeanDefinitionParser());
- registerBeanDefinitionParser("map",newMapBeanDefinitionParser());
- registerBeanDefinitionParser("properties",newPropertiesBeanDefinitionParser());
- }
实现了标签和对应parser的映射注册。
ListBeanDefinitionParser的实现如下:
- privatestaticclassListBeanDefinitionParserextendsAbstractSingleBeanDefinitionParser{
- protectedClassgetBeanClass(Elementelement){
- returnListFactoryBean.class;
- }
- protectedvoiddoParse(Elementelement,ParserContextparserContext,BeanDefinitionBuilderbuilder){
- StringlistClass=element.getAttribute("list-class");
- ListparsedList=parserContext.getDelegate().parseListElement(element,builder.getRawBeanDefinition());
- builder.addPropertyValue("sourceList",parsedList);
- if(StringUtils.hasText(listClass)){
- builder.addPropertyValue("targetListClass",listClass);
- }
- Stringscope=element.getAttribute(SCOPE_ATTRIBUTE);
- if(StringUtils.hasLength(scope)){
- builder.setScope(scope);
- }
- }
- }
这里父类代码不贴了,主要完成的是beanDifinition的生成。
源码实现
Spring 对于自定义(声明式)bean标签源码实现大概的源码结构如下:
XmlBeanDefinitionReader是核心类,它接收spring容器传给它的资源resource文件,由它负责完成整个转换。它调用DefaultDocumentLoader来完成将Resource到Dom树的转换。调用DefaultBeanDefinitionDocumentReader完成将Dom树到BeanDefinition的转换。
具体的代码流程细节完全可以基于这个结构去阅读,下面就贴几个核心源码段:
源码段1:加载spring.shemas,在PluggableSchemaResolver.java里实现:
- publicclassPluggableSchemaResolverimplementsEntityResolver{
- /***定义schemalocation的映射文件路径***/
- publicstaticfinalStringDEFAULT_SCHEMA_MAPPINGS_LOCATION="META-INF/spring.schemas";
- privatestaticfinalLoglogger=LogFactory.getLog(PluggableSchemaResolver.class);
- privatefinalClassLoaderclassLoader;
- privatefinalStringschemaMappingsLocation;
- /**StoresthemappingofschemaURL->localschemapath*/
- privatePropertiesschemaMappings;
- publicPluggableSchemaResolver(ClassLoaderclassLoader){
- this.classLoader=classLoader;
- this.schemaMappingsLocation=DEFAULT_SCHEMA_MAPPINGS_LOCATION;
- }
- publicPluggableSchemaResolver(ClassLoaderclassLoader,StringschemaMappingsLocation){
- Assert.hasText(schemaMappingsLocation,"'schemaMappingsLocation'mustnotbeempty");
- this.classLoader=classLoader;
- this.schemaMappingsLocation=schemaMappingsLocation;
- }
- /**==========中间省略部分代码=========**/
- /***此处完成schema的加载***/
- protectedStringgetSchemaMapping(StringsystemId){
- if(this.schemaMappings==null){
- if(logger.isDebugEnabled()){
- logger.debug("Loadingschemamappingsfrom["+this.schemaMappingsLocation+"]");
- }
- try{
- this.schemaMappings=
- PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation,this.classLoader);
- if(logger.isDebugEnabled()){
- logger.debug("Loadedschemamappings:"+this.schemaMappings);
- }
- }
- catch(IOExceptionex){
- thrownewFatalBeanException(
- "Unabletoloadschemamappingsfromlocation["+this.schemaMappingsLocation+"]",ex);
- }
- }
- returnthis.schemaMappings.getProperty(systemId);
- }
- }
源码段2:加载spring.handlers,在DefaultNamespaceHandlerResolver里实现:
- publicclassDefaultNamespaceHandlerResolverimplementsNamespaceHandlerResolver{
- /**
- *Thelocationtolookforthemappingfiles.CanbepresentinmultipleJARfiles.
- */
- publicstaticfinalStringDEFAULT_HANDLER_MAPPINGS_LOCATION="META-INF/spring.handlers";
- /**Loggeravailabletosubclasses*/
- protectedfinalLoglogger=LogFactory.getLog(getClass());
- /**ClassLoadertouseforNamespaceHandlerclasses*/
- privatefinalClassLoaderclassLoader;
- /**Resourcelocationtosearchfor*/
- privatefinalStringhandlerMappingsLocation;
- /**StoresthemappingsfromnamespaceURItoNamespaceHandlerclassname/instance*/
- privateMaphandlerMappings;
- publicDefaultNamespaceHandlerResolver(){
- this(null,DEFAULT_HANDLER_MAPPINGS_LOCATION);
- }
- publicDefaultNamespaceHandlerResolver(ClassLoaderclassLoader){
- this(classLoader,DEFAULT_HANDLER_MAPPINGS_LOCATION);
- }
- publicDefaultNamespaceHandlerResolver(ClassLoaderclassLoader,StringhandlerMappingsLocation){
- Assert.notNull(handlerMappingsLocation,"Handlermappingslocationmustnotbenull");
- this.classLoader=(classLoader!=null?classLoader:ClassUtils.getDefaultClassLoader());
- this.handlerMappingsLocation=handlerMappingsLocation;
- }
- /**==========中间省略部分代码=========**/
- /************************
- *LoadthespecifiedNamespaceHandlermappingslazily.
- *此处加载延迟加载spring.handlers,只有第一次自定义标签被解析到,才会被加载。
- ****************************/
- privateMapgetHandlerMappings(){
- if(this.handlerMappings==null){
- try{
- Propertiesmappings=
- PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation,this.classLoader);
- if(logger.isDebugEnabled()){
- logger.debug("Loadedmappings["+mappings+"]");
- }
- this.handlerMappings=newHashMap(mappings);
- }
- catch(IOExceptionex){
- IllegalStateExceptionise=newIllegalStateException(
- "UnabletoloadNamespaceHandlermappingsfromlocation["+this.handlerMappingsLocation+"]");
- ise.initCause(ex);
- throwise;
- }
- }
- returnthis.handlerMappings;
- }
- }
源码段3:xml到dom树的解析。
在XmlBeanDefinitionReader.java的doLoadBeanDefinitions方法里,调用DefaultDocumentLoader完成。
- protectedintdoLoadBeanDefinitions(InputSourceinputSource,Resourceresource)
- throwsBeanDefinitionStoreException{
- try{
- intvalidationMode=getValidationModeForResource(resource);
- Documentdoc=this.documentLoader.loadDocument(
- inputSource,getEntityResolver(),this.errorHandler,validationMode,isNamespaceAware());
- returnregisterBeanDefinitions(doc,resource);
- }
- catch(BeanDefinitionStoreExceptionex){
- throwex;
- }
- catch(SAXParseExceptionex){
- thrownewXmlBeanDefinitionStoreException(resource.getDescription(),
- "Line"+ex.getLineNumber()+"inXMLdocumentfrom"+resource+"isinvalid",ex);
- }
- catch(SAXExceptionex){
- thrownewXmlBeanDefinitionStoreException(resource.getDescription(),
- "XMLdocumentfrom"+resource+"isinvalid",ex);
- }
- catch(ParserConfigurationExceptionex){
- thrownewBeanDefinitionStoreException(resource.getDescription(),
- "ParserconfigurationexceptionparsingXMLfrom"+resource,ex);
- }
- catch(IOExceptionex){
- thrownewBeanDefinitionStoreException(resource.getDescription(),
- "IOExceptionparsingXMLdocumentfrom"+resource,ex);
- }
- catch(Throwableex){
- thrownewBeanDefinitionStoreException(resource.getDescription(),
- "UnexpectedexceptionparsingXMLdocumentfrom"+resource,ex);
- }
- }
其中的
- getEntityResolver()
会完成spring.schemas的装载,里面会间接调用源码段1。穿进去的entityResolver作为标签解析使用。
源码段4:dom树到Beandifinition:
在XmlBeanDefinitionReader.java的doLoadBeanDefinitions方法里,调用BeanDefinitionDocumentReader完成。
- publicintregisterBeanDefinitions(Documentdoc,Resourceresource)throwsBeanDefinitionStoreException{
- //SupportoldXmlBeanDefinitionParserSPIforbackwards-compatibility.
- if(this.parserClass!=null){
- XmlBeanDefinitionParserparser=
- (XmlBeanDefinitionParser)BeanUtils.instantiateClass(this.parserClass);
- returnparser.registerBeanDefinitions(this,doc,resource);
- }
- //ReaddocumentbasedonnewBeanDefinitionDocumentReaderSPI.
- BeanDefinitionDocumentReaderdocumentReader=createBeanDefinitionDocumentReader();
- intcountBefore=getRegistry().getBeanDefinitionCount();
- documentReader.registerBeanDefinitions(doc,createReaderContext(resource));
- returngetRegistry().getBeanDefinitionCount()-countBefore;
- }
具体细节这里不在累述。
总结
spring标签的扩展性做得还是不错的。在我们公司很多框架的一些通用配置都基于spring的声明式标签来实现。中间的一些约定和设计思想值得学习。本文很多代码细节没办法累述,有兴趣可以去看看。文章中若有不对的地方请大家指出,一起探讨。
后续的spring源码学习可能会是代理的具体实现,欢迎一起探讨!