java後端知識點梳理——Spring
開篇:感謝我是祖國的花朵,java3y,三太子敖丙等優秀博主!他們的文章為我學習java提供了莫大的幫助,膜拜大神!
Spring的優點有哪些呢?
- Spring的依賴注入將物件之間的依賴關係交給了框架來處理,減小了各個元件之間的耦合性;
- AOP面向切面程式設計,可以將通用的任務抽取出來,複用性更高;
- Spring對於其餘主流框架都提供了很好的支援,程式碼的侵入性很低。
Spring的核心模組
- Spring Core:是核心類庫,提供IOC服務;
- Spring Context:提供框架式的Bean訪問方式,以及企業級功能(JNDI、定時任務等);
- Spring AOP:提供AOP服務;
- Spring DAO:對JDBC進行了抽象,簡化了資料訪問異常等處理;
- Spring ORM:對現有的ORM持久層框架進行了支援;
- Spring Web:提供了基本的面向Web的綜合特性;
- Spring MVC:提供面向Web應用的Model-View-Controller實現。
IOC
控制反轉
IOC也叫控制反轉,將物件間的依賴關係交給Spring容器,使用配置檔案來建立所依賴的物件,由主動建立物件改為了被動方式,實現解耦合。
可以通過註解@Autowired和@Resource來注入物件,被注入的物件必須被下邊的四個註解之一標註:
- @Controller
- @Service
- @Repository
- @Component
在Spring配置檔案中配置 context:annotation-config/元素開啟註解。
依賴注入(DI)
和控制反轉是同一個概念的不同角度的描述,即應用程式在執行時依賴IOC容器來動態注入物件需要的外部資源(物件等)。
更多總結與補充
Spring 如何設計容器的呢
BeanFactory
- BeanFactory 簡單粗暴,可以理解為 HashMap:它一般只有 get, put 兩個功能
- Key - bean name
- Value - bean object
- BeanFactory 簡單粗暴,可以理解為 HashMap:它一般只有 get, put 兩個功能
ApplicationContext
- 它是
BeanFactory
的子類,更好的補充並實現了BeanFactory.ApplicationContext
ApplicationContext
的裡面有兩個具體的實現子類,用來讀取配置配件的ClassPathXmlApplicationContext
- 從 class path 中載入配置檔案,更常用一些;FileSystemXmlApplicationContext
- 從本地檔案中載入配置檔案,不是很常用,如果再到 Linux 環境中,還要改路徑,不是很方便。
- 它是
注:幾乎所有的場合都是使用ApplicationContext
BeanFactory和ApplicationContext的優缺點分析:
- BeanFactory的優缺點:
優點:應用啟動的時候佔用資源很少,對資源要求較高的應用,比較有優勢;
缺點:執行速度會相對來說慢一些。而且有可能會出現空指標異常的錯誤,而且通過Bean工廠建立的Bean生命週期會簡單一些。
- ApplicationContext的優缺點:
優點:所有的Bean在啟動的時候都進行了載入,系統執行的速度快;在系統啟動的時候,可以發現系統中的配置問題。
缺點:把費時的操作放到系統啟動中完成,所有的物件都可以預載入,缺點就是記憶體佔用較大。
IOC容器的原理
- 根據Bean配置資訊在容器內部建立Bean定義登錄檔
- 根據登錄檔載入、例項化bean、建立Bean與Bean之間的依賴關係
- 將這些準備就緒的Bean放到Map快取池中,等待應用程式呼叫
幾個關鍵問題
1.何為控制,控制的是什麼?
是 bean 的建立、管理的權利,控制 bean 的整個生命週期。
2.何為反轉,反轉了什麼?
把這個權利交給了 Spring 容器,而不是自己去控制,就是反轉。由之前的自己主動建立物件,變成現在被動接收別人給我們的物件的過程,這就是反轉。
3.何為依賴,依賴什麼?(DI)
程式執行需要依賴外部的資源,提供程式內物件的所需要的資料、資源。
4.何為注入,注入什麼?
配置檔案把資源從外部注入到內部,容器載入了外部的檔案、物件、資料,然後把這些資源注入給程式內的物件,維護了程式內外物件之間的依賴關係。
- IoC 是設計思想,DI 是具體的實現方式;
- IoC 是理論,DI 是實踐;
從而實現物件之間的解藕。
裝配Bean方式
- XML配置
- 註解
- JavaConfig
總的來說:我們以XML配置+註解來裝配Bean得多,其中註解這種方式佔大部分!
依賴注入方式
- 屬性注入-->通過setter()方法注入
- 建構函式注入
- 工廠方法注入
總的來說使用屬性注入是比較靈活和方便的,這是大多數人的選擇!
物件之間關係
- 依賴-->挺少用的(使用depends-on就是依賴關係了-->前置依賴【依賴的Bean需要初始化之後,當前Bean才會初始化】)
- 繼承-->可能會用到(指定abstract和parent來實現繼承關係)
- 引用-->最常見(使用ref就是引用關係了)
Bean的作用域
@Scope: 宣告 Spring Bean 的作用域
四種常見的 Spring Bean 的作用域:
- singleton : 唯一 bean 例項,Spring 中的 bean 預設都是單例的。
- prototype : 每次請求都會建立一個新的 bean 例項。
- request : 每一次 HTTP 請求都會產生一個新的 bean,該 bean 僅在當前 HTTP request 內有效。
- session : 每一次 HTTP 請求都會產生一個新的 bean,該 bean 僅在當前 HTTP session 內有效。
處理自動裝配的歧義性
一個介面兩個實現類怎麼在注入的時候優先呼叫某個實現類?
- 使用@Primary註解設定為首選的注入Bean
- 使用@Qualifier註解設定特定名稱的Bean來限定注入!
裝配Bean總結
分別的應用場景:
Spring IOC相關面試題
什麼是spring?
Spring 是個java企業級應用的開源開發框架。Spring主要用來開發Java應用,但是有些擴充套件是針對構建J2EE平臺的web應用。Spring框架目標是簡化Java企業級應用開發,並通過POJO為基礎的程式設計模型促進良好的程式設計習慣。
使用Spring框架的好處是什麼?
輕量:Spring 是輕量的,基本的版本大約2MB。
控制反轉:Spring通過控制反轉實現了鬆散耦合,物件們給出它們的依賴,而不是建立或查詢依賴的物件們。
面向切面的程式設計(AOP):Spring支援面向切面的程式設計,並且把應用業務邏輯和系統服務分開。
容器:Spring 包含並管理應用中物件的生命週期和配置。
MVC框架:Spring的WEB框架是個精心設計的框架,是Web框架的一個很好的替代品。
事務管理:Spring 提供一個持續的事務管理介面,可以擴充套件到上至本地事務下至全域性事務(JTA)。
異常處理:Spring 提供方便的API把具體技術相關的異常(比如由JDBC,Hibernate or JDO丟擲的)轉化為一致的unchecked 異常。
哪種依賴注入方式你建議使用,構造器注入,還是 Setter方法注入?
兩種依賴方式都可以使用,構造器注入和Setter方法注入。最好的解決方案是用構造器引數實現強制依賴,setter方法實現可選依賴。
解釋Spring框架中bean的生命週期
- Spring容器 從XML 檔案中讀取bean的定義,並例項化bean。
- Spring根據bean的定義填充所有的屬性。
- 如果bean實現了BeanNameAware 介面,Spring 傳遞bean 的ID 到 setBeanName方法。
- 如果Bean 實現了 BeanFactoryAware 介面, Spring傳遞beanfactory 給setBeanFactory 方法。
- 如果有任何與bean相關聯的BeanPostProcessors,Spring會在postProcesserBeforeInitialization()方法內呼叫它們。
- 如果bean實現IntializingBean了,呼叫它的afterPropertySet方法,如果bean聲明瞭初始化方法,呼叫此初始化方法。
- 如果有BeanPostProcessors 和bean 關聯,這些bean的postProcessAfterInitialization() 方法將被呼叫。
- 如果bean實現了 DisposableBean,它將呼叫destroy()方法。
解釋不同方式的自動裝配
- no:預設的方式是不進行自動裝配,通過顯式設定ref 屬性來進行裝配。
- byName:通過引數名 自動裝配,Spring容器在配置檔案中發現bean的autowire屬性被設定成byname,之後容器試圖匹配、裝配和該bean的屬性具有相同名字的bean。
- byType::通過引數型別自動裝配,Spring容器在配置檔案中發現bean的autowire屬性被設定成byType,之後容器試圖匹配、裝配和該bean的屬性具有相同型別的bean。如果有多個bean符合條件,則丟擲錯誤。
- constructor:這個方式類似於byType, 但是要提供給構造器引數,如果沒有確定的帶引數的構造器引數型別,將會丟擲異常。
- autodetect:首先嚐試使用constructor來自動裝配,如果無法工作,則使用byType方式。
只用註解的方式時,註解預設是使用byType的!
Spring框架中的單例Beans是執行緒安全的麼?
Spring框架並沒有對單例bean進行任何多執行緒的封裝處理。關於單例bean的執行緒安全和併發問題需要開發者自行去搞定。但實際上,大部分的Spring bean並沒有可變的狀態(比如Serview類和DAO類),所以在某種程度上說Spring的單例bean是執行緒安全的。如果你的bean有多種狀態的話(比如 View Model 物件),就需要自行保證執行緒安全。
最淺顯的解決辦法就是將多型bean的作用域由“singleton”變更為“prototype”
IOOC容器的初始化過程:
IOC容器的初始化主要包括Resource定位,載入和註冊三個步驟,接下來我們依次介紹。
- Resource資源定位:
Resouce定位是指BeanDefinition的資源定位,也就是IOC容器找資料的過程。Spring中使用外部資源來描述一個Bean物件,IOC容器第一步就是需要定位Resource外部資源。由ResourceLoader通過統一的Resource介面來完成定位。
- BeanDefinition的載入:
載入過程就是把定義好的Bean表示成IOC容器內部的資料結構,即BeanDefinition。在配置檔案中每一個Bean都對應著一個BeanDefinition物件。
通過BeanDefinitionReader讀取,解析Resource定位的資源,將使用者定義好的Bean表示成IOC容器的內部資料結構BeanDefinition。
在IOC容器內部維護著一個BeanDefinitionMap的資料結構,通過BeanDefinitionMap,IOC容器可以對Bean進行更好的管理。
- BeanDefinition的註冊:
註冊就是將前面的BeanDefition儲存到Map中的過程,通過BeanDefinitionRegistry介面來實現註冊。
續上:Spring中延遲載入Bean
IOC容器的初始化過程就是對Bean定義資源的定位、載入和註冊,此時容器對Bean的依賴注入並沒有發生。接下來,我們看下依賴注入的發生時刻吧。
ApplicationContext預設會在容器啟動的時候建立我們配置好的各個Bean,我們的Bean配置如下:
<bean id="oneBean" class="com.nowcoder.oneBean">
這裡邊的隱藏屬性是lazy-init,即上邊的配置和下邊的是一樣的:
<bean id="oneBean" class="com.nowcoder.oneBean" lazy-init="false">
lazy-init=false表示不開啟延遲載入,在容器啟動的時候即建立該Bean。對應的,我們還可以配置lazy-init=true表示開啟延遲載入,那麼該Bean的建立發生在應用程式第一次向容器索取Bean時,通過getBean()方法的呼叫完成。
BeanFactory和FactoryBean的區別:
- BeanFactory:Bean工廠,是一個工廠(Factory), 是Spring IOC容器的最頂層介面,它的作用是管理Bean,即例項化、定位、配置應用程式中的物件及建立這些物件間的依賴。
- FactoryBean:工廠Bean,是一個Bean,作用是產生其他Bean例項,需要提供一個工廠方法,該方法用來返回其他Bean例項。
AOP
AOP,面向切面程式設計是指當需要在某一個方法之前或者之後做一些額外的操作,比如說日誌記錄,許可權判斷,異常統計等,可以利用AOP將功能程式碼從業務邏輯程式碼中分離出來。
原理:動態代理
AOP中有如下的操作術語:
- Joinpoint(連線點): 類裡面可以被增強的方法,這些方法稱為連線點
- Pointcut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義
- Advice(通知/增強):所謂通知是指攔截到Joinpoint之後所要做的事情就是通知。
- Aspect(切面):是切入點和通知(引介)的結合
- Introduction(引介):引介是一種特殊的通知在不修改類程式碼的前提下,Introduction可以在執行期為類動態地新增一些方法或屬性
- Target(目標物件):代理的目標物件(要增強的類)
- Weaving(織入):是把增強應用到目標的過程,把advice 應用到 target的過程
- Proxy(代理):一個類被AOP織入增強後,就產生一個結果代理類
Spring中的AOP主要有兩種實現方式:
- 使用JDK動態代(dai)理實現,使用java.lang.reflection.Proxy類來處理
- 使用cglib來實現
JDK動態代(dai)理Demo:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy {
public static void main(String[] args){
//定義一個people作為被代理的例項
IPeople ple=new People();
//定義一個handler
InvocationHandler handle=new MyHandle(ple);
//獲得類載入器
ClassLoader cl=ple.getClass().getClassLoader();
//動態產生一個代理,下邊兩種方法均可
// IPeople p=(IPeople) Proxy.newProxyInstance(cl, new Class[]{IPeople.class}, handle);
IPeople p=(IPeople) Proxy.newProxyInstance(cl, ple.getClass().getInterfaces(), handle);
//執行被代理者的方法。
p.func();
}
}
class MyHandle implements InvocationHandler{
//被代理的例項
Object obj=null;
//我要代理誰
public MyHandle(Object obj){
this.obj=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=method.invoke(this.obj, args);
return result;
}
}
interface IPeople{
public void fun();
public void func();
}
//實際被代理的類
class People implements IPeople{
@Override
public void fun() {
System.out.println("這是fun方法");
}
@Override
public void func() {
System.out.println("這是func方法");
}
}
cglib實現動態代理Demo:
import net.sf.cglib.proxy.*;
import java.lang.reflect.Method;
public class TestCglib {
public static void main(String[] args) {
// 定義一個回撥介面的陣列
Callback[] callbacks = new Callback[] {
new MyApiInterceptor(), new MyApiInterceptorForPlay()
};
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class); // 設定要代理的父類
enhancer.setCallbacks(callbacks); // 設定回撥的攔截器陣列
enhancer.setCallbackFilter(new CallbackFilterImpl()); // 設定回撥選擇器
Person person = (Person) enhancer.create(); // 建立代理物件
person.eat();
System.out.println("--------------------");
person.play();
}
}
class MyApiInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("吃飯前我會先洗手"); // 此處可以做一些操作
Object result = proxy.invokeSuper(obj, args);
System.out.println("吃完飯我會先休息會兒" ); // 方法呼叫之後也可以進行一些操作
return result;
}
}
class MyApiInterceptorForPlay implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("出去玩我會先帶好玩具"); // 此處可以做一些操作
Object result = proxy.invokeSuper(obj, args);
System.out.println("玩一個小時我就回家了" ); // 方法呼叫之後也可以進行一些操作
return result;
}
}
class CallbackFilterImpl implements CallbackFilter {
@Override
public int accept(Method method) {
if (method.getName().equals("play"))
return 1;
else
return 0;
}
}
// 建立一個普通類做為代理類
class Person {
// 代理類中由普通方法
public void eat() {
System.out.println("我要開始吃飯咯...");
}
public void play() {
System.out.println("我要出去玩耍了,,,");
}
}
Spring AOP對這兩種代理方式的選擇:
- 如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP,也可以強制使用cglib實現AOP;
- 如果目標物件沒有實現介面,必須採用cglib庫,Spring會自動在JDK動態代理和cglib之間轉換。
兩種實現方式的不同之處:
JDK動態代理,只能對實現了介面的類生成代理,而不是針對類,該目標型別實現的介面都將被代理。原理是通過在執行期間建立一個介面的實現類來完成對目標物件的代理。實現步驟大概如下:
- 定義一個實現介面InvocationHandler的類
- 通過建構函式,注入被代理類
- 實現invoke( Object proxy, Method method, Object[ ] args)方法
- 在主函式中獲得被代(dai)理類的類載入器
- 使用Proxy.newProxyInstance( )產生一個代理物件
- 通過代理物件呼叫各種方法
cglib主要針對類實現代理,對是否實現介面無要求。原理是對指定的類生成一個子類,覆蓋其中的方法,因為是繼承,所以被代理的類或方法不可以宣告為final型別。實現步驟大概如下:
- 定義一個實現了MethodInterceptor介面的類
- 實現其 intercept()方法,在其中呼叫proxy.invokeSuper( )
Spring中有哪些不同的通知型別
通知(advice)是你在你的程式中想要應用在其他模組中的橫切關注點的實現。Advice主要有以下5種類型:
- 前置通知(Before Advice): 在連線點之前執行的Advice,不過除非它丟擲異常,否則沒有能力中斷執行流。使用 @Before 註解使用這個Advice。
- 返回之後通知(After Retuning Advice): 在連線點正常結束之後執行的Advice。例如,如果一個方法沒有丟擲異常正常返回。通過 @AfterReturning 關注使用它。
- 丟擲(異常)後執行通知(After Throwing Advice): 如果一個方法通過丟擲異常來退出的話,這個Advice就會被執行。通用 @AfterThrowing 註解來使用。
- 後置通知(After Advice): 無論連線點是通過什麼方式退出的(正常返回或者丟擲異常)都會執行在結束後執行這些Advice。通過 @After 註解使用。
- 圍繞通知(Around Advice): 圍繞連線點執行的Advice,就你一個方法呼叫。這是最強大的Advice。通過 @Around 註解使用。
迴圈依賴
引子
“如果A物件建立的過程需要使用到B物件,但是B物件建立的時候也需要A物件,也就是構成了迴圈依賴的現象,那麼Spring會如何解決?”
這是一種構造器迴圈依賴,通過構造器注入構成的迴圈依賴,此依賴是無法解決的,只能丟擲BeanCurrentlyInCreationException異常表示迴圈依賴。
解答迴圈依賴,主要分下面幾點
- 什麼是迴圈依賴?
- 什麼情況下迴圈依賴可以被處理?
- Spring是如何解決的迴圈依賴?
注意:
只有在setter方式注入的情況下,迴圈依賴才能解決(錯)
三級快取的目的是為了提高效率(錯)
什麼是迴圈依賴?
從字面上來理解就是A依賴B的同時B也依賴了A
例如:
以程式碼為例
@Component
public class A {
// A中注入了B
@Autowired
private B b;
}
@Component
public class B {
// B中也注入了A
@Autowired
private A a;
}
特殊的:
// 自己依賴自己
@Component
public class A {
// A中注入了A
@Autowired
private A a;
}
【未完待續】
Spring的事務
Spring支援程式設計式事務管理和宣告式事務管理兩種方式:
- 程式設計式事務管理:使用TransactionTemplate實現。
- 宣告式事務管理:建立在AOP之上的。其本質是通過AOP功能,對方法前後進行攔截,將事務處理的功能編織到攔截的方法中,也就是在目標方法開始之前加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。
宣告式事務的優點:
就是不需要在業務邏輯程式碼中摻雜事務管理的程式碼,只需在配置檔案中做相關的事務規則宣告或通過@Transactional註解的方式,便可以將事務規則應用到業務邏輯中。
【簡單一點的概括】
- 程式設計式事務管理
- 程式設計幫助下實現對事務的管理
- 宣告式事務管理
- 事務管理和業務程式碼分離,靠註解或者xml配置來管理事務
宣告式事務的傳播屬性
隔離級別
- 讀未提交:READ UNCOMMITTED
- 讀已提交:READ COMMITTED
- 可重複讀:REPEATABLE READ
- 序列化:SERIALIZABLE
SpringMVC工作流程
1、使用者傳送請求至前端控制器DispatcherServlet
2、DispatcherServlet收到請求呼叫HandlerMapping處理器對映器。
3、處理器對映器找到具體的處理器,生成處理器物件及處理器攔截器(如果有則生成)一併返回給DispatcherServlet。
4、DispatcherServlet呼叫HandlerAdapter處理器介面卡
5、HandlerAdapter經過適配呼叫具體的處理器(Controller,也叫後端控制器)。
6、Controller執行完成返回ModelAndView
7、HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet
8、DispatcherServlet將ModelAndView傳給ViewReslover檢視解析器
9、ViewReslover解析後返回具體View
10、DispatcherServlet根據View進行渲染檢視(即將模型資料填充至檢視中)。
11、DispatcherServlet響應使用者
SpringBoot的核心註解
@SpringBootApplication
建立 SpringBoot 專案之後會預設在主類加上
我們可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 註解的集合。
@EnableAutoConfiguration
:啟用 SpringBoot 的自動配置機制@ComponentScan
: 掃描被@Component (@Service,@Controller)註解的 bean,註解預設會掃描該類所在的包下所有的類。@Configuration
:允許在 Spring 上下文中註冊額外的 bean 或匯入其他配置類
SpringBoot專案啟動分析
Application類是通過SpringApplication類的靜態run方法來啟動應用的。開啟這個靜態方法,該靜態方法真正執行的是兩部分:new SpringApplication( )並且執行物件run()方法。
SpringBoot的自動配置原理
首先是上述的三個核心註解
其中@EnableAutoConfiguration是關鍵(啟用自動配置),內部實際上就去載入META-INF/spring.factories檔案的資訊,然後篩選出以EnableAutoConfiguration為key的資料,載入到IOC容器中,實現自動配置功能!