1. 程式人生 > >【Java面試題】spring+springMVC+mybatis原理及實現機制(持續更新)

【Java面試題】spring+springMVC+mybatis原理及實現機制(持續更新)

本文將持續更新,主要講解SSM框架的底層原理和實現機制等

1.什麼是IOC?

IOC即Inverse of Control,它包括兩個內容:控制與反轉

那到底什麼東西的“控制”被“反轉”了呢?對於軟體而言,即是某一個介面具體實現類的選擇控制權從呼叫類中移除,轉交給第三方決定

因為IOC確實不夠開門見山,因此業界曾經進行了廣泛的討論,最終軟體界的泰斗級人物Martin Fowler提出了DI(依賴注入:Dependency Injection)的概念用以代替IOC

即讓呼叫類對某一個介面的依賴關係由第三方(容器或者協作類)注入,以移除呼叫類對某一個介面實現類的依賴。

2.什麼是AOP?

AOP是Aspect Oritened Programming的簡稱,普遍翻譯成 面向切面程式設計。

按照軟體重構思想的理念,如果多個類中出現相同的程式碼,應該考慮定義一個共同的抽象類,將這些相同的程式碼提取到抽象類中。

比如,Horse、Pig、Camel這些物件都有run、eat方法,通過引入一個包含這兩個方法抽象的Animal父類,Horse、Pig、Camel就可以通過繼承Animal複用run和eat方法。

通過引入父類消除多個類中重複程式碼的方式在大多情況下是可行的,但世界並非永遠這麼簡單。

舉個事務管理的栗子,看下面程式碼

Connection conn ;
try{
conn = DriverManager.getConnection();①獲取資料連線
conn.setAutoCommit(false); ②關閉自動提交的機制
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); ③設定事務隔離級別
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate( "INSERT INTO t_topic VALUES(1,’tom’) " );
rows = stmt.executeUpdate( "UPDATE t_user set topic_nums = topic_nums +1 "+
"WHERE user_id = 1");
conn.commit();④提交事務
}catch(Exception e){
…
conn.rollback();⑤提交事務
}finally{
…
}


在上面的程式碼中,真正的業務邏輯就只有

int rows = stmt.executeUpdate( "INSERT INTO t_topic VALUES(1,’tom’) " );
rows = stmt.executeUpdate( "UPDATE t_user set topic_nums = topic_nums +1 "+
"WHERE user_id = 1");

其他的邏輯都可以稱之為橫切邏輯,這些橫切邏輯依附在業務方法的流程中,我們無法將之轉移到其他地方去。

AOP獨闢蹊徑通過橫向抽取機制為這類無法通過縱向繼承體系進行抽象的重複性程式碼提供瞭解決方案

Spring AOP使用動態代理技術

在執行期織入增強的程式碼。

3.Spring AOP使用了兩種代理機制:一種是基於JDK的動態代理;另一種是基於CGLib的動態代理,請簡述?

之所以需要兩種代理機制,很大程度上市因為JDK本身只提供介面的代理,而不支援類的代理。

JDK動態代理:

JDK動態代理主要涉及到java.lang.reflect包中兩個類:Proxy和InvocationHandler。 其中InvocationHandler是一個介面,可以通過實現該介面定義橫切邏輯,並通過反射機制呼叫目標類的程式碼,動態的將橫切邏輯和業務邏輯編織在一起。所以我們可以將InvacationHandler看成是一個編織器

而Proxy利用InvacationHandler動態建立一個符合某一介面的例項,生成目標類的代理物件。

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

具體的流程為:
  1. 我們實現InvacationHandler介面,該介面定義了一個invoke方法,proxy是最終生成的代理例項,一般不會用到。
  2. method是被代理目標例項的某個具體方法,通過它可以發起目標例項方法的反射呼叫
  3. args是傳給被代理例項的某個方法的入引數組,在方法反射呼叫時使用。

此外,我們還需要Proxy的newProxyInstance方法建立代理類物件

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:  一個ClassLoader物件,定義了由哪個ClassLoader物件來對生成的代理物件進行載入

interfaces:  一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理物件就宣稱實現了該介面(多型),這樣我就能呼叫這組介面中的方法了

h:  一個InvocationHandler物件,表示的是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上


使用JDK動態代理有一個限制,即它只能為介面建立代理例項,這一點我們可以從newProxyInstance方法的簽名中就看的很清楚:

第二個入參interfaces就是需要代理例項實現的介面列表。

雖然面向介面程式設計的思想被很多大師級人物推崇,但在實際開發中,許多開發者也對此深感疑惑:難道對一個簡單的業務表的操作也要老老實實的建立5個類(領域物件類、Dao介面、Dao實現類、Service介面和Service實現類)嗎?難道不能直接通過實現類構造程式嗎?對於這個問題,我們很難給出一個熟好熟劣的準確判斷,但我們確實發現有很多不適用介面的專案也取得了很好的效果。

對於沒有通過介面定義業務方法的類,如何動態建立代理例項呢?JDK的代理技術顯然已經黔驢技窮,CGLib作為一個替代者,填補了這個空缺。

CGLib動態代理:

CHLib採用非常底層的位元組碼技術,可以為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,並順勢織入橫切邏輯。

看如下程式碼:

package go.jacob.day1105;


import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyCglicProxy implements MethodInterceptor{
	private Enhancer enhancer=new Enhancer();
	public Object getProxy(Class clazz){
		enhancer.setSuperclass(clazz); //設定所需建立子類的類;即需要代理的 類
		enhancer.setCallback(this);
		return enhancer.create();//通過位元組碼技術動態建立子類例項
	}
	
	@Override
	//攔截父類中所有方法
	public Object intercept(Object obj, Method method1, Object[] args, MethodProxy proxy) throws Throwable {
		//..... 方法前橫切邏輯
		Object result=proxy.invokeSuper(obj, args); //通過代理類呼叫父類中的方法
		//..... 方法後橫切邏輯
		return result;
	}

}

在上面的程式碼中,使用者可以通過getProxy為一個類建立動態代理物件,該代理物件通過擴充套件clazz建立代理物件。在這個代理物件中,我們織入相應的橫切邏輯。

intercept是CDLib定義的Interceptor介面的方法,它攔截所有目標類方法的呼叫,obj表示目標類的例項;method為目標類方法的反射物件;args為方法的動態入參;而proxy為代理類的例項。

下面建立測試類:

package go.jacob.day1105;

public class Test {
	public static void main(String[] args) {
		MyCglicProxy proxy = new MyCglicProxy();
		Cat c=new Cat();
		Cat catProxy = (Cat)proxy.getProxy(c.getClass());
		catProxy.eat();
		
	}
}

class Cat{
	public void eat(){
		System.out.println("我是貓,我喜歡吃魚");
	}
}


執行結果


觀察以上的輸出,發現代理類的名字是 ,這個特殊的類就是CGLib為Cat建立的例項。

4.springMVC工作原理


Spring MVC框架圍繞著DispatcherServlet這個核心展開,DispatcherServlet是Spring MVC的總導演、總策劃,它負責截獲請求並將其分派給相應的處理器處理。

Spring MVC是基於Model 2實現的框架,所以它底層的機制也是MVC,我們通過上圖來描述Spring MVC的整體架構。

從接受請求到返回響應,Spring MVC框架的眾多元件通力合作、各司其職,有條不紊地完成分內的工作。在整個框架中,DispatcherServlet處於核心的位置,它負責協調和組織不同元件以完成請求處理並返回響應的工作。和大多數Web MVC框架一樣,Spring MVC通過一個前端Servlet接受所有的請求,並將具體工作委託給其他元件進行處理,DispatcherServlet就是SpringMVC的前端Servlet。下面我們對Spring MVC處理請求的整體過程做一下高空俯瞰。

  1. 整個過程始於客戶端發出一個Http請求,Web應用伺服器接受到這個請求,如果匹配DispatcherServlet的請求對映路徑(在web.xml中指定),web容器將該請求轉發給DispatcherServlet處理。
  2. DispatcherServlet接收到這個請求後,將根據請求的資訊(包括URL、HTTP方法、請求報文頭、請求引數、Cookie等)即HandlerMapping的配置找到處理請求的處理器(Handler)。可將HandlerMapping看成是路由器,將Handler看成是目標主機。值得注意的是:Spring MVC中並沒有定義一個handler介面,實際上任何一個Object都可以成為請求處理器。
  3. 當DispatcherServlet根據HandlerMapping得到對應當前請求的Handler後,通過HandlerAdapter對Handler進行封裝,再以統一的介面卡介面呼叫Handler。HandlerAdapter是Spring MVC的框架級介面,顧名思義,HandlerAdapter是一個是介面卡,它用統一的介面對各種Handler方法進行呼叫。
  4. 處理器完成業務邏輯的處理後將返回一個ModelAndView給DispatcherServlet,ModelAndView包含了邏輯檢視名和模型資料資訊。
  5. ModelAndView中包含的是“邏輯檢視名”而並非真正的檢視物件,DispatcherServlet藉由ViewResolver完成邏輯是檢視名到真實檢視物件的解析工作。
  6. 當得到真實的檢視物件View後,DispatcherServlet就使用這個View物件對ModelAndView中的模型資料進行檢視渲染。
  7. 最終客戶端得到的相應資訊,可能是一個普通的HTML頁面,也可能是一個XML或者JSON串,甚至可能是一張圖片或者一個PDF文件等不同的媒體形式。

5.HttpMessageConverter<T>詳解

HttpMessageConverter和@responsebody/@requestbody配合使用。

@responsebody表示該方法的返回結果直接寫入HTTP response body中 

HttpMessageConverter<T>是spring 3.0新新增的一個重要介面,它負責將請求資訊轉換成一個物件(型別為T),將物件(型別為T)輸出為響應資訊

DispatcherServlet預設已經安裝了AnnotationMethodHandlerAdapter作為HandlerAdapter的元件實現類,HttpMessageConverter即由AnnotationMethodHandlerAdapter使用,將請求資訊轉換為物件,或將物件轉換為相應資訊。

所以,HttpMessageConverter是由介面卡HandlerAdapter負責呼叫

public interface HttpMessageConverter<T> {  
  
      
    //判斷資料型別是否可讀  
    boolean canRead(Class<?> clazz, MediaType mediaType);  
  
      
    //判斷資料是否可寫  
    boolean canWrite(Class<?> clazz, MediaType mediaType);  
  
      
    //獲取支援的資料型別  
    List<MediaType> getSupportedMediaTypes();  
  
      
    //對引數值進行讀,轉換為需要的型別  
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)  
            throws IOException, HttpMessageNotReadableException;  
  
    //將返回值傳送給請求者  
    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)  
            throws IOException, HttpMessageNotWritableException;  
  
}  

只要再Spring Web容器中為AnnotationMethodHandlerAdapter裝配好相應的處理XML、JSON的HttpMessageConverter,並在互動中通過請求的Accept指定MIME型別,spring MVC就可使服務端的處理方法和客戶端透明地通過XML或JSON格式的訊息進行通訊了,開發者幾乎無需關心通訊層資料格式的問題,可以將精力集中到業務層的處理上。

在接受到一個HTTP請求時,控制器通過請求訊息頭的“Content-Type”和“Accept”分別可以知道請求訊息的格式以及響應訊息的格式。

-----------------------------------------------------------------

本文內容參考《Spring 3.X企業應用開發實戰》、