Spring面試題大全
Spring是什麼?
Spring是一個輕量級的IoC和AOP容器框架。是為Java應用程式提供基礎性服務的一套框架,目的是用於簡化企業應用程式的開發,它使得開發者只需要關心業務需求。常見的配置方式有三種:基於XML的配置、基於註解的配置、基於Java的配置。
主要由以下幾個模組組成:
Spring Core:核心類庫,可以說 Spring 其他所有的功能都需要依賴於該類庫。主要提供 IOC 依賴注入功能。
Spring Context:提供框架式的Bean訪問方式,以及企業級功能(JNDI、定時任務等);
Spring Aspects:該模組為與AspectJ的整合提供支援。
Spring AOP:AOP切面服務,體現了spring生命週期;
Spring DAO:對JDBC的抽象,簡化了資料訪問異常的處理;
Spring ORM:對現有的ORM框架的支援;
Spring Web:提供了基本的面向Web的綜合特性,例如多方檔案上傳;
Spring MVC:提供面向Web應用的Model-View-Controller實現。
Spring Test:提供了對JUnit和TestNG測試的支援。
Spring 的 6 個特徵:
- 核心技術 :依賴注入(DI),AOP,事件(events),資源,i18n,驗證,資料繫結,型別轉換,SpEL。
- 測試 :模擬物件,TestContext框架,Spring MVC 測試,WebTestClient。
- 資料訪問 :事務,DAO支援,JDBC,ORM,編組XML。
- Web支援 : Spring MVC和Spring WebFlux Web框架。
- 整合 :遠端處理,JMS,JCA,JMX,電子郵件,任務,排程,快取。
- 語言 :Kotlin,Groovy,動態語言。
Spring 的優點?
1.降低了元件之間的耦合性 ,實現了軟體各層之間的解耦
2.可以使用容易提供的眾多服務,如事務管理,訊息服務等
3.容器提供單例模式支援
4.容器提供了AOP技術,支援將一些通用任務,如安全、事務、日誌、許可權等進行集中式管理,從而提供更好的複用。
5.容器提供了眾多的輔助類,能加快應用的開發
6.spring對於主流的應用框架提供了整合支援,如
7.spring屬於低侵入式設計,程式碼的汙染極低
8.獨立於各種應用伺服器
9.spring的DI機制將物件之間的依賴關係交由框架處理,減低元件的耦合性;
10.Spring的高度開放性,並不強制應用完全依賴於Spring,開發者可以自由選擇spring的部分或全部
IOC
(1)IOC就是控制反轉,是指建立物件的控制權的轉移,以前建立物件的主動權和時機是由自己把控的,而現在這種權力轉移到Spring容器中,並由容器根據配置檔案去建立例項和管理各個例項之間的依賴關係,物件與物件之間鬆散耦合,也利於功能的複用。DI依賴注入,和控制反轉是同一個概念的不同角度的描述,即 應用程式在執行時依賴IoC容器來動態注入物件需要的外部資源。
(2)最直觀的表達就是,IOC讓物件的建立不用去new了,可以由spring自動生產,使用java的反射機制,根據配置檔案在執行時動態的去建立物件以及管理物件,並呼叫物件的方法的。
(3)Spring的IOC有三種注入方式 :構造器注入、setter方法注入、根據註解注入。
IoC讓相互協作的元件保持鬆散的耦合,而AOP程式設計允許你把遍佈於應用各層的功能分離出來形成可重用的功能元件。
AOP
OOP面向物件,允許開發者定義縱向的關係,但並適用於定義橫向的關係,導致了大量程式碼的重複,而不利於各個模組的重用。
AOP,一般稱為面向切面,作為面向物件的一種補充,用於將那些與業務無關,但卻對多個物件產生影響的公共行為和邏輯,抽取並封裝為一個可重用的模組,這個模組被命名為“切面”(Aspect),減少系統中的重複程式碼,降低了模組間的耦合度,同時提高了系統的可維護性。可用於許可權認證、日誌、事務處理。
AOP實現的關鍵在於 代理模式,AOP代理主要分為靜態代理和動態代理。靜態代理的代表為AspectJ;動態代理則以Spring AOP為代表。
(1)AspectJ是靜態代理的增強,所謂靜態代理,就是AOP框架會在編譯階段生成AOP代理類,因此也稱為編譯時增強,他會在編譯階段將AspectJ(切面)織入到Java位元組碼中,執行的時候就是增強之後的AOP物件。
(2)Spring AOP使用的動態代理,所謂的動態代理就是說AOP框架不會去修改位元組碼,而是每次執行時在記憶體中臨時為方法生成一個AOP物件,這個AOP物件包含了目標物件的全部方法,並且在特定的切點做了增強處理,並回調原物件的方法。
Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理:
①JDK動態代理只提供介面的代理,不支援類的代理。核心InvocationHandler介面和Proxy類,InvocationHandler 通過invoke()方法反射來呼叫目標類中的程式碼,動態地將橫切邏輯和業務編織在一起;接著,Proxy利用 InvocationHandler動態建立一個符合某一介面的的例項, 生成目標類的代理物件。
②如果代理類沒有實現 InvocationHandler 介面,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個程式碼生成的類庫,可以在執行時動態的生成指定類的一個子類物件,並覆蓋其中特定方法並新增增強程式碼,從而實現AOP。CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那麼它是無法使用CGLIB做動態代理的。
(3)靜態代理與動態代理區別在於生成AOP代理物件的時機不同,相對來說AspectJ的靜態代理方式具有更好的效能,但是AspectJ需要特定的編譯器進行處理,而Spring AOP則無需特定的編譯器處理。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最終生成的代理例項; method 是被代理目標例項的某個具體方法; args 是被代理目標例項某個方法的具體入參, 在方法反射呼叫時使用。
Spring AOP 和 AspectJ AOP 有什麼區別?
Spring AOP 屬於執行時增強,而 AspectJ 是編譯時增強。 Spring AOP 基於代理(Proxying),而 AspectJ 基於位元組碼操作(Bytecode Manipulation)。
Spring AOP 已經集成了 AspectJ ,AspectJ 應該算的上是 Java 生態系統中最完整的 AOP 框架了。AspectJ 相比於 Spring AOP 功能更加強大,但是 Spring AOP 相對來說更簡單,
如果我們的切面比較少,那麼兩者效能差異不大。但是,當切面太多的話,最好選擇 AspectJ ,它比Spring AOP 快很多。
Spring 中的 bean 的作用域有哪些?
- singleton : 唯一 bean 例項,Spring 中的 bean 預設都是單例的。
- prototype : 每次請求都會建立一個新的 bean 例項。
- request : 每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP request內有效。
- session : 每一次HTTP請求都會產生一個新的 bean,該bean僅在當前 HTTP session 內有效。
- global-session: 全域性session作用域,僅僅在基於portlet的web應用中才有意義,Spring5已經沒有了。Portlet是能夠生成語義程式碼(例如:HTML)片段的小型Java Web外掛。它們基於portlet容器,可以像servlet一樣處理HTTP請求。但是,與 servlet 不同,每個 portlet 都有不同的會話
Spring 中的單例 bean 的執行緒安全問題了解嗎?
Spring框架並沒有對單例bean進行任何多執行緒的封裝處理。關於單例bean的執行緒安全和併發問題需要開發者自行去搞定。但實際上,大部分的Spring bean並沒有可變的狀態(比如Serview類和DAO類),所以在某種程度上說Spring的單例bean是執行緒安全的。如果你的bean有多種狀態的話(比如 View Model 物件),就需要自行保證執行緒安全。最淺顯的解決辦法就是將多型bean的作用域由“singleton”變更為“prototype”。
常見的有兩種解決辦法:
- 在Bean物件中儘量避免定義可變的成員變數(不太現實)。
- 在類中定義一個ThreadLocal成員變數,將需要的可變成員變數儲存在 ThreadLocal 中(推薦的一種方式)。
BeanFactory和ApplicationContext有什麼區別?
BeanFactory和ApplicationContext是Spring的兩大核心介面,都可以當做Spring的容器。其中ApplicationContext是BeanFactory的子介面。
(1)BeanFactory:是Spring裡面最底層的介面,包含了各種Bean的定義,讀取bean配置文件,管理bean的載入、例項化,控制bean的生命週期,維護bean之間的依賴關係。ApplicationContext介面作為BeanFactory的派生,除了提供BeanFactory所具有的功能外,還提供了更完整的框架功能:
①繼承MessageSource,因此支援國際化。
②統一的資原始檔訪問方式。
③提供在監聽器中註冊bean的事件。
④同時載入多個配置檔案。
⑤載入多個(有繼承關係)上下文 ,使得每一個上下文都專注於一個特定的層次,比如應用的web層。
(2)①BeanFactroy採用的是延遲載入形式來注入Bean的,即只有在使用到某個Bean時(呼叫getBean()),才對該Bean進行載入例項化。這樣,我們就不能發現一些存在的Spring的配置問題。如果Bean的某一個屬性沒有注入,BeanFacotry載入後,直至第一次使用呼叫getBean方法才會丟擲異常。
②ApplicationContext,它是在容器啟動時,一次性建立了所有的Bean。這樣,在容器啟動時,我們就可以發現Spring中存在的配置錯誤,這樣有利於檢查所依賴屬性是否注入。 ApplicationContext啟動後預載入所有的單例項Bean,通過預載入單例項bean ,確保當你需要的時候,你就不用等待,因為它們已經建立好了。
③相對於基本的BeanFactory,ApplicationContext 唯一的不足是佔用記憶體空間。當應用程式配置Bean較多時,程式啟動較慢。
(3)BeanFactory通常以程式設計的方式被建立,ApplicationContext還能以宣告的方式建立,如使用ContextLoader。
(4)BeanFactory和ApplicationContext都支援BeanPostProcessor、BeanFactoryPostProcessor的使用,但兩者之間的區別是:BeanFactory需要手動註冊,而ApplicationContext則是自動註冊。
Spring通知有哪些型別?
(1)前置通知(Before advice):在某連線點(join point)之前執行的通知,但這個通知不能阻止連線點前的執行(除非它丟擲一個異常)。
(2)返回後通知(After returning advice):在某連線點(join point)正常完成後執行的通知:例如,一個方法沒有丟擲任何異常,正常返回。
(3)丟擲異常後通知(After throwing advice):在方法丟擲異常退出時執行的通知。
(4)後通知(After (finally) advice):當某連線點退出的時候執行的通知(不論是正常返回還是異常退出)。
(5)環繞通知(Around Advice):包圍一個連線點(join point)的通知,如方法呼叫。這是最強大的一種通知型別。 環繞通知可以在方法呼叫前後完成自定義的行為。它也會選擇是否繼續執行連線點或直接返回它們自己的返回值或丟擲異常來結束執行。 環繞通知是最常用的一種通知型別。大部分基於攔截的AOP框架,例如Nanning和JBoss4,都只提供環繞通知。
同一個aspect,不同advice的執行順序:
①沒有異常情況下的執行順序:
around before advice
before advice
target method 執行
around after advice
after advice
afterReturning②有異常情況下的執行順序:
around before advice
before advice
target method 執行
around after advice
after advice
afterThrowing:異常發生
java.lang.RuntimeException: 異常發生
解釋一下Spring AOP裡面的幾個名詞:
(1)切面(Aspect):被抽取的公共模組,可能會橫切多個物件。 在Spring AOP中,切面可以使用通用類(基於模式的風格) 或者在普通類中以 @AspectJ 註解來實現。
(2)連線點(Join point):指方法,在Spring AOP中,一個連線點 總是 代表一個方法的執行。
(3)通知(Advice):在切面的某個特定的連線點(Join point)上執行的動作。通知有各種型別,其中包括“around”、“before”和“after”等通知。許多AOP框架,包括Spring,都是以攔截器做通知模型, 並維護一個以連線點為中心的攔截器鏈。
(4)切入點(Pointcut):切入點是指 我們要對哪些Join point進行攔截的定義。通過切入點表示式,指定攔截的方法,比如指定攔截add、search。
(5)引入(Introduction):(也被稱為內部型別宣告(inter-type declaration))。宣告額外的方法或者某個型別的欄位。Spring允許引入新的介面(以及一個對應的實現)到任何被代理的物件。例如,你可以使用一個引入來使bean實現 IsModified 介面,以便簡化快取機制。
(6)目標物件(Target Object): 被一個或者多個切面(aspect)所通知(advise)的物件。也有人把它叫做 被通知(adviced) 物件。 既然Spring AOP是通過執行時代理實現的,這個物件永遠是一個 被代理(proxied) 物件。
(7)織入(Weaving):指把增強應用到目標物件來建立新的代理物件的過程。Spring是在執行時完成織入。
切入點(pointcut)和連線點(join point)匹配的概念是AOP的關鍵,這使得AOP不同於其它僅僅提供攔截功能的舊技術。 切入點使得定位通知(advice)可獨立於OO層次。 例如,一個提供宣告式事務管理的around通知可以被應用到一組橫跨多個物件中的方法上(例如服務層的所有業務操作)。
Spring框架中有哪些不同型別的事件?
Spring 提供了以下5種標準的事件:
(1)上下文更新事件(ContextRefreshedEvent):在呼叫ConfigurableApplicationContext 介面中的refresh()方法時被觸發。
(2)上下文開始事件(ContextStartedEvent):當容器呼叫ConfigurableApplicationContext的Start()方法開始/重新開始容器時觸發該事件。
(3)上下文停止事件(ContextStoppedEvent):當容器呼叫ConfigurableApplicationContext的Stop()方法停止容器時觸發該事件。
(4)上下文關閉事件(ContextClosedEvent):當ApplicationContext被關閉時觸發該事件。容器被關閉時,其管理的所有單例Bean都被銷燬。
(5)請求處理事件(RequestHandledEvent):在Web應用中,當一個http請求(request)結束觸發該事件。
如果一個bean實現了ApplicationListener介面,當一個ApplicationEvent 被髮布以後,bean會自動被通知。
Spring 框架中都用到了哪些設計模式?
(1)工廠模式:BeanFactory就是簡單工廠模式的體現,用來建立物件的例項;
(2)單例模式:Bean預設為單例模式。
(3)代理模式:Spring的AOP功能用到了JDK的動態代理和CGLIB位元組碼生成技術;
(4)模板方法:用來解決程式碼重複的問題。比如. RestTemplate, JmsTemplate, JpaTemplate。
(5)觀察者模式:定義物件鍵一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都會得到通知被制動更新,如Spring中listener的實現--ApplicationListener。
請解釋Spring Bean的生命週期?
首先說一下Servlet的生命週期:例項化,初始init,接收請求service,銷燬destroy;
Spring上下文中的Bean生命週期也類似,如下:
(1)例項化Bean:
對於BeanFactory容器,當客戶向容器請求一個尚未初始化的bean時,或初始化bean的時候需要注入另一個尚未初始化的依賴時,容器就會呼叫createBean進行例項化。對於ApplicationContext容器,當容器啟動結束後,通過獲取BeanDefinition物件中的資訊,例項化所有的bean。
(2)設定物件屬性(依賴注入):
例項化後的物件被封裝在BeanWrapper物件中,緊接著,Spring根據BeanDefinition中的資訊 以及 通過BeanWrapper提供的設定屬性的介面完成依賴注入。
(3)處理Aware介面:
接著,Spring會檢測該物件是否實現了xxxAware介面,並將相關的xxxAware例項注入給Bean:
①如果這個Bean已經實現了BeanNameAware介面,會呼叫它實現的setBeanName(String beanId)方法,此處傳遞的就是Spring配置檔案中Bean的id值;
②如果這個Bean已經實現了BeanFactoryAware介面,會呼叫它實現的setBeanFactory()方法,傳遞的是Spring工廠自身。
③如果這個Bean已經實現了ApplicationContextAware介面,會呼叫setApplicationContext(ApplicationContext)方法,傳入Spring上下文;
(4)BeanPostProcessor:
如果想對Bean進行一些自定義的處理,那麼可以讓Bean實現了BeanPostProcessor介面,那將會呼叫postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean 與 init-method:
如果Bean在Spring配置檔案中配置了 init-method 屬性,則會自動呼叫其配置的初始化方法。
(6)如果這個Bean實現了BeanPostProcessor介面,將會呼叫postProcessAfterInitialization(Object obj, String s)方法;由於這個方法是在Bean初始化結束時呼叫的,所以可以被應用於記憶體或快取技術;
以上幾個步驟完成後,Bean就已經被正確建立了,之後就可以使用這個Bean了。
(7)DisposableBean:
當Bean不再需要時,會經過清理階段,如果Bean實現了DisposableBean這個介面,會呼叫其實現的destroy()方法;
(8)destroy-method:
最後,如果這個Bean的Spring配置中配置了destroy-method屬性,會自動呼叫其配置的銷燬方法。
BeanDefinition
BeanDefinition 用於儲存 Bean 的相關資訊,包括屬性、構造方法引數、依賴的 Bean 名稱及是否單例、延遲載入等,它是例項化 Bean 的原材料,Spring 就是根據 BeanDefinition 中的資訊例項化 Bean。
一個 BeanDefinition 描述了一個 Bean 例項,例項包含屬性值、構造方法引數值以及更多實現資訊。該 BeanDefinition 只是是一個最小的介面,主要目的是允許修改屬性值和其他 Bean 元資料,這裡列出幾個核心方法。可以看到 BeanDefinition 介面提供了一系列操作 Bean 元資料的set、get方法,這些操作為 Bean 的描述定義了一套模板,具體的實現則交由子類。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
// 單例、原型識別符號
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
// 標識 Bean 的類別,分別對應 使用者定義的 Bean、來源於配置檔案的 Bean、Spring 內部的 Bean
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
// 設定、返回 Bean 的父類名稱
void setParentName(@Nullable String parentName);
String getParentName();
// 設定、返回 Bean 的 className
void setBeanClassName(@Nullable String beanClassName);
String getBeanClassName();
// 設定、返回 Bean 的作用域
void setScope(@Nullable String scope);
String getScope();
// 設定、返回 Bean 是否懶載入
void setLazyInit(boolean lazyInit);
boolean isLazyInit();
// 設定、返回當前 Bean 所依賴的其它 Bean 名稱。
void setDependsOn(@Nullable String... dependsOn);
String[] getDependsOn();
// 設定、返回 Bean 是否可以自動注入。只對 @Autowired 註解有效
void setAutowireCandidate(boolean autowireCandidate);
boolean isAutowireCandidate();
// 設定、返回當前 Bean 是否為主要候選 Bean 。
// 當同一個介面有多個實現類時,通過該屬性來配置某個 Bean 為主候選 Bean。
void setPrimary(boolean primary);
boolean isPrimary();
// 設定、返回建立該 Bean 的工廠類。
void setFactoryBeanName(@Nullable String factoryBeanName);
String getFactoryBeanName();
// 設定、返回建立該 Bean 的工廠方法
void setFactoryMethodName(@Nullable String factoryMethodName);
String getFactoryMethodName();
// 返回該 Bean 構造方法引數值、所有屬性
ConstructorArgumentValues getConstructorArgumentValues();
MutablePropertyValues getPropertyValues();
// 返回該 Bean 是否是單例、是否是非單例、是否是抽象的
boolean isSingleton();
boolean isPrototype();
boolean isAbstract();
// 返回 Bean 的類別。類別對應上面的三個屬性值。
int getRole();
...
}
AnnotatedBeanDefinition
AnnotatedBeanDefinition 是 BeanDefinition 子介面之一,該介面擴充套件了 BeanDefinition 的功能,其用來操作註解元資料。一般情況下,通過註解方式得到的 Bean(@Component、@Bean),其 BeanDefinition 型別都是該介面的實現類。
public interface AnnotatedBeanDefinition extends BeanDefinition {
// 獲得當前 Bean 的註解元資料
AnnotationMetadata getMetadata();
// 獲得當前 Bean 的工廠方法上的元資料
MethodMetadata getFactoryMethodMetadata();
}
該介面可以返回兩個元資料的類:
- AnnotationMetadata:主要對 Bean 的註解資訊進行操作,如:獲取當前 Bean 標註的所有註解、判斷是否包含指定註解。
- MethodMetadata:方法的元資料類。提供獲取方法名稱、此方法所屬類的全類名、是否是抽象方法、判斷是否是靜態方法、判斷是否是final方法等。
AbstractBeanDefinition
AbstractBeanDefinition 是 BeanDefinition 的子抽象類,也是其他 BeanDefinition 型別的基類,其實現了介面中定義的一系列操作方法,並定義了一系列的常量屬性,這些常量會直接影響到 Spring 例項化 Bean 時的策略。核心屬性如下。
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
implements BeanDefinition, Cloneable {
// 預設的 SCOPE,預設是單例
public static final String SCOPE_DEFAULT = "";
// 不進行自動裝配
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
// 根據 Bean 的名字進行自動裝配,byName
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
// 根據 Bean 的型別進行自動裝配,byType
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
// 根據構造器進行自動裝配
public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
// 首先嚐試按構造器自動裝配。如果失敗,再嘗試使用 byType 進行自動裝配。(Spring 3.0 之後已廢除)
public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
// 通過依賴檢查來檢視 Bean 的每個屬性是否都設定完成
// 以下常量分別對應:不檢查、對依賴物件檢查、對基本型別,字串和集合進行檢查、對全部屬性進行檢查
public static final int DEPENDENCY_CHECK_NONE = 0;
public static final int DEPENDENCY_CHECK_OBJECTS = 1;
public static final int DEPENDENCY_CHECK_SIMPLE = 2;
public static final int DEPENDENCY_CHECK_ALL = 3;
// 關閉應用上下文時需呼叫的方法名稱
public static final String INFER_METHOD = "(inferred)";
// 存放 Bean 的 Class 物件
private volatile Object beanClass;
// Bean 的作用範圍
private String scope = SCOPE_DEFAULT;
// 非抽象
private boolean abstractFlag = false;
// 非延遲載入
private boolean lazyInit = false;
// 預設不自動裝配
private int autowireMode = AUTOWIRE_NO;
// 預設不依賴檢查
private int dependencyCheck = DEPENDENCY_CHECK_NONE;
// 依賴的 Bean 列表
private String[] dependsOn;
// 可以作為自動裝配的候選者,意味著可以自動裝配到其他 Bean 的某個屬性中
private boolean autowireCandidate = true;
// 建立當前 Bean 例項工廠類名稱
private String factoryBeanName;
// 建立當前 Bean 例項工廠類中方法名稱
private String factoryMethodName;
// 儲存構造方法的引數
private ConstructorArgumentValues constructorArgumentValues;
// 儲存 Bean 屬性名稱以及對應的值
private MutablePropertyValues propertyValues;
// 儲存被覆蓋的方法資訊
private MethodOverrides methodOverrides;
// init、destroy 方法名稱
private String initMethodName;
private String destroyMethodName;
// 是否執行 init 和 destroy 方法
private boolean enforceInitMethod = true;
private boolean enforceDestroyMethod = true;
// Bean 是否是使用者定義的而不是應用程式本身定義的
private boolean synthetic = false;
// Bean 的身份類別,預設是使用者定義的 Bean
private int role = BeanDefinition.ROLE_APPLICATION;
// Bean 的描述資訊
private String description;
// Bean 定義的資源
private Resource resource;
...
}
以上是 AbstractBeanDefinition 中定義的一些常量和屬性,該類中還有一部分是操作這些屬性的 set 和 get 方法,這些方法都由子類來操作,且應用程式中真正使用的也是這些子類 BeanDefinition。
先來看 AbstractBeanDefinition 直接實現類:RootBeanDefinition、GenericBeanDefinition、ChildBeanDefinition。
RootBeanDefinition
該類繼承自 AbstractBeanDefinition,它可以單獨作為一個 BeanDefinition,也可以作為其他 BeanDefinition 的父類。
RootBeanDefinition 在 AbstractBeanDefinition 的基礎上定義了更多屬性。
public class RootBeanDefinition extends AbstractBeanDefinition {
// BeanDefinitionHolder 儲存 Bean 的名稱、別名、BeanDefinition
private BeanDefinitionHolder decoratedDefinition;
// AnnotatedElement 是java反射包的介面,通過它可以檢視 Bean 的註解資訊
private AnnotatedElement qualifiedElement;
// 允許快取
boolean allowCaching = true;
// 工廠方法是否唯一
boolean isFactoryMethodUnique = false;
// 封裝了 java.lang.reflect.Type,提供了泛型相關的操作
volatile ResolvableType targetType;
// 快取 Class,表示 RootBeanDefinition 儲存哪個類的資訊
volatile Class<?> resolvedTargetType;
// 快取工廠方法的返回型別
volatile ResolvableType factoryMethodReturnType;
// 這是以下四個構造方法欄位的通用鎖
final Object constructorArgumentLock = new Object();
// 用於快取已解析的構造方法或工廠方法
Executable resolvedConstructorOrFactoryMethod;
// 將構造方法引數標記為已解析
boolean constructorArgumentsResolved = false;
// 用於快取完全解析的構造方法引數
Object[] resolvedConstructorArguments;
// 快取待解析的構造方法引數
Object[] preparedConstructorArguments;
// 這是以下兩個後處理欄位的通用鎖
final Object postProcessingLock = new Object();
// 表明是否被 MergedBeanDefinitionPostProcessor 處理過
boolean postProcessed = false;
// 在生成代理的時候會使用,表明是否已經生成代理
volatile Boolean beforeInstantiationResolved;
// 實際快取的型別是 Constructor、Field、Method 型別
private Set<Member> externallyManagedConfigMembers;
// InitializingBean中 的 init 回撥函式名 afterPropertiesSet 會在這裡記錄,以便進行生命週期回撥
private Set<String> externallyManagedInitMethods;
// DisposableBean 的 destroy 回撥函式名 destroy 會在這裡記錄,以便進生命週期回撥
private Set<String> externallyManagedDestroyMethods;
...
}
ChildBeanDefinition
該類繼承自 AbstractBeanDefinition。其相當於一個子類,不可以單獨存在,必須依賴一個父 BeanDetintion,構造 ChildBeanDefinition 時,通過構造方法傳入父 BeanDetintion 的名稱或通過 setParentName 設定父名稱。它可以從父類繼承方法引數、屬性值,並可以重寫父類的方法,同時也可以增加新的屬性或者方法。若重新定義 init 方法,destroy 方法或者靜態工廠方法,ChildBeanDefinition 會重寫父類的設定。
從 Spring 2.5 開始,以程式設計方式註冊 Bean 定義的首選方法是 GenericBeanDefinition,GenericBeanDefinition 可以有效替代 ChildBeanDefinition 的絕大分部使用場合。
GenericBeanDefinition
GenericBeanDefinition 是 Spring 2.5 以後新引入的 BeanDefinition,是 ChildBeanDefinition 更好的替代者,它同樣可以通過 setParentName 方法設定父 BeanDefinition。
最後三個 BeanDefinition 既實現了 AnnotatedBeanDefinition 介面,又間接繼承 AbstractBeanDefinition 抽象類,這些 BeanDefinition 描述的都是註解形式的 Bean。
ConfigurationClassBeanDefinition
該類繼承自 RootBeanDefinition ,並實現了 AnnotatedBeanDefinition 介面。這個 BeanDefinition 用來描述在標註 @Configuration 註解的類中,通過 @Bean 註解例項化的 Bean。
其功能特點如下:
1、如果 @Bean 註解沒有指定 Bean 的名字,預設會用方法的名字命名 Bean。
2、標註 @Configuration 註解的類會成為一個工廠類,而標註 @Bean 註解的方法會成為工廠方法,通過工廠方法例項化 Bean,而不是直接通過構造方法初始化。
3、標註 @Bean 註解的類會使用構造方法自動裝配
AnnotatedGenericBeanDefinition
該類繼承自 GenericBeanDefinition ,並實現了 AnnotatedBeanDefinition 介面。這個 BeanDefinition 用來描述標註 @Configuration 註解的 Bean。
ScannedGenericBeanDefinition
該類繼承自 GenericBeanDefinition ,並實現了 AnnotatedBeanDefinition 介面。這個 BeanDefinition 用來描述標註 @Component 註解的 Bean,其派生註解如 @Service、@Controller 也同理。
最後,我們來做個總結。BeanDefinition 主要是用來描述 Bean,其儲存了 Bean 的相關資訊,Spring 例項化 Bean 時需讀取該 Bean 對應的 BeanDefinition。BeanDefinition 整體可以分為兩類,一類是描述通用的 Bean,還有一類是描述註解形式的 Bean。一般前者在 XML 時期定義 <bean‘> 標籤以及在 Spring 內部使用較多,而現今我們大都使用後者,通過註解形式載入 Bean。
Spring迴圈依賴
兩個物件相互引用,如相互set即造成迴圈依賴,通常通過3個Map來解決迴圈依賴
就Spring生態上而言,Spring的Bean是由一個建模的類BeanDefinition建立而來的,Bean有一系列複雜的生命週期。生命週期大概是這樣的:
首先Spring容器啟動,容器會進行掃描後會變成BeanDefinition後存放到一個BeanDefinition Map中,然後對這個map做一個遍歷並做驗證,如驗證是否單例、原型、懶載入、DepensOn、抽象、factoryBean、名字符合等等,驗證完後容器會獲取當前例項話的這個類有沒有存在單例池中、是否被提前暴露。如果沒有被提前暴露,Bean就會去開始建立Bean:先通過推斷構造方法,把當前這個Bean所代表的的類當中的構造方法,推斷得到一個最佳的構造方法,然後通過反射去例項化這個Java物件,接著對這個Bean做一些初始化工作:比如是否需要做BeanDefinition的合併,是否當前容器是否支援迴圈依賴,如果支援迴圈依賴會提前表露當前Java物件所對應的ObjectFactory工廠類,暴露即存放到二級快取中的map中,然後進行屬性填充,即自動注入。接著執行各種Aware介面的回撥,然後執行生命週期初始化的回撥,如@PostConstruct、InitializerBean、xml等來執行生命週期的初始化回撥方法。回撥完成之後,如果有AOP,還會生成代理物件,如果沒有則會進行事件釋出等,此時生命週期就完成了,之後放進單例池中,就會在Spring中存在了。
如何解決?
- Spring是通過遞迴的方式獲取目標bean及其所依賴的bean的;
- Spring例項化一個bean的時候,是分兩步進行的,首先例項化目標bean,然後為其注入屬性。
結合這兩點,也就是說,Spring在例項化一個bean的時候,是首先遞迴的例項化其所依賴的所有bean,直到某個bean沒有依賴其他bean,此時就會將該例項返回,然後反遞迴的將獲取到的bean設定為各個上層bean的屬性的。
簡單來講
spring官方推薦構造器注入,構造器注入的不能迴圈依賴,因為物件都還在建立中,Jvm都還沒返回例項,所以注入不了
用欄位/setter注入的就可以迴圈依賴,因為都是先初始化了例項再填充欄位值的。即使不用構造器注入依然可能會有迴圈依賴的問題。例如@Async就是典型的例子,@Transactional是在從單例工廠獲取時就實現代理了,而@Async則是在注入後才實現代理物件。而有時成功有時失敗是由於作業系統不同導致讀取jar時檔案順序不一樣,最後例項建立循序不一樣。
Spring如何處理執行緒併發問題?
在一般情況下,只有無狀態的Bean才可以在多執行緒環境下共享,在Spring中,絕大部分Bean都可以宣告為singleton作用域,因為Spring對一些Bean中非執行緒安全狀態採用ThreadLocal進行處理,解決執行緒安全問題。
ThreadLocal和執行緒同步機制都是為了解決多執行緒中相同變數的訪問衝突問題。同步機制採用了“時間換空間”的方式,僅提供一份變數,不同的執行緒在訪問前需要獲取鎖,沒獲得鎖的執行緒則需要排隊。而ThreadLocal採用了“空間換時間”的方式。
ThreadLocal會為每一個執行緒提供一個獨立的變數副本,從而隔離了多個執行緒對資料的訪問衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。
Spring基於xml注入bean的幾種方式:
(1)Set方法注入;
(2)構造器注入:①通過index設定引數的位置;②通過type設定引數型別;
(3)靜態工廠注入;
(4)例項工廠;
Spring的自動裝配:
在spring中,物件無需自己查詢或建立與其關聯的其他物件,由容器負責把需要相互協作的物件引用賦予各個物件,使用autowire來配置自動裝載模式。
在Spring框架xml配置中共有5種自動裝配:
(1)no:預設的方式是不進行自動裝配的,通過手工設定ref屬性來進行裝配bean。
(2)byName:通過bean的名稱進行自動裝配,如果一個bean的 property 與另一bean 的name 相同,就進行自動裝配。
(3)byType:通過引數的資料型別進行自動裝配。
(4)constructor:利用建構函式進行裝配,並且建構函式的引數通過byType進行裝配。
(5)autodetect:自動探測,如果有構造方法,通過 construct的方式自動裝配,否則使用 byType的方式自動裝配。
基於註解的方式:
使用@Autowired註解來自動裝配指定的bean。在使用@Autowired註解之前需要在Spring配置檔案進行配置,<context:annotation-config />。在啟動spring IoC時,容器自動裝載了一個AutowiredAnnotationBeanPostProcessor後置處理器,當容器掃描到@Autowied、@Resource或@Inject時,就會在IoC容器自動查詢需要的bean,並裝配給該物件的屬性。在使用@Autowired時,首先在容器中查詢對應型別的bean:
如果查詢結果剛好為一個,就將該bean裝配給@Autowired指定的資料;
如果查詢的結果不止一個,那麼@Autowired會根據名稱來查詢;
如果上述查詢的結果為空,那麼會丟擲異常。解決方法時,使用required=false。
@Autowired可用於:建構函式、成員變數、Setter方法
注:@Autowired和@Resource之間的區別
(1) @Autowired預設是按照型別裝配注入的,預設情況下它要求依賴物件必須存在(可以設定它required屬性為false)。
(2) @Resource預設是按照名稱來裝配注入的,只有當找不到與名稱匹配的bean才會按照型別來裝配注入。
Spring事務的實現方式和實現原理:
Spring事務的本質其實就是資料庫對事務的支援,沒有資料庫的事務支援,spring是無法提供事務功能的。真正的資料庫層的事務提交和回滾是通過binlog或者redo log實現的。
(1)Spring事務的種類:
spring支援程式設計式事務管理和宣告式事務管理兩種方式:
①程式設計式事務管理使用TransactionTemplate。
②宣告式事務管理建立在AOP之上的。其本質是通過AOP功能,對方法前後進行攔截,將事務處理的功能編織到攔截的方法中,也就是在目標方法開始之前加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。
宣告式事務最大的優點就是不需要在業務邏輯程式碼中摻雜事務管理的程式碼,只需在配置檔案中做相關的事務規則宣告或通過@Transactional註解的方式,便可以將事務規則應用到業務邏輯中。
宣告式事務管理要優於程式設計式事務管理,這正是spring倡導的非侵入式的開發方式,使業務程式碼不受汙染,只要加上註解就可以獲得完全的事務支援。唯一不足地方是,最細粒度只能作用到方法級別,無法做到像程式設計式事務那樣可以作用到程式碼塊級別。
(2)spring的事務傳播行為:
spring事務的傳播行為說的是,當多個事務同時存在的時候,spring如何處理這些事務的行為。
TransactionDefinition 介面中定義了7 個表示傳播行為的常量:
① PROPAGATION_REQUIRED:如果當前沒有事務,就建立一個新事務,如果當前存在事務,就加入該事務,該設定是最常用的設定。
② PROPAGATION_SUPPORTS:支援當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。‘
③ PROPAGATION_MANDATORY:支援當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就丟擲異常。
④ PROPAGATION_REQUIRES_NEW:建立新事務,無論當前存不存在事務,都建立新事務。
⑤ PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
⑥ PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
⑦ PROPAGATION_NESTED:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則按REQUIRED屬性執行。
(3)Spring中的隔離級別:
TransactionDefinition 介面中定義了五個表示隔離級別的常量:
① ISOLATION_DEFAULT:這是個 PlatfromTransactionManager 預設的隔離級別,使用資料庫預設的事務隔離級別。
② ISOLATION_READ_UNCOMMITTED:讀未提交,允許另外一個事務可以看到這個事務未提交的資料。
③ ISOLATION_READ_COMMITTED:讀已提交,保證一個事務修改的資料提交後才能被另一事務讀取,而且能看到該事務對已有記錄的更新。
④ ISOLATION_REPEATABLE_READ:可重複讀,保證一個事務修改的資料提交後才能被另一事務讀取,但是不能看到該事務對已有記錄的更新。
⑤ ISOLATION_SERIALIZABLE:一個事務在執行的過程中完全看不到其他事務對資料庫所做的更新。
Spring 管理事務的方式有幾種?
- 程式設計式事務,在程式碼中硬編碼。(不推薦使用)
- 宣告式事務,在配置檔案中配置(推薦使用)
宣告式事務又分為兩種:
- 基於XML的宣告式事務
- 基於註解的宣告式事務
@Component 和 @Bean 的區別是什麼?
- 作用物件不同:
@Component
註解作用於類,而@Bean
註解作用於方法。 @Component
通常是通過類路徑掃描來自動偵測以及自動裝配到Spring容器中(我們可以使用@ComponentScan
註解定義要掃描的路徑從中找出標識了需要裝配的類自動裝配到 Spring 的 bean 容器中)。@Bean
註解通常是我們在標有該註解的方法中定義產生這個 bean,@Bean
告訴了Spring這是某個類的示例,當我需要用它的時候還給我。@Bean
註解比Component
註解的自定義性更強,而且很多地方我們只能通過@Bean
註解來註冊bean。比如當我們引用第三方庫中的類需要裝配到Spring
容器時,則只能通過@Bean
來實現。
SpringMVC 工作原理了解嗎?
原理如下圖所示:
上圖的一個筆誤的小問題:Spring MVC 的入口函式也就是前端控制器 DispatcherServlet
的作用是接收請求,響應結果。
流程說明(重要):
- 客戶端(瀏覽器)傳送請求,直接請求到
DispatcherServlet
。 DispatcherServlet
根據請求資訊呼叫HandlerMapping
,解析請求對應的Handler
。- 解析到對應的
Handler
(也就是我們平常說的Controller
控制器)後,開始由HandlerAdapter
介面卡處理。 HandlerAdapter
會根據Handler
來呼叫真正的處理器開處理請求,並處理相應的業務邏輯。- 處理器處理完業務後,會返回一個
ModelAndView
物件,Model
是返回的資料物件,View
是個邏輯上的View
。 ViewResolver
會根據邏輯View
查詢實際的View
。DispaterServlet
把返回的Model
傳給View
(檢視渲染)。- 把
View
返回給請求者(瀏覽器)