1. 程式人生 > >Spring 手動註冊bean 定義

Spring 手動註冊bean 定義

手動註冊bean的兩種方式:

  • 實現ImportBeanDefinitionRegistrar
  • 實現BeanDefinitionRegistryPostProcessor

1. ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar其本質也是通過BeanDefinitionRegistryPostProcessor來實現的。實現ImportBeanDefinitionRegistrar比較簡單,也有多種方式,下面這個是最簡單的手動註冊bean的方式。

1.1 手動建立BeanDefinition

public
class Foo { public void foo() { System.out.println("Foo.foo() invoked!"); } } public class FooImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Foo.class); BeanDefinition beanDefinition = builder.getBeanDefinition(); registry.registerBeanDefinition("foo"
,beanDefinition); } } @SpringBootApplication @Import({FooImportBeanDefinitionRegistrar.class}) public class Demo implements CommandLineRunner { @Autowired private Foo foo; public static void main(String[] args) throws Exception { SpringApplication.run(Demo.class, args); } @Override
public void run(String... args) throws Exception { foo.foo(); } }

輸出:
Foo.foo() invoked!

1.2 ClassPathBeanDefinitionScanner

藉助spring類ClassPathBeanDefinitionScanner來掃描Bean並註冊

public class Foo {

    public void foo() {
        System.out.println("Foo.foo() invoked!");
    }
}

public class FooImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
        scanner.addIncludeFilter(new AssignableTypeFilter(Foo.class));
        scanner.scan("com.study.demo.domain");
        //這裡也可以掃描自定義註解並生成BeanDefinition並註冊到Spring上下文中
    }
}

@SpringBootApplication
@Import({FooImportBeanDefinitionRegistrar.class})
public class Demo implements CommandLineRunner {

    @Autowired
    private Foo foo;

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Demo.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        foo.foo();
    }
}

輸出:
Foo.foo() invoked!

1.3 ImportBeanDefinitionRegistrar

在上一個例子中我們直接使用的是ClassPathBeanDefinitionScanner,我們也可以繼承ImportBeanDefinitionRegistrar並重寫相關方法(一般都是掃描自定義的註解,才會繼承ClassPathBeanDefinitionScanner)。這裡你可以掃描自定義的註解,生成BeanDefinition(或其代理)的定義,然後註冊到spring容器中。實現程式碼可以參考Mybatis的
org.mybatis.spring.annotation.MapperScannerRegistrar類,Mybatis掃描Mapper註解,通過MapperFactoryBean來代理了Mapper,並將其註冊到spring中

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}

一般繼承ClassPathBeanDefinitionScanner需要實現這三個方法(並不絕對)

  • registerFilters
  • doScan
  • isCandidateComponent

2. BeanDefinitionRegistryPostProcessor

實現BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法。在spring原始碼中@Configuration @Import等配置類的註解就是通過這種方式實現的,具體實現方式可以參考spring原始碼

org.springframework.context.annotation.ConfigurationClassPostProcessor

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);

        processConfigBeanDefinitions(registry);
    }

另外一個可參考的樣例是Mybatis,在Mybatis中MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,在這個方法中註冊bean的方式同1.3

org.mybatis.spring.mapper.MapperScannerConfigurer

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }