如何通过注解的方式动态创建Spring Bean对象

358 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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<StringObject> 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的动态注入;