许久没记录笔记了,这回来重新熟悉一下 Spring 中 FactoryBean 的使用,顾名思义,它是用来获得相应 Bean 的工厂的。它与另一个 Spring 中的接口 BeanFactory 的作用不一样的,不能多说了。FactoryBean 和 BeanFactory 都是在 org.springframework.beans.factory
包中,谁能一看类名搞清楚它们的差别?
ApplicationContext
这儿说的是第一个 FactoryBean, 它的接口声明是
public interface FactoryBean<T> { T getObject() throws Exception; Class<?> getObjectType(); boolean isSingleton(); }
它最终的效果是,Spring 容器中注册一个名称为 abcFactoryBean 的 AbcFactoryBean
实例,通后名称 abcFactoryBean
获得的实际上是相应 AbcFactoryBean.getObject()
返回的对象,类型为 getObjectType()
, isSingleton()
是否是单例。
原本不太想细究它,由于看到了 FactoryBean
下的子子孙孙们,意识到在以后的 Spring 应用中还是大有文章可做。下面是在一个最基本的 SpringBoot 项目中的 FactoryBean 的所有实现类
例如其中的 ThreadPoolExecutorFactoryBean
, ForkJoinPoolFactoryBean
可用于便利的创建线程池, ServiceLocatorFactoryBean
用于查找 Bean 的。以后如果想要某一个具体的 Bean 声明起来可能麻烦,这时候可以查阅一下是否有相应的 FactoryBean,配置会更简单些。
看到这里后,还可能是不知所以,下面来看个实际的例子,分几步:
在应用中实际需要一个 Sender 实例,但我们不直接把它声明为一个 Spring 的 Bean
package yanbin.blog; //这里没有 @Name, @Component 之类的注册用于声明为一个 Spring Bean public class Sender { private String receiver; public Sender(String receiver) { this.receiver = receiver; } public void send() { System.out.println("Send message to " + receiver); } }
着重强调一下,在使用 FactoryBean 时,实际的 Bean 实现(这里的 Sender) 不需要显式的注册到 Spring 上下文中,它的实例会由相应的 FactoryBean 注册的。
加了 @Named 注解,根据 Spring Bean 默认命名规则,我们知道它会注册一个 senderFactory
的 Spring bean。
package yanbin.blog; import org.springframework.beans.factory.FactoryBean; import javax.inject.Named; @Named public class SenderFactoryBean implements FactoryBean<Sender> { private String receiver; public void setReceiver(String receiver) { this.receiver = receiver; } @Override public Sender getObject() throws Exception { return new Sender(receiver == null ? "Sun" : receiver); } @Override public Class<?> getObjectType() { return Sender.class; } @Override public boolean isSingleton() { return false; } }
那么这个名为 senderFactoryBean
的 Spring bean 的类型就显得有些特别了。看以下的测试代码
下面用代码来验证在 Spring 容器中类型分别为 Sender
和 SenderFactory
的 Bean 到底是什么
package yanbin.blog; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(DemoApplication.class, args); context.getBeansOfType(Sender.class).forEach((beanName, object) -> System.out.println(beanName + "=> " + object)); context.getBeansOfType(SenderFactory.class).forEach((beanName, object) -> System.out.println(beanName + "=> " + object)); context.getBean(Sender.class).send(); } }
以上代码输出如下:
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 中引用它,可以用以下方式指定名称
import org.springframework.beans.factory.annotation.Qualifier; ... @Inject @Qualifier("senderFactory") private Sender sender; @Resource(name = "&senderFactory") private SenderFactory senderFactory; public MailService(@Named("sender") Sender sender) { this.sender = sender; }
如果觉得 senderFactory
名称对应的竟然是一个 Sender
实例而别扭,那么注册 SenderFactory
时可以指定名称为 sender
, 如
@Named("sender") public class SenderFactory implements FactoryBean<Sender> { ......
或者用 JavaConfig 配置时用下面的形式
@Bean(name = "sender") public SenderFactoryBean senderFactoryBean() { SenderFactoryBean factory = new SenderFactoryBean(); factory.setReceiver("Moon"); return factory; }
记住,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
的例子
@Bean(name = "threadPool") public ThreadPoolExecutorFactoryBean threadPoolExecutorFactoryBean() { ThreadPoolExecutorFactoryBean factory = new ThreadPoolExecutorFactoryBean(); factory.setCorePoolSize(5); factory.setMaxPoolSize(5); factory.setQueueCapacity(50); factory.setThreadNamePrefix("kafka"); factory.setDaemon(true); factory.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return factory; }
这样的话,在容器里我们就有了一个名为 threadPool
线程池实例。看到上面,基本 ThreadPoolExecutorFactoryBean
我们可以轻松的定制具有以下功能线程池
Executors.newFixedThreadPool(5)
其他的 FactoryBean 都值得发掘。
链接: