Spring解析applicationContext.xml中自定义标签-03

spring通过ClassPathXmlApplicationContext解析applicationContext.xml的自定义标签解析过程

解析类DefaultBeanDefinitionDocumentReader.java中delegate.parseCustomElement(ele)这个方法用来解析自定义,为了方便理解整个解析过程,先在xml配置文件中加入一个非常常用的自定义标签context:component-scan,通过跟踪这个标签的解析过程,从而理解整个自定义标签的解析过程,进入BeanDefinitionParserDelegate类中该方法的实现:

    //spring版本为5.3
	public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	   //1.获取到元素的namespaceUri
       //component-scan标签的uri=http://www.springframework.org/schema/context
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}
		//2.根据传入的namespaceUri进行解析
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

步骤一: 获取到元素对应的namespaceUri,对于context:component-scan标签namespaceUri=http://www.springframework.org/schema/context,这个值就是在xml配置文件里配置的

在这里插入图片描述
步骤二:NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);传入刚才拿到的uri的值进行解析,首先调用this.readerContext.getNamespaceHandlerResolver()获取DefaultNamespaceHandlerResolver对象,在这个类的构造方法中传入了一个文件路径META-INF/spring.handlers。
在这里插入图片描述
以context:component-scan为例,找到context这个自定义注解的所在位置,打开spring-context-5.3.3.jar中的META-INF/spring.handlers,文件中保存了NamespaceUri和Handler处理类的对应关系,因此DefaultNamespaceHandlerResolver这个类的作用就是用来解析和收集NamespaceUri和对应Handler的映射关系。
在这里插入图片描述
进入resolve方法,在方法里的第一行加一个断点,使用调试方式启动,在DefaultNamespaceHandlerResolver类中维护了一个handlerMappings,观察里面的值发现,Spring将所有依赖包中的META-INF/spring.handlers里配置的映射关系都维护进了这个Map,这是Spring使用Spi机制加载自定义标签的方式,所有实现自定义标签解析的Handler都需要在META-INF/spring.handlers文件中进行配置,这样Spring在启动的时候就会把所有的Handler都维护到handlerMappings中。

在这里插入图片描述

	public NamespaceHandler resolve(String namespaceUri) {
	    //根据namespaceUri从Map中获取到对应的handler,获取到的是handler类全路径的字符串
		Map<String, Object> handlerMappings = getHandlerMappings();
		//handlerOrClassName是字符串类型,因此前面两个条件都不会进,只会进入这个条件分支
       //以Component-scan为例,此时的className是“org.springframework.context.config.ContextNamespaceHandler”
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
			String className = (String) handlerOrClassName;
			try {
			    //获取Handler的Class类型
				Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
				if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
					throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
							"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
				}
				//依据Class创建Handler实例,这一步就创建出一个ContextNamespaceHandler对象
				NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
				//这一步会调用ContextNamespaceHandler的init方法,每个Handler都会实现自己的init方法
				namespaceHandler.init();
				handlerMappings.put(namespaceUri, namespaceHandler);
				return namespaceHandler;
			}
			catch (ClassNotFoundException ex) {
				throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
						"] for namespace [" + namespaceUri + "]", ex);
			}
			catch (LinkageError err) {
				throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
						className + "] for namespace [" + namespaceUri + "]", err);
			}
		}
	}

在resolve方法中,调用了namespaceHandler.init()方法,这是父类提供的抽象方法,由不同的子类具体实现。进入ContextNamespaceHandler,在init方法中对标签中所有冒号(:)后面的属性都对应了一个解析器,这里就看到了熟悉的component-scan、property-placeholder等。而this.registerBeanDefinitionParser()方法的逻辑也很简单,就是把name和parser的对应关系加入到其父类维护的一个parsers集合中,集合类型是这样的:private final Map<String, BeanDefinitionParser> parsers = new HashMap()
在这里插入图片描述
在完成了init初始化操作后,程序接着调用了handlerMappings.put(namespaceUri, namespaceHandler)将NamespaceUri和Handler实例重新维护进handlerMappings中,把之前维护的namespaceUri和handler的全类名覆盖掉了,最终将namespaceHandler实例返回。继续回到BeanDefinitionParserDelegate.parseCustomElement()方法,通过上面对resolve方法的分析,该方法返回了一个namespaceHandler实例,调用namespaceHandler.parse()方法完成对注解的解析,这个方法同样是抽象方法,由子类NamespaceHandlerSupport去做具体实现,我们跟到方法中

NamespaceHandlerSupport
 
@Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //该方法通过传入的element对象返回对应的parser实例
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        return parser != null ? parser.parse(element, parserContext) : null;
    }    
 
@Nullable
    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        //获取到标签的名字,例如<component-scan>标签,获取到的localName就是“component-scan”
        String localName = parserContext.getDelegate().getLocalName(element);
        //从parsers集合中通过localName获取到对应的Parser实例
        BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
 
        return parser;
    }

首先调用this.findParserForElement(element, parserContext)方法获取到对应的parser对象。我们跟踪到这个方法中可以发现,就是从parsers集合中通过标签名称获取到对应的parser对象,parsers集合已经提到过,就是上面在调用init方法时维护的那个集合对象。

获取到parser对象后,调用parse方法。所有的parser最终都会实现BeanDefinitionParser接口。因此,所有的parser类都会重写parse方法,并在parse方法中实现自身的标签解析的逻辑。

上面对整个自定义标签的解析流程进行了记录,总结起来主要是以下流程:

1)在META-INF/spring.handlers文件中定义NamespaceUri和Handler的映射关系,以便Spring在启动的时候能够扫描到这个映射关系。

2)在Handler实现类中统一继承NamespaceHandlerSupport父类,并实现init()方法,在init()方法中为每一个标签名创建一个对应的Parser,并将其对应关系维护到父类的parsers集合中。

3)每一个Parser类都需要实现接口BeanDefinitionParser,并实现parse方法,在parse方法中实现具体的解析逻辑。

通过对自定义注解整体流程的分析,似乎自己实现一个自定义注解也很简单,来试一下。

第一步,在自己的代码工程下的classpath下创建一个META-INF文件夹,并在里面创建需要的文件,spring.handlers、spring.schema和custom-pring.xsd(这个文件是可以随意命名的)。第一个文件不用说了,上面已经提到过很多次了,第二个文件维护了xsd location和具体xsd文件的映射关系,打开这个文件:

https\://www.springframework.org/schema/context/spring-test.xsd=META-INF/custom-print.xsd

看到里面的内容就应该明白了,前面的key就是在spring配置文件的xsi:schemaLocation里配置的xsd的信息,后面的value指定的就是上面第三个文件,那不用说了,第三个文件就是对xml格式的限定文件。

<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.springframework.org/schema/test"
        elementFormDefault="qualified">
 
    <element name="print">
        <complexType>
            <attribute name="id" type="string"/>
            <attribute name="xmlPath" type="string"/>
        </complexType>
    </element>

第二步,在spring.handlers文件中定义NamespaceUri和Handler的映射关系,并创建Handler类继承NamespaceHandlerSupport,这里随便定义了一个TestParserHandler。

http\://www.springframework.org/schema/test=com.sgcc.uvmp.config.TestParserHandler

第三步,在TestParserHandler中,实现init方法,为每一个标签名都定义一个Parser类,并维护对应关系。这里只定义了一个名称为"print"的标签和它的Parser类PrintBeanDefinitionParser

public class TestParserHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        this.registerBeanDefinitionParser("print", new PrintBeanDefinitionParser());
    }
 
}

第四步,创建PrintBeanDefinitionParser类实现BeanDefinitionParser接口,实现parse方法,在方法中实现具体的业务逻辑。这个自定义的parser逻辑很简单,就是读取出配置文件配置的属性,填充到一个对应的类(PrintXml)中,并为这个类创建了一个BeanDefinition对象,加入到BeanDefinitionRegistry容器中,这样同样可以实现通过Spring管理这个类实例。

public class PrintBeanDefinitionParser implements BeanDefinitionParser {
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //获取标签中的属性
        String id = element.getAttribute("id");
        String xmlPath = element.getAttribute("xmlPath");
        //创建一个BeanDefinition对象,把需要实例化的类的class名填充到bd中
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setBeanClass(PrintXml.class);
        genericBeanDefinition.setBeanClassName(PrintXml.class.getName());
        //将对应的属性名称和属性值填充到bd的MutablePropertyValues对象中,这样在实例化这个类的时候,就会把对应的属性值填充到类实例中。
        genericBeanDefinition.getPropertyValues().add("id" , id);
        genericBeanDefinition.getPropertyValues().add("xmlPath" , xmlPath);
        //将beanName和bd对象注册到BeanDefinitionRegsitry容器中,这样Spring在做Bean的实例化的时候就会扫描到这个类,并把它进行实例化。        
parserContext.getRegistry().registerBeanDefinition("printXml" , genericBeanDefinition);
        return null;
    }
}

这样,一个自定义注解就完成了,和使用Spring提供的自定义注解方式相同,需要在spring的配置文件中对其进行配置即可。

<?xml version="1.0" encoding="UTF-8"?>
<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:test="http://www.springframework.org/schema/test"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        <!--这里是自己定义的NamespaceUri-->
        http://www.springframework.org/schema/test
        <!--这里是自己定义的xsd的namespaceuri-->
        https://www.springframework.org/schema/context/spring-test.xsd">
    
    <!--自己定义的标签就可以这样使用了,逻辑比较简单,只是在里面定义了两个属性,和parser里获取的那两个属性是可以对应上的-->
    <test:print id="hello" xmlPath="world"></test:print>
</beans>

来进行一下测试,可以看到PrintXml这个类我们并没有在Spring的配置文件里配置,在类上面也没有任何注解,但是通过context.getBean(PrintXml.class)是可以获取到这个类的实例的,说明自定义标签已经生效。

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        PrintXml printXml = context.getBean(PrintXml.class);
        System.out.println(printXml);

这样我们就自己定义了一个标签,如果需要实现更加复杂的业务逻辑,就只在Parser类中的parse方法中做相应的实现即可,在下一节中,我们将进入component-scan的Parser类,详细记录Spring通过context:component-scan标签进行包扫描的全过程。
————————————————

原文链接:https://blog.csdn.net/eighthspace/article/details/112960963

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值