问题
对于一些使用建造者模式的 Bean,我们往往不能直接 new 出来,这些 Bean 如果需要注册到 Spring 容器中,我们就需要使用工厂类。
比如我们项目中经常使用的okhttp
: 如果我们想把OkHttpClient注册到Spring容器中,该怎么做?
java
public class App {
public static void main(String[] args) {
//通过建造者模式去创建 OkHttpClient 对象
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
//构建一个具体的请求
Request getRequest = new Request.Builder()
.get()
.url("https://www.baidu.com")
.build();
Call call = client.newCall(getRequest);
CountDownLatch countDownLatch = new CountDownLatch(1);
//异步执行网络请求,处理请求结果;如果直接调用call.execute()就是同步,会阻塞
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
countDownLatch.countDown();
}
// 这个就是请求成功的回调函数
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
countDownLatch.countDown();
System.out.println("response.body().string() = " + response.body().string());
}
});
//会判断计数器是否为 0,如果为 0,才会继续执行后续的代码,否则就暂停在这里
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
又比如GSON
: 这里的Gson对象也不是直接 new 出来的,而是通过一个GsonBuilder对象建造出来的。
public class App {
public static void main(String[] args) {
User user = new User();
user.setName("hogen");
user.setBirthday(new Date());
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
String toJson = gson.toJson(user);
System.out.println(toJson);
}
解决方案
方法一 静态工厂
通过一个工厂方法,将 Bean 构建好之后,注入到 Spring 容器中,需要注意的是,这个工厂方法是一个静态方法
java
// 静态工厂,顾名思义,可以直接通过类名.方法名调用
public class OkHttpClientStaticFactory {
private static OkHttpClient okHttpClient;
static {
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
}
public static OkHttpClient getInstance() {
return okHttpClient;
}
}
然后在 XML 文件中配置静态工厂:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.example.OkHttpClientStaticFactory" id="okHttpClient" factory-method="getInstance"/>
</beans>
XML 中配置的时候,需要注意,虽然我们看起来向 Spring 容器中注册的是一个静态工厂类的对象,但是实际上,最终会调用对应的方法 factory-method
,向 Spring
容器中注册一个 OkHttpClient
对象,而且该对象还是单例的。
java
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
OkHttpClient client1 = ctx.getBean("okHttpClient", OkHttpClient.class);
OkHttpClient client2 = ctx.getBean("okHttpClient", OkHttpClient.class);
System.out.println("client2 = " + client2);
System.out.println(client2 == client1);
}
}
输出:
txt
client2 = okhttp3.OkHttpClient@41fecb8b
true
方法一 实例工厂
此种情况,工厂方法是一个实例方法(得先有对象,再调用方法)。
java
public class OkHttpClientStaticFactory {
private static OkHttpClient okHttpClient;
static {
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
}
public static OkHttpClient getInstance() {
return okHttpClient;
}
}
然后在 XML 文件中进行配置:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.example.OkHttpClientInstanceFactory" id="okHttpClientInstanceFactory"/>
<bean class="org.example.OkHttpClientInstanceFactory" factory-bean="okHttpClientInstanceFactory" factory-method="getInstance" id="okHttpClient"/>
</beans>
那么需要注意的是,实例工厂中,工厂方法无法直接调用,必须先有一个实例对象,然后才能调用工厂方法。当然,最终注册到 Spring 容器中的 Bean 也是单例的。
java
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
OkHttpClient client1 = ctx.getBean("okHttpClient", OkHttpClient.class);
OkHttpClient client2 = ctx.getBean("okHttpClient", OkHttpClient.class);
System.out.println("client2 = " + client2);
System.out.println(client2 == client1);
方法一 FactoryBean(推荐)
这种是开发中使用较多的一种方案。SqlSessionFactoryBean
、 ShiroFilterFactoryBean
。。。
首先,我们需要创建一个 OkHttpClientFactoryBean
:
java
/**
* 注意这个命名是有规则的,一般叫做 xxxFactoryBean,看到这个名字,就知道最终生成的 Bean 实际上是 xxx
*/
public class OkHttpClientFactoryBean implements FactoryBean<OkHttpClient> {
/**
* 返回具体的实例对象
*
* @return
* @throws Exception
*/
@Override
public OkHttpClient getObject() throws Exception {
return new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
}
/**
* 返回的实例对象的类型
*
* @return
*/
@Override
public Class<?> getObjectType() {
return OkHttpClient.class;
}
/**
* 是否是单例模式
* 如果为 false,就相当于 scope 为 prototype,默认该值为 true
*
* @return
*/
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
然后在 XML 文件中进行配置:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.example.OkHttpClientFactoryBean" id="okHttpClient"/>
</beans>
当然最终获取到的还是okHttpClient
对象
java
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
OkHttpClient okHttpClient = ctx.getBean("okHttpClient", OkHttpClient.class);
System.out.println(okHttpClient);
}
}
输出:
txt
okhttp3.OkHttpClient@693fe6c9
那么如何获取到这个工厂 Bean 呢?只需要 id 前面添加一个 & 符号,就可以获取到 FactoryBean。
java
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Object bean = ctx.getBean("&okHttpClient");
System.out.println(bean.getClass());
}
}
输出:
txt
class org.example.OkHttpClientFactoryBean
也可以用注解,像下面这样:
java
public class JavaConfig {
@Bean("okhttpclient3")
public OkHttpClientFactoryBean okHttpClientFactoryBean() {
return new OkHttpClientFactoryBean();
}
@Bean("okhttpclient3")
public OkHttpClient okHttpClientInstanceFactory() {
return new OkHttpClientInstanceFactory().getInstance();
}
@Bean("okhttpclient3")
public OkHttpClient okHttpClientStaticFactory() {
return OkHttpClientStaticFactory.getInstance();
}
}
java
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
Object bean = ctx.getBean("okhttpclient3"); // 获取okhttp对象
Object factbean = ctx.getBean("&okhttpclient3"); // 获取 okHttpClientFactoryBean
System.out.println(factbean.getClass());
}
}