• Spring源码分析-扩展点-配置文件自定义标签


    配置文件自定义标签

    说明

    自定义标签的意思是,在Spring的配置文件中(例如:applicationContext.xml)加入自己定义的标签,同时加入处理类,让IOC容器启动时可以自动解析到beanFactory中。

    代码出处

    在ioc容器初始化过程中,会调用类(XmlBeanDefinitionReader.java)的下面这个方法。在这个方法的(createReaderContext(resource))中会初始化上下文。同时会确定配置文件地址。

    //org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java
        public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
            // 对xml的beanDefinition进行解析
            BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
            int countBefore = getRegistry().getBeanDefinitionCount();
            // 完成具体的解析过程,createReaderContext这个方法会读取配置文件,读出不同命名空间对应的处理类
            documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
            return getRegistry().getBeanDefinitionCount() - countBefore;
        }
    
    	/**
    	 * 接着进入getNamespaceHandlerResolver()这个方法
    	 */
    	public XmlReaderContext createReaderContext(Resource resource) {
    		return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
    				this.sourceExtractor, this, getNamespaceHandlerResolver());
    	}
    
    	/**
    	 * 第一次进来肯定为空,所以进入createDefaultNamespaceHandlerResolver()
    	 */
    	public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    		if (this.namespaceHandlerResolver == null) {
    			this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
    		}
    		return this.namespaceHandlerResolver;
    	}
    
    	/**
    	 * 进到这个方法后,配置文件路径确定
    	 */
    	protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    		ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
    		return new DefaultNamespaceHandlerResolver(cl);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在下面的这个parseCustomElement方法,会解析非默认命名空间的配置项。这里面会使用上下文调用resolve方法找到命名空间对应的处理类。针对不同命名空间调用不同类的方法来解析。

    //org.springframework.beans.factory.xml.BeanDefinitionParserDelegate	
    	public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    		// 获取对应的命名空间
    		String namespaceUri = getNamespaceURI(ele);
    		if (namespaceUri == null) {
    			return null;
    		}
    		// 根据命名空间找到对应的Namespace Handler
    		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    		if (handler == null) {
    			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
    			return null;
    		}
    		// 调用自定义的NamespaceHandler进行解析
    		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    	}
    
    //org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver
    	public NamespaceHandler resolve(String namespaceUri) {
    		// 获取所有已经配置好的handler映射
    		Map<String, Object> handlerMappings = getHandlerMappings();
    		// 根据命名空间找到对应的信息
    		Object handlerOrClassName = handlerMappings.get(namespaceUri);
    		if (handlerOrClassName == null) {
    			return null;
    		}
    		else if (handlerOrClassName instanceof NamespaceHandler) {
    			// 如果已经做过解析,直接从缓存中读取
    			return (NamespaceHandler) handlerOrClassName;
    		}
    		else {
    			// 没有做过解析,则返回的是类路径
    			String className = (String) handlerOrClassName;
    			try {
    				// 通过反射将类路径转化为类
    				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");
    				}
    				// 实例化类
    				NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
    				// 调用自定义的namespaceHandler的初始化方法
    				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);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    步骤

    例如:我们自定义一个标签

    1. 定义一个实体类User

    2. 写一个类继承AbstractSingleBeanDefinitionParser,并覆盖父类方法。

    3. 写一个类继承NamespaceHandlerSupport,覆盖父类方法。可参考ContextNamespaceHandler。

      1. 上述步骤参考代码

      2. /**
        * 1.实体类,用于承载自定义标签中的信息
        */
        public class User {
            private String username;
            private String email;
            private String password;
        
            public String getUsername() {
                return username;
            }
        
            public void setUsername(String username) {
                this.username = username;
            }
        
            public String getEmail() {
                return email;
            }
        
            public void setEmail(String email) {
                this.email = email;
            }
        
            public String getPassword() {
                return password;
            }
        
            public void setPassword(String password) {
                this.password = password;
            }
        }
        
        /**
         * 自定义标签分析器。
        * 不继承AbstractPropertyLoadingBeanDefinitionParser是因为,我们标签中暂时不需要location,properties-ref等等属性。 */
        public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { /** * 返回属性值所对应的对象 * * @param element the {@code Element} that is being parsed * @return */ @Override protected Class<?> getBeanClass(Element element) { return User.class; } /** * 标签解析方法。负责解析标签的自定义属性。 * * @param element the XML element being parsed * @param builder used to define the {@code BeanDefinition} */ @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { String userName = element.getAttribute("userName"); String email = element.getAttribute("email"); String password = element.getAttribute("password"); if (StringUtils.hasText(userName)) { builder.addPropertyValue("username", userName); } if (StringUtils.hasText(email)) { builder.addPropertyValue("email", email); } if (StringUtils.hasText(password)) { builder.addPropertyValue("password", password); } } } /** * 3.自定义命名空间处理类。参考ContextNamespaceHandler */ public class UserNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("user",new UserBeanDefinitionParser()); } }
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57
        • 58
        • 59
        • 60
        • 61
        • 62
        • 63
        • 64
        • 65
        • 66
        • 67
        • 68
        • 69
        • 70
        • 71
        • 72
        • 73
        • 74
        • 75
        • 76
        • 77
        • 78
        • 79
        • 80
        • 81
        • 82
        • 83
    4. 在项目配置文件目录(resources/META-INF/)中,新增文件spring.handlers,加入处理类的映射。

      1. 注意:在idea中创建这个文件时,要确保他是properties类型的。不能是txt类型的。
      2. http\://www.test.com/schema/user=com.test.selftag.UserNamespaceHandler
    5. 在项目配置文件目录(resources/META-INF/)中,新增文件spring.schemas,加入命名空间和xsd的映射。

      1. 注意:在idea中创建这个文件时,要确保他是properties类型的。不能是txt类型的。
      2. http\://www.test.com/schema/user.xsd=META-INF/user.xsd
    6. 在项目配置文件目录(resources/META-INF/)中,新增文件user.xsd。

      1. 
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                targetNamespace="http://www.test.com/schema/user"
                xmlns:tns="http://www.test.com/schema/user"
                elementFormDefault="qualified">
            <element name="user">
                <complexType>
                    <attribute name ="id" type = "string"/>
                    <attribute name ="userName" type = "string"/>
                    <attribute name ="email" type = "string"/>
                    <attribute name ="password" type="string"/>
                complexType>
            element>
        schema>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
    7. Spring配置文件中,使用我们的自定义标签。

      1. applicationContext.xml

      2. 
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:context="http://www.springframework.org/schema/context"
               xmlns:test="http://www.test.com/schema/user"
               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
                http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd
                http://www.test.com/schema/user http://www.test.com/schema/user.xsd">
        
        	<test:user id="testTag" username="lisi" email="lisi@163.com" password="123456">test:user>
        
            <bean id="person"  class="com.test.Person" scope="prototype">
                <property name="id" value="1">property>
                <property name="name" value="zhangsan">property>
            bean>
        beans>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
    8. 写一个容器启动测试类。测试刚才的自定义标签。

      1. public class Test {
            public static void main(String[] args) {
         		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
                User user = (User) ac.getBean("testTag");
                System.out.println(user);
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
    9. 问题排查。

      1. 如果报错assert short name !=key,可能是docs.gradle中的这段代码引起的,把它注释掉就行了。

      2. task schemaZip(type: Zip) {
        	group = "Distribution"
        	archiveBaseName.set("spring-framework")
        	archiveClassifier.set("schema")
        	description = "Builds -${archiveClassifier} archive containing all " +
        			"XSDs for deployment at https://springframework.org/schema."
        	duplicatesStrategy DuplicatesStrategy.EXCLUDE
        	moduleProjects.each { module ->
        		def Properties schemas = new Properties();
        
        		module.sourceSets.main.resources.find {
        			(it.path.endsWith("META-INF/spring.schemas") || it.path.endsWith("META-INF\\spring.schemas"))
        		}?.withInputStream { schemas.load(it) }
        
        //把下面的代码注释。
        //		for (def key : schemas.keySet()) {
        //			def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1')
        //			assert shortName != key
        //			File xsdFile = module.sourceSets.main.resources.find {
        //				(it.path.endsWith(schemas.get(key)) || it.path.endsWith(schemas.get(key).replaceAll('\\/','\\\\')))
        //			}
        //			assert xsdFile != null
        //			into (shortName) {
        //				from xsdFile.path
        //			}
        //		}
        	}
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28

    应用场景

    暂无。工作中很少用到。

  • 相关阅读:
    【C++】STL — vector的使用 + 模拟实现
    HTTP协议解析
    Springboot整合Flowable6.x导出bpmn20
    限时开源,一份“扭转乾坤”的与时俱进的1700页Java八股文
    明远智睿IMX6Q烧写Ubuntu18系统,此方法同样适合其它系统!
    Hutool工具说明和使用步骤
    Werkzeug的Map
    mysql安装与配置及四大引擎和数据类型、建表以及约束、增删改查、常用函数、聚合函数以及合并
    Spring Security(6)
    Python 教程之控制流(9)Python 中的 Switch Case(替换)
  • 原文地址:https://blog.csdn.net/namelessmyth/article/details/132888866