深入理解Spring IOC之扩展篇(一)、自定义xml标签

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 深入理解Spring IOC之扩展篇(一)、自定义xml标签

这个扩展篇,是基于我的深入理解Spring Ioc 系列写的,主要讲的是spring 装载解析bean这个过程中可以扩展的地方,可能你之前知道一些Spring 中的扩展点,但是却又缺乏一个整体的认识,那么相信我,看完了整个扩展篇,你就能把之前的不熟练的姿势运用的很熟练,并且还能学会很多新的姿势。

我们通过之前正篇前两篇文章,知道了Spring Ioc的标签其实只有四个,那就是beans,bean,import,alias,其他的甚至是aop的,以及componentScan的其实都是自定义标签,只不过那是spring自己其他模块扩展的而已,今天在这篇文章里,我会给大家讲清怎样去定义自己xml标签。

在Spring中,我们定义一个自己的标签有如下步骤:

  • 自己定义一个XSD文件
  • 定义一个和XSD文件所对应的实体类
  • 创建实现了BeanDefinitionParser的类(其实更好的做法是继承抽象类AbstractBeanDefinitionParser),去解析我们的自定义标签
  • 创建一个继承了NamespaceHandlerSupport的类,去将我们上面创建的类注册到spring容器
  • 编写自己的Spring.handlers和Spring.schemas

我们先来看看自定义XSD文件:



<xsd:schema
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns="http://demo1.example.com/schema1"
        targetNamespace="http://demo1.example.com/schema1">
    <xsd:complexType name="billType">
        <xsd:attribute name="name" type="xsd:string">
        </xsd:attribute>
        <xsd:attribute name="age" type="xsd:int">
        </xsd:attribute>
    </xsd:complexType>
    <xsd:element name="bill" type="billType">
    </xsd:element>
</xsd:schema>


我们来看看这个xsd文件的东西吧。首先看到xsd:element这块,这里面的属性name就是我们以后标签的名字,type则指向了上面的标签xsd:complexType这里,这个标签里面有两个子标签都是xsd:attribute,一个代表string类型的name,另一个代表int类型的age,意思就是bill这个标签里面有name和age两个属性。再就是要注意最上面的几行,第二行的xmlns:xsd="www.w3.org/2001/XMLSch…"里面这个url你随便写,但是要和第四行的targetNamespace保持一致。

我们再看看第二步实体类:


@Data
public class Model {
    private String name;
    private Integer age;
}


相信这个是不需要我再多解释了。我们直接看第三步和第四步, 第三步创建的类:


public class BillBeanDefinitionParser implements BeanDefinitionParser {
    private final Class<?> beanClass;
    public BillBeanDefinitionParser(Class<?> beanClass) {
        this.beanClass = beanClass;
    }
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setBeanClass(beanClass);
        genericBeanDefinition.setLazyInit(false);
        genericBeanDefinition.getPropertyValues().add("name", element.getAttribute("name"));
        genericBeanDefinition.getPropertyValues().add("age", element.getAttribute("age"));
        parserContext.getRegistry().registerBeanDefinition(beanClass.getName(),genericBeanDefinition);
        return null;
    }
}


第四步创建的类


public class BillNameSpaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("bill",new BillBeanDefinitionParser(Model.class));
    }
}


相信看过我正文的这里已经不算什么了,然后我们再编写自己的Spring.handlers和Spring.schemas

Spring.Handlers:


http\://demo1.example.com/schema1=com.example.demo.external1.BillNameSpaceHandler


Spring.schemas:


http\://demo1.example.com/schema1/mytag.xsd=META-INF/mytag.xsd


这两个文件都是properties格式的文件,这两个文件和开头的那个xsd都要放在resource目录下的META-INF文件夹下,再注意Spring.Handlers中的key是要和上面xsd中你自己定义的xmlns一致,value一定要指向你自己定义的NameSpaceHandler的全路径,Spring.schemas中key前半部分是自己定义的xmlns,后半部分的mytag.xsd就是你自己xsd的文件名.

然后在application-context.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:billtag="http://demo1.example.com/schema1"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://demo1.example.com/schema1
        http://demo1.example.com/schema1/mytag.xsd">
    <!--    又是一个灰常简单的xml~  -->
    <bean id="aTest" class="com.example.demo.article2.entity.ATest" name="aTest">
    </bean>
    <billtag:bill name="bill.li" age="18"/>
    <import resource="classpath*:application-context1.xml"></import>
</beans>


最后跑个测试代码看看:


public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("classpath*:application-context.xml");
        Model model = (Model)applicationContext.getBean(Model.class.getName());
        System.out.println(model.getAge());
        System.out.println(model.getName());
    }



如果你跑通了这最后一步的代码,那么恭喜你,你已经可以自己定义一个灰常简单的xml标签了,为什么说是灰常简单的呢?因为我们自己定义的标签只有两个属性啊,并且还不能有子标签的,那么如果是想定义复杂的xml标签怎么搞呢?我来给大家教教学习的办法,我们回到我们的application-context.xml中去看第一行的xmlns,对着后边的http://www.springframework.org/schema/beans按ctrl可以点进去,我们在这里面可以找到关于beans标签的定义,剩下的就是照猫画虎去改我们的xsd就好了,然后记得我们自己的BeanDefinitionParser也要修改下就好了~。

然后还没完,我们需要补上正篇从xml到BeanDefinition那篇里面的坑,那里面代码块12说要在自定义标签这篇讲parseCustomElement,以便于让大家理解这里spring为我们做了什么事情,如果你把之前的代码看懂了之后那么这里的代码还是相对简单很多的:

我们先还原一下之前那篇从xml到BeanDefinition代码块12:


代码块1
        protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 就是在看标签中有没有"http://www.springframework.org/schema/beans",如果有,就走if
    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);
    }
  }


我们直接来看parseCustomElement这个方法.


代码块2
        // 先调的这个方法
  public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
  }
  //再调的这个
  public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    // 1.获取我们定义的namespace
    String namespaceUri = getNamespaceURI(ele);
    // 2。根据 namespaceUri 获取相应的 Handler
    // readerContext 其实就是XmlReadContext的实例
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    // 3.如果handler是null就报异常
    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));
  }


上面代码中最后获取到的这个handler的实例,就是我们之前定义的继承了NameSpaceHandlerSupport的类,这个类在parse方法中找到了我们定义的BeanDefinitionParser,然后调用了它的parse方法来解析我们的标签。我们再重点看看2处的代码,2中的这个readerContext是个什么呢?这玩意其实是个XmlReaderContext的实例,这个玩意是在之前的第三篇从xml到BeanDefinition那篇的代码块9中第4处调用XmlBeanDefinitionReader中的createReaderContext方法创建的,我们看看这个方法:


代码块3
        public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
        this.sourceExtractor, this, getNamespaceHandlerResolver());
  }


就只是很简单的一个new而已,我们来看看最后一个参数调用的方法,这个才是我们关注的重点,因为这个获取到的就是我们之前代码块中第二处拿到的那个NameSpaceHandlerResolver,我们通过追这个方法发现这个NameSpaceHandlerResolver其实是个DefaultNamespaceHandlerResolver,我们来看看它的resolve方法,就是代码块2第二处最后调用的resolve方法:


代码块4
public NamespaceHandler resolve(String namespaceUri) {
    // 获取到所有已经配置好的handlerMapping的映射
    Map<String, Object> handlerMappings = getHandlerMappings();
    // 根据nameSpace获取到对应的handler,用上面的Test代码debug的话,发现其实就是我们自己定义的Handler的
    // 全路径,也就是Spring.Handler里面配的
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
      return null;
    }
    // 这个else if就是看之前有没有使用过这个handler,如果有,就直接返回了
    else if (handlerOrClassName instanceof NamespaceHandler) {
      return (NamespaceHandler) handlerOrClassName;
    }
    // 没有的话,做初始化这个handler的工作
    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的init方法?
        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);
      }
    }
  }


再接下来的解析,就是我们之前讲的了,至此,自定义xml标签的定义和解析过程已分析完毕。


目录
相关文章
|
1月前
|
XML Java 数据格式
Spring从入门到入土(xml配置文件的基础使用方式)
本文详细介绍了Spring框架中XML配置文件的使用方法,包括读取配置文件、创建带参数的构造对象、使用工厂方法和静态方法创建对象、对象生命周期管理以及单例和多例模式的测试。
84 7
Spring从入门到入土(xml配置文件的基础使用方式)
|
19天前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
11天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
23 0
|
1月前
|
存储 开发框架 Java
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
文章详细介绍了Spring、IOC、DI的概念和关系,解释了控制反转(IOC)和依赖注入(DI)的原理,并提供了IOC的代码示例,阐述了Spring框架作为IOC容器的应用。
29 0
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
65 0
|
1月前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
45 0
|
Java 程序员 网络安全
spring4.1.8扩展实战之二:Aware接口揭秘
Aware.java是个没有定义任何方法的接口,拥有众多子接口,在spring源码中有多处都在使用这些子接口完成各种场景下的回调操作,当业务有需要时,我们只需创建类来实现相关接口,再声明为bean,就可以被spring容器主动回调
275 0
spring4.1.8扩展实战之二:Aware接口揭秘
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
163 2
|
9天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
20 2
 SpringBoot入门(7)- 配置热部署devtools工具