Spring FactoryBean作用及应用场景源码分析(Spring整合Mybatis的核心)


  • 背景

  • BeanFactoryPostProcessor

  • BeanDefinitionRegistryPostProcessor

  • ImportBeanDefinitionRegistrar

  • 总结

  • 参考


背景

假设我们有这种需求,像Mybaits需要将这些接口注入到Spring容器中

public interface OneTestDao {
    @Select("SELECT name FROM user WHERE id = 1")
    String query();
}

public interface TwoTestDao {
    @Select("SELECT name FROM user WHERE id = 2")
    String query();
}

首先我们想将我们的自己的Bean(比如代理对象)注入到Spring容器中,有什么方式呢?

一般都是通过Spring扫描Resouce资源然后解析为BeanDefinition,才能从getBean时解析BeanDefinition实例化对象放入此单例缓存中.但是我们这里的是接口,没法直接注入到Spring容器中。

不过Spring提供了一些扩展接口来供我们在Bean加载、初始化、加载完提供了一些接口,供我们扩展。

比如

BeanFactoryPostProcessor

来看看源码

@FunctionalInterface
public interface BeanFactoryPostProcessor {

 /**
  * Modify the application context's internal bean factory after its standard
  * initialization. All bean definitions will have been loaded, but no beans
  * will have been instantiated yet. This allows for overriding or adding
  * properties even to eager-initializing beans.
  * @param beanFactory the bean factory used by the application context
  * @throws org.springframework.beans.BeansException in case of errors
  */

 void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

可以看到这是一个函数式接口,实现这个接口,可以在spring的bean创建之前,修改bean的定义属性。也就是说,Spring允许BeanFactoryPostProcessor在容器实例化任何其它bean之前读取配置元数据,然后作一些修改操作,例如我可以将bean的scope从singleton改为prototype,也可以添加一些bean,具体能操作bean的方法全部在beanFactory中。我们可以看看beanFactory有哪些方法

Spring FactoryBean作用及应用场景源码分析(Spring整合Mybatis的核心)
在这里插入图片描述

其中提供的registerSingleton方法就可以让我注册bean,使用方式也很简单

@Component
public class TestPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerSingleton("test"new String("test"));
    }
}

这样我们就像spring容器中注册了一个String类型的bean,beanName为 test

这里在这个接口执行前其实spring BeanDefinition已经扫描完成了,我们使用这种方式还不不太好,相当于强制暴力的去修改BeanDefinition属性,可能修改不当会有一些bug。有没有什么方法在扫描的时候添加BeanDefinition呢?也是有的

主要提供了这两个接口:

BeanDefinitionRegistryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

 /**
  * Modify the application context's internal bean definition registry after its
  * standard initialization. All regular bean definitions will have been loaded,
  * but no beans will have been instantiated yet. This allows for adding further
  * bean definitions before the next post-processing phase kicks in.
  * @param registry the bean definition registry used by the application context
  * @throws org.springframework.beans.BeansException in case of errors
  */

 void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}

可以看到BeanDefinitionRegistryPostProcessor也是继承了BeanFactoryPostProcessor,但是我们在使用BeanDefinitionRegistryPostProcessor只需要实现他的postProcessBeanDefinitionRegistry方法即可, postProcessBeanFactory方法放一个空实现即可,这样让一个类只做一件事,满足单一原则

使用方式也很简单

public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {

 @Override
 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

 }

 @Override
 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  RootBeanDefinition testBean = new RootBeanDefinition(Test.class);
  //新增Bean定义
  registry.registerBeanDefinition("test", testBean);
 }

}

这样就完成了testBean的注册

ImportBeanDefinitionRegistrar

public interface ImportBeanDefinitionRegistrar {

 /**
  * Register bean definitions as necessary based on the given annotation metadata of
  * the importing {@code @Configuration} class.
  * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
  * registered here, due to lifecycle constraints related to {@code @Configuration}
  * class processing.
  * @param importingClassMetadata annotation metadata of the importing class
  * @param registry current bean definition registry
  */

 public void registerBeanDefinitions(
   AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
;

}

ImportBeanDefinitionRegistrar可以看到除了像BeanDefinitionRegistryPostProcessor提供了BeanDefinitionRegistry参数,还提供了一个AnnotationMetadata参数,该参数可以获取指定注解的属性。一般来说BeanDefinitionRegistryPostProcessor用来解析外部资源配置ImportBeanDefinitionRegistrar解析注解配置

如果对Mybatis源码了解多的就可以发现mybatis整合spring其中的两个配置类

MapperScannerConfigurer就是实现了BeanDefinitionRegistryPostProcessor,

MapperScannerRegistrar就是实现了ImportBeanDefinitionRegistrar

说了这么多,我们还没有聊今天的主角FactoryBean

来看看源码

public interface FactoryBean<T{
  //获取bean
 @Nullable
 getObject() throws Exception;
  
 @Nullable
  //获取classType
 Class<?> getObjectType();
  //是否单例
 default boolean isSingleton() {
  return true;
 }

}

三个方法也是比较简单,实现FactoryBean的Bean本事 就是一个Bean, 不同的是 Spring 对其做了特殊处理,在试图获取时FactoryBean时,获取的是getObject() 返回的对象。

既然获取FactoryBean对象的时候,获取的是getObject() 方法的bean对象,那么我们是不是可以基于FactoryBean设置出一个获取所有mapper代理对象的bean呢?

public class MyFactoryBean<Timplements FactoryBean<T{

    private final Class<T> clazz;

    public MyFactoryBean(Class<T> clazz) {
        this.clazz = clazz;
    }
    @Override
    public T getObject() throws Exception {
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, new MyFactoryBeanHandler());
    }
    @Override
    public Class<?> getObjectType() {
        return clazz;
    }
}
  • MyFactoryBeanHandler
public class MyFactoryBeanHandler implements InvocationHandler
{
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 仅仅打印注解上的sql 假装执行代理对象执行sql
        Select annotation = method.getAnnotation(Select.class);
        if (annotation == nullreturn null;
        Class<?> returnType = method.getReturnType();
        System.out.println("-------> " + Arrays.toString(annotation.value()));
        return null;
    }
}

这样我们在getObject()的时候就可以返回我们的代理对象了,但是有个问题我们的class类型是通过构造方法传入的。如果我们使用

beanDefinition.setBeanClass(MyFactoryBean.class);

注入对象MyFactoryBean我们能使用getObject()获取到代理对象吗?明显是不能的,因为我们没有传入clazz属性

那我们的class属性哪里来呢?从BeanDefinition中获取

beanDefinition.getBeanClassName()

这样就可以获取到我之前的接口OneTestDao类名

然后通过设置构造方法的参数

beanDefinition.getConstructorArgumentValues().addGenericArgumentValue()

最后代码就变成了如下

beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());

到这里就能实例化我们自己的FactoryBean,从而在获取我们的Dao接口时获取的都是代理对象,这就是Spring整合Mybatis的核心实现。也是FactoryBean的一个应用场景,如果你知道了这些,再去看spring整合Mybatis源码就会发现整个过程特别清晰,也就理解了FactoryBean的作用

总结

所以说FactoryBean本质就是用来给我们实例化、或者动态的注入一些比较复杂的Bean,比如像一些接口的代理对象。这里简单说一下和BeanFactory的区别,最主要的一个区别是FactoryBean是动态注入Bean和BeanFactory主要是负责Bean实例化、定位、配置应用程序中的对象及建立这些对象间的依赖.三言两语无法说清,后续有机会重点分析BeanFactory再来细说吧

参考

博客


原文始发于微信公众号(小奏技术):Spring FactoryBean作用及应用场景源码分析(Spring整合Mybatis的核心)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/30427.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!