拓展Spring-xml标签,自定义个性化标签
背景
最近看一个项目,在spirng配置文件中,引用了非正常标签,上面配置几个参数,启动后就可以在别的bean中注入了
于是就引发了我的好奇心,决定一探究竟
加载过程
- spring在启动的时候,读取配置文件的标签,根据标签找到xml文件头上定义的namespace,xmlns
- 根据xmlns配置的值,到xsi:schemaLocation中找到自己对应的url路径 和 xsd路径
- spring会默认到META-INF下去找spring.handlers和spring.schemas
- spring.handlers中会配置一个Handler类,此类决定如何去处理这个标签
- spring.schemas中指定xsd文件的所在位置,xsd文件是对一个标签的表述,以及对一些字段的验证
- 在Handler中指定标签用什么parser来对标签内容的解析,创建bean
实践
创建一个xsd文件
配置文件写的很精简,没有过多的描述,详细的可以参照spirng的配置文件,复制出来改一改
核心点是这个 targetNamespace 它的值表示了这个xsd的地址
这个文件的位置默认放到META-INF下,也可以自己随意放置,在spring.schema中指定清楚就可以了
<?xml version="1.0" encoding="UTF-8"?>
<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 等(都可以,只是具体实现的方法不一样)
- 这里用了AbstractSingleBeanDefinitionParser来实现的,执行完doParse方法,这个类就被保存在了容器中了
- AbstractSimpleBeanDefinitionParser 是对 AbstractSingleBeanDefinitionParser 的继承
- 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的。
<?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: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());
}
}
问题
- 在xml中配置的时候id我写了1,但是在main方法中getBean时候我用了user,死活得不到bean,改用User.class就可以正常得到。郁闷死我了
- 在写xmlns的时候,没有理顺他们之间的关系,xmlns:xx 只针对于本文件有效,是为了区分一个xml中不同的命名空间。真正的用的是后面的值
- 其实这么初始化bean,只有在吃饱了撑了没事干的时候可以玩玩,正常项目中还是别干了,直接在xml定义一个bean需要的属性注入进去完事。完全没有必要搞的这么麻烦。