一、Spring
1.請你說說Spring的核心是什么
參考答案
Spring框架包含眾多模塊,如Core、Testing、Data Access、Web Servlet等,其中Core是整個Spring框架的核心模塊。Core模塊提供了IoC容器、AOP功能、數據綁定、類型轉換等一系列的基礎功能,而這些功能以及其他模塊的功能都是建立在IoC和AOP之上的,所以IoC和AOP是Spring框架的核心。
IoC(Inversion of Control)是控制反轉的意思,這是一種面向對象編程的設計思想。在不采用這種思想的情況下,我們需要自己維護對象與對象之間的依賴關系,很容易造成對象之間的耦合度過高,在一個大型的項目中這十分的不利于代碼的維護。IoC則可以解決這種問題,它可以幫我們維護對象與對象之間的依賴關系,降低對象之間的耦合度。
說到IoC就不得不說DI(Dependency Injection),DI是依賴注入的意思,它是IoC實現的實現方式,就是說IoC是通過DI來實現的。由于IoC這個詞匯比較抽象而DI卻更直觀,所以很多時候我們就用DI來代替它,在很多時候我們簡單地將IoC和DI劃等號,這是一種習慣。而實現依賴注入的關鍵是IoC容器,它的本質就是一個工廠。
AOP(Aspect Oriented Programing)是面向切面編程思想,這種思想是對OOP的補充,它可以在OOP的基礎上進一步提高編程的效率。簡單來說,它可以統一解決一批組件的共性需求(如權限檢查、記錄日志、事務管理等)。在AOP思想下,我們可以將解決共性需求的代碼獨立出來,然后通過配置的方式,聲明這些代碼在什么地方、什么時機調用。當滿足調用條件時,AOP會將該業務代碼織入到我們指定的位置,從而統一解決了問題,又不需要修改這一批組件的代碼。
(1)實現依賴注入(DI)的是IOC容器,IOC容器本身就是工廠。
(2)AOP面向切面編程,統一解決一批組件的共性需求(權限檢查、記錄日志、事務管理)。把共性需求的代碼獨立出來,通過配置,聲明代碼在什么地方,什么時機調用。滿足調用條件的時候,AOP會將該業務植入到我們所指定的位置。
(3)所以IoC和AOP是Spring框架的核心。
2.說一說你對Spring容器的了解
參考答案
Spring主要提供了兩種類型的容器:BeanFactory和ApplicationContext。
(1)BeanFactory:是基礎類型的IoC容器,提供完整的IoC服務支持。如果沒有特殊指定,默認采用延 遲初始化策略。只有當客戶端對象需要訪問容器中的某個受管對象的時候,才對該受管對象進行初始化以及依賴注入操作。所以,相對來說,容器啟動初期速度較快,所需要的資源有限。對于資源有限,并且功能要求不是很嚴格的場景,BeanFactory是比較合適的IoC容器選擇。
(2)ApplicationContext:它是在BeanFactory的基礎上構建的,是相對比較高級的容器實現,除了擁有BeanFactory的所有支持,ApplicationContext還提供了其他高級特性,比如事件發布、國際化信息支持等**。ApplicationContext所管理的對象,在該類型容器啟動之后,默認全部初始化并綁定完成。**所以,相對于BeanFactory來說,**ApplicationContext要求更多的系統資源,同時,因為在啟動時就完成所有初始化,**容 器啟動時間較之BeanFactory也會長一些。在那些系統資源充足,并且要求更多功能的場景中,ApplicationContext類型的容器是比較合適的選擇。
3 說一說你對BeanFactory的了解
參考答案
BeanFactory是一個類工廠,與傳統類工廠不同的是,BeanFactory是類的通用工廠,它可以創建并管理各種類的對象。這些可被創建和管理的對象本身沒有什么特別之處,僅是一個POJO,Spring稱這些被創建和管理的Java對象為Bean。并且,Spring中所說的Bean比JavaBean更為寬泛一些,所有可以被Spring容器實例化并管理的Java類都可以成為Bean。
BeanFactory是Spring容器的頂層接口,Spring為BeanFactory提供了多種實現,最常用的是XmlBeanFactory。但它在Spring 3.2中已被廢棄,建議使用XmlBeanDefinitionReader、DefaultListableBeanFactory替代。BeanFactory最主要的方法就是 getBean(String beanName),該方法從容器中返回特定名稱的Bean。
4.說一說你對Spring IOC的理解
參考答案
IoC(Inversion of Control)是控制反轉的意思,這是一種面向對象編程的設計思想。在不采用這種思想的情況下,我們需要自己維護對象與對象之間的依賴關系,很容易造成對象之間的耦合度過高,在一個大型的項目中這十分的不利于代碼的維護。IoC則可以解決這種問題,它可以幫我們維護對象與對象之間的依賴關系,降低對象之間的耦合度。
說到IoC就不得不說DI(Dependency Injection),DI是依賴注入的意思,它是IoC實現的實現方式,就是說IoC是通過DI來實現的。由于IoC這個詞匯比較抽象而DI卻更直觀,所以很多時候我們就用DI來代替它,在很多時候我們簡單地將IoC和DI劃等號,這是一種習慣。而實現依賴注入的關鍵是IoC容器,它的本質就是一個工廠。
在具體的實現中,主要由三種注入方式:
(1)構造方法注入
就是被注入對象可以在它的構造方法中聲明依賴對象的參數列表,讓外部知道它需要哪些依賴對象。然后,IoC Service Provider會檢查被注入的對象的構造方法,取得它所需要的依賴對象列表,進而為其注入相應的對象。構造方法注入方式比較直觀,對象被構造完成后,即進入就緒狀態,可以馬上使用。
(2)setter方法注入
通過setter方法,可以更改相應的對象屬性。所以,當前對象只要為其依賴對象所對應的屬性添加setter方法,就可以通過setter方法將相應的依賴對象設置到被注入對象中。setter方法注入雖不像構造方法注入那樣,讓對象構造完成后即可使用,但相對來說更寬松一些, 可以在對象構造完成后再注入。
(3)接口注入
相對于前兩種注入方式來說,接口注入沒有那么簡單明了。被注入對象如果想要IoC Service Provider為其注入依賴對象,就必須實現某個接口。這個接口提供一個方法,用來為其注入依賴對象。IoC Service Provider最終通過這些接口來了解應該為被注入對象注入什么依賴對象。相對于前兩種依賴注入方式,接口注入比較死板和煩瑣。
總體來說,構造方法注入和setter方法注入因為其侵入性較弱,且易于理解和使用,所以是現在使用最多的注入方式。而接口注入因為侵入性較強,近年來已經不流行了。
個人總結:IOC容器實現依賴注入的三種方式
(1)構造方法注入:入侵性弱易使用和理解。被注入對象可以在它的構造方法中聲明依賴對象的參數列表。IoC Service Provider會檢查被注入的對象的構造方法*,取得它所需要的依賴對象列表,進而為其注入相應的對象。(可以在當前對象構造完成后使用)
(2)setter方法注入:入侵性弱易使用和理解。通過setter方法,可以更改相應的對象屬性。當前對象在依賴對象設置setter方法,該setter方法會把依賴對象 設置到被注入的對象。(可以在當前對象構造完成后注入)
(3)接口注入:入侵性強,不流行。被注入對象如果想要IoC Service Provider為其注入依賴對象,就必須實現某個接口,這個接口提供一個方法,用來為其注入依賴對象。
5 Spring是如何管理Bean的?
參考答案
Spring通過IoC容器來管理Bean,我們可以通過XML配置或者注解配置,來指導IoC容器對Bean的管理。因為注解配置比XML配置方便很多,所以現在大多時候會使用注解配置的方式。
以下是管理Bean時常用的一些注解:
@ComponentScan用于聲明掃描策略,通過它的聲明,容器就知道要掃描哪些包下帶有聲明的類,也可以知道哪些特定的類是被排除在外的。
@Component、@Repository、@Service、@Controller用于聲明Bean,它們的作用一樣,但是語義不同。@Component用于聲明通用的Bean,
@Repository用于聲明DAO層的Bean,
@Service用于聲明業務層的Bean,
@Controller用于聲明視圖層的控制器Bean,
被這些注解聲明的類就可以被容器掃描并創建。
@Autowired、@Qualifier用于注入Bean,即告訴容器應該為當前屬性注入哪個Bean。其中,@Autowired是按照Bean的類型進行匹配的,如果這個屬性的類型具有多個Bean,就可以通過@Qualifier指定Bean的名稱,以消除歧義。
@Scope用于聲明Bean的作用域,默認情況下Bean是單例的,即在整個容器中這個類型只有一個實例。可以通過@Scope注解指定prototype值將其聲明為多例的,也可以將Bean聲明為session級作用域、request級作用域等等,但最常用的還是默認的單例模式。
@PostConstruct、@PreDestroy用于聲明Bean的生命周期。
其中,被**@PostConstruct修飾的方法將在Bean實例化后被調用**,@PreDestroy修飾的方法將在容器銷毀前被調用。
6 介紹Bean的作用域
參考答案
默認情況下,Bean在Spring容器中是單例的,我們可以通過**@Scope注解修改Bean的作用域**。該注解有如下5個取值,它們代表了Bean的5種不同類型的作用域:
7 說一說Bean的生命周期(重點但是很難懂,需要再看)
參考答案
Spring容器管理Bean,涉及對Bean的創建、初始化、調用、銷毀等一系列的流程,這個流程就是Bean的生命周期。整個流程參考下圖:
這個過程是由Spring容器自動管理的,其中有兩個環節我們可以進行干預。
我們可以自定義初始化方法,并在該方法前增加@PostConstruct注解,屆時Spring容器將在調用SetBeanFactory方法之后調用該方法。
我們可以自定義銷毀方法,并在該方法前增加@PreDestroy注解,屆時Spring容器將在自身銷毀前,調用這個方法。
8 Spring是怎么解決循環依賴的?(需要再理解)
首先,需要明確的是spring對循環依賴的處理有三種情況:
構造器的循環依賴:這種依賴spring是處理不了的,直接拋出BeanCurrentlylnCreationException異常。
單例模式下的setter循環依賴:通過“三級緩存”處理循環依賴。
非單例循環依賴:無法處理。
接下來,我們具體看看spring是如何處理第二種循環依賴的。
Spring單例對象的初始化大略分為三步:
createBeanInstance:實例化,其實也就是調用對象的構造方法實例化對象;
populateBean:填充屬性,這一步主要是多bean的依賴屬性進行填充;
initializeBean:調用spring xml中的init 方法。
從上面講述的單例bean初始化步驟我們可以知道,循環依賴主要發生在第一步、第二步。也就是構造器循環依賴和field循環依賴。 Spring為了解決單例的循環依賴問題,使用了三級緩存。
/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);/** Cache of singleton factories: bean name –> ObjectFactory */private final Map> singletonFactories = new HashMap>(16); /** Cache of early singleton objects: bean name –> bean instance */ private final Map earlySingletonObjects = new HashMap(16);
這三級緩存的作用分別是:
singletonFactories : 進入實例化階段的單例對象工廠的cache (三級緩存);
earlySingletonObjects :完成實例化但是尚未初始化的,提前暴光的單例對象的Cache (二級緩存);
singletonObjects:完成初始化的單例對象的cache(一級緩存)。
我們在創建bean的時候,會首先從cache中獲取這個bean,這個緩存就是sigletonObjects。主要的調用方法是:
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); //isSingletonCurrentlyInCreation()判斷當前單例bean是否正在創建中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)){ synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); //allowEarlyReference 是否允許從singletonFactories中通過getObject拿到對象
if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//從singletonFactories中移除,并放入earlySingletonObjects中。
//其實也就是從三級緩存移動到了二級緩存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
從上面三級緩存的分析,我們可以知道,Spring解決循環依賴的訣竅就在于singletonFactories這個三級cache。這個cache的類型是ObjectFactory,定義如下:
public interface ObjectFactory<T>
{ T getObject() throws BeansException; }
這個接口在AbstractBeanFactory里實現,并在核心方法doCreateBean()引用下面的方法:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects){ if (!this.singletonObjects.containsKey(beanName)){ this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
這段代碼發生在createBeanInstance之后,populateBean()之前,也就是說單例對象此時已經被創建出來(調用了構造器)。這個對象已經被生產出來了,此時將這個對象提前曝光出來,讓大家使用。
這樣做有什么好處呢?讓我們來分析一下“A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象”這種循環依賴的情況。A首先完成了初始化的第一步,并且將自己提前曝光到singletonFactories中,此時進行初始化的第二步,發現自己依賴對象B,此時就嘗試去get(B),發現B還沒有被create,所以走create流程,B在初始化第一步的時候發現自己依賴了對象A,于是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒有,因為A還沒初始化完全),嘗試二級緩存earlySingletonObjects(也沒有),嘗試三級緩存singletonFactories,由于A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A對象(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A對象后順利完成了初始化階段1、2、3,完全初始化之后將自己放入到一級緩存singletonObjects中。此時返回A中,A此時能拿到B的對象順利完成自己的初始化階段2、3,最終A也完成了初始化,進去了一級緩存singletonObjects中,而且更加幸運的是,由于B拿到了A的對象引用,所以B現在hold住的A對象完成了初始化。
9 @Autowired和@Resource注解有什么區別?
參考答案
@Autowired是Spring提供的注解,
@Resource是JDK提供的注解。
@Autowired是只能按類型注入,
@Resource默認按名稱注入,也支持按類型注入。
(1)@Autowired按類型裝配依賴對象,默認情況下它要求依賴對象必須存在,如果允許null值,可以設置它required屬性為false,如果我們想使用按名稱裝配,可以結合@Qualifier注解一起使用。
(2)@Resource有兩個中重要的屬性:name和type。name屬性指定byName,如果沒有指定name屬性,當注解標注在字段上,即默認取字段的名稱作為bean名稱尋找依賴對象,
當注解標注在屬性的setter方法上,即默認取屬性名作為bean名稱尋找依賴對象。
需要注意的是,@Resource如果沒有指定name屬性,并且按照默認的名稱仍然找不到依賴對象時, @Resource注解會回退到按類型裝配。但一旦指定了name屬性,就只能按名稱裝配了。
10 Spring中默認提供的單例是線程安全的嗎?
參考答案
不是。
Spring容器本身并沒有提供Bean的線程安全策略。
如果單例的Bean是一個無狀態的Bean,即線程中的操作不會對Bean的成員執行查詢以外的操作,那么這個單例的Bean是線程安全的。比如,Controller、Service、DAO這樣的組件,通常都是單例且線程安全的。
如果單例的Bean是一個有狀態的Bean,則可以采用ThreadLocal對狀態數據做線程隔離,來保證線程安全。