1. 程式人生 > >Spring mvc的註解是如何工作的

Spring mvc的註解是如何工作的

轉自:http://blog.csdn.net/fly_sky520/article/details/21522903

從前年開始使用spring和hibernate,mybatis等框架時,就轉到註解來了。直到前些時,突然對註解開始好奇起來。為什麼寫註解就可以了?不需要大量配置檔案呢?於是我查看了一些資料,對註解有了初步瞭解。

引言:什麼是註解?

在IDE中,我們可以連結spring mvc中的@RequestMapping註解,發現以下原始碼

[java] view plain copy print?
  1. @Target(value = {ElementType.METHOD, ElementType.TYPE})  
  2. @Retention(value = RetentionPolicy.RUNTIME)  
  3. @Documented
  4. @Mapping
  5. public@interface RequestMapping {  
  6.     public String[] value() default {};  
  7.     public RequestMethod[] method() default {};  
  8.     public String[] params() default {};  
  9.     public String[] headers() default {};  
  10.     public String[] consumes() 
    default {};  
  11.     public String[] produces() default {};  
  12. }  
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

    public String[] value() default {};

    public RequestMethod[] method() default {};

    public String[] params() default {};

    public String[] headers() default {};

    public String[] consumes() default {};

    public String[] produces() default {};
}
這其實就是註解的寫法。從這裡我們可以發現,註解的寫法比較簡單,只要在intface前面加上@,就可以定義一個註解。但有幾個其他的註解我們還不是很明白,同樣spring是怎麼通過這個註解進行運轉的呢?

首先:註解的作用是什麼?

1》生成文件,比如我們用的ide裡面會自動加上比如@param,@return,@author等註解。

2》編譯時格式檢查。這個最常見的是@override,@SuppressWarnings等等。

3》跟蹤程式碼依賴性,實現替代配置檔案功能。上面的原始碼例子其實就是這個作用。

其次:元註解

在包 java.lang.annotation 中包含所有定義【自定義註解】所需用到的原註解和介面。如介面 java.lang.annotation.Annotation 是所有註解繼承的介面,並且是自動繼承,不需要定義時指定,類似於所有類都自動繼承Object。檢視Documented.class,可以看到這是個藉口。它有三個註解(@Documented,@Retention,@Target),除此外,還有@Inherited,構成4個元註解。

@Documented 將此註解包含在 javadoc 中 ,它代表著此註解會被javadoc工具提取成文件。

在doc文件中的內容會因為此註解的資訊內容不同而不同。相當與@see,@param 等。

@Retention 表示在什麼級別儲存該註解資訊。可選的引數值在列舉型別 RetentionPolicy 中,包括: 
          RetentionPolicy.SOURCE 註解將被編譯器丟棄 
          RetentionPolicy.CLASS 註解在class檔案中可用,但會被VM丟棄 
          RetentionPolicy.RUNTIME VM將在執行期也保留註釋,因此可以通過反射機制讀取註解的資訊。

@Target 表示該註解用於什麼地方,可能的值在列舉類 ElemenetType 中,包括: 
          ElemenetType.CONSTRUCTOR 構造器宣告 
          ElemenetType.FIELD 域宣告(包括 enum 例項) 
          ElemenetType.LOCAL_VARIABLE 區域性變數宣告
          ElemenetType.ANNOTATION_TYPE 作用於註解量宣告
          ElemenetType.METHOD 方法宣告
          ElemenetType.PACKAGE 包宣告 
          ElemenetType.PARAMETER 引數宣告 
          ElemenetType.TYPE 類,介面(包括註解型別)或enum宣告 

@Inherited 允許子類繼承父類中的註解。

然後:我們來自己編寫註解

[java] view plain copy print?
  1. /** 
  2.  * 自定義註解 
  3.  * @author Fly 
  4.  */
  5. @Documented
  6. @Target({ElementType.METHOD, ElementType.TYPE})  
  7. @Retention(RetentionPolicy.RUNTIME)  
  8. public@interface AnnotationTest {  
  9.     public String name() default“”;  
  10.     public String sex() default“男”;  
  11. }  
/**
 * 自定義註解
 * @author Fly
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {

    public String name() default "";

    public String sex() default "男";
}
[java] view plain copy print?
  1. /** 
  2.  * 註解測試 
  3.  * 
  4.  * @author Fly 
  5.  */
  6. @AnnotationTest(sex = “男”, name = “張飛”)  
  7. publicclass MyAnnotationTest {  
  8.     @AnnotationTest(sex = “男”, name = “Fly”)  
  9.     publicvoid setFly() {  
  10.     }  
  11.     @AnnotationTest(sex = “女”, name = “李明”)  
  12.     publicvoid setLiMing() {  
  13.     }  
  14.     publicstaticvoid main(String[] args) {  
  15.         //檢查類MyAnnotationTest是否含有@AnnotationTest註解
  16.         if (MyAnnotationTest.class.isAnnotationPresent(AnnotationTest.class)) {  
  17.             //若存在就獲取註解
  18.             AnnotationTest annotation = (AnnotationTest) MyAnnotationTest.class.getAnnotation(AnnotationTest.class);  
  19.             System.out.println(annotation);  
  20.             //獲取註解屬性
  21.             System.out.println(annotation.sex());  
  22.             System.out.println(annotation.name());  
  23.             System.out.println(”///////////////////////////////////////////”);  
  24.             Method[] _methods = MyAnnotationTest.class.getDeclaredMethods();  
  25.             for (Method method : _methods) {  
  26.                 System.out.println(method);  
  27.                 if (method.isAnnotationPresent(AnnotationTest.class)) {  
  28.                     AnnotationTest test = method.getAnnotation(AnnotationTest.class);  
  29.                     System.out.println(”AnnotationTest(method=” + method.getName() + “,name=” + test.name() + “,sex=” + test.sex() + “)”);  
  30.                 }  
  31.             }  
  32.         }  
  33.     }  
  34. }  
/**
 * 註解測試
 *
 * @author Fly
 */
@AnnotationTest(sex = "男", name = "張飛")
public class MyAnnotationTest {


    @AnnotationTest(sex = "男", name = "Fly")
    public void setFly() {
    }


    @AnnotationTest(sex = "女", name = "李明")
    public void setLiMing() {
    }


    public static void main(String[] args) {
        //檢查類MyAnnotationTest是否含有@AnnotationTest註解
        if (MyAnnotationTest.class.isAnnotationPresent(AnnotationTest.class)) {
            //若存在就獲取註解
            AnnotationTest annotation = (AnnotationTest) MyAnnotationTest.class.getAnnotation(AnnotationTest.class);
            System.out.println(annotation);
            //獲取註解屬性
            System.out.println(annotation.sex());
            System.out.println(annotation.name());
            System.out.println("///////////////////////////////////////////");
            Method[] _methods = MyAnnotationTest.class.getDeclaredMethods();
            for (Method method : _methods) {
                System.out.println(method);
                if (method.isAnnotationPresent(AnnotationTest.class)) {
                    AnnotationTest test = method.getAnnotation(AnnotationTest.class);
                    System.out.println("AnnotationTest(method=" + method.getName() + ",name=" + test.name() + ",sex=" + test.sex() + ")");


                }
            }
        }
    }
}
測試結果如下:

@test.AnnotationTest(sex=男, name=張飛)

張飛
///////////////////////////////////////////
public static void test.MyAnnotationTest.main(java.lang.String[])
public void test.MyAnnotationTest.setLiMing()
AnnotationTest(method=setLiMing,name=李明,sex=女)
public void test.MyAnnotationTest.setFly()
AnnotationTest(method=setFly,name=Fly,sex=男)

到這裡,我們對註解的基本有點了解了,註解的運用其實與反射式分不開的。我們可以利用程式碼中的註解間接控制程式程式碼的執行,它們通過Java反射機制讀取註解的資訊,並根據這些資訊更改目標程式的邏輯。但是我們怎麼使用註解呢?怎麼讓註解發揮作用,例如spring等框架時如何應用註解的呢?

然後:註解理解的深入
我們結合spring的控制反轉和依賴注入來繼續說明這個問題。

看下面的程式碼,首先是一個IUser介面,包含一個login方法。然後又一箇中文登入方法和英文登入方法都實現了Iuser介面。

[java] view plain copy print?
  1. publicinterface IUser {  
  2.     publicvoid login();  
  3. }  
public interface IUser {

    public void login();
}
[java] view plain copy print?
  1. publicclass ChineseUserImpl implements IUser {  
  2.     @Override
  3.     publicvoid login() {  
  4.         System.err.println(”使用者登入!”);  
  5.     }  
  6. }  
public class ChineseUserImpl implements IUser {
    @Override
    public void login() {
        System.err.println("使用者登入!");
    }
}
[java] view plain copy print?
  1. publicclass EnglishUserImpl implements IUser {  
  2.     @Override
  3.     publicvoid login() {  
  4.         System.err.println(”User Login!”);  
  5.     }  
  6. }  
public class EnglishUserImpl implements IUser {
    @Override
    public void login() {
        System.err.println("User Login!");
    }
}
然後有一個Test類,要注入IUser介面 [java] view plain copy print?
  1. @AnnotationTest
  2. publicclass Test {  
  3.     private IUser userdao;  
  4.     public IUser getUserdao() {  
  5.         return userdao;  
  6.     }  
  7.     @AnnotationTest(nation = “ChineseUserImpl”)  
  8.     publicvoid setUserdao(IUser userdao) {  
  9.         this.userdao = userdao;  
  10.     }  
  11.     publicvoid loginTest() {  
  12.         userdao.login();  
  13.     }  
  14. }  
@AnnotationTest
public class Test {

    private IUser userdao;

    public IUser getUserdao() {
        return userdao;
    }

    @AnnotationTest(nation = "ChineseUserImpl")
    public void setUserdao(IUser userdao) {
        this.userdao = userdao;
    }

    public void loginTest() {
        userdao.login();
    }
}
我們實現的是setter注入方式。為了配合這個例子,我把@AnnotationTest也稍作修改。 [java] view plain copy print?
  1. @Documented
  2. @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})  
  3. @Retention(RetentionPolicy.RUNTIME)  
  4. public@interface AnnotationTest {  
  5.     public String nation() default“”;  
  6. }  
@Documented
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {

    public String nation() default "";
}
然後再引入一個類Container,類似spring容器的作用 [java] view plain copy print?
  1. publicclass Container {  
  2.     publicstatic Test getBean() {  
  3.         Test test = new Test();  
  4.         if (Test.class.isAnnotationPresent(AnnotationTest.class)) {  
  5.             Method[] methods = Test.class.getDeclaredMethods();  
  6.             for (Method method : methods) {  
  7.                 System.out.println(method);  
  8.                 if (method.isAnnotationPresent(AnnotationTest.class)) {  
  9.                     AnnotationTest annotest = method.getAnnotation(AnnotationTest.class);  
  10.                     System.out.println(”AnnotationTest(field=” + method.getName()  
  11.                             + ”,nation=” + annotest.nation() + “)”);  
  12.                     IUser userdao;  
  13.                     try {  
  14.                         userdao = (IUser) Class.forName(”test.” + annotest.nation()).newInstance();  
  15.                         test.setUserdao(userdao);  
  16.                     } catch (Exception ex) {  
  17.                         Logger.getLogger(Container.class.getName()).log(Level.SEVERE, null, ex);  
  18.                     }  
  19.                 }  
  20.             }  
  21.         } else {  
  22.             System.out.println(”沒有註解標記!”);  
  23.         }  
  24.         return test;  
  25.     }  
  26. }  
public class Container {

    public static Test getBean() {
        Test test = new Test();
        if (Test.class.isAnnotationPresent(AnnotationTest.class)) {
            Method[] methods = Test.class.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println(method);
                if (method.isAnnotationPresent(AnnotationTest.class)) {
                    AnnotationTest annotest = method.getAnnotation(AnnotationTest.class);
                    System.out.println("AnnotationTest(field=" + method.getName()
                            + ",nation=" + annotest.nation() + ")");
                    IUser userdao;
                    try {
                        userdao = (IUser) Class.forName("test." + annotest.nation()).newInstance();
                        test.setUserdao(userdao);
                    } catch (Exception ex) {
                        Logger.getLogger(Container.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        } else {
            System.out.println("沒有註解標記!");
        }
        return test;
    }
}
在容器裡面我使用反射獲取註解屬性nation所標註的內容,然後對Test類中的介面進行具體實現。這裡的Container就是所謂的外部容器,可以對我們的註解或者是xml配置檔案進行解析,以降低耦合性。

最後我們再進行測試,程式碼如下

[java] view plain copy print?
  1. /** 
  2.  * 註解測試 
  3.  * 
  4.  * @author Fly 
  5.  */
  6. publicclass MyAnnotationTest {  
  7.     publicstaticvoid main(String[] args) {  
  8.         Test test = Container.getBean();  
  9.         test.loginTest();  
  10.     }  
  11. }  
/**
 * 註解測試
 *
 * @author Fly
 */
public class MyAnnotationTest {

    public static void main(String[] args) {
        Test test = Container.getBean();
        test.loginTest();
    }
}
測試結果如下: [java] view plain copy print?
  1. publicvoid test.Test.loginTest()  
  2. publicvoid test.Test.setUserdao(test.IUser)  
  3. AnnotationTest(field=setUserdao,nation=ChineseUserDaoImpl)  
  4. public test.IUser test.Test.getUserdao()  
  5. 使用者登入!  
public void test.Test.loginTest()
public void test.Test.setUserdao(test.IUser)
AnnotationTest(field=setUserdao,nation=ChineseUserDaoImpl)
public test.IUser test.Test.getUserdao()
使用者登入!
如果我把Test類中的[java] view plain copy print?
  1. @AnnotationTest(nation = “ChineseUserImpl”)  
@AnnotationTest(nation = "ChineseUserImpl")

修改成

[java] view plain copy print?
  1. @AnnotationTest(nation = “EnglishUserImpl”)  
@AnnotationTest(nation = "EnglishUserImpl")
結構就變成 [java] view plain copy print?
  1. publicvoid test.Test.loginTest()  
  2. public test.IUser test.Test.getUserdao()  
  3. publicvoid test.Test.setUserdao(test.IUser)  
  4. AnnotationTest(field=setUserdao,nation=EnglishUserImpl)  
  5. User Login!  
public void test.Test.loginTest()
public test.IUser test.Test.getUserdao()
public void test.Test.setUserdao(test.IUser)
AnnotationTest(field=setUserdao,nation=EnglishUserImpl)
User Login!

總結

1、所有的註解類都隱式繼承於 java.lang.annotation.Annotation,註解不允許顯式繼承於其他的介面。

2、註解不能直接干擾程式程式碼的執行,無論增加或刪除註解,程式碼都能夠正常執行。Java語言直譯器會忽略這些註解,而由第三方工具負責對註解進行處理。

3、一個註解可以擁有多個成員,成員宣告和介面方法宣告類似,這裡,我們僅定義了一個成員,成員的宣告有以下幾點限制:
a)   成員以無入參無丟擲異常的方式宣告,如boolean value(String str)、boolean value() throws Exception等方式是非法的;
b)   可以通過default為成員指定一個預設值,如String level() default “LOW_LEVEL”、int high() default 2是合法的,當然也可以不指定預設值;
c)   成員型別是受限的,合法的型別包括原始型別及其封裝類、String、Class、enums、註解型別,以及上述型別的陣列型別。如ForumService value()、List foo()是非法的。
d)   如果註解只有一個成員,則成員名必須取名為value(),在使用時可以忽略成員名和賦值號(=),如@Description(“使用註解的例項”)。註解類擁有多個成員時,如果僅對value成員進行賦值則也可不使用賦值號,如果同時對多個成員進行賦值,則必須使用賦值號,如@DeclareParents (value = “NaiveWaiter”, defaultImpl = SmartSeller.class)。
e)   註解類可以沒有成員,沒有成員的註解稱為標識註解,解釋程式以標識註解存在與否進行相應的處理;