Spring
中正逐漸采用注解方式取代XML
配置方式,所以,使用XML
配置的機會正越來越少。然后,如果你開發的工具模塊可能會被很多系統使用,考慮到兼容性問題,就需要提供XML
方式集成,這時就需要自定義標簽;還有,你在看一些開源源碼時,一般也是提供自定義標簽方式集成。所以,還是可以去了解一下自定義標簽實現。
在Spring
中使用自定義標簽還是比較簡單,下面我們就實現一個自定義標簽
,其功能類似
標簽:將指定包路徑下帶有指定注解的Bean
掃描注冊。
【資料圖】
1、首先,在resources/META-INF
目錄下定義一個xsd
文件,描述自定義
標簽屬性:
2、自定義NamespaceHandler
,注冊
使用CustomScannerBeanDefinitionParser
解析器進行處理:
public class ScannerNameSpaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("scan", new CustomScannerBeanDefinitionParser()); }}
3、自定義CustomScannerBeanDefinitionParser
解析器:
public class CustomScannerBeanDefinitionParser extends AbstractBeanDefinitionParser { @Override protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CustomScannerConfigurer.class); ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); try { String annotationClassName = element.getAttribute("annotation"); if (StringUtils.hasText(annotationClassName)) { Class extends Annotation> annotationClass = (Class extends Annotation>) classLoader .loadClass(annotationClassName); builder.addPropertyValue("annotationClass", annotationClass); } } catch (Exception ex) { XmlReaderContext readerContext = parserContext.getReaderContext(); readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause()); } builder.addPropertyValue("basePackage", element.getAttribute("base-package")); return builder.getBeanDefinition(); }}
parseInternal()
方法解析標簽,然后生成一個BeanDefinition
,Spring
會自動將其注冊到IoC
容器中。如果標簽只會注冊單個Bean
,這里是需要返回注冊Bean
對應的BeanDefinition
即可;如果是多個情況,這里一般是注冊一個配置類,將標簽配置的屬性注入到配置類中,然后由配置類統一處理。
4、自定義CustomScannerConfigurer
配置類:
public class CustomScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean { private String basePackage; private Class extends Annotation> annotationClass; @Override public void afterPropertiesSet() throws Exception { //參數校驗 notNull(this.basePackage, "Property "basePackage" is required"); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false); scanner.addIncludeFilter(new AnnotationTypeFilter(annotationClass)); scanner.setIncludeAnnotationConfig(false); int beanCount = scanner.scan(basePackage); registry.getBeanDefinitionNames(); } public String getBasePackage() { return basePackage; } public void setBasePackage(String basePackage) { this.basePackage = basePackage; } public Class extends Annotation> getAnnotationClass() { return annotationClass; } public void setAnnotationClass(Class extends Annotation> annotationClass) { this.annotationClass = annotationClass; }}
CustomScannerConfigurer
實現了BeanDefinitionRegistryPostProcessor
, InitializingBean
兩個接口,之前分析過這兩個接口。重點在BeanDefinitionRegistryPostProcessor
這個接口,其是一個BeanFactoryPostProcessor
類型擴展,可以向IoC
容器注冊BeanDefinition
。在postProcessBeanDefinitionRegistry()
方法中創建一個ClassPathBeanDefinitionScanner
對象,并將標簽中配置設置進去,即可實現掃描指定包路徑下帶有指定注解的Bean
。
5、xsd
是標簽描述文件,NamespaceHandler
則是標簽后臺處理邏輯入口,現在需要將兩者進行關聯,在resources/META-INF
目錄下創建兩個文件:Spring.schemas
和Spring.handlers
,分別指定xsd
文件位置和NamespaceHandler
位置,這樣就實現了標簽和后臺邏輯關聯,其內容見下:
Spring.schemashttp\://www.simon.org/schema/scan.xsd=META-INF/custom-scan.xsd
Spring.handlershttp\://www.simon.org/schema/scan=customschema.demo03.ScannerNameSpaceHandler
自定義標簽描述以及對于的后臺處理邏輯都配置完成,下面我們就開始進行測試。
1、首先,定義個注解,用于在掃描Bean
時過濾使用:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Indexedpublic @interface MyComponent { String value() default "";}
2、在customschema.demo03.bean
包路徑下定義三個類:TestService01
、TestService02
、TestService03
,將后面兩個類使用@MyComponent
注解標注下;
3、編寫Spring
的Xml
配置文件,這里就可以使用我們剛才自定義的標簽:
4、測試用例:
@Testpublic void test01() { ApplicationContext context = new ClassPathXmlApplicationContext("custom-schema.xml"); Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);}
從輸出結果就可以看到,TestService01
由于沒有帶有@MyComponent
注解,所以沒有注冊,TestService02
和TestService03
都會被注冊到容器中。
關鍵詞: