天天看點

FactoryBean接口執行個體化

FactoryBean接口執行個體化
Java程式設計規範中聲明,Java接口類是不能直接執行個體化的,但是我們在平時的開發中經常會遇到隻聲明接口就可以直接使用的。

eg:

  1. Mybatis中隻用使用​

    ​@MapperScan​

    ​聲明要掃描的Mapper接口類就可以直接從Spring中擷取使用,進行操作資料庫
  2. Dubbo中隻要用Dubbo提供的​

    ​@Service​

    ​注解,同樣可以直接從Spring中擷取使用進行遠端調用。

那麼以上這些功能在Spring中是如何實作的呢?

由此就引出本篇主要介紹的接口​

​FactoryBean​

public interface FactoryBean<T> {
    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}      

1

2

3

4

5

6

7

8

9

10

11

12

在Spring中當發現一個Bean的類型是​

​FactoryBean​

​​,此時執行個體化時候就會執行,該對象的​

​getObject()​

​​方法進而來進行執行個體化。那麼如何擷取真實​

​FactoryBean​

​​呢?隻需要在執行個體化的Bean的name前面加​

​&​

​​符号才是擷取真正​

​FactoryBean​

​的執行個體對象。

Java程式設計規範中聲明,Java接口類是不能直接執行個體化的,Spring實作接口的執行個體化操作,本質上隻是調用​

​FactoryBean​

​​的​

​getObject()​

​​方法,而真正的執行個體化操作,還是有開發者來實作的。以我們常見的使用架構為例。​

​MyBatis​

​​ and ​

​Dubbo​

  • MyBatis中實作FactoryBean的類​

    ​MapperFactoryBean​

  • Dubbo中實作FactoryBean的類​

    ​ReferenceBean​

在此我們以MyBatis為例,講述MyBatis是如何實作FactoryBean來實作接口執行個體化操作的。 對Spring的源碼有研究的同學知道,在Spring中Bean的讀取會生成BeanDefinition對象,執行個體化實際就是找到Bean對象的BeanDefinition對象,然後根據 BeanDefinition資訊來執行個體的。那麼在這裡我們首先要看下MyBatis是如何為接口生成BeanDefinition對象的吧。

我們一起看下​

​ImportBeanDefinitionRegistrar​

​​接口。ImportBeanDefinitionRegistrar接口就是允許開發者來根據開發者的規則來生成BeanDefinition的并注冊到​

​BeanDefinitionRegistry​

​中。因為Spring預設是根據自己的規則去生成BeanDefinition的,但是這裡也提供了一個切口,供開發者使用。

public interface ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}      

1

2

3

4

5

MyBatis中​

​MapperScannerRegistrar​

​​實作了​

​ImportBeanDefinitionRegistrar​

​。掃描Mapper接口所在的包,為每個接口生成特定的BeanDefinition

​​#​​ MapperScannerRegistrar

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //掃描Mapper接口所在的包
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    ....
    //為每個接口生成特定的BeanDefinition 
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }      

1

2

3

4

5

6

7

8

​​#​​ ClassPathMapperScanner

在doScan方法為每個标記的Mapper接口生成一個BeanName。而執行個體化工廠都指定為​

​MapperFactoryBean​

​​。隻用調用其​

​getObject()​

​方法即可完成接口的執行個體化。

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      //開始配置自己的規則
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      //設定每個Bean的工廠類,MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      ...
    }
  }
}      

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

我們舉一個例子

@Mapper
public interface TUserMapper {

    int insert(TUser record);

    List<TUser> selectAll();

    TUser selectOne(@Param("id") Integer id);

    TUser selectByName(@Param("name") String name);
}      

1

2

3

4

5

6

7

8

9

10

11

這裡BeanDefinition的名字就是​

​TUserMapper​

​​,而工廠方法就是​

​MapperFactoryBean​

​​。如下僞代碼,getBean("TUserMapper"),就是調用​

​MapperFactoryBean.getObject()​

​​,而​

​getBean("&TUserMapper")​

​​才是擷取​

​MapperFactoryBean​

​的執行個體。

@Test
    public void factoryBeanTest(){
        System.out.println(applicationContextTools.getApp().getBean("&TUserMapper"));
        //org.mybatis.spring.mapper.MapperFactoryBean@3ab6678b
    }      

1

2

3

4

5

​​#​​ 我們如何定制自己的解析的注解呢?

編寫一個類似于​

​MapperScan​

​​的注解類,​

​MyMapperScan​

​。

注意: 自定義的注解隻能聲明在配置類上才有效,配置類就是一定要被​

​@Configuration​

​修飾。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyMapperScannerRegistrar.class)
public @interface MyMapperScan {
    String value();
}

public class MyMapperScannerRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
        String value = annoAttrs.getString("value");
        System.out.println(value);
        System.out.println("配置類:"+importingClassMetadata.getClassName());
    }
}      

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

​​#​​ 驗證

@Configuration
@MyMapperScan(value = "ConfigBean")
public class ConfigBean {
}

@SpringBootApplication //被@Configuration修飾就等同于配置類
@MyMapperScan(value = "test")
@MapperScan(value = "orm.example.dal.mapper")
public class LxchinesszzMybatisStudyApplication {
    public static void main(String[] args) {
        new SpringApplicationBuilder().web(WebApplicationType.NONE).run(args);
    }

}

ConfigBean
配置類:orm.example.ConfigBean
test
配置類:orm.example.LxchinesszzMybatisStudyApplication