在很多情況下,我們需要為系統(tǒng)提供可配置化支持,簡單的做法可以直接基于Spring的標(biāo)準(zhǔn) bean 來配置,但配置較為復(fù)雜或者需要更多豐富控制的時(shí)候,會顯得非常笨拙。一般的做法會用原生態(tài)的方式去解析定義好的XML文件,然后轉(zhuǎn)化為配置對象。這種方式當(dāng)然可以解決所有問題,但實(shí)現(xiàn)起來比較繁瑣,特別是在配置非常復(fù)雜的時(shí)候,解析工作是一個不得不考慮的負(fù)擔(dān)。Spring提供了可擴(kuò)展Schema的支持,這是一個不錯的折中方案,擴(kuò)展Spring自定義標(biāo)簽配置大致需要以下幾個步驟(前提是要把Spring的Core包加入項(xiàng)目中)。
- 創(chuàng)建一個需要擴(kuò)展的組件。
- 定義一個XSD文件描述組件內(nèi)容。
- 創(chuàng)建一個文件,實(shí)現(xiàn)BeanDefinitionParser接口,用來解析XSD文件中的定義和組件定義。
- 創(chuàng)建一個Handler文件,擴(kuò)展自NamespaceHandlerSupport,目的是將組件注冊到Spring容器。
- 編寫Spring.handlers和Spring.schemas文件。
現(xiàn)在我們就按照上面的步驟帶領(lǐng)讀者一步步地體驗(yàn)自定義標(biāo)簽的過程。
簡單例子
- 定義一個對象User ,帶解析
public class User {
private String userName;
private String email;
...
}
- 對象解析器UserBeanDefinitionParser
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
//Element對應(yīng)的類
protected Class getBeanClass(Element element) {
return User.class;
}
//從element中解析并提取對應(yīng)的元素
protected void doParse(Element element, BeanDefinitionBuilder bean) {
String userName = element.getAttribute("userName");
String email = element.getAttribute("email");
//將提取的數(shù)據(jù)放入到BeanDefinitionBuilder中,待到完成所有bean的解析后統(tǒng)一注冊到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>
- 調(diào)用
@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());
}
- 運(yùn)行結(jié)果
aaa,bbb
源碼分析
我們上面定義了很多東西,目標(biāo)就是能實(shí)現(xiàn)以下定義,來初始化User對象
<myname:user id="testbean" userName="aaa" email="bbb"/>
我們首先分析,根據(jù)命名空間,獲取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();
//獲取相應(yīng)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);
//實(shí)例化,并調(diào)用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);
}
}
}
}
根據(jù)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;
}
}
根據(jù)解析器調(diào)用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();
....
//調(diào)用定義的doParse方法。
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
doParse(element, builder);
}
}
這里parseInternal,會調(diào)用AbstractSingleBeanDefinitionParser 類的parseInternal是因?yàn)槲覀兌x的解析器繼承的這個類AbstractSingleBeanDefinitionParser
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser