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è)話題。
(資料圖片)
不知道各位小伙伴們有沒有用過 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)鍵詞:
小伙伴們知道,當(dāng)我們使用Spring容器的時(shí)候,如果遇到一些特殊的Bean,
昨天是2023年女足世界杯倒計(jì)時(shí)10天,中國女足隊(duì)員王霜當(dāng)日從美國飛抵澳
佛山日?qǐng)?bào)訊記者江樂詩攝影報(bào)道:暑假開始,如何讓孩子度過一段精彩充實(shí)
設(shè)計(jì)模式(Designpattern)代表了最佳的實(shí)踐,通常被有經(jīng)驗(yàn)的面向?qū)ο蟮能?/p>
一、線程池的實(shí)現(xiàn)原理下圖所示為線程池的實(shí)現(xiàn)原理:調(diào)用方不斷地向線程
本文經(jīng)AI新媒體量子位(公眾號(hào)ID:QbitAI)授權(quán)轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)聯(lián)系出處。
開放的網(wǎng)絡(luò)端口是網(wǎng)絡(luò)最簡(jiǎn)單的接入點(diǎn)。很多時(shí)候,我們需要在從Internet
一、背景與行業(yè)現(xiàn)狀1、數(shù)據(jù)湖理解的幾個(gè)誤區(qū)現(xiàn)在很多企業(yè)都對(duì)數(shù)據(jù)湖存
門店內(nèi),店員正熱情地為市民介紹享受補(bǔ)貼的洗烘一體機(jī)的功能,講解當(dāng)前
分時(shí)圖快速拉升意味此時(shí)存在大單買入,在大單的推動(dòng)下,股價(jià)快速地上漲
7月10日晚間,白云山(600332)發(fā)布關(guān)于“王老吉”商標(biāo)法律糾紛訴訟結(jié)
7月11日浦城縣強(qiáng)美礦業(yè)有限公司螢石出廠價(jià)格暫穩(wěn),廠家報(bào)價(jià)3000-3100元
近日,韓國愛寶樂園為大熊貓福寶招聘“一日飼養(yǎng)員助理”的公告,吸引逾
7月10日,青云科技(688316)融資買入122 82萬元,融資償還426 74萬元
今日正式入伏!中央氣象臺(tái)繼續(xù)發(fā)布高溫橙色預(yù)警:今天入伏,今年的三伏