Spring注入外部 工厂类Bean

问题

对于一些使用建造者模式的 Bean,我们往往不能直接 new 出来,这些 Bean 如果需要注册到 Spring 容器中,我们就需要使用工厂类。

比如我们项目中经常使用的okhttp: 如果我们想把OkHttpClient注册到Spring容器中,该怎么做?

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 容器中,需要注意的是,这个工厂方法是一个静态方法

// 静态工厂,顾名思义,可以直接通过类名.方法名调用
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 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 对象,而且该对象还是单例的。

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);
    }
}

输出:

client2 = okhttp3.OkHttpClient@41fecb8b
true
方法一 实例工厂

此种情况,工厂方法是一个实例方法(得先有对象,再调用方法)。

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 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 也是单例的。

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

/**
 * 注意这个命名是有规则的,一般叫做 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 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对象

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);
    }
}

输出:

okhttp3.OkHttpClient@693fe6c9

那么如何获取到这个工厂 Bean 呢?只需要 id 前面添加一个 & 符号,就可以获取到 FactoryBean。

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());
    }
}

输出:

class org.example.OkHttpClientFactoryBean

也可以用注解,像下面这样:

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();
    }
}
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 区别

详情查看链接:https://mp.weixin.qq.com/s/r3rnVhU8vr58Cw__UWOVLA
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值