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 ""; }
@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 ""; }
顯然,兩個注解的源碼里面都包含許多的注解:
@Controller的注解包括:@Target({ElementType.TYPE})、@Retention(RetentionPolicy.RUNTIME)、@Documented、@Component
@RestController的注解包括:@Target(ElementType.TYPE)、@Retention(RetentionPolicy.RUNTIME)、@Documented、@Controller、@ResponseBody
所以,源碼的分析也就是對注解的分析。
內置注解和元注解
注解(也被稱為元數據)為我們在代碼中添加信息提供了一種形式化的方法,使我們可以在稍后某個時刻非常方便地使用這些數據。
Java SE5中有三種內置注解:
@Override
|
表示當前的方法定義將覆蓋超類中的方法
|
@Deprecated
|
如果程序中使用了注解為它的元素,那么編譯器會發出警告
|
@SuppressWarnings
|
關閉不當的編譯器警告信息
|
@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 — 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.