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

相关推荐
蓝染-惣右介2 分钟前
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
java·设计模式
一只淡水鱼6631 分钟前
【mybatis】详解 # 和 $ 的区别,两者分别适用于哪种场景,使用 $ 不当会造成什么影响
sql·spring·mybatis·sql注入
秋恬意35 分钟前
IBatis和MyBatis在细节上的不同有哪些
java·mybatis
荆州克莱1 小时前
Golang的性能监控指标
spring boot·spring·spring cloud·css3·技术
齐 飞1 小时前
BeanFactory和FactoryBean
java·sprint
大霞上仙1 小时前
lxml 解析xml\html
java·服务器·网络
Xiaoweidumpb1 小时前
tomcat temp临时文件不清空,占用硬盘,jdk字体内存泄漏
java·tomcat
AI人H哥会Java2 小时前
【Spring】控制反转(IoC)与依赖注入(DI)—IoC容器在系统中的位置
java·开发语言·spring boot·后端·spring
凡人的AI工具箱2 小时前
每天40分玩转Django:Django表单集
开发语言·数据库·后端·python·缓存·django