Spring mvc的註解是如何工作的
轉自:http://blog.csdn.net/fly_sky520/article/details/21522903
從前年開始使用spring和hibernate,mybatis等框架時,就轉到註解來了。直到前些時,突然對註解開始好奇起來。為什麼寫註解就可以了?不需要大量配置檔案呢?於是我查看了一些資料,對註解有了初步瞭解。
引言:什麼是註解?
在IDE中,我們可以連結spring mvc中的@RequestMapping註解,發現以下原始碼
[java] view plain copy print?- @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()
- public String[] produces() default {};
- }
這其實就是註解的寫法。從這裡我們可以發現,註解的寫法比較簡單,只要在intface前面加上@,就可以定義一個註解。但有幾個其他的註解我們還不是很明白,同樣spring是怎麼通過這個註解進行運轉的呢?@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 {}; }
首先:註解的作用是什麼?
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?- /**
- * 自定義註解
- * @author Fly
- */
- @Documented
- @Target({ElementType.METHOD, ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- public@interface AnnotationTest {
- public String name() default“”;
- public String sex() default“男”;
- }
/**
* 自定義註解
* @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?- /**
- * 註解測試
- *
- * @author Fly
- */
- @AnnotationTest(sex = “男”, name = “張飛”)
- publicclass MyAnnotationTest {
- @AnnotationTest(sex = “男”, name = “Fly”)
- publicvoid setFly() {
- }
- @AnnotationTest(sex = “女”, name = “李明”)
- publicvoid setLiMing() {
- }
- publicstaticvoid 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() + “)”);
- }
- }
- }
- }
- }
/**
* 註解測試
*
* @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?- publicinterface IUser {
- publicvoid login();
- }
public interface IUser {
public void login();
}
[java] view plain copy print?- publicclass ChineseUserImpl implements IUser {
- @Override
- publicvoid login() {
- System.err.println(”使用者登入!”);
- }
- }
public class ChineseUserImpl implements IUser {
@Override
public void login() {
System.err.println("使用者登入!");
}
}
[java] view plain copy print?- publicclass EnglishUserImpl implements IUser {
- @Override
- publicvoid login() {
- System.err.println(”User Login!”);
- }
- }
public class EnglishUserImpl implements IUser {
@Override
public void login() {
System.err.println("User Login!");
}
}
然後有一個Test類,要注入IUser介面
[java] view plain copy print?- @AnnotationTest
- publicclass Test {
- private IUser userdao;
- public IUser getUserdao() {
- return userdao;
- }
- @AnnotationTest(nation = “ChineseUserImpl”)
- publicvoid setUserdao(IUser userdao) {
- this.userdao = userdao;
- }
- publicvoid loginTest() {
- userdao.login();
- }
- }
@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?- @Documented
- @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public@interface AnnotationTest {
- public String nation() default“”;
- }
@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?- publicclass Container {
- publicstatic 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;
- }
- }
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?- /**
- * 註解測試
- *
- * @author Fly
- */
- publicclass MyAnnotationTest {
- publicstaticvoid main(String[] args) {
- Test test = Container.getBean();
- test.loginTest();
- }
- }
/**
* 註解測試
*
* @author Fly
*/
public class MyAnnotationTest {
public static void main(String[] args) {
Test test = Container.getBean();
test.loginTest();
}
}
測試結果如下:
[java] view plain copy print?- publicvoid test.Test.loginTest()
- publicvoid test.Test.setUserdao(test.IUser)
- AnnotationTest(field=setUserdao,nation=ChineseUserDaoImpl)
- public test.IUser test.Test.getUserdao()
- 使用者登入!
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?- @AnnotationTest(nation = “ChineseUserImpl”)
@AnnotationTest(nation = "ChineseUserImpl")
修改成
[java] view plain copy print?- @AnnotationTest(nation = “EnglishUserImpl”)
@AnnotationTest(nation = "EnglishUserImpl")
結構就變成
[java] view plain copy print?- publicvoid test.Test.loginTest()
- public test.IUser test.Test.getUserdao()
- publicvoid test.Test.setUserdao(test.IUser)
- AnnotationTest(field=setUserdao,nation=EnglishUserImpl)
- 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) 註解類可以沒有成員,沒有成員的註解稱為標識註解,解釋程式以標識註解存在與否進行相應的處理;