Spring注入外部 工厂类Bean

问题

对于一些使用建造者模式的 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(推荐)

这种是开发中使用较多的一种方案。SqlSessionFactoryBeanShiroFilterFactoryBean 。。。

首先,我们需要创建一个 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());
    }
}

FactoryBean 和 BeanFactory

相关推荐
javachen__11 分钟前
Spring Boot配置error日志发送至企业微信
spring boot·后端·企业微信
seabirdssss19 分钟前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续28 分钟前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben04430 分钟前
ReAct模式解读
java·ai
轮到我狗叫了1 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法
OC溥哥9992 小时前
Flask论坛与个人中心页面开发教程完整详细版
后端·python·flask·html
Volunteer Technology2 小时前
三高项目-缓存设计
java·spring·缓存·高并发·高可用·高数据量
栗子~~2 小时前
bat脚本- 将jar 包批量安装到 Maven 本地仓库
java·maven·jar
Mr.Entropy3 小时前
ecplise配置maven插件
java·maven
叙白冲冲3 小时前
tomcat 为啥能一直运行?不像方法那样结束?
java·tomcat