FactoryBean——Spring的扩展点之一

本文深入探讨了Spring中的FactoryBean,解释了其与BeanFactory的区别,通过示例展示了FactoryBean如何创建并注册两个Bean。文章分析了FactoryBean的源码,揭示了其在Spring-Mybatis插件原理中的作用,解释了为何Spring整合Mybatis时需要SqlSessionFactoryBean。此外,还介绍了mybatis-spring-boot-starter的整合原理,强调了FactoryBean在Spring Boot中创建SqlSessionFactory和Mapper动态代理对象的关键作用。
摘要由CSDN通过智能技术生成

扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,阅读更多Spring源码分析文章

微信公众号

首先需要说明的是,FactoryBean和BeanFactory虽然名字很像,但是这两者是完全不同的两个概念,用途上也是天差地别。BeanFactory是一个Bean工厂,在一定程度上我们可以简单理解为它就是我们平常所说的Spring容器(注意这里说的是简单理解为容器),它完成了Bean的创建、自动装配等过程,存储了创建完成的单例Bean。而FactoryBean通过名字看,我们可以猜出它是Bean,但它是一个特殊的Bean,究竟有什么特殊之处呢?它的特殊之处在我们平时开发过程中又有什么用处呢?

1. FactoryBean的用法

FactoryBean的特殊之处在于它可以向容器中注册两个Bean,一个是它本身,一个是FactoryBean.getObject()方法返回值所代表的Bean。先通过如下示例代码来感受下FactoryBean的用处吧。

  • 自定义一个类CustomerFactoryBean,让它实现了FactoryBean接口,重写了接口中的两个方法,在getObejct()方法中,返回了一个UserService的实例对象;在getObjectType()方法中返回了UserService.class。然后在CustomerFactoryBean添加了注解@Component注解,意思是将CustomerFactoryBean类交给Spring管理。
package com.tiantang.study.component;

import com.tiantang.study.service.UserService;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

@Component
public class CustomerFactoryBean implements FactoryBean<UserService> {
   
    @Override
    public UserService getObject() throws Exception {
   
        return new UserService();
    }

    @Override
    public Class<?> getObjectType() {
   
        return UserService.class;
    }
}
  • 定义了一个UserService类,在构造方法中打印了一行日志。
package com.tiantang.study.service;

public class UserService {
   

    public UserService(){
   
        System.out.println("userService construct");
    }
}
  • 定义了一个配置类AppConfig,在类中指明了Spring需要扫描包com.tiantang.study.component
package com.tiantang.study.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.tiantang.study.component")
public class AppConfig {
   
}
  • 启动类
public class MainApplication {
   

    public static void main(String[] args) {
   
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println("容器启动完成");
        UserService userService = applicationContext.getBean(UserService.class);
        System.out.println(userService);
        Object customerFactoryBean = applicationContext.getBean("customerFactoryBean");
        System.out.println(customerFactoryBean);
    }
}
  • 控制台打印的结果
    测试结果

  • 在进行源码分析之前,我们可以先看下这两个问题:

    1. 在AppConfig类中我们只扫描了com.tiantang.study.component这个包下的类,按照我们的常规理解,这个时候应该只会有CustomerFactoryBean这个类被放进Spring容器中了,UserService并没有被扫描。而我们在测试时却可以通过applicationContext.getBean(UserService.class)从容器中获取到Bean,为什么?
    1. 我们知道默认情况下,在我们没有自定义命名策略的情况下,我们自定义的类被Spring扫描进容器后,Bean在Spring容器中的beanName默认是类名的首字母小写,所以这本次demo中,CustomerFactoryBean类的单例对象在容器中的beanName是customerFactoryBean。所以这个时候我们调用方法getBean(beanName)通过beanName去获取Bean,这个时候理论上应该返回的是CustomerFactoryBean类的单例对象。然而,我们将结果打印出来,却发现,这个对象的hashCode竟然和userService对象的hashCode一模一样,这说明这两个对象是同一个对象,为什么会出现这种情况呢?为什么不是CustomerFactoryBean类的实例对象呢?
    1. 既然通过customerFactoryBean这个beanName无法获取到CustomerFactoryBean的单例对象,那么应该怎么获取呢?

以上3个问题的答案可以用一个答案解决,那就是FactoryBean是一个特殊的Bean。我们自定义的CustomerFactoryBean实现了FactoryBean接口,所以当CustomerFactoryBean被扫描进Spring容器时,实际上它向容器中注册了两个bean,一个是CustomerFactoryBean类的单例对象;另外一个就是getObject()方法返回的对象,在demo中,我们重写的getObject()方法中,我们通过new UserService()返回了一个UserService的实例对象,所以我们从容器中能获取到UserService的实例对象。如果我们想通过beanName去获取CustomerFactoryBean的单例对象,需要在beanName前面添加一个&符号,如下代码,这样就能根据beanName获取到原生对象了。

public class MainApplication {
   

    public static void main(String[] args) {
   
        CustomerFactoryBean rawBean = (CustomerFactoryBean) applicationContext.getBean("&customerFactoryBean");
        System.out.println(rawBean);
    }
}

2. FactoryBean的源码

通过上面的示例代码,我们知道了FactoryBean的作用,也知道该如何使用FactoryBean,那么接下来我们就通过源码来看看FactoryBean的工作原理。

  • 在Spring容器启动阶段,会调用到refresh()方法,在refresh()中有调用了finishBeanFactoryInitialization()方法,最终会调用到beanFactory.preInstantiateSingletons()方法。所以我们先看下这个方法的源码。(对refresh()方法不太熟悉的朋友,可以去看下笔者的另外两篇文章:Spring源码系列之容器启动流程通过源码看Bean的创建过程)。
p
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值