自己总结的一些spring面试题
目錄
請談一談Spring中自動裝配的方式有哪些?
依賴注入有哪幾種方式?以及這些方法如何使用?
請問Spring中Bean的作用域有哪些?
什么是Spring IOC?Spring IOC的原理是什么?如果你要實現IOC要怎么做?
Spring中BeanFactory和ApplicationContext的區別是什么?
@Autowired和@Resource之間的區別
談談Spring Bean的生命周期?
談一下Spring AOP?
Spring中實現AOP的幾種方式?
Spring中循環依賴如何解決?說一下解決的原理
請談一談Spring中自動裝配的方式有哪些?
自動裝配即spring容器來幫我們進行bean的匹配和依賴注入(裝配包含類型匹配和依賴注入,這是按實際功能進行的劃分,我覺得這樣更能理解兩者關系),總的來說有兩種方式:
- 按名稱裝配:在容器中查找與依賴屬性名字相同的bean進行注入。
- 按類型裝配:在容器中查找與依賴屬性類型相同的bean進行注入。
在具體的實現上,可以通過Xml配置文件或注解來完成。
1. 如果通過xml配置文件實現,需要在<bean/>標簽中設置autowire屬性,有四個可選項:no、byName、byType、constructor。
- no:不進行自動裝配,必須手動設置ref屬性指定注入對象。
- byName:按名稱裝配。
- byType:按類型裝配。
- constructor:構造器注入,本質上還是按類型裝配,容器會根據構造器參數的類型來匹配,如果與依賴屬性的類型相同且容器中有這個bean,就在對象實例化的時候通過這個構造器注入。
2. 如果通過注解來實現,可以選擇@Autowired或@Resource注解,與xml中的byName和byType選項是一個道理。
- @Autowired:按類型裝配,可以放在屬性、setter方法或構造器上。
- @Resource:按名稱裝配,可以放在屬性和setter方法上。
說了這么多,總結起來就是:自動裝配可以分為按名稱裝配和按類型裝配。具體實現可以通過配置xml文件或者使用注解來實現。
依賴注入有哪幾種方式?以及這些方法如何使用?
Xml配置文件:
Java Bean:
setter注入發生在bean實例化之后,constructor注入發生在bean實例化之前。上面Info是通過setter方法注入的,HelloSpring是通過constructor方法注入的。
請問Spring中Bean的作用域有哪些?
在spring5.x中,作用域有:
- singleton:bean在容器中是單例存在的。
- prototype:每次從spring容器中獲取bean時都會創建一個新的實例。
- request:每次http請求都會創建一個新的實例。僅在web環境下有效。
- session:在每個http session的生命周期里,會創建一個新的實例。
- application:在整個web生命周期里僅創建一個新的實例。
- websocket:在websocket生命周期里僅創建一個新的實例。
注意:request、session、application和websocket作用域只有在web環境中才有效(使用web ApplicationContext,如XmlWebApplicationContext)。如果在非web環境下(如使用ClassPathXmlApplicationContext)使用這些作用域,會拋出IllegalStateException。
什么是Spring IOC?Spring IOC的原理是什么?如果你要實現IOC要怎么做?
1. 什么是Spring IOC?
IOC是Inversion of Control的縮寫,意思是控制反轉。傳統上,對象是由程序員編寫程序代碼直接操控的,控制反轉就是反過來把這些對象的操控權交給容器,通過容器來實現對象組件的裝配和管理,由容器來創建對象并管理對象之間的依賴關系。Spring IOC就是由spring來負責控制對象的生命周期和對象間的關系。
2. Spring IOC的原理是什么?
IOC主要是獲得依賴對象的過程被反轉了,就是獲得依賴對象的過程由自身管理變為了由IOC容器主動注入,所以Spring IOC中最主要的就是依賴注入,Spring IOC的原理可以引申為DI的原理。
比如對象A需要操作數據庫,以前我們總是要在A中自己編寫代碼來獲得一個Connection對象,有了?spring我們就只需要告訴spring,A中需要一個Connection,至于這個Connection怎么構造,何時構造,A不需要知道。在系統運行時,spring會在適當的時候制造一個Connection,然后像打針一樣,注射到A當中,這樣就完成了對各個對象之間關系的控制。A需要依賴?Connection才能正常運行,而這個Connection是由spring注入到A中的,依賴注入的名字就這么來的。那么DI是如何實現的呢??Java 1.3之后一個重要特征是反射(reflection),它允許程序在運行的時候動態的生成對象、執行對象的方法、改變對象的屬性,spring就是通過反射來實現注入的。
1. 通過xml配置文件進行屬性注入:就是通過反射拿到待注入屬性的Field,調用Field.set()來實現注入。
? ? 2. 通過注解來實現注入:判斷注解是放到屬性上還是方法上,然后通過反射進行注入。
舉個簡單的例子,我們找女朋友常見的情況是,我們到處去看哪里有長得漂亮身材又好的女孩子,然后打聽她們的興趣愛好、qq號、電話號、ip號、iq號………,想辦法認識她們,投其所好送其所要,這個過程是復雜深奧的,我們必須自己設計和面對每個環節。傳統的程序開發也是如此,在一個對象中,如果要使用另外的對象,就必須得到它(自己new一個,或者從JNDI中查詢一個),使用完之后還要將對象銷毀(比如Connection等),對象始終會和其他的接口或類藕合起來。(比如類A要使用類B,類B要使用類C,如果你在對象A創建之后就能使用完整功能,必須在實例化對象A之前將創建好的對象B注入到A中,同理也必須在實例化對象B之前將創建好的對象C注入到B中......這里只是一個類依賴另一個類的情況,如果A不僅僅依賴于B,還依賴于D、E、F呢?B不僅僅依賴于C,還依賴于G、H、I呢?那豈不是非常復雜了?所以容器幫我們解決了這個問題)
Spring中BeanFactory和ApplicationContext的區別是什么?
BeanFactory和ApplicationContext是Spring的兩大核心接口,都可以當做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
BeanFactory:是Spring里面最底層的接口,包含了各種Bean的定義,讀取bean配置文檔,管理bean的加載、實例化,控制bean的生命周期,維護bean之間的依賴關系。
ApplicationContext接口作為BeanFactory的派生,除了提供BeanFactory所具有的功能外,還提供了更完整的框架功能:
-
繼承MessageSource,因此支持國際化。
-
能自動注冊BeanPostProcessor和BeanFactoryPostProcessor。
-
統一的資源文件訪問方式。
-
提供在監聽器中注冊bean的事件。
-
同時加載多個配置文件。
-
提供了更加完整的生命周期管理。
下面是Spring 5.2.7官方文檔的說明:
@Autowired和@Resource之間的區別
@Autowired可用于:構造函數、成員變量、Setter方法;@Resource可以用于類、成員變量、setter方法。
@Autowired和@Resource之間的區別
-
@Autowired默認是按照類型裝配注入的,默認情況下它要求依賴對象必須存在(可以設置它required屬性為false)。
-
@Resource默認是按照名稱來裝配注入的,只有當找不到與名稱匹配的bean才會按照類型來裝配注入。
談談Spring Bean的生命周期?
Spring Bean的生命周期可以分為五個階段:
加載階段,即使當Spring容器被創建的時候,會根據xml配置文件或Spring配置類來獲取配置的bean信息,并將這些bean解析成BeanDefinition,存入到一個map中,key是beanName,value就是BeanDefinition。
實例化階段,就是通過構造器或者工廠方法(xml中factory-bean指定工廠類,factory-method指定前面工廠類中的工廠方法,通過工廠方法來創建該bean對象)來實例化所有單例且非懶加載的bean(非單例的bean會在使用時實例化如getBean())。在實例化操作的前后會遍歷所有的BeanDefinition,檢查其是否實現了InstantiationAwareBeanPostProcessor接口,是則執行這個接口的前置(postProcessBeforeInitialization)和后置(postProcessAfterInitialization)方法。
屬性賦值階段,會根據名稱或者類型使用反射進行依賴注入。
初始化階段,主要是執行自定義的初始化方法(如在xml配置文件中設置的init-method屬性),如果bean實現了InitializingBean接口,還會調用bean的afterPropertiesSet()方法,實現InitializingBean接口的目的是為了對“bean的屬性設置完成”這個事件做出反應。在初始化階段前后,會遍歷所有的BeanPostProcessor,并執行前置(postProcessBeforeInitialization)和后置(postProcessAfterInitialization)方法。同時在初始化之前也會執行所有的aware對象的相關方法,有一些aware(如BeanNameAware)是直接執行,有一些(如ApplicationContextAware)是通過ApplicationContextAwareProcessor接口的before方法執行,這個接口也是BeanPostProcessor的子接口,它會檢查當前bean是否是Aware接口的實現類,是的話就執行Aware接口的特有方法。
請別再問Spring Bean的生命周期了! - 簡書 (jianshu.com)
Spring源碼系列 — Bean生命周期 - 懷瑾握瑜XI - 博客園 (cnblogs.com)
Spring AOP也是在初始化完成后,通過BeanPostProcessor的后置方法運行的。方法內容為,通過切點(如execution(* *.find*(..))
)對bean中的方法進行匹配,如果匹配成功就為這個bean生成代理對象并放入容器中來,之后從容器中獲取的都是代理對象。
Spring AOP 源碼分析 - 篩選合適的通知器 | 田小波的技術博客 (tianxiaobo.com)
class ApplicationContextAwareProcessor implements BeanPostProcessor { ......@Overridepublic Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {AccessControlContext acc = null;if (System.getSecurityManager() != null &&(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {acc = this.applicationContext.getBeanFactory().getAccessControlContext();}if (acc != null) {AccessController.doPrivileged(new PrivilegedAction<Object>() {@Overridepublic Object run() {invokeAwareInterfaces(bean);return null;}}, acc);}else {invokeAwareInterfaces(bean);}return bean;}private void invokeAwareInterfaces(Object bean) {if (bean instanceof Aware) {if (bean instanceof EnvironmentAware) {((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());}if (bean instanceof EmbeddedValueResolverAware) {((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);}if (bean instanceof ResourceLoaderAware) {((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);}if (bean instanceof ApplicationEventPublisherAware) {((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);}if (bean instanceof MessageSourceAware) {((MessageSourceAware) bean).setMessageSource(this.applicationContext);}if (bean instanceof ApplicationContextAware) {((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);}}}......}銷毀階段,調用DisposableBean接口的destroy方法,若容器關閉,則調用所有bean的destroy方法。如果在xml中自定義了銷毀方法(通過destroy-method屬性),則調用自定義的方法。
談一下Spring AOP?
AOP(Aspect-Oriented Programming),一般稱為面向切面編程,作為面向對象的一種補充,用于將那些與業務無關,但卻對多個對象產生影響的公共行為和邏輯,抽取并封裝為一個可重用的模塊,這個模塊被命名為“切面”(Aspect),減少系統中的重復代碼,降低了模塊間的耦合度,同時提高了系統的可維護性。可用于權限認證、日志、事務處理等。AOP相關概念可以參考:Spring AOP 源碼分析系列文章導讀。
如何接入
實現元素
TODO(連接點,切點,切面,通知)
生成代理類
Spring啟動的時候根據Spring AOP的相關配置(注解或xml配置)生成通知器類(Advisor,可以獲取通知),然后通過代理BeanPostProcessor,在bean初始化結束后為每個bean查找合適的通知器,找到了就為bean生成代理對象并放入容器中,之后從容器中獲取的就是代理對象。
public interface Advisor {Advice getAdvice();... }AOP中生成代理類總共有兩種方式:JDKProxy和CGLIB。若目標bean實現了接口,就使用JDKProxy,否則使用CGLIB。也可以通過配置制定開啟CGLIB代理。如設置下面屬性為true。
<aop:aspectj-autoproxy proxy-target-class="true"/>執行代理方法
AOP生成的代理類,業務增強的邏輯是放到攔截器中的(把before、after等通知封裝成攔截器),在執行代理類的方法時先獲取這個方法匹配的通知器,然后將通知器中的通知轉化成相應的攔截器,最終獲得一個ArrayList類型的攔截器列表。(先會去緩存中獲取方法的攔截器列表,拿不到再自己組裝)
之后按順序調用攔截器的invoke方法。調用的邏輯是,對于前置通知攔截器,invoke方法是先執行通知邏輯,然后調用下一個攔截器的invoke方法;對于后置通知攔截器,invoke方法先調用下一個攔截器的invoke方法,然后再執行通知邏輯,最后一個攔截器執行目標方法。這就使得目標方法是在前置通知和后置通知之間執行(看下面代碼和圖片)。
// 前置通知攔截器 public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {......@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );return mi.proceed();}...... } // ReflectiveMethodInvocation.proceed()@Overridepublic Object proceed() throws Throwable {// 如果是最后一個攔截器,執行目標方法if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint();}// 獲取下一個攔截器,索引++Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);......// 執行下一個攔截器的invoke()interceptorOrInterceptionAdvice.invoke(this)......}Spring中實現AOP的幾種方式?
Spring AOP的實現發生在bean初始化之后,通過BeanPostProcessor接口類來生成代理類。因此在ApplicationContext構造完成之后,代理類已經被生成,后面從IOC容器獲取的都是代理類。
Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。當類實現了接口,就是用JDKProxy;否則使用cglib。
-
JDK動態代理只提供接口的代理,不支持類的代理。核心InvocationHandler接口和Proxy類,InvocationHandler 通過invoke()方法反射來調用目標類中的代碼,動態地將橫切邏輯和業務編織在一起;接著,Proxy利用 InvocationHandler動態創建一個符合某一接口的的實例, 生成目標類的代理對象。
-
如果代理類沒有實現 InvocationHandler 接口,那么Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成指定類的一個子類對象,并覆蓋其中特定方法并添加增強代碼,從而實現AOP。CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那么它是無法使用CGLIB做動態代理的。
具體可以參見:【Java基礎】代理模式、靜態代理、動態代理、JDK與CGLIB代理區別、在sp
ring AOP中的應用_奮進的小白粥-CSDN博客。
Spring中循環依賴如何解決?說一下解決的原理
將依賴注入的方式改成setter注入即可。
Spring中出現循環依賴問題的場景最常見于使用構造器進行依賴注入的情況,因為使用構造器進行依賴注入時,要求被依賴屬性的實例化發生在當前類被實例化之前。假設A和B相互依賴且都是用構造器進行注入,如果要實例化A,必須先創建B,保證容器中有B這個對象,A才能實例化成功;而實例化B的時候,又必須保證A先被創建,所以就會陷入一個死循環,A和B都不能被實例化。而采用setter注入時,依賴注入發生在bean實例化之后,在A和B被創建出來之后再去進行依賴注入,這樣就能注入成功。
推薦參考文章:Spring-bean的循環依賴以及解決方式_惜暮-CSDN博客_spring循環依賴
Spring事務傳播機制?
帶你讀懂Spring 事務——事務的傳播機制 - 知乎
總結
以上是生活随笔為你收集整理的自己总结的一些spring面试题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《基于数字信号处理的相干光通信技术》读书
- 下一篇: Mac 终端命令大全