问题
对于一些使用建造者模式的 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());
    }
}