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

相关推荐
Themberfue2 分钟前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
让学习成为一种生活方式19 分钟前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
晨曦_子画24 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
假装我不帅44 分钟前
asp.net framework从webform开始创建mvc项目
后端·asp.net·mvc
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
神仙别闹1 小时前
基于ASP.NET+SQL Server实现简单小说网站(包括PC版本和移动版本)
后端·asp.net
Heavydrink1 小时前
HTTP动词与状态码
java
ktkiko111 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田1 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue
神里大人1 小时前
idea、pycharm等软件的文件名红色怎么变绿色
java·pycharm·intellij-idea