本文收錄于《面試小抄》系列,Github地址(可下載pdf):https://github.com/cosen1024/Java-Interview 國內(nèi)Gitee(可下載pdf):https://gitee.com/cosen1024/Java-Interview
1. 什么是依賴注入?可以通過多少種方式完成依賴注入?
在依賴注入中,您不必創(chuàng)建對象,但必須描述如何創(chuàng)建它們。您不是直接在代碼中將組件和服務(wù)連接在一起,而是描述配置文件中哪些組件需要哪些服務(wù)。由 IoC 容器將它們裝配在一起。
通常,依賴注入可以通過三種方式完成,即:
- 構(gòu)造函數(shù)注入
- setter 注入
- 接口注入
在 Spring Framework 中,僅使用構(gòu)造函數(shù)和 setter 注入。
2. 區(qū)分 BeanFactory 和 ApplicationContext?
| BeanFactory | ApplicationContext |
|---|---|
| 它使用懶加載 | 它使用即時加載 |
| 它使用語法顯式提供資源對象 | 它自己創(chuàng)建和管理資源對象 |
| 不支持國際化 | 支持國際化 |
| 不支持基于依賴的注解 | 支持基于依賴的注解 |
BeanFactory和ApplicationContext的優(yōu)缺點分析:
BeanFactory的優(yōu)缺點:
- 優(yōu)點:應(yīng)用啟動的時候占用資源很少,對資源要求較高的應(yīng)用,比較有優(yōu)勢;
- 缺點:運(yùn)行速度會相對來說慢一些。而且有可能會出現(xiàn)空指針異常的錯誤,而且通過Bean工廠創(chuàng)建的Bean生命周期會簡單一些。
ApplicationContext的優(yōu)缺點:
- 優(yōu)點:所有的Bean在啟動的時候都進(jìn)行了加載,系統(tǒng)運(yùn)行的速度快;在系統(tǒng)啟動的時候,可以發(fā)現(xiàn)系統(tǒng)中的配置問題。
- 缺點:把費時的操作放到系統(tǒng)啟動中完成,所有的對象都可以預(yù)加載,缺點就是內(nèi)存占用較大。
3. spring 提供了哪些配置方式?
- 基于 xml 配置
bean 所需的依賴項和服務(wù)在 XML 格式的配置文件中指定。這些配置文件通常包含許多 bean 定義和特定于應(yīng)用程序的配置選項。它們通常以 bean 標(biāo)簽開頭。例如:
<bean id="studentbean" class="org.edureka.firstSpring.StudentBean">
<property name="name" value="Edureka"></property>
</bean>
- 基于注解配置
您可以通過在相關(guān)的類,方法或字段聲明上使用注解,將 bean 配置為組件類本身,而不是使用 XML 來描述 bean 裝配。默認(rèn)情況下,Spring 容器中未打開注解裝配。因此,您需要在使用它之前在 Spring 配置文件中啟用它。例如:
<beans>
<context:annotation-config/>
<!-- bean definitions go here -->
</beans>
- 基于 Java API 配置
Spring 的 Java 配置是通過使用 @Bean 和 @Configuration 來實現(xiàn)。
- @Bean 注解扮演與
<bean />元素相同的角色。 - @Configuration 類允許通過簡單地調(diào)用同一個類中的其他 @Bean 方法來定義 bean 間依賴關(guān)系。
例如:
@Configuration
public class StudentConfig {
@Bean
public StudentBean myStudent() {
return new StudentBean();
}
}
4. Spring 中的 bean 的作用域有哪些?
- singleton : 唯一 bean 實例,Spring 中的 bean 默認(rèn)都是單例的。
- prototype : 每次請求都會創(chuàng)建一個新的 bean 實例。
- request : 每一次HTTP請求都會產(chǎn)生一個新的bean,該bean僅在當(dāng)前HTTP request內(nèi)有效。
- session : :在一個HTTP Session中,一個Bean定義對應(yīng)一個實例。該作用域僅在基于web的Spring ApplicationContext情形下有效。
- global-session: 全局session作用域,僅僅在基于portlet的web應(yīng)用中才有意義,Spring5已經(jīng)沒有了。Portlet是能夠生成語義代碼(例如:HTML)片段的小型Java Web插件。它們基于portlet容器,可以像servlet一樣處理HTTP請求。但是,與 servlet 不同,每個 portlet 都有不同的會話
5. 如何理解IoC和DI?
IOC就是控制反轉(zhuǎn),通俗的說就是我們不用自己創(chuàng)建實例對象,這些都交給Spring的bean工廠幫我們創(chuàng)建管理。這也是Spring的核心思想,通過面向接口編程的方式來是實現(xiàn)對業(yè)務(wù)組件的動態(tài)依賴。這就意味著IOC是Spring針對解決程序耦合而存在的。在實際應(yīng)用中,Spring通過配置文件(xml或者properties)指定需要實例化的java類(類名的完整字符串),包括這些java類的一組初始化值,通過加載讀取配置文件,用Spring提供的方法(getBean())就可以獲取到我們想要的根據(jù)指定配置進(jìn)行初始化的實例對象。
- 優(yōu)點:IOC或依賴注入減少了應(yīng)用程序的代碼量。它使得應(yīng)用程序的測試很簡單,因為在單元測試中不再需要單例或JNDI查找機(jī)制。簡單的實現(xiàn)以及較少的干擾機(jī)制使得松耦合得以實現(xiàn)。IOC容器支持勤性單例及延遲加載服務(wù)。
DI:DI—Dependency Injection,即“依賴注入”:組件之間依賴關(guān)系由容器在運(yùn)行期決定,形象的說,即由容器動態(tài)的將某個依賴關(guān)系注入到組件之中。依賴注入的目的并非為軟件系統(tǒng)帶來更多功能,而是為了提升組件重用的頻率,并為系統(tǒng)搭建一個靈活、可擴(kuò)展的平臺。通過依賴注入機(jī)制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標(biāo)需要的資源,完成自身的業(yè)務(wù)邏輯,而不需要關(guān)心具體的資源來自何處,由誰實現(xiàn)。
6. 將一個類聲明為Spring的 bean 的注解有哪些?
我們一般使用 @Autowired 注解自動裝配 bean,要想把類標(biāo)識成可用于 @Autowired 注解自動裝配的 bean 的類,采用以下注解可實現(xiàn):
- @Component :通用的注解,可標(biāo)注任意類為 Spring 組件。如果一個Bean不知道屬于哪個層,可以使用@Component 注解標(biāo)注。
8 @Repository : 對應(yīng)持久層即 Dao 層,主要用于數(shù)據(jù)庫相關(guān)操作。 - @Service : 對應(yīng)服務(wù)層,主要涉及一些復(fù)雜的邏輯,需要用到 Dao層。
- @Controller : 對應(yīng) Spring MVC 控制層,主要用戶接受用戶請求并調(diào)用 Service 層返回數(shù)據(jù)給前端頁面。
7. Spring 中的 bean 生命周期?
Bean的生命周期是由容器來管理的。主要在創(chuàng)建和銷毀兩個時期。

創(chuàng)建過程:
1,實例化bean對象,以及設(shè)置bean屬性;
2,如果通過Aware接口聲明了依賴關(guān)系,則會注入Bean對容器基礎(chǔ)設(shè)施層面的依賴,Aware接口是為了感知到自身的一些屬性。容器管理的Bean一般不需要知道容器的狀態(tài)和直接使用容器。但是在某些情況下是需要在Bean中對IOC容器進(jìn)行操作的。這時候需要在bean中設(shè)置對容器的感知。SpringIOC容器也提供了該功能,它是通過特定的Aware接口來完成的。
比如BeanNameAware接口,可以知道自己在容器中的名字。
如果這個Bean已經(jīng)實現(xiàn)了BeanFactoryAware接口,可以用這個方式來獲取其它Bean。
(如果Bean實現(xiàn)了BeanNameAware接口,調(diào)用setBeanName()方法,傳入Bean的名字。
如果Bean實現(xiàn)了BeanClassLoaderAware接口,調(diào)用setBeanClassLoader()方法,傳入ClassLoader對象的實例。
如果Bean實現(xiàn)了BeanFactoryAware接口,調(diào)用setBeanFactory()方法,傳入BeanFactory對象的實例。)
3,緊接著會調(diào)用BeanPostProcess的前置初始化方法postProcessBeforeInitialization,主要作用是在Spring完成實例化之后,初始化之前,對Spring容器實例化的Bean添加自定義的處理邏輯。有點類似于AOP。
4,如果實現(xiàn)了BeanFactoryPostProcessor接口的afterPropertiesSet方法,做一些屬性被設(shè)定后的自定義的事情。
5,調(diào)用Bean自身定義的init方法,去做一些初始化相關(guān)的工作。
6,調(diào)用BeanPostProcess的后置初始化方法,postProcessAfterInitialization去做一些bean初始化之后的自定義工作。
7,完成以上創(chuàng)建之后就可以在應(yīng)用里使用這個Bean了。
銷毀過程:
當(dāng)Bean不再用到,便要銷毀
1,若實現(xiàn)了DisposableBean接口,則會調(diào)用destroy方法;
2,若配置了destry-method屬性,則會調(diào)用其配置的銷毀方法;
總結(jié)
主要把握創(chuàng)建過程和銷毀過程這兩個大的方面;
創(chuàng)建過程:首先實例化Bean,并設(shè)置Bean的屬性,根據(jù)其實現(xiàn)的Aware接口(主要是BeanFactoryAware接口,BeanFactoryAware,ApplicationContextAware)設(shè)置依賴信息,
接下來調(diào)用BeanPostProcess的postProcessBeforeInitialization方法,完成initial前的自定義邏輯;afterPropertiesSet方法做一些屬性被設(shè)定后的自定義的事情;調(diào)用Bean自身定義的init方法,去做一些初始化相關(guān)的工作;然后再調(diào)用postProcessAfterInitialization去做一些bean初始化之后的自定義工作。這四個方法的調(diào)用有點類似AOP。
此時,Bean初始化完成,可以使用這個Bean了。
銷毀過程:如果實現(xiàn)了DisposableBean的destroy方法,則調(diào)用它,如果實現(xiàn)了自定義的銷毀方法,則調(diào)用之。
8. 什么是 AOP?
AOP(Aspect-Oriented Programming), 即 面向切面編程, 它與 OOP( Object-Oriented Programming, 面向?qū)ο缶幊? 相輔相成, 提供了與 OOP 不同的抽象軟件結(jié)構(gòu)的視角.
在 OOP 中, 我們以類(class)作為我們的基本單元, 而 AOP 中的基本單元是 Aspect(切面)
9. AOP 有哪些實現(xiàn)方式?
實現(xiàn) AOP 的技術(shù),主要分為兩大類:
- 靜態(tài)代理 - 指使用 AOP 框架提供的命令進(jìn)行編譯,從而在編譯階段就可生成 AOP 代理類,因此也稱為編譯時增強(qiáng);
- 編譯時編織(特殊編譯器實現(xiàn))
- 類加載時編織(特殊的類加載器實現(xiàn))。
- 動態(tài)代理 - 在運(yùn)行時在內(nèi)存中“臨時”生成 AOP 動態(tài)代理類,因此也被稱為運(yùn)行時增強(qiáng)。
-
JDK動態(tài)代理:通過反射來接收被代理的類,并且要求被代理的類必須實現(xiàn)一個接口 。JDK 動態(tài)代理的核心是 InvocationHandler 接口和 Proxy 類 。 -
CGLIB動態(tài)代理: 如果目標(biāo)類沒有實現(xiàn)接口,那么Spring AOP會選擇使用CGLIB來動態(tài)代理目標(biāo)類 。CGLIB( Code Generation Library ),是一個代碼生成的類庫,可以在運(yùn)行時動態(tài)的生成某個類的子類,注意,CGLIB是通過繼承的方式做的動態(tài)代理,因此如果某個類被標(biāo)記為final,那么它是無法使用CGLIB做動態(tài)代理的。
-
10. Spring AOP and AspectJ AOP 有什么區(qū)別?
Spring AOP 基于動態(tài)代理方式實現(xiàn);AspectJ 基于靜態(tài)代理方式實現(xiàn)。
Spring AOP 僅支持方法級別的 PointCut;提供了完全的 AOP 支持,它還支持屬性級別的 PointCut。
11. Spring中出現(xiàn)同名bean怎么辦?
- 同一個配置文件內(nèi)同名的Bean,以最上面定義的為準(zhǔn)
- 不同配置文件中存在同名Bean,后解析的配置文件會覆蓋先解析的配置文件
- 同文件中ComponentScan和@Bean出現(xiàn)同名Bean。同文件下@Bean的會生效,@ComponentScan掃描進(jìn)來不會生效。通過@ComponentScan掃描進(jìn)來的優(yōu)先級是最低的,原因就是它掃描進(jìn)來的Bean定義是最先被注冊的~
12. Spring 怎么解決循環(huán)依賴問題?
spring對循環(huán)依賴的處理有三種情況:
①構(gòu)造器的循環(huán)依賴:這種依賴spring是處理不了的,直 接拋出BeanCurrentlylnCreationException異常。
②單例模式下的setter循環(huán)依賴:通過“三級緩存”處理循環(huán)依賴。
③非單例循環(huán)依賴:無法處理。
下面分析單例模式下的setter循環(huán)依賴如何解決
Spring的單例對象的初始化主要分為三步:

(1)createBeanInstance:實例化,其實也就是調(diào)用對象的構(gòu)造方法實例化對象
(2)populateBean:填充屬性,這一步主要是多bean的依賴屬性進(jìn)行填充
(3)initializeBean:調(diào)用spring xml中的init 方法。
從上面講述的單例bean初始化步驟我們可以知道,循環(huán)依賴主要發(fā)生在第一、第二部。也就是構(gòu)造器循環(huán)依賴和field循環(huán)依賴。

A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象”這種循環(huán)依賴的情況。A首先完成了初始化的第一步(createBeanINstance實例化),并且將自己提前曝光到singletonFactories中,此時進(jìn)行初始化的第二步,發(fā)現(xiàn)自己依賴對象B,此時就嘗試去get(B),發(fā)現(xiàn)B還沒有被create,所以走create流程,B在初始化第一步的時候發(fā)現(xiàn)自己依賴了對象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也完成了初始化,進(jìn)去了一級緩存singletonObjects中,而且更加幸運(yùn)的是,由于B拿到了A的對象引用,所以B現(xiàn)在hold住的A對象完成了初始化。
13. SpringMVC 工作原理了解嗎?
原理如下圖所示:
上圖的一個筆誤的小問題:Spring MVC 的入口函數(shù)也就是前端控制器 DispatcherServlet 的作用是接收請求,響應(yīng)結(jié)果。
流程說明(重要):
- 客戶端(瀏覽器)發(fā)送請求,直接請求到
DispatcherServlet。 -
DispatcherServlet根據(jù)請求信息調(diào)用HandlerMapping,解析請求對應(yīng)的Handler。 - 解析到對應(yīng)的
Handler(也就是我們平常說的Controller控制器)后,開始由HandlerAdapter適配器處理。 -
HandlerAdapter會根據(jù)Handler來調(diào)用真正的處理器開處理請求,并處理相應(yīng)的業(yè)務(wù)邏輯。 - 處理器處理完業(yè)務(wù)后,會返回一個
ModelAndView對象,Model是返回的數(shù)據(jù)對象,View是個邏輯上的View。 -
ViewResolver會根據(jù)邏輯View查找實際的View。 -
DispaterServlet把返回的Model傳給View(視圖渲染)。 - 把
View返回給請求者(瀏覽器)
14. Spring 框架中用到了哪些設(shè)計模式?
工廠設(shè)計模式 : Spring使用工廠模式通過 BeanFactory、ApplicationContext 創(chuàng)建 bean 對象。
代理設(shè)計模式 : Spring AOP 功能的實現(xiàn)。
單例設(shè)計模式 : Spring 中的 Bean 默認(rèn)都是單例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 結(jié)尾的對數(shù)據(jù)庫操作的類,它們就使用到了模板模式。
包裝器設(shè)計模式 : 我們的項目需要連接多個數(shù)據(jù)庫,而且不同的客戶在每次訪問中根據(jù)需要會去訪問不同的數(shù)據(jù)庫。這種模式讓我們可以根據(jù)客戶的需求能夠動態(tài)切換不同的數(shù)據(jù)源。
觀察者模式: Spring 事件驅(qū)動模型就是觀察者模式很經(jīng)典的一個應(yīng)用。
適配器模式 :Spring AOP 的增強(qiáng)或通知(Advice)使用到了適配器模式、spring MVC 中也是用到了適配器模式適配Controller。
15. Spring 事務(wù)實現(xiàn)方式有哪些?
- 編程式事務(wù)管理:這意味著你可以通過編程的方式管理事務(wù),這種方式帶來了很大的靈活性,但很難維護(hù)。
- 聲明式事務(wù)管理:這種方式意味著你可以將事務(wù)管理和業(yè)務(wù)代碼分離。你只需要通過注解或者XML配置管理事務(wù)。
16. spring事務(wù)定義的傳播規(guī)則
- PROPAGATION_REQUIRED: 支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就新建一個事務(wù)。這是最常見的選擇。
- PROPAGATION_SUPPORTS: 支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行。
- PROPAGATION_MANDATORY: 支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就拋出異常。
- PROPAGATION_REQUIRES_NEW: 新建事務(wù),如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起。
- PROPAGATION_NOT_SUPPORTED: 以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。
- PROPAGATION_NEVER: 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。
- PROPAGATION_NESTED:如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒有事務(wù),則進(jìn)行與PROPAGATION_REQUIRED類似的操作。
17 .Spring 中的單例 bean 的線程安全問題?
當(dāng)多個用戶同時請求一個服務(wù)時,容器會給每一個請求分配一個線程,這時多個線程會并發(fā)執(zhí)行該請求對應(yīng)的業(yè)務(wù)邏輯(成員方法),此時就要注意了,如果該處理邏輯中有對單例狀態(tài)的修改(體現(xiàn)為該單例的成員屬性),則必須考慮線程同步問題。
線程安全問題都是由全局變量及靜態(tài)變量引起的。
若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全.
無狀態(tài)bean和有狀態(tài)bean
- 有狀態(tài)就是有數(shù)據(jù)存儲功能。有狀態(tài)對象(Stateful Bean),就是有實例變量的對象,可以保存數(shù)據(jù),是非線程安全的。在不同方法調(diào)用間不保留任何狀態(tài)。
- 無狀態(tài)就是一次操作,不能保存數(shù)據(jù)。無狀態(tài)對象(Stateless Bean),就是沒有實例變量的對象 .不能保存數(shù)據(jù),是不變類,是線程安全的。
在spring中無狀態(tài)的Bean適合用不變模式,就是單例模式,這樣可以共享實例提高性能。有狀態(tài)的Bean在多線程環(huán)境下不安全,適合用Prototype原型模式。
Spring使用ThreadLocal解決線程安全問題。如果你的Bean有多種狀態(tài)的話(比如 View Model 對象),就需要自行保證線程安全 。