开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情
前言
在Spring项目当中,我们在做一些组件工具的时候,往往需要根据使用者传入的参数来动态创建Bean对象;比如Mybatis里面的@MapperScan需要使用者传入包名,这样Spring才能根据传入的Mapper包名来动态创建Mapper的代理对象;如果我们也想自己在项目当中实现根据参数动态创建Bean对象的功能应该怎么做呢?
模仿@MybatisScan
我们可以看一下@MybatisScan的源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
}
上述源码中,真正让@MapperScan起作用的就是@Import({MapperScannerRegistrar.class})这一段代码,MapperScannerRegistrar会读取注解中的属性值,并通过BeanDefinitionRegistry把相关类的BeanDefinition注册到容器中,这样就能够完成动态创建Bean的功能;
同样的,我们在实际开发中也可以仿照@MybatisScan这样来做;我们来举一个例子,我们为一个项目开发了多个验证码的实现方式,里面包括图片验证码、滑块验证码、短信验证码等等,但是在项目中不是所有验证码都会被使用到,很可能有的开发人员需要使用到图片验证码和短信验证码,有的开发人员只需要滑块验证码,这个时候我们就可以使用自定义注解来处理了;
首先我们自定义一个@EnableValidateCode注解:
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(ValidateCodeConfigSelector.class)
public @interface EnableValidateCode {
/**
* 验证码实现类
*
* @return
*/
Class<? extends AbstractValidateCodeProcessor>[] value() default {
ImageValidateCodeProcessor.class
};
}
value值对应的就是各种验证码的实现类,在使用的时候只需要通过@EnableValidateCode注解标注哪些实现类需要注入进来:
@EnableValidateCode(value = {SlideImageCodeProcessor.class, SmsValidateCodeProcessor.class})
上面是使用方式,开发人员只需要把用到的验证码实现类放进来,那么它就会被Spring管理起来,就可以被@Autowired拿来使用;
其实最关键的还是@Import(ValidateCodeConfigSelector.class)这一段代码,我们来看一下ValidateCodeConfigSelector是怎么实现的:
public class ValidateCodeConfigSelector implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取@EnableValidateCode的所有属性列表
Map<String, Object> attributeMap =
importingClassMetadata.getAnnotationAttributes(
EnableValidateCode.class.getName());
AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
// 取出value对应的class数组
Class<? extends AbstractValidateCodeProcessor>[] validateCodeProcessorClass =
(Class<? extends AbstractValidateCodeProcessor>[])
attributes.getClassArray("value");
// 依次通过BeanDefinitionRegistry注册到Spring中
if (ArrayUtils.isNotEmpty(validateCodeProcessorClass)) {
for (Class<? extends AbstractValidateCodeProcessor> clazz : validateCodeProcessorClass) {
registry.registerBeanDefinition(
clazz.getSimpleName(), new RootBeanDefinition(clazz));
}
}
// 通过注册一个验证码拦截器到Spring中,这样验证码才能生效
if (!registry.containsBeanDefinition("validateCodeFilter")) {
registry.registerBeanDefinition(
"validateCodeFilter", new RootBeanDefinition(ValidateCodeFilter.class));
}
}
}
通过上述代码,我们可以发现,其实最关键的就是实现ImportBeanDefinitionRegistrar这个类,它可以让我们通过注解的方式完成Bean的动态注入;