zoukankan      html  css  js  c++  java
  • 自定义标签解析

    1 示例

    自定义标签的实现:

    • XSD文件、META-INF/spring.schemas文件
    • 自定义标签的解析类:BeanDefinitionParser
    • 注册自定义标签解析类:NamespaceHandlerSupport、META-INF/spring.handlers

    1.1 自定义标签

    1. POJO

    public class UserPO {
        private String userName;
        private String email;
        // setter,getter
    }
    

    2. 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"
           xmlns:myname="http://www.lexueba.com/schema/user"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.lexueba.com/schema/user http://www.lexueba.com/schema/user.xsd">
           
       <myname:user id="userPO" email="test@163.com" userName="zxt" />
    </beans>
    

    1.2 自定义XSD文件

    1. XSD文件

    该文件位置:META-INF/customer_user.xsd

    <?xml version="1.0" encoding="UTF-8"?>
    <schema xmlns="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.lexueba.com/schema/user"
            xmlns:tns="http://www.lexueba.com/schema/user"
            elementFormDefault="qualified">
       <element name="user">
          <complexType>
             <attribute name="id" type="string" />
             <attribute name="userName" type="string" />
             <attribute name="email" type="string" />
          </complexType>
       </element>
    </schema>
    

    2. spring.schemas文件

    http://www.lexueba.com/schema/user.xsd=META-INF/custom_user.xsd
    

    1.3 解析自定义标签

    实现BeanDefinitionParsr接口用于解析自定义标签。这里推荐使用AbstractSingleBeanDefinitionParser类,内容如下:

    public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
        @Override
        protected void doParse(Element element, BeanDefinitionBuilder bean) {
            String userName = element.getAttribute("userName");
            String email = element.getAttribute("email");
    
            if (StringUtils.hasText(userName)) {
                bean.addPropertyValue("userName", userName);
            }
            if (StringUtils.hasText(email)) {
                bean.addPropertyValue("email", email);
            }
        }
        
        @Override
        protected Class getBeanClass(Element element) {
            return UserPO.class;
        }
    }
    

    1.4 注册解析类

    1. spring.handlers

    该文件用于根据XML文件中自定义标签的命名空间URI找到对应的

    http://www.lexueba.com/schema/user=wolfdriver.learn.spring.bean.customXml.UserNamespaceHandler
    

    2. NamespaceHandlerSupport

    推荐继承NamespaceHandlerSupport抽象类完成。代码如下:

    public class UserNamespaceHandler extends NamespaceHandlerSupport {
        @Override
        public void init() {
            registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
        }
    }
    

    2. 源码分析

    2.1 EntityResolver用法

    在Spring中使用SAX读取XML文件。在加载、解析XML文件的过程中SAX会验证XML文档的规范性(DTD或XSD模式)。对于DTD或XSD文件的读取,默认寻找规则是网络下载,利用EntityResolver接口我们可以自定义XML文档规范声明的寻找方式。改接口定义如下:

    package org.xml.sax;
    import java.io.IOException;
    
    /**
     * Basic interface for resolving entities.
     * <pre>
     * public class MyResolver implements EntityResolver {
     *   public InputSource resolveEntity (String publicId, String systemId) {
     *     if (systemId.equals("http://www.myhost.com/today")) {
     *              // return a special input source
     *       MyReader reader = new MyReader();
     *       return new InputSource(reader);
     *     } else {
     *              // use the default behaviour
     *       return null;
     *     }
     *   }
     * }
     * </pre>
     * @since SAX 1.0
     * @see org.xml.sax.XMLReader#setEntityResolver
     * @see org.xml.sax.InputSource
     */
    public interface EntityResolver {
        public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
    }
    
    

    注意:
    该接口入参:publicId和systemId。

    • XSD文件
    <?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:myname="http://www.lexueba.com/schema/user"
           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

    • DTD文件
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//Spring//DTO BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">
    

    publicId: -//Spring//DTO BEAN 2.0//EN-//Spring//DTO BEAN 2.0//EN
    systemId: http://www.Springframework.org/dtd/Spring-beans-2.0.dtd

    2.2 Spring利用EntityResolver寻找XSD并校验

    XmlBeanDefinitionReader类中通过getEntityResolver()方法获取EntityResolverEntityResolver接口的默认实现类DelegatingEntityResolver

    public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
        protected EntityResolver getEntityResolver() {
    		if (this.entityResolver == null) {
    			ResourceLoader resourceLoader = getResourceLoader();
    			if (resourceLoader != null) {
    				this.entityResolver = new ResourceEntityResolver(resourceLoader);
    			} else {
    				this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
    			}
    		}
    		return this.entityResolver;
    	}
    

    DelegatingEntityResolver类以委托的形式,内部分别封装了dtd和xsd文件的EntityResolver的实习类,如下:

    public class DelegatingEntityResolver implements EntityResolver {
    	public static final String DTD_SUFFIX = ".dtd";
    	public static final String XSD_SUFFIX = ".xsd";
    
    	private final EntityResolver dtdResolver;
    	private final EntityResolver schemaResolver;
    	
    	public DelegatingEntityResolver(ClassLoader classLoader) {
    		this.dtdResolver = new BeansDtdResolver();
    		this.schemaResolver = new PluggableSchemaResolver(classLoader);
    	}
    	// 省略其他构造器
    
        /*根据systemId判断具体使用哪个EntityResolver解析器进行查找。*/
    	@Override
    	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;
    	}
    

    由上述代码可知,XSD使用PluggableSchemaResolver进行XSD文件的查找。具体代码如下:

    public InputSource resolveEntity(String publicId, String systemId) throws IOException {
    	if (systemId != null) {
    		String resourceLocation = getSchemaMappings().get(systemId);
    		if (resourceLocation != null) {
    			Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
    			try {
    				InputSource source = new InputSource(resource.getInputStream());
    				source.setPublicId(publicId);
    				source.setSystemId(systemId);
    				return source;
    			} catch (FileNotFoundException ex) {/*省略日志*/}
        	}
    	}
    	return null;
    }
    /* schemaMappingsLocation:META-INF/spring.schemas*/
    private Map<String, String> getSchemaMappings() {
    	if (this.schemaMappings == null) {
    		synchronized (this) {
    			if (this.schemaMappings == null) {
    				try {
    					Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
    					Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
    					CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
    					this.schemaMappings = schemaMappings;
    				} catch (IOException ex) { /*异常处理*/}
    			}
    		}
    	}
    	return this.schemaMappings;
    }
    

    由此可以知道,XSD可以通过META-INF/spring.schemas文件的方式进行配置。之后SAX就可以寻找到本地配置的XSD文件来校验XML中标签是否符合规范。

    2.3 解析自定义标签代码

    2.3.1 解析自定义标签

    在解析XML文件的过程中,Spring会对标签进行分类解析:

    • 解析Spring默认标签
    • 解析自定义标签

    Spring标签解析的方法委托给了DefaultBeanDefinitionDocumentReader类。

    public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
        public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    		this.readerContext = readerContext;
    		Element root = doc.getDocumentElement();
    		doRegisterBeanDefinitions(root);
    	}
    	/**
    	 * 注意,在解析所有标签的前后都有可扩展的前后置方法。 
    	 */
    	protected void doRegisterBeanDefinitions(Element root) {
    		//省略...
    		preProcessXml(root);    // 前置方法
    		parseBeanDefinitions(root, this.delegate);
    		postProcessXml(root);   // 后置方法
    		this.delegate = parent;
    	}
    	/**
    	 * 该方法解析、注册标签的核心方法。 
    	 */
    	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);
    		}
    	}
    }
    

    2.3.2 NamespaceHandler解析自定义标签

    由最终的解析方法可知,若判断Element为自定义标签,则将解析委托给BeanDefinitionParserDelegate进行操作。

    public BeanDefinition parseCustomElement(Element ele) {
    	return parseCustomElement(ele, null);
    }
    // 获取element对应的namespaceUri,并根据namespaceUri找到对应的NamespaceHandler
    // 由代码可知,自定义标签的解析工作,由NamespaceHandler的parse()方法完成
    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    	String namespaceUri = getNamespaceURI(ele);
    	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    	if (handler == null) {
    		return null;
    	}
    	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }
    

    方法this.readerContext.getNamespaceHandlerResolver()将获取DefaultNamespaceHandlerResolver类对象,该对象的resolve方法如下所示:

    public NamespaceHandler resolve(String namespaceUri) {
        // 注意:该方法将会获取
    	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);
    			NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
    			// 此处执行init()方法,将会把自定义标签的解析类注册到Spring
    			namespaceHandler.init();
    			handlerMappings.put(namespaceUri, namespaceHandler);
    			return namespaceHandler;
    		} catch () {}
    	}
    }
    
    private Map<String, Object> getHandlerMappings() {
    	if (this.handlerMappings == null) {
    		synchronized (this) {
    			if (this.handlerMappings == null) {
    				try {
    				    // handlerMappingsLocation="META-INF/spring.handlers"
    					Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
    					Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
    					CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
    					this.handlerMappings = handlerMappings;
    				}catch (IOException ex) {}
    			}
    		}
    	}
    	return this.handlerMappings;
    }
    

    2.4 NamespaceHandler类介绍

    执行完自定义的NamespaceHandler的init方法,通常是注册标签解析器

    public class UserNamespaceHandler extends NamespaceHandlerSupport {
        @Override
        public void init() {
            registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
        }
    }
    

    如下为NamespaceHandlerSupport类的实现。通过该方法的parse()方法直接使用对应的自定义解析类解析自定义标签。

    public abstract class NamespaceHandlerSupport implements NamespaceHandler {
        // 自定义标签 以及其对应的解析类
    	private final Map<String, BeanDefinitionParser> parsers = new HashMap<String, BeanDefinitionParser>();
    	// 将自定义标签和解析类绑定
    	protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
    		this.parsers.put(elementName, parser);
    	}
    	// 从parsers中获取element对应的解析器进行解析。
    	@Override
    	public BeanDefinition parse(Element element, ParserContext parserContext) {
    		return findParserForElement(element, parserContext).parse(element, parserContext);
    	}
    
    	private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    		String localName = parserContext.getDelegate().getLocalName(element);
    		BeanDefinitionParser parser = this.parsers.get(localName);
    		return parser;
    	}
    }
    

    2.5 BeanDefinitionParser类

    类图结构如下:

    代码AbstractBeanDefinitionParser代码如下:

    public final BeanDefinition parse(Element element, ParserContext parserContext) {
        // parseInternal()方法具体解析属性
    	AbstractBeanDefinition definition = parseInternal(element, parserContext);
    	if (definition != null && !parserContext.isNested()) {
    		try {
    			String id = resolveId(element, definition, parserContext);
    			String[] aliases = null;
    			// 是否应该解析ele的"name"属性作为bean的别名
    			if (shouldParseNameAsAliases()) {
    				String name = element.getAttribute(NAME_ATTRIBUTE);
    				if (StringUtils.hasLength(name)) {
    					aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
    				}
    			}
    			BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
    			// 将解析的BeanDefinition注册到Spring容器
    			registerBeanDefinition(holder, parserContext.getRegistry());
    			/* 是否应该在解析bean之后发送一个BeanComponentDefinition事件*/
    			if (shouldFireEvents()) {
    				BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
    				postProcessComponentDefinition(componentDefinition);
    				parserContext.registerComponent(componentDefinition);
    			}
    		} catch (BeanDefinitionStoreException ex) { }
    	}
    	return definition;
    }
    

    AbstractSingleBeanDefinitionParser类的parseInternal()方法

    protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
    	String parentName = getParentName(element);
    	if (parentName != null) {
    		builder.getRawBeanDefinition().setParentName(parentName);
    	}
    	Class<?> beanClass = getBeanClass(element);
    	if (beanClass != null) {
    		builder.getRawBeanDefinition().setBeanClass(beanClass);
    	} else {
    		String beanClassName = getBeanClassName(element);
    		if (beanClassName != null) {
    			builder.getRawBeanDefinition().setBeanClassName(beanClassName);
    		}
    	}
    	builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
    	if (parserContext.isNested()) {
    		builder.setScope(parserContext.getContainingBeanDefinition().getScope());
    	}
    	if (parserContext.isDefaultLazyInit()) {
    		builder.setLazyInit(true);
    	}
    	doParse(element, parserContext, builder);
    	return builder.getBeanDefinition();
    }
    
  • 相关阅读:
    Eclipse Mars5.4.2集成scala IDE
    ajax请求无法下载文件
    Linux
    解决IE下URL传参中文乱码的问题
    jquery easyui datagrid.load方法参数传递问题 .
    Easyui设置datagrid自适应屏幕宽度的方法
    MapReduce方法的理解和遇到的问题总结
    hadoop操作中遇到的问题
    Maven中常见的问题
    jdk及mysql安装常见问题
  • 原文地址:https://www.cnblogs.com/wolfdriver/p/10520920.html
Copyright © 2011-2022 走看看