自订条码能说是spring提供更多的最小、最强大的Hook(钳子),浅显的讲是给先期爸爸妈妈合作开发模块,提供更多两个国际标准公用可拔插”USB”,我们能认知为智能手机插座的轻工业国际标准口,为的是方便快捷各插座供货商制造,而制订的国际标准。
Spring导出条码有三个方式,上一则听完预设条码导出后,接下去步入parseCustomElement方式重新认识下怎样导出自订条码。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 推论重新命名内部空间是不是自订 与http://www.springframework.org/schema/beans对照 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)) { // 对bean的处置 parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { // 自订重新命名内部空间 delegate.parseCustomElement(root); }}这里,首先看
http://www.springframework.org/schema/beans/spring-beans.xsd这里如果使用自订条码,那么根据readerContext会调用namespace-HandlerResolver的resolve拿到对应的导出器NamespaceHandler,调用NamespaceHandler的parse方式。
public BeanDefinition parseCustomElement(Element ele, @NullableBeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } // 根据重新命名内部空间寻找对应的NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error(“Unable to locate Spring NamespaceHandler for XML schema namespace [“ + namespaceUri + “]”, ele); return null; } returnhandler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}这里其实如果点开NamespaceHandlerSupport的实现类我们可知:AOP扩展解析入口为AopNamespaceHandlers,声明式事务tx导出入口为:TxNamespaceHandler,Context导出入口为:ContextNames-paceHandler ,接下去我们看下resolve方式自订条码怎样被导出。
public NamespaceHandler resolve(String namespaceUri) { Map<String, Object> handlerMappings = getHandlerMappings(); ObjecthandlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceofNamespaceHandler) { // 如果已经导出 从缓存读取 return (NamespaceHandler) handlerOrClassName; } else { // 没有导出 则返回的是类的路径 String className = (String) handlerOrClassName; // 使用反射转化为类Class<?> handlerClass = ClassUtils.forName(className,this.classLoader); if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { throw newFatalBeanException(“Class [“ + className + “] for namespace [“ + namespaceUri + “] does not implement the [“+ NamespaceHandler.class.getName() +“] interface”); } // 初始化类NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); // 调用自订的init方式 namespaceHandler.init(); // 记录在缓存中handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; }}能看到,在处置重新命名内部空间url的时候,首先会推论是否存在当前url的处置逻辑,不存在则直接返回。如果存在,则会推论其为两个NamespaceHandler对象,还是两个全路径的类名,是Namespace-Handler对象则强制类型转换后返回,否则通过反射初始化该类,并调用其初始化方式,然后才返回。这里还有两个getHandlerMappings方式,我们大概能猜测到,这里是拿到所有映射的配置关系:
privateMap<String, Object> getHandlerMappings() { Map<String, Object> handlerMappings = this.handlerMappings; // 如果不为空才加载 if(handlerMappings ==null) { synchronized (this) { handlerMappings = this.handlerMappings; if(handlerMappings ==null) { try { // 导出META-INF/spring.handlers的配置加载到handlerMappings中 Properties mappings =PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);handlerMappings = new ConcurrentHashMap<>(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } catch (IOException ex) { throw new IllegalStateException( “Unable to load NamespaceHandler mappings from location [“ + this.handlerMappingsLocation + “]”, ex); } } } } return handlerMappings;}这里借助了PropertiesLoaderUtils工具类对属性handlerMappings-Location进行了配置文件的读取,而handlerMappingsLocation被预设构造初始化为META-INF/spring.handlers。
这里拿到导出器后,这里会调用NamespaceHandlerSupport的parse方式:
publicBeanDefinitionparse(Element element, ParserContext parserContext) {BeanDefinitionParser parser = findParserForElement(element, parserContext); return (parser != null ? parser.parse(element, parserContext) : null);}这里使用findParserForElement方式找到对应的导出器,然后调用其parse方式 ,这里的parser也即我们定义的BeanDefinitionParser.parse()方式,比如aop的实现AopNamespaceHandler实现:
public void init() { // In 2.0 XSD as well as in 2.1 XSD.registerBeanDefinitionParser(“config”, new ConfigBeanDefinitionParser()); registerBeanDefinitionParser(“aspectj-autoproxy”, new AspectJAutoProxyBeanDefinitionParser()); registerBeanDefinitionDecorator(“scoped-proxy”, newScopedProxyBeanDefinitionDecorator()); // Only in 2.0 XSD: moved to context namespace as of 2.1registerBeanDefinitionParser(“spring-configured”, new SpringConfiguredBeanDefinitionParser());}我们注册的导出器通过元素的后缀名,拿到对应的导出器,我们步入findParserForElement方式查看:
privateBeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal( “Cannot locate BeanDefinitionParser for element [“ + localName + “]”, element); } return parser;}对于parse方式的处置,这里我们能查看AbstractBeanDefinition-Parser的预设实现,
publicfinal BeanDefinition parse(Element element, ParserContext parserContext) { // 实质性导出操作 AbstractBeanDefinition definition = parseInternal(element, parserContext); if(definition !=null && !parserContext.isNested()) { String id = resolveId(element, definition, parserContext); String[] aliases = null; if (shouldParseNameAsAliases()) { Stringname = element.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } } // 包装为BeanDefinitionHolder对象并注册 BeanDefinitionHolder holder = newBeanDefinitionHolder(definition, id, aliases); registerBeanDefinition(holder, parserContext.getRegistry()); if (shouldFireEvents()) { // 通知监听器处置 BeanComponentDefinition componentDefinition = newBeanComponentDefinition(holder); postProcessComponentDefinition(componentDefinition);parserContext.registerComponent(componentDefinition); } } } return definition;}这里的导出方式又再次委托给了parseInternal实现,这里做的最多的工作则是将AbstractBeanDefinition包装成BeanDefinitionHolder属性,最后通过registerBeanDefinition注册bean的属性,点进parseInternal可知:
protectedfinal AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { // 生成承载对象BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); StringparentName = 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));BeanDefinition containingBd = parserContext.getContainingBeanDefinition(); if (containingBd != null) { // Inner bean definition must receive same scope as containing bean. builder.setScope(containingBd.getScope()); } if (parserContext.isDefaultLazyInit()) { // Default-lazy-init applies to custom bean definitions as well. builder.setLazyInit(true); } // 调用子类实现的导出方式doParse(element, parserContext, builder); return builder.getBeanDefinition();}这里parseInternal也不是直接调用导出方式,而是做了一些承载准备,包括对class、scope、lazyinit等,最后调用了子类的doParse方式。
实战操作
这里还是讲一下怎样实现自订条码导出,看十遍不如敲一遍,这里大概步骤为:
1.自订两个UserBeanDefinitionParser集成自AbstractSingleBean-DefinitionParser覆写doParse方式用于导出所有自订条码的属性,覆写getBeanClass方式用于获取类型。
2.自订两个MyNameSpaceHander集成自NamespaceHandler-Support用于注册UserBeanDefinitionParser,注册导出信息。
3.编写两个POJO,用于接收配置文件的属性,get/set方式略
public class User { private String userName; private String email;}4.编写两个xsd文件,描述模块的内容
<?xml version=”1.0″ encoding=”UTF-8″?><schema xmlns=“http://www.w3.org/2001/XMLSchema” targetNamespace=“http://www.example.org/schema/user” xmlns:tns=“http://www.example.org/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>5.编写Spring.handlers和Spring.schemas文件,预设位置为META-INF/文件夹下,当然,也能修改源代码到你想设置的位置
Spring.handlers
http\://www.example.org/schema/user=org.springframework.mytests.custom.MyNameSpaceHanderSpring.schemas
http\://www.example.org/schema/myxsd.xsd=META-INF/myxsd.xsd分别指定MyNameSpaceHander的位置和xsd文件的位置即可,Spring导出自订条码的流程大概是到Spring.handlers和Spring.schemas找到对应的Hander和对应的xsd文件,最后根据Hander找到对应的Parser,调用其doparse方式导出参数。
6.编写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.example.org/schema/user” xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.example.org/schema/user http://www.example.org/schema/myxsd.xsd”> <myname:user id = “testbean” userName = “tao” email = “626683392@qq.com”/></beans>BeanFactory factory = newXmlBeanFactory(new ClassPathResource(“myname_test.xml”));User bean = (User)factory.getBean(“testbean”);System.out.println(bean);控制台能看到:
User{userName=tao, email=626683392@qq.com}总结
这里回顾一下Spring自订自订Bean的导出过程,首先Spring通过重新命名内部空间推论是不是自订条码,然后通过NamespaceHandlerResolver找到对应的重新命名内部空间导出器,这里预设会去读取META-INF/spring.handlers的文件映射信息,如果找到对应的Handler,通过Handler拿到对应的导出器,调用其parse方式,对于parse方式,Spring有其预设的实现,实质上还做了一些初始化信息,帮助我们完成承载和封装,我们只需要实现doParse方式实现我们自己的逻辑即可。
总之,Spring为自订条码做了很多努力,我们在接下去AOP及TX事务中还会见到它,感受它的强大。