Spring 中 FactoryBean 的使用

许久没记录笔记了,这回来重新熟悉一下 Spring 中 FactoryBean 的使用,顾名思义,它是用来获得相应 Bean 的工厂的。它与另一个 Spring 中的接口 BeanFactory 的作用不一样的,不能多说了。FactoryBean 和 BeanFactory 都是在 org.springframework.beans.factory 包中,谁能一看类名搞清楚它们的差别?

  1. FactoryBean: 用于创建某个特定的 Spring bean 的工厂类
  2. BeanFactory: Spring 上下文的最顶层接口,如 ApplicationContext 就继承了该接口,它可称之为所有 Spring bean 的工厂

这儿说的是第一个 FactoryBean, 它的接口声明是

1public interface FactoryBean<T> {
2    T getObject() throws Exception;
3    Class<?> getObjectType();
4    boolean isSingleton();
5}

它最终的效果是,Spring 容器中注册一个名称为 abcFactoryBean 的 AbcFactoryBean 实例,通后名称 abcFactoryBean 获得的实际上是相应 AbcFactoryBean.getObject() 返回的对象,类型为 getObjectType(), isSingleton() 是否是单例。

原本不太想细究它,由于看到了 FactoryBean 下的子子孙孙们,意识到在以后的 Spring 应用中还是大有文章可做。下面是在一个最基本的 SpringBoot 项目中的 FactoryBean 的所有实现类

例如其中的 ThreadPoolExecutorFactoryBean, ForkJoinPoolFactoryBean 可用于便利的创建线程池,ServiceLocatorFactoryBean 用于查找 Bean 的。以后如果想要某一个具体的 Bean 声明起来可能麻烦,这时候可以查阅一下是否有相应的 FactoryBean,配置会更简单些。

看到这里后,还可能是不知所以,下面来看个实际的例子,分几步:

实际要的 Bean 实现(Sender)

在应用中实际需要一个 Sender 实例,但我们不直接把它声明为一个 Spring 的 Bean

 1package yanbin.blog;
 2
 3//这里没有 @Name, @Component 之类的注册用于声明为一个 Spring Bean
 4public class Sender {
 5
 6    private String receiver;
 7
 8    public Sender(String receiver) {
 9        this.receiver = receiver;
10    }
11
12    public void send() {
13        System.out.println("Send message to " + receiver);
14    }
15}

着重强调一下,在使用 FactoryBean 时,实际的 Bean 实现(这里的 Sender) 不需要显式的注册到 Spring 上下文中,它的实例会由相应的 FactoryBean 注册的。

Sender 的 FactoryBean 实现 SenderFactoryBean

加了 @Named 注解,根据 Spring Bean 默认命名规则,我们知道它会注册一个 senderFactory 的 Spring bean。

 1package yanbin.blog;
 2
 3import org.springframework.beans.factory.FactoryBean;
 4
 5import javax.inject.Named;
 6
 7@Named
 8public class SenderFactoryBean implements FactoryBean<Sender> {
 9
10    private String receiver;
11
12    public void setReceiver(String receiver) {
13        this.receiver = receiver;
14    }
15
16    @Override
17    public Sender getObject() throws Exception {
18        return new Sender(receiver == null ? "Sun" : receiver);
19    }
20
21    @Override
22    public Class<?> getObjectType() {
23        return Sender.class;
24    }
25
26    @Override
27    public boolean isSingleton() {
28        return false;
29    }
30}

那么这个名为 senderFactoryBean 的 Spring bean 的类型就显得有些特别了。看以下的测试代码

客户端测试程序 DemoApplication

下面用代码来验证在 Spring 容器中类型分别为 SenderSenderFactory 的 Bean 到底是什么

 1package yanbin.blog;
 2
 3import org.springframework.boot.SpringApplication;
 4import org.springframework.boot.autoconfigure.SpringBootApplication;
 5import org.springframework.context.ApplicationContext;
 6
 7@SpringBootApplication
 8public class DemoApplication {
 9
10    public static void main(String[] args) {
11        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
12
13        context.getBeansOfType(Sender.class).forEach((beanName, object) ->
14            System.out.println(beanName + "=> " + object));
15
16        context.getBeansOfType(SenderFactory.class).forEach((beanName, object) ->
17            System.out.println(beanName + "=> " + object));
18
19        context.getBean(Sender.class).send();
20    }
21}

以上代码输出如下:

senderFactoryBean=> yanbin.blog.Sender@12591ac8
&senderFactoryBean=> yanbin.blog.SenderFactoryBean@38145825
Send message to Sun

senderFactory 的类型是一个 Sender 实例,它就是 SenderFactory.getObject() 返回的实例。而 &senderFactory 才是我们看似用 @Named 注册到 Spring 上下文的 SenderFactory 实例,此处, & 像是 C 中的取地址操作一般。也就是说,如果我们要在其他的 Spring Bean 中引用它,可以用以下方式指定名称

 1import org.springframework.beans.factory.annotation.Qualifier;
 2...
 3
 4@Inject
 5@Qualifier("senderFactory")
 6private Sender sender;
 7
 8@Resource(name = "&senderFactory")
 9private SenderFactory senderFactory;
10
11public MailService(@Named("sender") Sender sender) {
12    this.sender = sender;
13}

如果觉得 senderFactory 名称对应的竟然是一个 Sender 实例而别扭,那么注册 SenderFactory 时可以指定名称为 sender, 如

1@Named("sender")
2public class SenderFactory implements FactoryBean<Sender> {
3    ......

或者用 JavaConfig 配置时用下面的形式

1@Bean(name = "sender")
2public SenderFactoryBean senderFactoryBean() {
3    SenderFactoryBean factory = new SenderFactoryBean();
4    factory.setReceiver("Moon");
5    return factory;
6}

记住,AbcFactoryBean 在 Spring 中会返回它的 getObject() 对应的类型 Abc,所以声明的 FactoryBean 最好指定一个更有意义的名称。

使用 JavaConfig 时看下程序的输出(需要把 SenderFactoryBean 上的 @Name 注解去掉)

sender=> yanbin.blog.Sender@1df8da7a
&sender=> yanbin.blog.SenderFactoryBean@7486b455
Send message to Moon

这时看到 sender 对应的是 Sender 实例,而带 & 前缀的 &sender 是相应的 SenderFactoryBean 实例。一般来说我们不会直接用到这个 FactoryBean 实例,除非我们基于它再行配置,手动调用它 getObject() 方法来获得一个不同的实例。不过这种用法会比较危险,它可能会修改现有的 sender 对应实例的状态。

一个应用实例

有关于 Spring 的  FactoryBean 的内容就这么多了,最后来看一个应用 ThreadPoolExecutorFactoryBean 的例子

 1@Bean(name = "threadPool")
 2public ThreadPoolExecutorFactoryBean threadPoolExecutorFactoryBean() {
 3    ThreadPoolExecutorFactoryBean factory = new ThreadPoolExecutorFactoryBean();
 4    factory.setCorePoolSize(5);
 5    factory.setMaxPoolSize(5);
 6    factory.setQueueCapacity(50);
 7    factory.setThreadNamePrefix("kafka");
 8    factory.setDaemon(true);
 9    factory.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
10    return factory;
11}

这样的话,在容器里我们就有了一个名为 threadPool 线程池实例。看到上面,基本 ThreadPoolExecutorFactoryBean 我们可以轻松的定制具有以下功能线程池

  1. 线程池的大小是 5
  2. 线程池的等待队列大小为 50,如果用 Executors.newFixedThreadPool(5) 得到的是一个等待队列超大的线程池,容易暴掉内存
  3. 线程池中线程名的前缀为 kafka, 日志中输出更友好, 这比定义一个自己的 ThreadFactory 来指定名称简单
  4. 线程池中线程的 daemon 属性可定制,可决定主线退出后,线程池是否强制关掉。以前也要通过 ThreadFactory 来定制 daemon
  5. RejectedExecutionHandler 设置为 CallerRunsPolicy 后,可以在线程池等待队列满了之后,任务提交线程自己撸起袖子亲自干塞不进去的活,不至于光看着别人忙,白白浪费一个劳动力

其他的 FactoryBean 都值得发掘。

链接:

  1. How to use the Spring FactoryBean?
永久链接 https://yanbin.blog/spring-work-with-factorybean/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。