框架篇面試題 - 參考回答#
面試官:Spring 框架中的單例 bean 是線程安全的嗎?#
候選人:
嗯!
不是線程安全的,是這樣的
當多用戶同時請求一個服務時,容器會給每一個請求分配一個線程,這是多個線程會並發執行該請求對應的業務邏輯(成員方法),如果該處理邏輯中有對該單例狀態的修改(體現為該單例的成員屬性),則必須考慮線程同步問題。
Spring 框架並沒有對單例 bean 進行任何多線程的封裝處理。關於單例 bean 的線程安全和並發問題需要開發者自行去搞定。
比如:我們通常在項目中使用的 Spring bean 都是不可變的狀態(比如 Service 類和 DAO 類),所以在某種程度上說 Spring 的單例 bean 是線程安全的。
如果你的 bean 有多種狀態的話(比如 View Model 對象),就需要自行保證線程安全。最淺顯的解決辦法就是將多態 bean 的作用由 “singleton” 變更為 “prototype”。
面試官:什麼是 AOP
候選人:
AOP 是面向切面編程,在 spring 中用於將那些與業務無關,但卻對多個對象產生影響的公共行為和邏輯,抽取公共模塊重用,降低耦合,一般比如可以作為公共日誌保存,事務處理等
面試官:你們項目中有沒有使用到 AOP
候選人:
我們當時在後台管理系統中,就是使用 AOP 來記錄了系統的操作日誌
主要思路是這樣的,使用 AOP 中的環繞通知 + 切點表達式,這個表達式就是要找到要記錄日誌的方法,然後通過環繞通知的參數獲取請求方法的參數,比如類信息、方法信息、註解、請求方式等,獲取到這些參數以後,保存到數據庫
面試官:Spring 中的事務是如何實現的
候選人:
Spring 實現的事務本質就是 AOP 完成,對方法前後進行攔截,在執行方法之前開啟事務,在執行完目標方法之後根據執行情況提交或者回滾事務。
面試官:Spring 中事務失效的場景有哪些
候選人:
嗯!這個在項目中之前遇到過,我想想啊
第一個,如果方法上異常捕獲處理,自已處理了異常,沒有拋出,就會導致事務失效,所以一般處理了異常以後,別忘了拋出去就行了
第二個,如果方法拋出檢查異常,如果報錯也會導致事務失效,最後在 Spring 事務的註解上,就是 @Transactional 上配置 rollbackFor 屬性為 Exception,這樣不管是什麼異常,都会回滾事務
第三,我之前還遇到過一個,如果方法上不是 public 修飾的,也會導致事務失效
嗯,就能想起來那麼多
面試官:Spring 的 bean 的生命週期
候選人:
嗯!,這個步驟還是挺多的,我之前看過一些源碼,它大概流程是這樣的
首先會通過一個非常重要的類,叫做 BeanDefinition 獲取 bean 的定義信息,這裡面就封裝了 bean 的所有信息,比如,類的全路徑,是否是延遲加載,是否是單例等等這些信息
在創建 bean 的時候,第一步是調用構造函數實例化 bean
第二步是 bean 的依賴注入,比如一些 set 方法注入,像平時開發用的 @Autowire 都是這一步完成
第三步是處理 Aware 接口,如果某一個 bean 實現了 Aware 接口就會重寫方法執行
第四步是 bean 的後置處理器 BeanPostProcessor,這個是前置處理器
第五步是初始化方法,比如實現了接口 InitializingBean 或者自定義了方法 init-method 標籤或 @PostConstruct
第六步是執行了 bean 的後置處理器 BeanPostProcessor,主要是對 bean 進行增強,有可能在這裡產生代理對象
最後一步是銷毀 bean
面試官:Spring 中的循環引用
候選人:
嗯,好的,我來解釋一下
循環依賴:循環依賴其實就是循環引用,也就是兩個或兩個以上的 bean 互相持有對方,最終形成閉環。比如 A 依賴於 B,B 依賴於 A
循環依賴在 Spring 中是允許存在,Spring 框架依據三級緩存已經解決了大部分的循環依賴
①一級緩存:單例池,緩存已經經歷了完整的生命週期,已經初始化完成的 bean 對象
②二級緩存:緩存早期的 bean 對象(生命週期還沒走完)
③三級緩存:緩存的是 ObjectFactory,表示對象工廠,用來創建某個對象的
面試官:那具體解決流程清楚嗎?
候選人:
第一,先實例 A 對象,同時會創建 ObjectFactory 對象存入三級緩存 singletonFactories
第二,A 在初始化的時候需要 B 對象,這個走 B 的創建的邏輯
第三,B 實例化完成,也會創建 ObjectFactory 對象存入三級緩存 singletonFactories
第四,B 需要注入 A,通過三級緩存中獲取 ObjectFactory 來生成一個 A 的對象同時存入二級緩存,這個是有兩種情況,一個是可能是 A 的普通對象,另外一個是 A 的代理對象,都可以讓 ObjectFactory 來生產對應的對象,這也是三級緩存的關鍵
第五,B 通過從二級緩存 earlySingletonObjects 獲得到 A 的對象後可以正常注入,B 創建成功,存入一級緩存 singletonObjects
第六,回到 A 對象初始化,因為 B 對象已經創建完成,則可以直接注入 B,A 創建成功存入一次緩存 singletonObjects
第七,二級緩存中的臨時對象 A 清除
面試官:構造方法出現了循環依賴怎麼解決?
候選人:
由於 bean 的生命週期中構造函數是第一個執行的,Spring 框架並不能解決構造函數的依賴注入,可以使用 @Lazy 懶加載,什麼時候需要對象再進行 bean 對象的創建
面試官:SpringMVC 的執行流程知道嗎
候選人:
嗯,這個知道的,它分了好多步驟
1、用戶發送出請求到前端控制器 DispatcherServlet,這是一個調度中心
2、DispatcherServlet 收到請求調用 HandlerMapping(處理器映射器)。
3、HandlerMapping 找到具體的處理器(可查找 xml 配置或註解配置),生成處理器對象及處理器攔截器(如果有),再一起返回給 DispatcherServlet。
4、DispatcherServlet 調用 HandlerAdapter(處理器適配器)。
5、HandlerAdapter 經過適配調用具體的處理器(Handler/Controller)。
6、Controller 執行完成返回 ModelAndView 對象。
7、HandlerAdapter 將 Controller 執行結果 ModelAndView 返回給 DispatcherServlet。
8、DispatcherServlet 將 ModelAndView 傳給 ViewResolver(視圖解析器)。
9、ViewResolver 解析後返回具體 View(視圖)。
10、DispatcherServlet 根據 View 進行渲染視圖(即將模型數據填充至視圖中)。
11、DispatcherServlet 響應用戶。
當然現在的開發,基本都是前後端分離的開發的,並沒有視圖這些,一般都是 handler 中使用 Response 直接結果返回
面試官:Spring Boot 自動配置原理
候選人:
嗯,好的,它是這樣的。
在 Spring Boot 項目中的引導類上有一個註解 @SpringBootApplication,這個註解是對三個註解進行了封裝,分別是:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
其中
@EnableAutoConfiguration
是實現自動化配置的核心註解。該註解通過
@Import
註解導入對應的配置選擇器。關鍵的是內部就是讀取了該項目和該項目引用的 Jar 包的 classpath 路徑下META-INF/spring.factories文件中的所配置的類的全類名。在這些配置類中所定義的 Bean 會根據條件註解所指定的條件來決定是否需要將其導入到 Spring 容器中。
一般條件判斷會有像
@ConditionalOnClass
這樣的註解,判斷是否有對應的 class 文件,如果有則加載該類,把這個配置類的所有的 Bean 放入 Spring 容器中使用。面試官:Spring 的常見註解有哪些?
候選人:
嗯,這個就很多了
第一類是:聲明 bean,有 @Component、@Service、@Repository、@Controller
第二類是:依賴注入相關的,有 @Autowired、@Qualifier、@Resource
第三類是:設置作用域 @Scope
第四類是:Spring 配置相關的,比如 @Configuration,@ComponentScan 和 @Bean
第五類是:跟 AOP 相關做增強的註解 @Aspect,@Before,@After,@Around,@Pointcut
面試官:SpringMVC 常見的註解有哪些?
候選人:
嗯,這個也很多的
有 @RequestMapping:用於映射請求路徑;
@RequestBody:註解實現接收 http 請求的 json 數據,將 json 轉換為 java 對象;
@RequestParam:指定請求參數的名稱;
@PathVariable:從請求路徑下中獲取請求參數 (/user/{id}),傳遞給方法的形式參數;@ResponseBody:註解實現將 controller 方法返回對象轉化為 json 對象響應給客戶端。@RequestHeader:獲取指定的請求頭數據,還有像 @PostMapping、@GetMapping 這些。
面試官:Spring Boot 常見註解有哪些?
候選人:
嗯~~
Spring Boot 的核心註解是 @SpringBootApplication,它由幾個註解組成:
- @SpringBootConfiguration:組合了 - @Configuration 註解,實現配置文件的功能;
- @EnableAutoConfiguration:打開自動配置的功能,也可以關閉某個自動配置的選項
- @ComponentScan:Spring 組件掃描
面試官:MyBatis 執行流程
候選人:
好,這個知道的,不過步驟也很多
①讀取 MyBatis 配置文件:mybatis-config.xml 加載運行環境和映射文件
②構造會話工廠 SqlSessionFactory,一個項目只需要一個,單例的,一般由 Spring 進行管理
③會話工廠創建 SqlSession 對象,這裡面就含了執行 SQL 語句的所有方法
④操作數據庫的接口,Executor 執行器,同時負責查詢緩存的維護
⑤Executor 接口的執行方法中,有一個 MappedStatement 類型的參數,封裝了映射信息
⑥輸入參數映射
⑦輸出結果映射
面試官:MyBatis 是否支持延遲加載?
候選人:
是支持的~
延遲加載的意思是:就是在需要用到數據時才進行加載,不需要用到數據時就不加載數據。
MyBatis 支持一對一關聯對象和一對多關聯集合對象的延遲加載
在 MyBatis 配置文件中,可以配置是否啟用延遲加載 lazyLoadingEnabled=true|false,默認是關閉的
面試官:延遲加載的底層原理知道嗎?
候選人:
嗯,我想想啊
延遲加載在底層主要使用的 CGLIB 動態代理完成的
第一是,使用 CGLIB 創建目標對象的代理對象,這裡的目標對象就是開啟了延遲加載的 mapper
第二個是當調用目標方法時,進入攔截器 invoke 方法,發現目標方法是 null 值,再執行 sql 查詢
第三個是獲取數據以後,調用 set 方法設置屬性值,再繼續查詢目標方法,就有值了
面試官:MyBatis 的一级、二级缓存用过吗?
候選人:
嗯~~,用過的~
MyBatis 的一级缓存:基於 PerpetualCache 的 HashMap 本地緩存,其存儲作用域為 Session,當 Session 進行 flush 或 close 之後,該 Session 中的所有 Cache 就將清空,默認打開一级缓存
關於二級緩存需要單獨開啟
二級緩存是基於 namespace 和 mapper 的作用域起作用的,不是依賴於 SQL session,默認也是採用 PerpetualCache,HashMap 存儲。
如果想要開啟二級緩存需要在全局配置文件和映射文件中開啟配置才行。
面試官:MyBatis 的二級緩存什麼時候會清理緩存中的數據
候選人:
嗯!!
當某一個作用域(一级缓存 Session / 二级缓存 Namespaces)的進行了新增、修改、刪除操作後,默認該作用域下所有 select 中的緩存將被 clear。