项目概况

FactoryBean接口实例化

2026-01-292 min read后端技术
Spring

文章头

Java编程规范中声明,Java接口类是不能直接实例化的,但是我们在平时的开发中经常会遇到只声明接口就可以直接使用的。

eg:

  1. Mybatis中只用使用@MapperScan声明要扫描的Mapper接口类就可以直接从Spring中获取使用,进行操作数据库
  2. Dubbo中只要用Dubbo提供的@Service注解,同样可以直接从Spring中获取使用进行远程调用。

那么以上这些功能在Spring中是如何实现的呢?

由此就引出本篇主要介绍的接口FactoryBean

java
1 2public interface FactoryBean<T> { 3 @Nullable 4 T getObject() throws Exception; 5 6 @Nullable 7 Class<?> getObjectType(); 8 9 default boolean isSingleton() { 10 return true; 11 } 12}

在Spring中当发现一个Bean的类型是FactoryBean,此时实例化时候就会执行,该对象的getObject()方法从而来进行实例化。那么如何获取真实FactoryBean呢?只需要在实例化的Bean的name前面加&符号才是获取真正FactoryBean的实例对象。

Java编程规范中声明,Java接口类是不能直接实例化的,Spring实现接口的实例化操作,本质上只是调用FactoryBeangetObject()方法,而真正的实例化操作,还是有开发者来实现的。以我们常见的使用框架为例。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的,但是这里也提供了一个切口,供开发者使用。

java
1 2public interface ImportBeanDefinitionRegistrar { 3 public void registerBeanDefinitions( 4 AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); 5}

MyBatis中MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar。扫描Mapper接口所在的包,为每个接口生成特定的BeanDefinition

MapperScannerRegistrar

java
1 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 2 //扫描Mapper接口所在的包 3 AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); 4 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); 5 .... 6 //为每个接口生成特定的BeanDefinition 7 scanner.doScan(StringUtils.toStringArray(basePackages)); 8 }

ClassPathMapperScanner

在doScan方法为每个标记的Mapper接口生成一个BeanName。而实例化工厂都指定为MapperFactoryBean。只用调用其getObject()方法即可完成接口的实例化。

java
1public Set<BeanDefinitionHolder> doScan(String... basePackages) { 2 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); 3 4 if (beanDefinitions.isEmpty()) { 5 logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); 6 } else { 7 //开始配置自己的规则 8 processBeanDefinitions(beanDefinitions); 9 } 10 11 return beanDefinitions; 12 } 13 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { 14 GenericBeanDefinition definition; 15 for (BeanDefinitionHolder holder : beanDefinitions) { 16 definition = (GenericBeanDefinition) holder.getBeanDefinition(); 17 //设置每个Bean的工厂类,MapperFactoryBean 18 definition.setBeanClass(this.mapperFactoryBean.getClass()); 19 ... 20 } 21 } 22}

我们举一个例子

text
1@Mapper 2public interface TUserMapper { 3 4 int insert(TUser record); 5 6 List<TUser> selectAll(); 7 8 TUser selectOne(@Param("id") Integer id); 9 10 TUser selectByName(@Param("name") String name); 11}

这里BeanDefinition的名字就是TUserMapper,而工厂方法就是MapperFactoryBean。如下伪代码,getBean("TUserMapper"),就是调用MapperFactoryBean.getObject(),而getBean("&TUserMapper")才是获取MapperFactoryBean的实例。

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

我们如何定制自己的解析的注解呢?

编写一个类似于MapperScan的注解类,MyMapperScan

注意: 自定义的注解只能声明在配置类上才有效,配置类就是一定要被@Configuration修饰。

java
1@Retention(RetentionPolicy.RUNTIME) 2@Target(ElementType.TYPE) 3@Documented 4@Import(MyMapperScannerRegistrar.class) 5public @interface MyMapperScan { 6 String value(); 7} 8 9public class MyMapperScannerRegistrar implements ImportBeanDefinitionRegistrar { 10 @Override 11 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 12 AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName())); 13 String value = annoAttrs.getString("value"); 14 System.out.println(value); 15 System.out.println("配置类:"+importingClassMetadata.getClassName()); 16 } 17}

验证

text
1@Configuration 2@MyMapperScan(value = "ConfigBean") 3public class ConfigBean { 4} 5 6@SpringBootApplication //被@Configuration修饰就等同于配置类 7@MyMapperScan(value = "test") 8@MapperScan(value = "orm.example.dal.mapper") 9public class LxchinesszzMybatisStudyApplication { 10 public static void main(String[] args) { 11 new SpringApplicationBuilder().web(WebApplicationType.NONE).run(args); 12 } 13 14} 15 16ConfigBean 17配置类:orm.example.ConfigBean 18test 19配置类:orm.example.LxchinesszzMybatisStudyApplication