Spring中正逐渐采用注解方式取代 XML配置方式,所以,使用XML配置的机会正越来越少。然后,如果你开发的工具模块可能会被很多系统使用,考虑到兼容性问题,就需要提供 XML方式集成,这时就需要自定义标签;还有,你在看一些开源源码时,一般也是提供自定义标签方式集成。所以,还是可以去了解一下自定义标签实现。
在Spring中使用自定义标签还是比较简单,下面我们就实现一个自定义标签 <scan>,其功能类似<context:component-scan base-package="aa.bb"/>标签:将指定包路径下带有指定注解的 Bean扫描注册。
1、首先,在resources/META-INF目录下定义一个 xsd文件,描述自定义<scan>标签属性:
<?xml version="1.0" encoding="UTF-8"?><xsd:schema
xmlns="http://www.simon.org/schema/scan" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.simon.org/schema/scan" elementFormDefault="qualified">
<xsd:complexType name="scan">
<xsd:attribute name="id"
type="xsd:string"> <xsd:annotation>
<xsd:documentation> <![CDATA[ The unique identifier for a bean. ]]>
</xsd:documentation> </xsd:annotation>
</xsd:attribute> <xsd:attribute
name="annotation" type="xsd:string">
<xsd:annotation> <xsd:documentation> <![CDATA[ The unique identifier for a bean. ]]>
</xsd:documentation> </xsd:annotation>
</xsd:attribute> <xsd:attribute
name="base-package" type="xsd:string">
<xsd:annotation> <xsd:documentation> <![CDATA[ The price for a bean. ]]>
</xsd:documentation> </xsd:annotation>
</xsd:attribute> </xsd:complexType>
<xsd:element name="scan"
type="scan"> <xsd:annotation>
<xsd:documentation><![CDATA[ The service config ]]>
</xsd:documentation> </xsd:annotation>
</xsd:element></xsd:schema>
2、自定义NamespaceHandler,注册 <scan>使用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
@Indexed
public
@interface MyComponent {
String
value
()
default "";}
2、在 customschema.demo03.bean包路径下定义三个类: TestService01、 TestService02、 TestService03,将后面两个类使用 @MyComponent注解标注下;
3、编写 Spring的 Xml配置文件,这里就可以使用我们刚才自定义的标签:
<?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:custom=
"http://www.simon.org/schema/scan"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.simon.org/schema/scan http://www.simon.org/schema/scan.xsd">
<
custom:scan
id=
"scan"
annotation=
"customschema.demo03.MyComponent"
base-package=
"customschema.demo03.bean"/>
</
beans>
4、测试用例:
@Test
public
void
test01
() { ApplicationContext context =
new ClassPathXmlApplicationContext(
"custom-schema.xml"); Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);}
从输出结果就可以看到, TestService01由于没有带有 @MyComponent注解,所以没有注册, TestService02和 TestService03都会被注册到容器中。
长按识别关注, 持续输出原创
