资讯专栏INFORMATION COLUMN

Spring解密 - 自定义标签与解析

Taste / 3493人阅读

摘要:自定义标签在讲解自定义标签解析之前,先看下如何自定义标签定义文件定义一个文件描述组件内容声明命名空间值得注意的是与可以是不存在,只要映射到指定就行了。

Spring是一个开源的设计层面框架,解决了业务逻辑层和其他各层的松耦合问题,将面向接口的编程思想贯穿整个系统应用,同时它也是Java工作中必备技能之一...

前言

在 上一节 Spring解密 - 默认标签的解析 中,重点分析了 Spring默认标签是如何解析的,那么本章继续讲解标签解析,着重讲述如何对自定义标签进行解析。

自定义标签

在讲解 自定义标签解析 之前,先看下如何自定义标签

定义 XSD 文件

定义一个 XSD 文件描述组件内容




    

    
        
            
                
                    
                
            
        
    

声明命名空间: 值得注意的是 xmlnstargetNamespace 可以是不存在,只要映射到指定 XSD 就行了。

定义复合元素: 这里的 application 就是元素的名称,使用时

定义元素属性: 元素属性就是 attribute 标签,我们声明了一个必填的 name 的属性,使用时

定义解析规则

1.创建一个类实现 BeanDefinitionParser 接口(也可继承 Spring 提供的类),用来解析 XSD 文件中的定义和组件定义

public class ApplicationBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    @Override
    protected Class getBeanClass(Element element) {
        // 接收对象的类型 如:String name = (String) context.getBean("battcn");
        return String.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        // 在 xsd 中定义的 name 属性
        String name = element.getAttribute("name");
        bean.addConstructorArgValue(name);
    }
}

这里创建了一个 ApplicationBeanDefinitionParser 继承 AbstractSingleBeanDefinitionParser(是:BeanDefinitionParser 的子类), 重点就是重写的 doParse,在这个里面解析 XML 标签的,然后将解析出的 value(Levin) 通过构造器方式注入进去

2.创建一个类继承 NamespaceHandlerSupport 抽象类

public class BattcnNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("application", new ApplicationBeanDefinitionParser());
    }

}

BattcnNamespaceHandler 的作用特别简单,就是告诉 Spring 容器,标签 应该由那个解析器解析(这里是我们自定义的:ApplicationBeanDefinitionParser),负责将组件注册到 Spring 容器

3.编写 spring.handlersspring.schemas 文件

文件存放的目录位于 resources/META-INF/文件名

spring.handlers
http://www.battcn.com/schema/battcn=com.battcn.handler.BattcnNamespaceHandler
spring.schemas
http://www.battcn.com/schema/battcn.xsd=battcn.xsd

4.使用自定义标签

申明 bean.xml 文件,定义如下




    

创建一个测试类,如果看到控制台输出了 Levin 字眼,说明自定义标签一切正常

public class Application {

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        String name = (String) context.getBean("battcn");
        System.out.println(name);

    }
}

5.如图所示

源码分析
自定义标签解析入口
public class BeanDefinitionParserDelegate {

    @Nullable
    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
        // 获取命名空间地址 http://www.battcn.com/schema/battcn
        String namespaceUri = getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        }
        // NamespaceHandler 就是 自定义的 BattcnNamespaceHandler 中注册的 application
        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));
    }

}

与默认标签解析规则一样的是,都是通过 getNamespaceURI(Node node) 来获取命名空间,那么 this.readerContext.getNamespaceHandlerResolver() 是从哪里获取的呢?我们跟踪下代码,可以发现在项目启动的时候,会在 XmlBeanDefinitionReader 将所有的 META-INF/spring.handles 文件内容解析,存储在 handlerMappers(一个ConcurrentHashMap) 中,在调用 resolve(namespaceUri) 校验的时候在将缓存的内容提取出来做对比

public class XmlBeanDefinitionReader {

    public NamespaceHandlerResolver getNamespaceHandlerResolver() {
        if (this.namespaceHandlerResolver == null) {
            this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
        }
        return this.namespaceHandlerResolver;
    }

}
resolve

1.加载指定的 NamespaceHandler 映射,并且提取的 NamespaceHandler 缓存起来,然后返回

public class DefaultNamespaceHandlerResolver {

    @Override
    @Nullable
    public NamespaceHandler resolve(String namespaceUri) {
        Map handlerMappings = getHandlerMappings();
        // 从 handlerMappings 提取 handlerOrClassName
        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);
                // Handler 初始化
                namespaceHandler.init();
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
            catch (ClassNotFoundException ex) {
                throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "] not found", ex);
            }
            catch (LinkageError err) {
                throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "]: problem with handler class file or dependent class", err);
            }
        }
    }

}
标签解析

加载完 NamespaceHandler 之后,BattcnNamespaceHandler 就已经被初始化为 了,而 BattcnNamespaceHandler 也调用了 init() 方法完成了初始化的工作。因此就接着执行这句代码: handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); 具体标签解。

public class NamespaceHandlerSupport {

    @Override
    @Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        return (parser != null ? parser.parse(element, parserContext) : null);
    }

    @Nullable
    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        // 解析出  中的  application
        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;
    }


}

简单来说就是从 parsers 中寻找到 ApplicationBeanDefinitionParser 实例,并调用其自身的 doParse 方法进行进一步解析。最后就跟解析默认标签的套路一样了...

总结

熬过几个无人知晓的秋冬春夏,撑过去一切都会顺着你想要的方向走...

说点什么

全文代码:https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter2

个人QQ:1837307557

battcn开源群(适合新手):391619659

微信公众号:battcn(欢迎调戏)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68242.html

相关文章

  • Spring解密 - 默认标签解析

    Spring是一个开源的设计层面框架,解决了业务逻辑层和其他各层的松耦合问题,将面向接口的编程思想贯穿整个系统应用,同时它也是Java工作中必备技能之一... 前言 紧跟上篇 Spring解密 - XML解析 与 Bean注册 ,我们接着往下分析源码 解密 在 Spring 的 XML 配置里面有两大类声明,一个是默认的如 ,另一类就是自定义的如,两种标签的解析方式差异是非常大的。parseBe...

    snowLu 评论0 收藏0
  • Spring解密 - XML解析 Bean注册

    摘要:解密是注册及加载的默认实现,整个模板中它可以称得上始祖。中是这样介绍的自动装配时忽略给定的依赖接口,比如通过其他方式解析上下文注册依赖,类似于通过进行的注入或者通过进行的注入。解析是资源文件读取解析注册的实现,要重点关注该类。 Spring是一个开源的设计层面框架,解决了业务逻辑层和其他各层的松耦合问题,将面向接口的编程思想贯穿整个系统应用,同时它也是Java工作中必备技能之一......

    cncoder 评论0 收藏0
  • Spring Cloud 参考文档(Spring Cloud Config Server)

    摘要:,这是标记配置文件集版本化的服务器端特性。要配置对称密钥,需要将设置为秘密字符串或使用环境变量将其排除在纯文本配置文件之外。 Spring Cloud Config Server Spring Cloud Config Server为外部配置提供基于HTTP资源的API(名称—值对或等效的YAML内容),通过使用@EnableConfigServer注解,服务器可嵌入Spring Bo...

    harryhappy 评论0 收藏0
  • Spring Cloud 参考文档(Spring Cloud Context:应用程序上下文服务)

    摘要:它们的优先级低于或以及作为创建应用程序过程的正常部分添加到子级的任何其他属性源。为引导配置类使用单独的包名称,并确保或注解的配置类尚未涵盖该名称。在这种情况下,它会在刷新时重建,并重新注入其依赖项,此时,它们将从刷新的重新初始化。 Spring Cloud Context:应用程序上下文服务 Spring Boot有一个关于如何使用Spring构建应用程序的主见,例如,它具有通用配置文...

    魏明 评论0 收藏0
  • API数据加密框架monkey-api-encrypt

    摘要:相比之前的变化内置加密算法,可以配置不同的加密不再绑定,通过配置即可使用加解密框架也可以支持支持用户自定义加密算法地址示例代码没有发布到中央仓库,只发布到这个仓库,大家也可以自行下载源码打包传到自己公司的私服上。 之前有写过一篇加密的文章《前后端API交互如何保证数据安全性》。主要是在Spring Boot中如何对接口的数据进行自动加解密操作,通过注解的方式来指定是否需要加解密。 原理...

    BetaRabbit 评论0 收藏0

发表评论

0条评论

Taste

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<