spring的13個(gè)經(jīng)典面試題
1、JDK 動(dòng)態(tài)代理和 CGLIB 代理有什么區(qū)別?
- JDK 動(dòng)態(tài)代理主要是針對(duì)類實(shí)現(xiàn)了某個(gè)接口,AOP 則會(huì)使用 JDK 動(dòng)態(tài)代理。他基于反射的機(jī)制實(shí)現(xiàn),生成一個(gè)實(shí)現(xiàn)同樣接口的一個(gè)代理類,然后通過(guò)重寫方法的方式,實(shí)現(xiàn)對(duì)代碼的增強(qiáng)。
- 而如果某個(gè)類沒(méi)有實(shí)現(xiàn)接口,AOP 則會(huì)使用 CGLIB 代理。他的底層原理是基于 ASM 第三方框架,通過(guò)修改字節(jié)碼生成一個(gè)子類,然后重寫父類的方法,實(shí)現(xiàn)對(duì)代碼的增強(qiáng)。
詳細(xì)分析參考:【Java萌新】面試常問(wèn)設(shè)計(jì)模式——代理模式
2、FactoryBean、BeanFactory、ApplicationContext 有什么區(qū)別?
- BeanFactory:是一個(gè) Bean 工廠,使用簡(jiǎn)單工廠模式,是 Spring IoC 容器頂級(jí)接口,是用于管理 Bean 的工廠,最核心的功能是通過(guò)
getBean()
方法加載 Bean 對(duì)象,通常我們不會(huì)直接使用該接口,而是使用其子接口 ApplicationContext。 - FactoryBean:是一個(gè)工廠 Bean,使用了工廠方法模式,實(shí)現(xiàn)該接口的類可以自己定義要?jiǎng)?chuàng)建的 Bean 實(shí)例,只需要實(shí)現(xiàn)它的
getObject()
方法即可。 - ApplicationConext:是 BeanFactory 的子接口,擴(kuò)展了 BeanFactory 的功能(高級(jí) IOC 容器)。
3、說(shuō)一說(shuō)Spring Bean 的生命周期?
Spring Bean 生命周期簡(jiǎn)單概括為 5 個(gè)階段:
- Bean 的實(shí)例化階段:創(chuàng)建一個(gè) Bean 對(duì)象。
- Bean 實(shí)例的屬性填充階段:為 Bean 實(shí)例的屬性賦值。
- Bean 實(shí)例的初始化階段:對(duì) Bean 實(shí)例進(jìn)行初始化。
- Bean 實(shí)例的正常使用階段。
- Bean 實(shí)例的銷毀階段:容器關(guān)閉后,將 Bean 實(shí)例銷毀。
4、依賴注入的實(shí)現(xiàn)方法,以及相關(guān)注解?
構(gòu)造方法注入、Setter 方法注入、接口注入 三種。
依賴注入的相關(guān)注解
- @Autowired:自動(dòng)按類型注入,如果有多個(gè)匹配則按照指定 Bean 的 id 查找,查找不到會(huì)報(bào)錯(cuò)。
- @Qualifier:在自動(dòng)按照類型注入的基礎(chǔ)上再按照 Bean 的 id 注入,給變量注入時(shí)必須搭配@Autowired,給方法注入時(shí)可單獨(dú)使用。
- @Resource :直接按照 Bean 的 id 注入,只能注入 Bean 類型。
- @Value :用于注入基本數(shù)據(jù)類型和 String 類型。
5、什么是 Spring IOC ?
IOC 即控制反轉(zhuǎn),簡(jiǎn)單來(lái)說(shuō)就是把原來(lái)代碼里需要實(shí)現(xiàn)的對(duì)象創(chuàng)建、依賴反轉(zhuǎn)給容器來(lái)幫忙實(shí)現(xiàn),Spring 中管理對(duì)象及其依賴關(guān)系都是通過(guò) Spring 的 IOC 容器實(shí)現(xiàn)的。
IOC 的實(shí)現(xiàn)方式有依賴注入和依賴查找,由于依賴查找使用的很少,因此 IOC 也叫做依賴注入。
我們之前在創(chuàng)建一個(gè)對(duì)象的時(shí)候都是直接 new
一個(gè)對(duì)象實(shí)例,而有了 IOC ,對(duì)象實(shí)例的創(chuàng)建都交給容器去實(shí)現(xiàn)即可。
6、Spring IOC 容器的構(gòu)建流程(初始化過(guò)程)
我們以 XML 方式的容器初始化為例:
通過(guò) ClassPathXmlApplicationContext
,去創(chuàng)建 ApplicationContext
容器對(duì)象:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
ClassPathXmlApplicationContext
創(chuàng)建容器對(duì)象時(shí),構(gòu)造方法做了如下兩件事:
- ① 調(diào)用父容器的構(gòu)造方法為容器先設(shè)置好 Bean 資源加載器。
- ② 調(diào)用父類的 setConfigLocations() 方法設(shè)置 Bean 配置信息的定位路徑
- ③ 調(diào)用父類 AbstractApplicationContext 的 refresh() 方法啟動(dòng)整個(gè) IOC 容器對(duì) Bean 的載入,在創(chuàng)建 IOC 容器前如果已有容器存在,需要把已有的容器銷毀,保證在 refresh() 方法后使用的是新創(chuàng)建的 IOC 容器。
- 容器創(chuàng)建完成后,通過(guò)
loadBeanDefinitions()
方法加載 Bean 配置資源,該方法在加載資源時(shí),首先解析配置文件路徑,讀取配置文件的內(nèi)容,然后通過(guò) XML 解析器將 Bean 的配置信息轉(zhuǎn)換成文檔對(duì)象,之后按照 Spring Bean 的定義規(guī)則將文檔對(duì)象解析為 BeanDefinition 對(duì)象。 - 接下來(lái),將解析得到的 BeanDefinition 對(duì)象存入本地緩存(一個(gè) HashMap 集合,key 是字符串,值是 BeanDefinition)中。
- 最后,實(shí)例化所有的 Bean 實(shí)例(非懶加載):包括實(shí)例的創(chuàng)建,實(shí)例的屬性填充,實(shí)例的初始化。
7、依賴注入的過(guò)程(Bean 的加載流程)?
源碼分析可以參考我的文章:Spring源碼分析——Bean的加載
先來(lái)看下面幾行代碼:
public class BeanFactoryTest { public static void main(String[] args) { // 加載與解析XML配置文件,獲得BeanFactory: BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-bf.xml")); // 從BeanFactory中加載Bean對(duì)象 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 } }
- 首先通過(guò) BeanFactory/ApplicationContext 調(diào)用
getBean()
方法,來(lái)獲取 Bean 實(shí)例,該方法中,真正獲取 Bean 實(shí)例的是其內(nèi)層方法doGetBean()
方法(真正實(shí)現(xiàn)從 IOC 容器獲取 Bean ,也是觸發(fā)依賴注入的地方)。 - 在
doGetBean()
方法中,主要做了以下幾件事:- ① beanName 的轉(zhuǎn)換方法
transformedBeanName(name),
該方法的作用是,根據(jù)傳入的 name 參數(shù),獲取真正的 Bean 對(duì)應(yīng)的 beanName。該方法的 name 參數(shù),有可能是一個(gè)別名(alias 屬性設(shè)置的別名),也有可能是一個(gè)&開(kāi)頭的 name (工廠 Bean 對(duì)象)。 - ② 嘗試從緩存中加載 Bean 的單實(shí)例,根據(jù)上面
transformedBeanName
方法轉(zhuǎn)換 name 后得到的真實(shí) beanName,getSingleton(beanName)
方法直接嘗試從緩存中獲取 Bean 的共享單實(shí)例,這時(shí)候獲取的是初始狀態(tài),尚未實(shí)例化。(從緩存中加載的流程就是,根據(jù) beanName 依次從一級(jí)緩存、二級(jí)緩存、三級(jí)緩存中嘗試獲取,通過(guò)三級(jí)緩存機(jī)制也可以有效避免循環(huán)依賴) - ③ Bean 的實(shí)例化,
getSingleton(beanName)
方法執(zhí)行后,從緩存中得到了 Bean 的原始狀態(tài),接下來(lái)需要對(duì)該 Bean 進(jìn)行實(shí)例化。 - ④ Bean 的初始化:尋找依賴(循環(huán)依賴檢查、依賴注入),因?yàn)?Bean 的初始化過(guò)程中很可能會(huì)用到某些屬性,而某些屬性很可能是動(dòng)態(tài)配置的,并且配置的成依賴于其他的 Bean,那么此時(shí)應(yīng)該先加載依賴的 Bean。所以在流程中,Spring初始化一個(gè) Bean,會(huì)先初始化其依賴的所有的其他 Bean。
- ⑤ 根據(jù)不同的 scope 作用域創(chuàng)建 Bean,調(diào)用
doCreateBean()
方法創(chuàng)建 Bean。 - ⑥ 類型轉(zhuǎn)換,根據(jù) scope 創(chuàng)建完 Bean 成功后,一般可以直接返回即可。但當(dāng)傳入
doGetBean
方法中的requireType
參數(shù)不為空時(shí),意味著我們對(duì)最后返回的 Bean 有著類型上的要求。Spring 通過(guò) 類型轉(zhuǎn)換器 將第 ⑤ 步創(chuàng)建完成的 Bean 轉(zhuǎn)換為requireType
指定的類型。
- ① beanName 的轉(zhuǎn)換方法
8、Bean 的作用范圍?
通過(guò) scope 屬性指定 Bean 的作用范圍,包括:
- ①
singleton
:?jiǎn)卫J?,是默認(rèn)作用域,不管收到多少 Bean 請(qǐng)求每個(gè)容器中只有一個(gè)唯一的 Bean 實(shí)例。 - ②
prototype
:原型模式,和 singleton 相反,每次 Bean 請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的實(shí)例。 - ③
request
:每次 HTTP 請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的 Bean 并把它放到 request 域中,在請(qǐng)求完成后 Bean 會(huì)失效并被垃圾收集器回收。 - ④
session
:和 request 類似,確保每個(gè) session 中有一個(gè) Bean 實(shí)例,session 過(guò)期后 bean 會(huì)隨之失效。 - ⑤
global session
:當(dāng)應(yīng)用部署在 Portlet 容器時(shí),如果想讓所有 Portlet 共用全局存儲(chǔ)變量,那么該變量需要存儲(chǔ)在 global session 中。
9、Spring事務(wù)傳播機(jī)制有哪些?
- ① REQUIRED:Spring 默認(rèn)的事務(wù)傳播級(jí)別,如果上下文中已經(jīng)存在事務(wù),那么就加入到事務(wù)中執(zhí)行,如果當(dāng)前上下文中不存在事務(wù),則新建事務(wù)執(zhí)行。
- ② REQUIRES_NEW:每次都會(huì)新建一個(gè)事務(wù),如果上下文中有事務(wù),則將上下文的事務(wù)掛起,當(dāng)新建事務(wù)執(zhí)行完成以后,上下文事務(wù)再恢復(fù)執(zhí)行。
- ③ SUPPORTS:如果上下文存在事務(wù),則加入到事務(wù)執(zhí)行,如果沒(méi)有事務(wù),則使用非事務(wù)的方式執(zhí)行。
- ④ MANDATORY:上下文中必須要存在事務(wù),否則就會(huì)拋出異常。
- ⑤ NOT_SUPPORTED :如果上下文中存在事務(wù),則掛起事務(wù),執(zhí)行當(dāng)前邏輯,結(jié)束后恢復(fù)上下文的事務(wù)。
- ⑥ NEVER:上下文中不能存在事務(wù),否則就會(huì)拋出異常。
- ⑦ ESTED:嵌套事務(wù)。如果上下文中存在事務(wù),則嵌套事務(wù)執(zhí)行,如果不存在事務(wù),則新建事務(wù)。
10、Spring 的事務(wù)隔離級(jí)別有哪些?
Spring 的事務(wù)隔離級(jí)別底層其實(shí)是基于數(shù)據(jù)庫(kù)的,Spring 并沒(méi)有自己的一套隔離級(jí)別。
- DEFAULT:使用數(shù)據(jù)庫(kù)的默認(rèn)隔離級(jí)別。
- READ_UNCOMMITTED:讀未提交,最低的隔離級(jí)別,會(huì)讀取到其他事務(wù)還未提交的內(nèi)容,存在臟讀。
- READ_COMMITTED:讀已提交,讀取到的內(nèi)容都是已經(jīng)提交的,可以解決臟讀,但是存在不可重復(fù)讀。
- REPEATABLE_READ:可重復(fù)讀,在一個(gè)事務(wù)中多次讀取時(shí)看到相同的內(nèi)容,可以解決不可重復(fù)讀,但是存在幻讀。
- SERIALIZABLE:串行化,最高的隔離級(jí)別,對(duì)于同一行記錄,寫會(huì)加寫鎖,讀會(huì)加讀鎖。在這種情況下,只有讀讀能并發(fā)執(zhí)行,其他并行的讀寫、寫讀、寫寫操作都是沖突的,需要串行執(zhí)行??梢苑乐古K讀、不可重復(fù)度、幻讀,沒(méi)有并發(fā)事務(wù)問(wèn)題。
11、AOP 是什么?AOP有哪些應(yīng)用場(chǎng)景?
AOP 概念: 即面向切面編程,使用動(dòng)態(tài)代理技術(shù),在不修改源碼的基礎(chǔ)上對(duì)目標(biāo)方法進(jìn)行增強(qiáng)。
Spring 中的 AOP 目前支持 JDK 動(dòng)態(tài)代理和 Cglib 代理。如果被代理對(duì)象實(shí)現(xiàn)了接口,則使用 JDK 動(dòng)態(tài)代理,否則使用 Cglib 代理。另外,也可以通過(guò)指定 proxyTargetClass=true
來(lái)實(shí)現(xiàn)強(qiáng)制走 Cglib 代理。
應(yīng)用場(chǎng)景:
- 權(quán)限認(rèn)證
- 日志打印
- 事務(wù)
- …
12、AOP 的相關(guān)注解有哪些?
@Aspect
:切面,聲明被注解標(biāo)注的類是一個(gè)切面 Bean。
@Aspect @Component public class LogAspect { ... }
@Pointcut
:切入點(diǎn),可以通過(guò)@Pointcut("execution(* top.csp1999.service.impl.*.*(..))")
去指定要切入的目標(biāo)對(duì)象,并對(duì)其符合表達(dá)式要求的方法進(jìn)行增強(qiáng)。
@Pointcut("execution(* top.csp1999.service.impl.*.*(..))") public void operationLog(){}
@Before
:前置通知,指在某個(gè)連接點(diǎn)之前執(zhí)行的通知。
@Before("operationLog()") public void doBeforeAdvice(JoinPoint joinPoint){ System.out.println("進(jìn)入方法前執(zhí)行....."); }
@After
:后置通知,指某個(gè)連接點(diǎn)退出時(shí)執(zhí)行的通知(不論正常返回還是異常退出)。
@After("operationLog()") public void after(JoinPoint jp){ System.out.println("方法最后執(zhí)行....."); }
@AfterReturning
:后置返回通知,指某連接點(diǎn)正常完成之后執(zhí)行的通知,返回值可以在返回后通知方法里接收。
@AfterReturning(returning = "ret", pointcut = "operationLog()") public void doAfterReturning(Object ret) { System.out.println("方法的返回值 : " + ret); }
@AfterThrowing
:后置異常通知,指方法拋出異常導(dǎo)致退出時(shí)執(zhí)行的通知,和@AfterReturning只會(huì)有一個(gè)執(zhí)行,異常使用 throwing 屬性接收。
@AfterThrowing(throwing = "jp", pointcut = "operationLog()") public void throwss(JoinPoint jp){ System.out.println("方法異常時(shí)執(zhí)行....."); }
@Around
:環(huán)繞通知,可以用來(lái)在調(diào)用一個(gè)具體方法前和調(diào)用后來(lái)完成一些具體的任務(wù)。
@Around("operationLog()") public Object run2(ProceedingJoinPoint joinPoint) throws Throwable { // 獲取方法參數(shù)值數(shù)組 Object[] args = joinPoint.getArgs(); // 得到其方法簽名 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); // 獲取方法參數(shù)類型數(shù)組 Class[] paramTypeArray = methodSignature.getParameterTypes(); if (EntityManager.class.isAssignableFrom(paramTypeArray[paramTypeArray.length - 1])) { // 如果方法的參數(shù)列表最后一個(gè)參數(shù)是entityManager類型,則給其賦值 args[args.length - 1] = entityManager; } logger.info("請(qǐng)求參數(shù)為{}",args); // 動(dòng)態(tài)修改其參數(shù) // 注意,如果調(diào)用joinPoint.proceed()方法,則修改的參數(shù)值不會(huì)生效,必須調(diào)用joinPoint.proceed(Object[] args) Object result = joinPoint.proceed(args); logger.info("響應(yīng)結(jié)果為{}",result); // 如果這里不返回result,則目標(biāo)對(duì)象實(shí)際返回值會(huì)被置為null return result; }
13、AOP 的相關(guān)術(shù)語(yǔ)有什么?
Aspect
:切面,一個(gè)關(guān)注點(diǎn)的模塊化,這個(gè)關(guān)注點(diǎn)可能會(huì)橫切多個(gè)對(duì)象。
Joinpoint
:連接點(diǎn),程序執(zhí)行過(guò)程中的某一行為,即業(yè)務(wù)層中的所有方法。。
Advice
:通知,指切面對(duì)于某個(gè)連接點(diǎn)所產(chǎn)生的動(dòng)作,包括前置通知、后置通知、返回后通知、異常通知和環(huán)繞通知。
Pointcut
:切入點(diǎn),指被攔截的連接點(diǎn),切入點(diǎn)一定是連接點(diǎn),但連接點(diǎn)不一定是切入點(diǎn)。
Proxy
:代理,Spring AOP 中有 JDK 動(dòng)態(tài)代理和 CGLib 代理,目標(biāo)對(duì)象實(shí)現(xiàn)了接口時(shí)采用 JDK 動(dòng)態(tài)代理,反之采用 CGLib 代理。
Target
:代理的目標(biāo)對(duì)象,指一個(gè)或多個(gè)切面所通知的對(duì)象。
Weaving
:織入,指把增強(qiáng)應(yīng)用到目標(biāo)對(duì)象來(lái)創(chuàng)建代理對(duì)象的過(guò)程。
14、總結(jié)
文章會(huì)不定時(shí)更新,有時(shí)候一天多更新幾篇,還請(qǐng)三連支持一下,后續(xù)會(huì)億點(diǎn)點(diǎn)的更新!也希望大家可以關(guān)注腳本之家其他文章!
相關(guān)文章
SpringBoot中的RestTemplate使用方法詳解
這篇文章主要介紹了SpringBoot中的RestTemplate使用方法詳解,為了方便使用,這里我封裝成一個(gè)工具類來(lái)靜態(tài)調(diào)用RestTemplate,基于SpringBoot2.4.2版本,需要的朋友可以參考下2024-01-01IDEA-SpringBoot項(xiàng)目Debug啟動(dòng)不了(卡住不動(dòng))的原因分析
這篇文章主要介紹了IDEA-SpringBoot項(xiàng)目Debug啟動(dòng)不了(卡住不動(dòng))的原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11IDEA個(gè)性化設(shè)置注釋模板詳細(xì)講解版
IDEA自帶的注釋模板不是太好用,我本人到網(wǎng)上搜集了很多資料系統(tǒng)的整理了一下制作了一份比較完整的模板來(lái)分享給大家,下面這篇文章主要給大家介紹了IDEA個(gè)性化設(shè)置注釋模板的相關(guān)資料,需要的朋友可以參考下2024-01-01在controller中如何設(shè)置接收參數(shù)的默認(rèn)值
這篇文章主要介紹了在controller中如何設(shè)置接收參數(shù)的默認(rèn)值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java異常繼承結(jié)構(gòu)解析_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java異常繼承結(jié)構(gòu)解析的相關(guān)知識(shí),需要的朋友可以參考下2017-04-04Java中System.currentTimeMillis()計(jì)算方式與時(shí)間單位轉(zhuǎn)換講解
本文詳細(xì)講解了Java中System.currentTimeMillis()計(jì)算方式與時(shí)間單位轉(zhuǎn)換,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12