在很多情况下,我们需要为系统提供可配置化支持,简单的做法可以直接基于Spring的标准 bean 来配置,但配置较为复杂或者需要更多丰富控制的时候,会显得非常笨拙。一般的做法会用原生态的方式去解析定义好的XML文件,然后转化为配置对象。这种方式当然可以解决所有问题,但实现起来比较繁琐,特别是在配置非常复杂的时候,解析工作是一个不得不考虑的负担。Spring提供了可扩展Schema的支持,这是一个不错的折中方案,扩展Spring自定义标签配置大致需要以下几个步骤(前提是要把Spring的Core包加入项目中)。
- 创建一个需要扩展的组件。
- 定义一个XSD文件描述组件内容。
- 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义。
- 创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。
- 编写Spring.handlers和Spring.schemas文件。
现在我们就按照上面的步骤带领读者一步步地体验自定义标签的过程。
简单例子
- 定义一个对象User ,带解析
public class User {
private String userName;
private String email;
...
}
- 对象解析器UserBeanDefinitionParser
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
//Element对应的类
protected Class getBeanClass(Element element) {
return User.class;
}
//从element中解析并提取对应的元素
protected void doParse(Element element, BeanDefinitionBuilder bean) {
String userName = element.getAttribute("userName");
String email = element.getAttribute("email");
//将提取的数据放入到BeanDefinitionBuilder中,待到完成所有bean的解析后统一注册到beanFactory中
if (StringUtils.hasText(userName)) {
bean.addPropertyValue("userName", userName);
}
if (StringUtils.hasText(email)) {
bean.addPropertyValue("email", email);
}
}
}
- 注册解析器MyNamespaceHandler
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
- xsd定义,user.xsd
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lexueba.com/schema/user"
xmlns:tns="http://www.lexueba.com/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>
- spring.schemas定义
http\://www.lexueba.com/schema/user.xsd=META-INF/user.xsd
- spring.handlers
···
http://www.lexueba.com/schema/user=com.jc.MyNamespaceHandler
··· - 使用user.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:myname="http://www.lexueba.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.lexueba.com/schema/user
http://www.lexueba.com/schema/user.xsd">
<myname:user id="testbean" userName="aaa" email="bbb"/>
</beans>
- 调用
@Test
public void customerUserLoad() throws IOException {
ApplicationContext bf = new ClassPathXmlApplicationContext ("user.xml");
User user=(User) bf.getBean("testbean");
System.out.println(user.getUserName()+","+user.getEmail());
}
- 运行结果
aaa,bbb
源码分析
我们上面定义了很多东西,目标就是能实现以下定义,来初始化User对象
<myname:user id="testbean" userName="aaa" email="bbb"/>
我们首先分析,根据命名空间,获取handler
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
@Override
public NamespaceHandler resolve(String namespaceUri) {
//通过 "META-INF/spring.handlers"定义
Map<String, Object> handlerMappings = getHandlerMappings();
//获取相应handler ClassName
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
....
}
else if (handlerOrClassName instanceof NamespaceHandler) {
....
}
else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
//实例化,并调用init方法,
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
//init方法,主要注册我们的parser,下一步使用
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);
}
}
}
}
根据handler获取parse解析器
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
//这里的parsers在前面init方法注册了。获得解析器。
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
}
根据解析器调用parse,并后置处理
public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser {
@Override
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = parseInternal(element, parserContext);
...
return definition;
}
}
public abstract class AbstractSingleBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
....
//调用定义的doParse方法。
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
doParse(element, builder);
}
}
这里parseInternal,会调用AbstractSingleBeanDefinitionParser 类的parseInternal是因为我们定义的解析器继承的这个类AbstractSingleBeanDefinitionParser
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser
网友评论