人人妻人人澡人人爽人人精品av_精品乱码一区内射人妻无码_老司机午夜福利视频_精品成品国色天香摄像头_99精品福利国产在线导航_野花社区在线观看视频_大地资源在线影视播放_东北高大肥胖丰满熟女_金门瓶马车内剧烈运动

首頁>國內(nèi) > 正文

Spring5 中更優(yōu)雅的第三方 Bean 注入

2023-07-11 11:18:11來源:江南一點(diǎn)雨

小伙伴們知道,當(dāng)我們使用 Spring 容器的時(shí)候,如果遇到一些特殊的 Bean,一般來說可以通過如下三種方式進(jìn)行配置:

靜態(tài)工廠方法實(shí)例工廠方法FactoryBean

不過從 Spring5 開始,在 AbstractBeandefinition 類中多了一個(gè)屬性,對(duì)于特殊的 Bean 我們有了更多的選擇:

/** * Specify a callback for creating an instance of the bean, * as an alternative to a declaratively specified factory method. * 

If such a callback is set, it will override any other constructor * or factory method metadata. However, bean property population and * potential annotation-driven injection will still apply as usual. * @since 5.0 * @see #setConstructorArgumentValues(ConstructorArgumentValues) * @see #setPropertyValues(MutablePropertyValues) */public void setInstanceSupplier(@Nullable Supplier instanceSupplier) { this.instanceSupplier = instanceSupplier;}/** * Return a callback for creating an instance of the bean, if any. * @since 5.0 */@Nullablepublic Supplier getInstanceSupplier() { return this.instanceSupplier;}

接下來松哥就來和大家簡(jiǎn)單聊一聊這個(gè)話題。


(資料圖片)

1. 傳統(tǒng)解決方案1.1 問題

不知道各位小伙伴們有沒有用過 OkHttp,這是一個(gè)專門做網(wǎng)絡(luò)請(qǐng)求的工具,在微服務(wù)的 HTTP 調(diào)用組件中,我們可以配置底層使用 OkHttp 這個(gè)工具。

一般來說,如果我們想直接使用 OkHttp,代碼如下:

OkHttpClient client = new OkHttpClient.Builder()        .connectTimeout(5, TimeUnit.SECONDS)        .readTimeout(5, TimeUnit.SECONDS)        .build();Request getReq = new Request.Builder().get().url("http://www.javaboy.org").build();Call call = client.newCall(getReq);call.enqueue(new Callback() {    @Override    public void onFailure(@NotNull Call call, @NotNull IOException e) {        System.out.println("e.getMessage() = " + e.getMessage());    }    @Override    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {        System.out.println("response.body().string() = " + response.body().string());    }});

先通過建造者模式創(chuàng)建出來一個(gè) OkHttpClient 對(duì)象,然后還是建造者模式創(chuàng)建出來 Request 對(duì)象,接下來去發(fā)送請(qǐng)求就可以了。那么對(duì)于這樣的代碼,我們可以將 OkHttpClient 對(duì)象交由 Spring 容器統(tǒng)一管理,那么該如何將 OkHttpClient 注冊(cè)到 Spring 容器中呢?

1.2 靜態(tài)工廠方法

首先可以采用靜態(tài)工廠方法,也就是工廠方法是一個(gè)靜態(tài)方法,如下:

public class OkHttpStaticFactory {    private static OkHttpClient okHttpClient;    static {        okHttpClient = new OkHttpClient.Builder()                .connectTimeout(5, TimeUnit.SECONDS)                .readTimeout(5, TimeUnit.SECONDS)                .build();    }        public static OkHttpClient getOkHttpClient() {        return okHttpClient;    }}

然后在 Spring 配置文件中進(jìn)行注入:

靜態(tài)工廠的特點(diǎn)是靜態(tài)方法可以直接調(diào)用,并必須要獲取到工廠類的實(shí)例,所以上面配置的時(shí)候只需要指定factory-method就可以了。

這就可以了,將來我們?nèi)?Spring 容器中查找一個(gè)名為 httpClient 的對(duì)象,拿到手的就是 OkHttpClient 了。

1.3 實(shí)例工廠方法

實(shí)例工廠方法意思就是說工廠方法是一個(gè)實(shí)例方法。如下:

public class OkHttpInstanceFactory {    private volatile static OkHttpClient okHttpClient;    public OkHttpClient getInstance() {        if (okHttpClient == null) {            synchronized (OkHttpInstanceFactory.class) {                if (okHttpClient == null) {                    okHttpClient = new OkHttpClient.Builder()                            .connectTimeout(5, TimeUnit.SECONDS)                            .readTimeout(5, TimeUnit.SECONDS)                            .build();                }            }        }        return okHttpClient;    }}

這是一個(gè)簡(jiǎn)單的單例模式。但是這里的工廠方法是一個(gè)實(shí)例方法,實(shí)例方法的調(diào)用必須得先獲取到對(duì)象然后才能調(diào)用實(shí)例方法,因此配置方式如下:

好啦,接下來我們就可以去 Spring 容器中獲取一個(gè)名為 httpClient 的對(duì)象了,拿到手的就是 OkHttpClient 實(shí)例。

1.4 FactoryBean

當(dāng)然,也可以通過 FactoryBean 來解決上述問題,F(xiàn)actoryBean 松哥在之前的文章中剛剛和大家介紹過,我們來看下:

public class OkHttpClientFactoryBean implements FactoryBean {    @Override    public OkHttpClient getObject() throws Exception {        return new OkHttpClient.Builder()                .connectTimeout(5, TimeUnit.SECONDS)                .readTimeout(5, TimeUnit.SECONDS)                .build();    }    @Override    public Class getObjectType() {        return OkHttpClient.class;    }    @Override    public boolean isSingleton() {        return true;    }}

最后在 Spring 中配置即可:

這個(gè)就不做過多解釋了,不熟悉的小伙伴可以翻看前面的文章。

上面這三種方案都是傳統(tǒng)方案。

特別是前兩種,其實(shí)我們用的比較少,前兩種有一個(gè)缺陷,就是我們配置的的 factory-method 都是通過反射來調(diào)用的,通過反射調(diào)用的話,多多少少性能受點(diǎn)影響。

這種 factory-method 在 Spring 中處理的源碼執(zhí)行時(shí)序圖如下:

圖片

所以最終反射是在SimpleInstantiationStrategy#instantiate方法中執(zhí)行的,就是大家非常熟悉的反射代碼了:

@Overridepublic Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,  @Nullable Object factoryBean, final Method factoryMethod, Object... args) {  ReflectionUtils.makeAccessible(factoryMethod);  Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();  try {   currentlyInvokedFactoryMethod.set(factoryMethod);   Object result = factoryMethod.invoke(factoryBean, args);   if (result == null) {    result = new NullBean();   }   return result;  }  finally {   if (priorInvokedFactoryMethod != null) {    currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);   }   else {    currentlyInvokedFactoryMethod.remove();   }  }}

好了,這是傳統(tǒng)的解決方案。

2. Spring5 解決方案

Spring5 中開始提供了 Supplier,可以通過接口回調(diào)獲取到一個(gè) Bean 的實(shí)例,這種方式顯然性能更好一些。

如下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();GenericBeanDefinition definition = new GenericBeanDefinition();definition.setBeanClass(Book.class);definition.setInstanceSupplier((Supplier) () -> {    Book book = new Book();    book.setName("深入淺出 Spring Security");    book.setAuthor("江南一點(diǎn)雨");    return book;});ctx.registerBeanDefinition("b1", definition);ctx.refresh();Book b = ctx.getBean("b1", Book.class);System.out.println("b = " + b);

關(guān)鍵就是通過調(diào)用 BeanDefinition 的 setInstanceSupplier 方法去設(shè)置回調(diào)。當(dāng)然,上面這段代碼還可以通過 Lambda 進(jìn)一步簡(jiǎn)化:

public class BookSupplier {    public Book getBook() {        Book book = new Book();        book.setName("深入淺出 Spring Security");        book.setAuthor("江南一點(diǎn)雨");        return book;    }}

然后調(diào)用這個(gè)方法即可:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();GenericBeanDefinition definition = new GenericBeanDefinition();definition.setBeanClass(Book.class);BookSupplier bookSupplier = new BookSupplier();definition.setInstanceSupplier(bookSupplier::getBook);ctx.registerBeanDefinition("b1", definition);ctx.refresh();Book b = ctx.getBean("b1", Book.class);System.out.println("b = " + b);

這是不是更有一點(diǎn) Lambda 的感覺了~

在 Spring 源碼中,處理獲取 Bean 實(shí)例的時(shí)候,有如下一個(gè)分支,就是處理 Supplier 這種情況的:

AbstractAutowireCapableBeanFactory#createBeanInstance

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // Make sure bean class is actually resolved at this point. Class beanClass = resolveBeanClass(mbd, beanName); if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {  throw new BeanCreationException(mbd.getResourceDescription(), beanName,    "Bean class isn"t public, and non-public access not allowed: " + beanClass.getName()); } Supplier instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) {  return obtainFromSupplier(instanceSupplier, beanName); } if (mbd.getFactoryMethodName() != null) {  return instantiateUsingFactoryMethod(beanName, mbd, args); } //... return instantiateBean(beanName, mbd);}@Nullableprivate Object obtainInstanceFromSupplier(Supplier supplier, String beanName) { String outerBean = this.currentlyCreatedBean.get(); this.currentlyCreatedBean.set(beanName); try {  if (supplier instanceof InstanceSupplier instanceSupplier) {   return instanceSupplier.get(RegisteredBean.of((ConfigurableListableBeanFactory) this, beanName));  }  if (supplier instanceof ThrowingSupplier throwableSupplier) {   return throwableSupplier.getWithException();  }  return supplier.get(); }}

上面 obtainFromSupplier 這個(gè)方法,最終會(huì)調(diào)用到第二個(gè)方法。第二個(gè)方法中的supplier.get();其實(shí)最終就調(diào)用到我們自己寫的 getBook 方法了。

關(guān)鍵詞:

相關(guān)新聞

Copyright 2015-2020   三好網(wǎng)  版權(quán)所有 聯(lián)系郵箱:435 22 [email protected]  備案號(hào): 京ICP備2022022245號(hào)-21