Spring框架中ImportBeanDefinitionRegistrar的应用详解

 更新时间:2024年01月29日 10:15:17   作者:Smallc0de  
这篇文章主要介绍了Spring框架中ImportBeanDefinitionRegistrar的应用详解,如果实现了ImportSelector接口,在配置类中被@Import加入到Spring容器中以后,Spring容器就会把ImportSelector接口方法返回的字符串数组中的类new出来对象然后放到工厂中去,需要的朋友可以参考下

前言

我们讲过如果一个类实现了ImportSelector接口,并且在配置类中被@Import加入到Spring容器中以后。

Spring容器就会把ImportSelector接口方法返回的字符串数组中的类new出来对象然后放到工厂中去。

并且做了一个功能开关的例子辅助讲解其功能。这次我们就接着上次讲解ImportSelector接口的内容继续扩展讲解ImportBeanDefinitionRegistrar的用法。

ImportBeanDefinitionRegistrar

按照惯例我们还是先介绍一下这个接口里面最重要的方法:registerBeanDefinitions。

public interface ImportBeanDefinitionRegistrar {
	//虽然是俩方法,但是等于一个方法
	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
         BeanNameGenerator importBeanNameGenerator) {
	//直接调用了下面的方法
      registerBeanDefinitions(importingClassMetadata, registry);
   }
   default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   }
}

里面一共两个方法而且都被default注解了,可见确实用的不多。但是这个方法却拥有ImportSelector接口内方法的一切功能,而且更强大。这俩方法是重载方法,区别就在于有没有BeanNameGenerator,这个接口是Spring内置的BeanName生成器,无关大雅。但是注意到第一个方法其实是调用了第二个方法去实现的,可以说方法一是一个扩展,也可以说方法一等于方法二。那就直接解析参数。

  1. 第一个参数AnnotationMetadata importingClassMetadata:这个参数和ImportSelector中的一样,可以拿到被@Import注解过的类的元数据,具体到例子就是笔者一直写的配置类AppConfig.class。因为也不打算进行修改,所以这个不多说。
  2. 第二个参数BeanDefinitionRegistry registry:这个参数厉害了。BeanDefinitionRegistry这个接口我们以前说过,Spring想要把一个类变成对象就一定会把这个类变成一个BeanDefinition对象。这个过程怎么来的呢?就是通过实现BeanDefinitionRegistry接口类的构造方法做的。

Spring把在方法中把这个接口开放出来,就意味着我们可以在这里手动添加一个BeanDefinition给Spring容器,然后构建对象出来。通过registry我们就可以注册一个BeanDefinition进入Spring容器,就使用下面的这个方法:

registry.registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

ImportBeanDefinitionRegistrar例子

按照常规我们先把必要的类都先创建出来。一个业务接口ImportTestDao,一个业务类依赖该接口ImportTestService,一个配置类AppConfig,一个测试类Test,以及一个实现了ImportBeanDefinitionRegistrar的类MyImportDBR。

public interface ImportTestDao {
   public void query();
}
@Component
public class ImportTestService {
    @Autowired
    ImportTestDao importTestDao;
    public void find(){
        System.out.println("ImportTestService importTestDao.query()");
        importTestDao.query();
    }
}
@ComponentScan("com.demo")
public class AppConfig {
}
public class Test {
   public static void main(String[] args) {
     AnnotationConfigApplicationContext anno= new AnnotationConfigApplicationContext(AppConfig.class);
     ImportTestDao importTestDao= (ImportTestDao) anno.getBean("importTestDao");
     importTestDao.query();
   }
}
public class MyImportDBR implements ImportBeanDefinitionRegistrar {
   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   }
}

要完成MyImportDBR最重要的就是实现里面的方法。所以先分析一下如何使用这个方法:首先看参数需要一个beanName和一个beanDefinition,那么第一步就是需要得到要注册的bean的beanDefinition。Spring也给我们提供了相应的类BeanDefinitionBuilder。那么最终这个类构造成这个样子:

public class MyImportDBR implements ImportBeanDefinitionRegistrar {
   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      //得到BD,扫描接口
      BeanDefinitionBuilder builder= BeanDefinitionBuilder.genericBeanDefinition(ImportTestDao.class);
      GenericBeanDefinition beanDefinition= (GenericBeanDefinition) builder.getBeanDefinition();
      beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
      registry.registerBeanDefinition("importTestDao",beanDefinition);
   }
}

但是这里有一个问题:因为我们这里虽然把ImportTestDao.class写在这里了,想要构建一个BeanDefinition。但是我们没有办法使用,因为ImportTestDao是一个接口, Spring没有办法给你new一个出来,也就是说没有办法实例化。那么怎么办呢?我们知道这里用的不应该是一个类,而是应该是ImportTestDao这个接口的一个代理类。所以要怎么样才能获取到这个代理呢?这需要提起来我们很早以前就提到的一个知识点FactoryBean。那我们就需要构造这么一个FactoryBean,以及为了构造一个代理对象还需要一个InvocationHandler。

public class MyfactoryBean implements FactoryBean {
   private Class clazz;
   public MyfactoryBean(Class clazz) {
      this.clazz=clazz;
   }
   @Override
   public Object getObject() throws Exception {
      Class[] clazzes=new Class[]{this.clazz};
      Object proxy= Proxy.newProxyInstance(this.getClass().getClassLoader(),clazzes,new MyInvocation());
      return proxy;
   }
   @Override
   public Class<?> getObjectType() {
      return this.clazz;
   }
   @Override
   public boolean isSingleton() {
      return false;
   }
}
public class MyInvocation implements InvocationHandler {
   public MyInvocation() {
   }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) {
   	  System.out.println("This is a proxy of ImportTestDao");
      return null;
   }
}

然后把这个FactoryBean加入进去。

public class MyImportDBR implements ImportBeanDefinitionRegistrar {
   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      //得到BD,扫描接口,这里笔者写死了,但是其实可以做一个包扫描,把某一个包下的所有类都扫描进来,就像Mybatis的@MapperScan注解一样
      BeanDefinitionBuilder builder= BeanDefinitionBuilder.genericBeanDefinition(ImportTestDao.class);
      //拿到一个BeanDefinition, 这里使用一个其中一个子类来接收,代表这里构建的就是一个普通的BeanDefinition
      GenericBeanDefinition beanDefinition= (GenericBeanDefinition) builder.getBeanDefinition();
      //这里打印是因为笔者当时不确定类名要不要包含包名
      System.out.println(beanDefinition.getBeanClassName());
      //给我们的beanDefinition添加一个构造方法,并且传入我们需要的bean名字
      beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
      //把代理对象给赋予给BeanDefinition
      beanDefinition.setBeanClass(MyfactoryBean.class);
      //这里的名字可以随便取,这里是随着Spring名规范取的。话说这个接口的另一个方法有BeanNameGenerator这个参数就是让大家自由发挥的
      registry.registerBeanDefinition("importTestDao",beanDefinition);
   }
}

为了更有逼格一些,我们把这个ImportBeanDefinitionRegistrar封装成为一个注解MyScaner,这个是仿照@MapperScan的

@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportDBR.class)
public @interface MyScaner {
}

直接加载到AppConfig类上。

@ComponentScan("com.demo")
@MyScaner
public class AppConfig {
}
运行,拿到结果:
This is a proxy of ImportTestDao
query

这样就完成了从外部直接加载一个BeanDefinition到Spring容器的过程。但是小伙伴们看到这里一定会觉得:你这一顿操作猛如虎,一看战绩0比5。搞这么多有个毛线用啊?

ImportBeanDefinitionRegistrar作用

看起来笔者的例子卵用没有,但是仔细想想,大家经常使用的Mybatis是不是就是这个原理?我特意把Mybatis官网的代码调出来,想必大家都配置过。

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

一般的博客会说,这个是把我们自己定义的UserMapper注册给MapperFactoryBean,看到这里的同学,笔者可以明确的告诉你,这个解释是错的。我们要想转换UserMapper成为MapperFactoryBean,你就必须显式的告诉Spring他们之间的关系。然后Spring拿到UserMapper接口传入MapperFactoryBean,再由MapperFactoryBean动态产生UserMapper的代理对象,然后你程序里使用的一直都是这个代理对象,这一切的原理就是我们写的MyfactoryBean的一系列操作。

为了验证我的说法咱们去Mybatis的MapperFactoryBean的源码看下,是不是和我们写的基本上解构一样。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
//通过这个接口反射代理对象,就是我们例子中的clazz
   private Class<T> mapperInterface;  
   public MapperFactoryBean() {
      // intentionally empty
   }
   public MapperFactoryBean(Class<T> mapperInterface) {
      this.mapperInterface = mapperInterface;
   }
   ...无关,略...
   /**
    * {@inheritDoc}
    */
   @Override
   public T getObject() throws Exception {
	//通过传入接口反射得到代理对象,如果没有接口你就没有办法获取代理对象
	//这个接口怎么拿到,就是通过上面的xml配置的
      return getSqlSession().getMapper(this.mapperInterface);
   }
   /**
    * {@inheritDoc}
    */
   @Override
   public Class<T> getObjectType() {
      return this.mapperInterface;
   }
   /**
    * {@inheritDoc}
    */
   @Override
   public boolean isSingleton() {
      return true;
   }
 // ...无关,略...
}

所以我们配置的这个Mybatis的xml是干嘛的呢?就是为了让MapperFactoryBean生成我们需要的(UserMapper)代理对象而已,根本就不是什么注册。

到此这篇关于Spring框架中ImportBeanDefinitionRegistrar的应用详解的文章就介绍到这了,更多相关ImportBeanDefinitionRegistrar应用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

    详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

    这篇文章主要介绍了java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • 基于JSON实现传输byte数组过程解析

    基于JSON实现传输byte数组过程解析

    这篇文章主要介绍了基于JSON实现传输byte数组过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • Java httpClient介绍以及使用示例

    Java httpClient介绍以及使用示例

    这篇文章主要介绍了Java httpClient介绍以及使用示例,帮助大家更好的利用Java实现HTTP请求,感兴趣的朋友可以了解下
    2020-10-10
  • Mybatis 插入和删除批处理操作

    Mybatis 插入和删除批处理操作

    在操作数据库时,经常会碰到批量插入、批量删除的情况,直接执行SQL语句还好做一点,当使用Mybatis进行批量插入、批量删除时会有一些问题。下面对使用Mybatis批量插入,批量删除进行介绍
    2016-12-12
  • Java HttpClient实现socks代理的示例代码

    Java HttpClient实现socks代理的示例代码

    这篇文章主要介绍了Java HttpClient 实现 socks 代理的示例代码,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2020-11-11
  • JAVA中JSONObject对象和Map对象之间的相互转换

    JAVA中JSONObject对象和Map对象之间的相互转换

    这篇文章主要介绍了JAVA中JSONObject对象和Map对象之间的相互转换,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • java连接池Druid获取连接getConnection示例详解

    java连接池Druid获取连接getConnection示例详解

    这篇文章主要为大家介绍了java连接池Druid获取连接getConnection示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • springboot构造树形结构数据并查询的方法

    springboot构造树形结构数据并查询的方法

    本文主要介绍了springboot怎样构造树形结构数据并查询,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • Java实现超级实用的日记本

    Java实现超级实用的日记本

    一个用Java语言编写的,实现日记本的基本编辑功能、各篇日记之间的上下翻页、查询日记内容的程序。全部代码分享给大家,有需要的小伙伴参考下。
    2015-05-05
  • Java使用JDBC连接数据库

    Java使用JDBC连接数据库

    本文详细讲解了Java使用JDBC连接数据库,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12

最新评论