拓展Spring-xml标签,自定义个性化标签注入到Spring容器中

拓展Spring-xml标签,自定义个性化标签

    • 背景
    • 加载过程
    • 实践
      • 创建一个xsd文件
      • 创建一个bean类
      • 创建spring表述文件
      • Handler处理类
      • 标签解析器
      • 在配置中添加bean
      • 测试从容器中取到bean
    • 问题

背景

最近看一个项目,在spirng配置文件中,引用了非正常标签,上面配置几个参数,启动后就可以在别的bean中注入了
于是就引发了我的好奇心,决定一探究竟

加载过程

  1. spring在启动的时候,读取配置文件的标签,根据标签找到xml文件头上定义的namespace,xmlns
  2. 根据xmlns配置的值,到xsi:schemaLocation中找到自己对应的url路径 和 xsd路径
  3. spring会默认到META-INF下去找spring.handlers和spring.schemas
  4. spring.handlers中会配置一个Handler类,此类决定如何去处理这个标签
  5. spring.schemas中指定xsd文件的所在位置,xsd文件是对一个标签的表述,以及对一些字段的验证
  6. 在Handler中指定标签用什么parser来对标签内容的解析,创建bean

实践

创建一个xsd文件

配置文件写的很精简,没有过多的描述,详细的可以参照spirng的配置文件,复制出来改一改
核心点是这个 targetNamespace 它的值表示了这个xsd的地址
这个文件的位置默认放到META-INF下,也可以自己随意放置,在spring.schema中指定清楚就可以了


<schema xmlns="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
        targetNamespace="http://www.example.org/schema/user">

    <element name="user">
        <complexType>
            <attribute name="id" type="string" use="required"/>
            <attribute name="name" type="string"/>
            <attribute name="description" type="string"/>
        complexType>
    element>
schema>

创建一个bean类

这里用了lombok插件来简化代码量,这个类就是要用自定义标签创建的类

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

@Getter
@Setter
public class User implements Serializable {
    private String id;
    private String name;
    private String description;
}

创建spring表述文件

spring启动后,遇到不认识的标签,默认回去META-INF下去找spring.handlers和spring.schemas这两个文件

spring.handlers
application.xml中会表述出标签的地址,然后用到到handler文件中找到处理的类

http\://www.example.org/schema/user=com.bat.handler.UserNamespaceHandler

spring.schemas
application.xml中会表述xsd文件的地址,拿地址到schemas中找到xsd实际的位置

http\://www.example.org/schema/user.xsd=META-INF/user-1.0.xsd

Handler处理类

通过继承 NamespaceHandlerSupport ,在初始化init()方法中,可以指定对标签使用何种解析器,对于自定义很多标签来说,本类只需要创建一个,在init方法中添加多个parse即可

import com.bat.parser.UserBeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class UserNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("user",new UserBeanDefinitionParser());// 这里可以创建多个
    }
}

标签解析器

通过继承 AbstractSingleBeanDefinitionParser / AbstractSimpleBeanDefinitionParser / BeanDefinitionParser 等(都可以,只是具体实现的方法不一样)

  1. 这里用了AbstractSingleBeanDefinitionParser来实现的,执行完doParse方法,这个类就被保存在了容器中了
  2. AbstractSimpleBeanDefinitionParser 是对 AbstractSingleBeanDefinitionParser 的继承
  3. BeanDefinitionParser 是他们的父接口,如果实现这个类的parse方法,bean需要通过BeanDefinitionBuilder.rootBeanDefinition把这个bean构建出来,在用parserContext.getRegistry().registerBeanDefinition(id, client);把bean注到容器中
import com.bat.domain.User;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    @Override
    protected Class<?> getBeanClass(Element element) {
        return User.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String id = element.getAttribute("id");
        String name = element.getAttribute("name");
        String desc = element.getAttribute("description");

        if(StringUtils.isEmpty(id)){
            throw new RuntimeException("id is required");
        }
        builder.addPropertyValue("id",id);
        builder.addPropertyValue("name",name);
        builder.addPropertyValue("description",desc);
    }
}

在配置中添加bean

为什么在标签前加上tt(test)呢?
xmlns表示每一个标签的命名空间,每个标签都有自己的属性,默认情况下标签会去xmlns中查找。
多个xmlns是通过加上 冒号名称 [ :xxx ] 来对多个标签进行区分的,tt:user 会到上面找到 xmlns:tt 的地址
然后到xsi:schemaLocation中找到处理自己的handler和xsd的。


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tt="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/user.xsd">

    <tt:user id="user" name="caoke" description="consumer"/>

beans>

测试从容器中取到bean

容器启动,成功的得到了bean

import com.bat.domain.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationStart {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
		context.getBean(User.class);
		User user = (User) context.getBean("user");
		System.out.println(user.getId()+":"+user.getName()+":"+user.getDescription());
	}
}

问题

  1. 在xml中配置的时候id我写了1,但是在main方法中getBean时候我用了user,死活得不到bean,改用User.class就可以正常得到。郁闷死我了
  2. 在写xmlns的时候,没有理顺他们之间的关系,xmlns:xx 只针对于本文件有效,是为了区分一个xml中不同的命名空间。真正的用的是后面的值
  3. 其实这么初始化bean,只有在吃饱了撑了没事干的时候可以玩玩,正常项目中还是别干了,直接在xml定义一个bean需要的属性注入进去完事。完全没有必要搞的这么麻烦。

你可能感兴趣的:(java,Spring)