1. 程式人生 > >@Controller和@RestController原始碼解析

@Controller和@RestController原始碼解析

2018年不知不覺已經走到了尾聲,你還在為分不清@Controller和@Restcontroller而煩惱嗎?這篇博文從原始碼層面分析這兩個註解,值得一讀。

首先貼一張原始碼的圖,對比一下,左邊是@Controller的原始碼,右邊是@RestController的。

如果覺得不清楚,看下面程式碼:

@Controller:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
     
/** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) */ @AliasFor(annotation = Component.class) String value()
default ""; }
View Code

@RestController:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

     /**
      * The value may indicate a suggestion for a logical  component name,
      * to be turned into a Spring bean in case of an  autodetected component.
      * 
@return the suggested component name, if any (or empty String otherwise) * @since 4.0.1 */ @AliasFor(annotation = Controller.class) String value() default ""; }
View Code

顯然,兩個註解的原始碼裡面都包含許多的註解:

@Controller的註解包括:@Target({ElementType.TYPE})、@Retention(RetentionPolicy.RUNTIME)、@Documented、@Component

@RestController的註解包括:@Target(ElementType.TYPE)、@Retention(RetentionPolicy.RUNTIME)、@Documented、@Controller、@ResponseBody

所以,原始碼的分析也就是對註解的分析。

內建註解和元註解

註解(也被稱為元資料)為我們在程式碼中新增資訊提供了一種形式化的方法,使我們可以在稍後某個時刻非常方便地使用這些資料。

Java SE5中有三種內建註解

@Override 表示當前的方法定義將覆蓋超類中的方法
@Deprecated 如果程式中使用了註解為它的元素,那麼編譯器會發出警告
@SuppressWarnings 關閉不當的編譯器警告資訊
  元註解是專門負責註解其他的註解,Java內建了四中元註解,專門負責新註解的建立,直接看這張表格(摘自Java程式設計思想):
@Target 表示該註解可以用於什麼地方。可能的ElementType引數包括: CONSTRUCTOR:構造器的宣告 FIELD:域宣告(包括enum例項) LOCAL_VARIABLE:區域性變數宣告 METHOD:方法宣告 PACKAGE:包宣告 PARAMETER:引數宣告 TYPE:類、介面(包括註解型別)和enum宣告
@Retention 表示需要在什麼級別儲存該註解資訊。可選的RetentionPolicy引數包括: SOURCE:註解將在編譯器丟棄 CLASS:註解在class檔案中可用,但會被VM丟棄 RUNTIME:VM將在執行期也保留註解,因此可以通過反射機制讀取註解的資訊
@Documented 將此註解包含在Javadoc中
@Inherited 允許子類繼承父類中的註解

現在,明白了@Target({ElementType.TYPE})、@Retention(RetentionPolicy.RUNTIME)、@Documented的作用,我們也可以自定義一個註解@Algorithms:

/**
*
* Define a annotation named Algorithms
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Algorithms {
    String value() default "";
}

@Component註解

@Component註解原始碼如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

     /**
      * The value may indicate a suggestion for a logical  component name,
      * to be turned into a Spring bean in case of an  autodetected component.
      * @return the suggested component name, if any (or empty  String otherwise)
      */
     String value() default "";

}

加了@Component註解,表明這是一個邏輯元件,告知Spring要為它建立bean。相當於xml配置檔案中的 <bean id="" class=""/>的作用。

@AliasFor註解

@AliasFor註解是一個用於宣告註解屬性別名的註解,原始碼如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AliasFor {

     /**
      * Alias for {@link #attribute}.
      * <p>Intended to be used instead of {@link #attribute}  when {@link #annotation}
      * is not declared &mdash; for example: {@code  @AliasFor("value")} instead of
      * {@code @AliasFor(attribute = "value")}.
      */
     @AliasFor("attribute")
     String value() default "";
     /**
      * The name of the attribute that <em>this</em> attribute  is an alias for.
      * @see #value
      */
     @AliasFor("value")
     String attribute() default "";
     /**
      * The type of annotation in which the aliased {@link  #attribute} is declared.
      * <p>Defaults to {@link Annotation}, implying that the  aliased attribute is
      * declared in the same annotation as <em>this</em>  attribute.
      */
     Class<? extends Annotation> annotation() default  Annotation.class;

}

@Controller中的@AliasFor(annotation = Component.class)說明@Controller是Component的一個別名,本質上還是一個Component,正如註釋中所說“to be turned into a Spring bean in case of an  autodetected component.”,可以被掃描成一個bean。

同理,@RestController中的@AliasFor(annotation = Controller.class)說明@RestController是Controller的一個別名,是一個Controller,再本質一點說,是個Component,是個Spring bean。

@ResponseBody註解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {

}

提到@ResponseBody註解,就不得不提一個名詞:表述性狀態轉移(Representational State Transfer,REST)。

什麼是表述性狀態轉移呢?拆開來看:

表述性:REST資源實際上可以用各種形式來表述,包括JSON、XML、HTML等;

狀態:使用TEST的時候,我們更關注資源的狀態而不是對資源採取的行為;

轉移:以某種形式(例如JSON)從一個應用轉換到另一個應用,例如從伺服器到客戶端。

簡單講,REST就是將資源的狀態以最適合客戶端或者伺服器的形式從伺服器轉移到客戶端(或者反過來)。

在Spring 4.0版本中,Spring支援藉助@ResponseBody註解和各種HttpMethodConverter,替換基於檢視的渲染方式,實現對REST的支援。當然Spring對REST的支援遠不止這一種方式。

@ResponseBody註解告知Spring,要將返回的物件作為資源傳送給客戶端。這個過程跳過正常的模型/檢視流程中檢視解析的過程,而是使用Spring自帶的各種Http訊息轉換器將控制器產生的資料轉換為客戶端需要的表述形式。如果客戶端請求頭的Accept表明他能接受“application/json”,並且Jackson JSON在類路徑下面,那麼處理方法返回的物件將交給MappingJacksonHTTPMessageConverter,並由他轉換為JSON返回給客戶端;如果客戶端想要“text/html”格式,那麼Jaxb2RootElementHttpMessageConverter將會為客戶端產生XML響應。

當處理請求時候,@ResponseBody和@RequestBody是啟用訊息轉換的一種簡介和強大方式。但是,如果控制器裡面的每個方法都需要資訊轉換功能的話,那麼這些註解就會帶有一定程度的重複性。

所以,Spring 4.0引入了@RestController註解,如果在控制器類上面使用@RestController註解,我們不必再為每個方法新增@ResponseBody註解,因為Spring會為該控制器下面的所有方法應用訊息轉換功能。這也是這個Controller之所以叫RestController的原因,正所謂見名知意。

總結

@RestController相當於@ResponseBody + @Controller一起作用。

如果控制器產生的結果希望讓人看到,那麼它產生的模型資料需要渲染到檢視中,從而可以展示到瀏覽器中,使用@Controller。

如果控制器產生的結果不需要讓人看到,那麼它產生的資料經過訊息轉換器直接返回到瀏覽器,使用@RestController。

參考文獻:

[1] Bruce Eckel. Java程式設計思想(第四版)[M]. 陳昊鵬譯. 北京:機械工業出版社,2007.

[2] Craig Walls. Spring實戰(第4版)[M]. 張衛濱譯. 北京:人民郵電出版社,2016.