博主真會玩,Spring單例bean中使用多例bean,你未必會玩?
lookup-method:方法查詢
通常情況下,我們使用的bean都是單例的,如果一個bean需要依賴於另一個bean的時候,可以在當前bean中宣告另外一個bean引用,然後注入依賴的bean,此時被依賴的bean在當前bean中自始至終都是同一個例項。
本文分享給需要面試刷題的朋友,也祝願大家順利拿到自己想要的offer,這份資料主要包含了Java基礎,資料結構,jvm,多執行緒等等,由於篇幅有限,以下只展示小部分面試題,有需要完整版的朋友可以點一點領取:戳這裡即可領取下面資料,獲取碼:CSDN
先來個案例回顧一下
package com.javacode2018.lesson001.demo13.normal;
public class ServiceA {
}
package com.javacode2018.lesson001.demo13.normal;
public class ServiceB {
private ServiceA serviceA;
public ServiceA getServiceA() {
return serviceA;
}
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
上面2個類,ServiceA和ServiceB,而ServiceB中需要用到ServiceA,可以通過setServiceA將serviceA注入到ServiceB中,spring配置如下:
<?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
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="serviceA" class="com.javacode2018.lesson001.demo13.normal.ServiceA" scope="prototype"/>
<bean id="serviceB" class="com.javacode2018.lesson001.demo13.normal.ServiceB">
<property name="serviceA" ref="serviceA"/>
</bean>
</beans>
上面serviceA的scope是prototype,表示serviceA是多例的,每次從容器中獲取serviceA都會返回一個新的物件。
而serviceB的scope沒有配置,預設是單例的,通過property元素將serviceA注入。
來個測試案例,如下:
package com.javacode2018.lesson001.demo13;
import com.javacode2018.lesson001.demo13.normal.ServiceA;
import com.javacode2018.lesson001.demo13.normal.ServiceB;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
lookupMethod的使用
public class LookupMethodTest {
@Test
public void normalBean() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo13/normalBean.xml";
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
System.out.println(context.getBean(ServiceA.class)); //@1
System.out.println(context.getBean(ServiceA.class)); //@2
System.out.println("serviceB中的serviceA");
ServiceB serviceB = context.getBean(ServiceB.class); //@3
System.out.println(serviceB.getServiceA()); //@4
System.out.println(serviceB.getServiceA()); //@5
}
}
@1和@2從容器中按照型別查詢ServiceA對應的bean。
@3:從容器中獲取ServiceB
@4和@5:獲取serviceB中的serviceA物件
執行normalBean()看一下效果:
com.javacode2018.lesson001.demo13.normal.ServiceA@5bfa9431
com.javacode2018.lesson001.demo13.normal.ServiceA@5db250b4
serviceB中的serviceA
com.javacode2018.lesson001.demo13.normal.ServiceA@223f3642
com.javacode2018.lesson001.demo13.normal.ServiceA@223f3642
從輸出中可以看出,@1和@2輸出了不同的ServiceA,而@4和@5輸出的是同一個serviceA,這是因為serviceB是單例的,serviceB中的serviceA會在容器建立serviceB的時候,從容器中獲取一個serviceA將其注入到serviceB中,所以自始至終serviceB中的serviceA都是同一個物件。
如果我們希望beanB中每次使用beanA的時候beanA都是一個新的例項,我們怎麼實現呢?
我們可以在serviceB中加個方法去獲取serviceA,這個方法中我們主動去容器中獲取serviceA,那麼每次獲取到的都是不同的serviceA例項。
那麼問題來了,我們如何在serviceB中獲取到spring容器呢?
spring中有個介面ApplicationContextAware:
org.springframework.context.ApplicationContextAware
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
上面這個介面有一個方法setApplicationContext,這個介面給了自定義的bean中獲取applicationContext的能力,當我們的類實現這個介面之後,spring容器建立bean物件的時候,如果bean實現了這個介面,那麼容器會自動呼叫setApplicationContext方法,將容器物件applicationContext傳入,此時在我們的bean物件中就可以使用容器的任何方法了。
下面我們就通過ApplicationContextAware介面來實現單例bean中使用多例bean的案例。
單例bean中使用多例bean:ApplicationContext介面的方式
ServiceA.java
package com.javacode2018.lesson001.demo13.applicationcontextaware;
public class ServiceA {
}
ServiceB.java
package com.javacode2018.lesson001.demo13.applicationcontextaware;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class ServiceB implements ApplicationContextAware { //@1
public void say(){
ServiceA serviceA = this.getServiceA();//@2
System.out.println("this:"+this+",serviceA:"+ serviceA);
}
public ServiceA getServiceA() {
return this.context.getBean(ServiceA.class);//@3
}
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
注意上面程式碼,ServiceB實現了ApplicationContextAware介面,然後實現了這個介面中的setApplicationContext方法,spring容器在建立ServiceB的時候會自動呼叫setApplicationContext方法。
@3:從容器中主動去獲取ServiceA,這樣每次獲取到的ServiceA都是一個新的例項。
@2:say方法中呼叫getServiceA方法獲取ServiceA物件,然後將其輸出。
alicationcontextaware.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
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="serviceA" class="com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA" scope="prototype"/>
<bean id="serviceB" class="com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB"/>
</beans>
上面定義了2個bean,第一個是多例的。
測試用例
@Test
public void alicationcontextaware() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo13/alicationcontextaware.xml";
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
System.out.println(context.getBean(com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA.class)); //@1
System.out.println(context.getBean(com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA.class)); //@2
System.out.println("serviceB中的serviceA");
com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB serviceB = context.getBean(com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB.class); //@3
serviceB.say();
serviceB.say();
}
執行輸出:
com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@78047b92
com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@8909f18
serviceB中的serviceA
this:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB@79ca92b9,serviceA:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@1460a8c0
this:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB@79ca92b9,serviceA:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@4f638935
最後2行是是serviceB中的say方法輸出的,可以看出serviceB是一個物件,而serviceA是不同的物件。
單例bean中使用多例bean:lookup-method方式實現
上面這種方式實現了單例bean中使用多例bean的需求,但是用到spring中的介面ApplicationContextAware,此時對spring的api有耦合的作用,我們一直推行高內聚低耦合,所以我們得尋求更好的辦法。
能不能有這樣的功能,當serviceB中呼叫getServiceA的時候,系統自動將這個方法攔截,然後去spring容器中查詢對應的serviceA物件然後返回,spring中的lookup-method就可以實現這樣的功能。
下面我們使用lookup-method來實現一下。
ServiceA.java
package com.javacode2018.lesson001.demo13.lookupmethod;
public class ServiceA {
}
ServiceB.java
package com.javacode2018.lesson001.demo13.lookupmethod;
public class ServiceB {
public void say() {
ServiceA serviceA = this.getServiceA();
System.out.println("this:" + this + ",serviceA:" + serviceA);
}
public ServiceA getServiceA() { //@1
return null;
}
}
注意上面的@1,這個方法中返回了一個null物件,下面我們通過spring來建立上面2個bean物件,然後讓spring對上面的getServiceA方法進行攔截,返回指定的bean,如下:
lookupmethod.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
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="serviceA" class="com.javacode2018.lesson001.demo13.lookupmethod.ServiceA" scope="prototype"/>
<bean id="serviceB" class="com.javacode2018.lesson001.demo13.lookupmethod.ServiceB">
<lookup-method name="getServiceA" bean="serviceA"/>
</bean>
</beans>
注意上面的配置,重點在於這行配置:
<lookup-method name="getServiceA" bean="serviceA"/>
當我們呼叫serviceB中的getServiceA方法的時候,這個方法會攔截,然後會按照lookup-method元素中bean屬性的值作為bean的名稱去容器中查詢對應bean,然後作為getServiceA的返回值返回,即呼叫getServiceA方法的時候,會從spring容器中查詢id為serviceA的bean然後返回。
測試用例
LookupMethodTest中加個方法,如下:
@Test
public void lookupmethod() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo13/lookupmethod.xml";
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
System.out.println(context.getBean(com.javacode2018.lesson001.demo13.lookupmethod.ServiceA.class)); //@1
System.out.println(context.getBean(com.javacode2018.lesson001.demo13.lookupmethod.ServiceA.class)); //@2
System.out.println("serviceB中的serviceA");
com.javacode2018.lesson001.demo13.lookupmethod.ServiceB serviceB = context.getBean(com.javacode2018.lesson001.demo13.lookupmethod.ServiceB.class); //@3
serviceB.say();
serviceB.say();
}
執行看看效果:
com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@619713e5
com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@708f5957
serviceB中的serviceA
this:com.javacode2018.lesson001.demo13.lookupmethod.ServiceB$$EnhancerBySpringCGLIB$$aca8be5a@68999068,serviceA:com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@7722c3c3
this:com.javacode2018.lesson001.demo13.lookupmethod.ServiceB$$EnhancerBySpringCGLIB$$aca8be5a@68999068,serviceA:com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@2ef3eef9
注意最後2行的輸出,serviceA是呼叫this.getServiceA()方法獲取 ,原始碼中這個方法返回的是null,但是spring內部對這個方法進行了攔截,每次呼叫這個方法的時候,都會去容器中查詢serviceA,然後返回,所以上面最後2行的輸出中serviceA是有值的,並且是不同的serviceA例項。
lookup-method:看其名字,就知道意思:方法查詢,呼叫name屬性指定的方法的時候,spring會對這個方法進行攔截,然後去容器中查詢lookup-method元素中bean屬性指定的bean,然後將找到的bean作為方法的返回值返回。
這個地方底層是使用cglib代理實現的,後面有篇文章會詳細介紹代理的2種實現,到時候大家注意下,spring中很多牛逼的功能都是靠代理實現的。
spring提供的還有一個功能,同樣可以可以解決上面單例bean中用到多例bean的問題,也就是下面我們要說的replaced-method。
replaced-method:方法替換
replaced-method:方法替換,比如我們要呼叫serviceB中的getServiceA的時候,我們可以對serviceB這個bean中的getServiceA方法進行攔截,把這個呼叫請求轉發到一個替換者處理。這就是replaced-method可以實現的功能,比lookup-method更強大更靈活。
replaced-method的使用3個步驟
步驟一:定義替換者
自定義一個替換者,替換者需要實現spring中的MethodReplacer介面,看一下這個介面的定義:
package org.springframework.beans.factory.support;
import java.lang.reflect.Method;
public interface MethodReplacer {
/**
* @param obj 被替換方法的目標物件
* @param method 目標物件的方法
* @param args 方法的引數
* @return return value for the method
*/
Object reimplement(Object obj, Method method, Object[] args) throws Throwable;
}
當呼叫目標物件需要被替換的方法的時候,這個呼叫請求會被轉發到上面的替換者的reimplement方法進行處理。
如:
package com.javacode2018.lesson001.demo14;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.MethodReplacer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.lang.reflect.Method;
import java.util.Map;
/**
* servieB的方法替換者
*/
public class ServiceBMethodReplacer implements MethodReplacer, ApplicationContextAware {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
return this.context.getBean(ServiceA.class);
}
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
步驟二:定義替換者bean
<!-- 定義替換者bean -->
<bean id="serviceBMethodReplacer" class="com.javacode2018.lesson001.demo14.ServiceBMethodReplacer" />
步驟二:通過replaced-method元素配置目標bean需要被替換的方法
<bean id="serviceB" class="com.javacode2018.lesson001.demo14.ServiceB">
<replaced-method name="getServiceA" replacer="serviceAMethodReplacer"/>
</bean>
注意上面的replaced-method元素的2個屬性:
name:用於指定當前bean需要被替換的方法
replacer:替換者,即實現了MethodReplacer介面的類對應的bean
上面配置中當呼叫serviceB的getServiceA的時候,會自動呼叫serviceAMethodReplacer這個bean中的reimplement方法進行處理。
案例
ServiceA.java
package com.javacode2018.lesson001.demo14;
public class ServiceA {
}
ServiceB.java
package com.javacode2018.lesson001.demo14;
public class ServiceB {
public void say() {
ServiceA serviceA = this.getServiceA();
System.out.println("this:" + this + ",serviceA:" + serviceA);
}
public ServiceA getServiceA() { //@1
return null;
}
}
上面getServiceA需要返回一個ServiceA物件,此處返回的是null,下面我們通過spring對這個方法進行替換,然後從容器中獲取ServiceA然後返回,下面我們來看看替換者的程式碼。
替換者ServiceBMethodReplacer.java
這個替換者會替換ServiceB中的getServiceA方法
package com.javacode2018.lesson001.demo14;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.MethodReplacer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.lang.reflect.Method;
import java.util.Map;
/**
* servieB的方法替換者
*/
public class ServiceBMethodReplacer implements MethodReplacer, ApplicationContextAware {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
return this.context.getBean(ServiceA.class);
}
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
spring中bean配置檔案:replacedmethod.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
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- 定義替換者bean -->
<bean id="serviceBMethodReplacer" class="com.javacode2018.lesson001.demo14.ServiceBMethodReplacer" />
<bean id="serviceA" class="com.javacode2018.lesson001.demo14.ServiceA" scope="prototype"/>
<bean id="serviceB" class="com.javacode2018.lesson001.demo14.ServiceB">
<replaced-method name="getServiceA" replacer="serviceBMethodReplacer"/>
</bean>
</beans>
測試用例ReplacedMethodTest
package com.javacode2018.lesson001.demo14;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
* replaced-method:方法替換
public class ReplacedMethodTest {
@Test
public void replacedmethod() {
String beanXml = "classpath:/com/javacode2018/lesson001/demo14/replacedmethod.xml";
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
System.out.println(context.getBean(ServiceA.class)); //@1
System.out.println(context.getBean(ServiceA.class)); //@2
System.out.println("serviceB中的serviceA");
ServiceB serviceB = context.getBean(ServiceB.class); //@3
serviceB.say();
serviceB.say();
}
}
執行輸出
com.javacode2018.lesson001.demo14.ServiceA@7722c3c3
com.javacode2018.lesson001.demo14.ServiceA@2ef3eef9
serviceB中的serviceA
this:com.javacode2018.lesson001.demo14.ServiceB$$EnhancerBySpringCGLIB$$21bb8912@243c4f91,serviceA:com.javacode2018.lesson001.demo14.ServiceA@291ae
this:com.javacode2018.lesson001.demo14.ServiceB$$EnhancerBySpringCGLIB$$21bb8912@243c4f91,serviceA:com.javacode2018.lesson001.demo14.ServiceA@61df66b6
從輸出中可以看出結果和lookup-method案例效果差不多,實現了單例bean中使用多例bean的案例。
輸出中都有CGLIB這樣的字樣,說明這玩意也是通過cglib實現的。
總結
lookup-method:方法查詢,可以對指定的bean的方法進行攔截,然後從容器中查詢指定的bean作為被攔截方法的返回值
replaced-method:方法替換,可以實現bean方法替換的效果,整體來說比lookup-method更靈活一些
單例bean中使用多例bean,本文中列出了3種方式,大家消化一下。
本文分享給需要面試刷題的朋友,也祝願大家順利拿到自己想要的offer,這份資料主要包含了Java基礎,資料結構,jvm,多執行緒等等,由於篇幅有限,以下只展示小部分面試題,有需要完整版的朋友可以點一點領取:戳這裡即可領取下面資料,獲取碼:CSDN
相關文章
- Spring系列第十四講 單例bean中使用多例bean,你未必會玩?Spring單例Bean
- spring中如何向一個單例bean中注入非單例beanSpring單例Bean
- Spring系列第六講 玩轉bean scope,避免跳坑裡!SpringBean
- Spring中Bean的例項化詳細流程SpringBean
- spring動態註冊bean會使AOP失效?SpringBean
- 在Spring Bean例項過程中,如何使用反射和遞迴處理的Bean屬性填充?SpringBean反射遞迴
- Spring 原始碼學習 - 單例bean的例項化過程Spring原始碼單例Bean
- [Spring]BeanSpringBean
- Spring中用註解建立bean例項SpringBean
- Spring 的Controller 是單例or多例SpringController單例
- Spring中bean的含義SpringBean
- Spring中Bean的作用域SpringBean
- Spring Bean容器SpringBean
- 【Spring】Bean管理SpringBean
- 你會單例嗎?單例
- Spring裝配Bean(六)Bean的作用域SpringBean
- Spring 當中的Bean 作用域SpringBean
- Spring中bean的生命週期SpringBean
- spring中FactoryBean是什麼beanSpringBean
- Spring 原始碼分析之 bean 例項化原理Spring原始碼Bean
- 【spring 原始碼】IOC 之bean例項的建立Spring原始碼Bean
- 【Spring進階指南】Spring 為啥預設把bean設計成單例的?SpringBean單例
- 【Spring面試題】Spring 為啥預設把bean設計成單例的?Spring面試題Bean單例
- Spring原始碼解析之八finishBeanFactoryInitialization方法即初始化單例beanSpring原始碼Bean單例
- Spring bean 裝配SpringBean
- Spring Bean 綜述SpringBean
- Spring Bean作用域SpringBean
- spring boot factory beanSpring BootBean
- 淺談Spring BeanSpringBean
- Spring基礎(Bean)SpringBean
- Spring Bean 詳解SpringBean
- spring和springmvc是單例還是多例SpringMVC單例
- 解密Spring中的Bean例項化:推斷構造方法(上)解密SpringBean構造方法
- 你說,怎麼把Bean塞到Spring容器?BeanSpring
- Spring Bean 名稱暗藏玄機,這樣取名就不會被代理SpringBean
- 一次性講清楚spring中bean的生命週期之三:bean是如何例項化的SpringBean
- spring中bean.xml的http://www.springframework.org/schema/bean報錯SpringBeanXMLHTTPFramework
- Spring Bean 的例項化過程原始碼解析SpringBean原始碼