spring的13個經典面試題
1、JDK 動態代理和 CGLIB 代理有什麼區別?
- JDK 動態代理主要是針對類實現瞭某個接口,AOP 則會使用 JDK 動態代理。他基於反射的機制實現,生成一個實現同樣接口的一個代理類,然後通過重寫方法的方式,實現對代碼的增強。
- 而如果某個類沒有實現接口,AOP 則會使用 CGLIB 代理。他的底層原理是基於 ASM 第三方框架,通過修改字節碼生成一個子類,然後重寫父類的方法,實現對代碼的增強。
詳細分析參考:【Java萌新】面試常問設計模式——代理模式
2、FactoryBean、BeanFactory、ApplicationContext 有什麼區別?
- BeanFactory:是一個 Bean 工廠,使用簡單工廠模式,是 Spring IoC 容器頂級接口,是用於管理 Bean 的工廠,最核心的功能是通過
getBean()
方法加載 Bean 對象,通常我們不會直接使用該接口,而是使用其子接口 ApplicationContext。 - FactoryBean:是一個工廠 Bean,使用瞭工廠方法模式,實現該接口的類可以自己定義要創建的 Bean 實例,隻需要實現它的
getObject()
方法即可。 - ApplicationConext:是 BeanFactory 的子接口,擴展瞭 BeanFactory 的功能(高級 IOC 容器)。
3、說一說Spring Bean 的生命周期?
Spring Bean 生命周期簡單概括為 5 個階段:
- Bean 的實例化階段:創建一個 Bean 對象。
- Bean 實例的屬性填充階段:為 Bean 實例的屬性賦值。
- Bean 實例的初始化階段:對 Bean 實例進行初始化。
- Bean 實例的正常使用階段。
- Bean 實例的銷毀階段:容器關閉後,將 Bean 實例銷毀。
4、依賴註入的實現方法,以及相關註解?
構造方法註入、Setter 方法註入、接口註入 三種。
依賴註入的相關註解
- @Autowired:自動按類型註入,如果有多個匹配則按照指定 Bean 的 id 查找,查找不到會報錯。
- @Qualifier:在自動按照類型註入的基礎上再按照 Bean 的 id 註入,給變量註入時必須搭配@Autowired,給方法註入時可單獨使用。
- @Resource :直接按照 Bean 的 id 註入,隻能註入 Bean 類型。
- @Value :用於註入基本數據類型和 String 類型。
5、什麼是 Spring IOC ?
IOC 即控制反轉,簡單來說就是把原來代碼裡需要實現的對象創建、依賴反轉給容器來幫忙實現,Spring 中管理對象及其依賴關系都是通過 Spring 的 IOC 容器實現的。
IOC 的實現方式有依賴註入和依賴查找,由於依賴查找使用的很少,因此 IOC 也叫做依賴註入。
我們之前在創建一個對象的時候都是直接 new
一個對象實例,而有瞭 IOC ,對象實例的創建都交給容器去實現即可。
6、Spring IOC 容器的構建流程(初始化過程)
我們以 XML 方式的容器初始化為例:
通過 ClassPathXmlApplicationContext
,去創建 ApplicationContext
容器對象:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
ClassPathXmlApplicationContext
創建容器對象時,構造方法做瞭如下兩件事:
- ① 調用父容器的構造方法為容器先設置好 Bean 資源加載器。
- ② 調用父類的 setConfigLocations() 方法設置 Bean 配置信息的定位路徑
- ③ 調用父類 AbstractApplicationContext 的 refresh() 方法啟動整個 IOC 容器對 Bean 的載入,在創建 IOC 容器前如果已有容器存在,需要把已有的容器銷毀,保證在 refresh() 方法後使用的是新創建的 IOC 容器。
- 容器創建完成後,通過
loadBeanDefinitions()
方法加載 Bean 配置資源,該方法在加載資源時,首先解析配置文件路徑,讀取配置文件的內容,然後通過 XML 解析器將 Bean 的配置信息轉換成文檔對象,之後按照 Spring Bean 的定義規則將文檔對象解析為 BeanDefinition 對象。 - 接下來,將解析得到的 BeanDefinition 對象存入本地緩存(一個 HashMap 集合,key 是字符串,值是 BeanDefinition)中。
- 最後,實例化所有的 Bean 實例(非懶加載):包括實例的創建,實例的屬性填充,實例的初始化。
7、依賴註入的過程(Bean 的加載流程)?
源碼分析可以參考我的文章:Spring源碼分析——Bean的加載
先來看下面幾行代碼:
public class BeanFactoryTest { public static void main(String[] args) { // 加載與解析XML配置文件,獲得BeanFactory: BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-bf.xml")); // 從BeanFactory中加載Bean對象 Object a = beanFactory.getBean("componentA"); Object b = beanFactory.getBean("componentB"); System.out.println(a);// com.myspring.test.xmltest.ComponentA@1c93084c System.out.println(b);// com.myspring.test.xmltest.ComponentB@6ef888f6 } }
- 首先通過 BeanFactory/ApplicationContext 調用
getBean()
方法,來獲取 Bean 實例,該方法中,真正獲取 Bean 實例的是其內層方法doGetBean()
方法(真正實現從 IOC 容器獲取 Bean ,也是觸發依賴註入的地方)。 - 在
doGetBean()
方法中,主要做瞭以下幾件事:- ① beanName 的轉換方法
transformedBeanName(name),
該方法的作用是,根據傳入的 name 參數,獲取真正的 Bean 對應的 beanName。該方法的 name 參數,有可能是一個別名(alias 屬性設置的別名),也有可能是一個&開頭的 name (工廠 Bean 對象)。 - ② 嘗試從緩存中加載 Bean 的單實例,根據上面
transformedBeanName
方法轉換 name 後得到的真實 beanName,getSingleton(beanName)
方法直接嘗試從緩存中獲取 Bean 的共享單實例,這時候獲取的是初始狀態,尚未實例化。(從緩存中加載的流程就是,根據 beanName 依次從一級緩存、二級緩存、三級緩存中嘗試獲取,通過三級緩存機制也可以有效避免循環依賴) - ③ Bean 的實例化,
getSingleton(beanName)
方法執行後,從緩存中得到瞭 Bean 的原始狀態,接下來需要對該 Bean 進行實例化。 - ④ Bean 的初始化:尋找依賴(循環依賴檢查、依賴註入),因為 Bean 的初始化過程中很可能會用到某些屬性,而某些屬性很可能是動態配置的,並且配置的成依賴於其他的 Bean,那麼此時應該先加載依賴的 Bean。所以在流程中,Spring初始化一個 Bean,會先初始化其依賴的所有的其他 Bean。
- ⑤ 根據不同的 scope 作用域創建 Bean,調用
doCreateBean()
方法創建 Bean。 - ⑥ 類型轉換,根據 scope 創建完 Bean 成功後,一般可以直接返回即可。但當傳入
doGetBean
方法中的requireType
參數不為空時,意味著我們對最後返回的 Bean 有著類型上的要求。Spring 通過 類型轉換器 將第 ⑤ 步創建完成的 Bean 轉換為requireType
指定的類型。
- ① beanName 的轉換方法
8、Bean 的作用范圍?
通過 scope 屬性指定 Bean 的作用范圍,包括:
- ①
singleton
:單例模式,是默認作用域,不管收到多少 Bean 請求每個容器中隻有一個唯一的 Bean 實例。 - ②
prototype
:原型模式,和 singleton 相反,每次 Bean 請求都會創建一個新的實例。 - ③
request
:每次 HTTP 請求都會創建一個新的 Bean 並把它放到 request 域中,在請求完成後 Bean 會失效並被垃圾收集器回收。 - ④
session
:和 request 類似,確保每個 session 中有一個 Bean 實例,session 過期後 bean 會隨之失效。 - ⑤
global session
:當應用部署在 Portlet 容器時,如果想讓所有 Portlet 共用全局存儲變量,那麼該變量需要存儲在 global session 中。
9、Spring事務傳播機制有哪些?
- ① REQUIRED:Spring 默認的事務傳播級別,如果上下文中已經存在事務,那麼就加入到事務中執行,如果當前上下文中不存在事務,則新建事務執行。
- ② REQUIRES_NEW:每次都會新建一個事務,如果上下文中有事務,則將上下文的事務掛起,當新建事務執行完成以後,上下文事務再恢復執行。
- ③ SUPPORTS:如果上下文存在事務,則加入到事務執行,如果沒有事務,則使用非事務的方式執行。
- ④ MANDATORY:上下文中必須要存在事務,否則就會拋出異常。
- ⑤ NOT_SUPPORTED :如果上下文中存在事務,則掛起事務,執行當前邏輯,結束後恢復上下文的事務。
- ⑥ NEVER:上下文中不能存在事務,否則就會拋出異常。
- ⑦ ESTED:嵌套事務。如果上下文中存在事務,則嵌套事務執行,如果不存在事務,則新建事務。
10、Spring 的事務隔離級別有哪些?
Spring 的事務隔離級別底層其實是基於數據庫的,Spring 並沒有自己的一套隔離級別。
- DEFAULT:使用數據庫的默認隔離級別。
- READ_UNCOMMITTED:讀未提交,最低的隔離級別,會讀取到其他事務還未提交的內容,存在臟讀。
- READ_COMMITTED:讀已提交,讀取到的內容都是已經提交的,可以解決臟讀,但是存在不可重復讀。
- REPEATABLE_READ:可重復讀,在一個事務中多次讀取時看到相同的內容,可以解決不可重復讀,但是存在幻讀。
- SERIALIZABLE:串行化,最高的隔離級別,對於同一行記錄,寫會加寫鎖,讀會加讀鎖。在這種情況下,隻有讀讀能並發執行,其他並行的讀寫、寫讀、寫寫操作都是沖突的,需要串行執行。可以防止臟讀、不可重復度、幻讀,沒有並發事務問題。
11、AOP 是什麼?AOP有哪些應用場景?
AOP 概念: 即面向切面編程,使用動態代理技術,在不修改源碼的基礎上對目標方法進行增強。
Spring 中的 AOP 目前支持 JDK 動態代理和 Cglib 代理。如果被代理對象實現瞭接口,則使用 JDK 動態代理,否則使用 Cglib 代理。另外,也可以通過指定 proxyTargetClass=true
來實現強制走 Cglib 代理。
應用場景:
- 權限認證
- 日志打印
- 事務
- …
12、AOP 的相關註解有哪些?
@Aspect
:切面,聲明被註解標註的類是一個切面 Bean。
@Aspect @Component public class LogAspect { ... }
@Pointcut
:切入點,可以通過@Pointcut("execution(* top.csp1999.service.impl.*.*(..))")
去指定要切入的目標對象,並對其符合表達式要求的方法進行增強。
@Pointcut("execution(* top.csp1999.service.impl.*.*(..))") public void operationLog(){}
@Before
:前置通知,指在某個連接點之前執行的通知。
@Before("operationLog()") public void doBeforeAdvice(JoinPoint joinPoint){ System.out.println("進入方法前執行....."); }
@After
:後置通知,指某個連接點退出時執行的通知(不論正常返回還是異常退出)。
@After("operationLog()") public void after(JoinPoint jp){ System.out.println("方法最後執行....."); }
@AfterReturning
:後置返回通知,指某連接點正常完成之後執行的通知,返回值可以在返回後通知方法裡接收。
@AfterReturning(returning = "ret", pointcut = "operationLog()") public void doAfterReturning(Object ret) { System.out.println("方法的返回值 : " + ret); }
@AfterThrowing
:後置異常通知,指方法拋出異常導致退出時執行的通知,和@AfterReturning隻會有一個執行,異常使用 throwing 屬性接收。
@AfterThrowing(throwing = "jp", pointcut = "operationLog()") public void throwss(JoinPoint jp){ System.out.println("方法異常時執行....."); }
@Around
:環繞通知,可以用來在調用一個具體方法前和調用後來完成一些具體的任務。
@Around("operationLog()") public Object run2(ProceedingJoinPoint joinPoint) throws Throwable { // 獲取方法參數值數組 Object[] args = joinPoint.getArgs(); // 得到其方法簽名 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); // 獲取方法參數類型數組 Class[] paramTypeArray = methodSignature.getParameterTypes(); if (EntityManager.class.isAssignableFrom(paramTypeArray[paramTypeArray.length - 1])) { // 如果方法的參數列表最後一個參數是entityManager類型,則給其賦值 args[args.length - 1] = entityManager; } logger.info("請求參數為{}",args); // 動態修改其參數 // 註意,如果調用joinPoint.proceed()方法,則修改的參數值不會生效,必須調用joinPoint.proceed(Object[] args) Object result = joinPoint.proceed(args); logger.info("響應結果為{}",result); // 如果這裡不返回result,則目標對象實際返回值會被置為null return result; }
13、AOP 的相關術語有什麼?
Aspect
:切面,一個關註點的模塊化,這個關註點可能會橫切多個對象。
Joinpoint
:連接點,程序執行過程中的某一行為,即業務層中的所有方法。。
Advice
:通知,指切面對於某個連接點所產生的動作,包括前置通知、後置通知、返回後通知、異常通知和環繞通知。
Pointcut
:切入點,指被攔截的連接點,切入點一定是連接點,但連接點不一定是切入點。
Proxy
:代理,Spring AOP 中有 JDK 動態代理和 CGLib 代理,目標對象實現瞭接口時采用 JDK 動態代理,反之采用 CGLib 代理。
Target
:代理的目標對象,指一個或多個切面所通知的對象。
Weaving
:織入,指把增強應用到目標對象來創建代理對象的過程。
14、總結
文章會不定時更新,有時候一天多更新幾篇,還請三連支持一下,後續會億點點的更新!也希望大傢可以關註WalkonNet其他文章!
推薦閱讀:
- 基於SpringAop中JoinPoint對象的使用說明
- 深入瞭解Spring控制反轉IOC原理
- 一篇文章帶你瞭解Spring AOP 的註解
- Spring BeanPostProcessor(後置處理器)的用法
- 關於spring中單例Bean引用原型Bean產生的問題及解決