在很多情況下,我們需要爲系統提供可配置化支持。因此 spring 提供了可擴展 schema 的支持,擴展spring 自定義標籤配置大致需要以下幾個步驟:
- 創建一個需要擴展的組件
- 定義一個 XSD 文件描述組件內容
- 創建一個文件,實現 BeanDefinitionParser 接口,用於解析 XSD 文件中的定義和組件定義
- 創建一個 Handler 文件,擴展自 NamespaceHandlerSupport,目的是將組件註冊到 Spring 容器
- 編寫 spring.handlers 和 spring.schemas 文件
接下就按照上面的步驟來看看 spring 自定義標籤的整個過程。
1.解析自定義標籤
爲了擴展 xml 配置文件的標籤,spring 提供了一個機制用於對自定義的元素進行解析。
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 通過 w3c 獲取節點所在的 namespace URI
String namespaceUri = getNamespaceURI(ele);
// 解析 namespace URI,獲取 NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 調用自定義的 NamespaceHandler 進行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
方法只做了三件事:
- 獲取節點所屬的 namespace URI
- 解析 namespace URI 得到 NamespaceHandler
- 調用 NamespaceHandler.parse() 方法解析自定義標籤
那麼問題來了,NamespaceHandler 是什麼?官方文檔是這樣定義的:NamespaceHandler 是 spring 解析在 xml 配置文件配置的自定義標籤的基本接口。因此可以知道我們遇到如 <context: componenet-scan /> 這樣的標籤時,會實現 NamespaceHandler 接口,可是 spring 怎麼知道我自己的 NamespaceHandler 呢?並且 spring 在解析時怎麼驗證標籤配置是否正確呢?是的,我們需要在 META-INF 目錄添加兩個文件:spring.schemas 和 spring.handlers。spring.schemas 告知 spring 標籤的 xsd 文件在哪;spring.handlers 告知 spring 解析標籤的 NamespaceHandler 的全路徑名。形式分別如下:
http://www.springframework.org/schema/context/spring-context-4.3.xsd=org/springframework/context/config/spring-context-4.3.xsd
http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
從名字上看 NamespaceHandler 是處理 namespace 的,它並不直接用於解析自定義標籤,它底層會委託給 BeanDefinitionParser 接口。那麼 BeanDefinitionParser 又是在哪註冊的呢?我們在看 NamespaceHandler 接口的文檔時,注意到它的 init() 方法,**init() 方法是在解析自定義標籤之前被調用。**因此我們可以在 init() 方法裏面註冊我們自定義的 BeanDefinitionParser 接口。
我們拿 <context: component-scan /> 舉例子,看 spring 本身怎麼做的。在上面說明 spring.schemas 和 spring.handlers 文件時就是拿它作爲示例,因此就不再說了,同時也可以看到,此標籤的 NamespaceHandler 是 org.springframework.context.config.ContextNamespaceHandler。
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
// 對 componenet-scan 的解析
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
// ...省略字段定義
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
// ...省略其他方法
}
所以瞭解其工作原理之後,發現也並不負載,主要 Spring 幫我們定義好了開發標準,我們只需要按照它的約定去實現我們自己的邏輯就可以。如果看過 dubbo 源碼的肯定也知道,dubbo 跟 spring 集成,也是因爲 dubbo 定義了自己的 DubboNamespaceHandler,註冊了對各個標籤的解析器。