限於個人能力,升入到源碼層面分析注解的作用機制還是個無法完成的任務,只能通過一些簡單的示例理解注解。
1.java中的注解。簡單說,java中的注解的功能類似標簽,一般是要配合java反射機制來使用的。創建一個自定注解很簡單,只需要遵循java的語言規范即可,
1)自定義一個注解
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})//指定注解的作用范圍:從左到右分別是表示:作用在類上,作用在變量(字段)上,作用在方法上 @Retention(RetentionPolicy.RUNTIME)//注解生效失效時間:此處指明的運行時生效,可以指明其注解在其他階段生效,一般情況下都是指定運行時生效的
//定義注解 public @interface MyAnnotation{ //注解中可以定義屬性,也可以定義方法,在使用注解時可以改變屬性值 public abstract String getValue() default "no description"; //注解中屬性可以有八種基本數據類型,String,枚舉(CiytEnum cityName()),Class類型:(Class Clazz()),注解(annotataion=@MyAnnotation ) //以上數據類型的一維數組names{"xxx","yyy"} int intValue(); long longValue(); }
2)注解如果沒有被使用,那就是毫無價值的,可以像下面這樣來使用注解
//使用注解
//因為定義注解的時候指定了注解的作用范圍,和作用時間,所以注解可以標注到類頭上,方法頭上,和變量頭上 @MyAnnotation(getValue = "annotation on class",intValue = 2,longValue = 10) public class Demo { //注解中有多個屬性是用如下方式為每一個屬性指定值 @MyAnnotation(getValue = "annotation on field",intValue = 2,longValue = 10) public String name; @MyAnnotation(getValue = "annotation on method",intValue = 2,longValue = 10) public void hello(){} @MyAnnotation(intValue = 2,longValue = 10) public void methodDefault(){}
3)讀取目標類的注解信息。為了能方便理解下面的程序,需要先解釋一下字節碼對象:下面的程序中此處的字節碼對象指的就是Demo這個類的.class文件。試想如果讓你對無數的class文件進行抽象,你應該能想每個類方法都有成員變量(屬性),方法,注解等信息,把這些信息封裝到一個類中,這個類就是Class類(實際的Class類的抽象偶更加復雜,但是道理是一樣的),如此一來你可以通過class類的對象獲取其中的屬性,操縱這些屬性,不通過某個類的實例對象訪問和操作類屬性,java中的反射機制就這樣工作的。
public class TestDemo { public static void main(String[] args) throws Exception { //通過獲取類的字節碼對象,獲取類上的注解
Class<Demo> clazz = Demo.class; MyAnnotation annotationOnClass = clazz.getAnnotation(MyAnnotation.class);
//獲取注解的值,就是你在使用注解的時候設置的值 System.out.println(annotationOnClass.getValue()); System.out.println(annotationOnClass.intValue()); System.out.println(annotationOnClass.longValue()); //通過字節碼對象獲取成員變量上的注解 Field name = clazz.getField("name"); MyAnnotation annotationOnField = name.getAnnotation(MyAnnotation.class); System.out.println(annotationOnField.getValue()); //通過字節碼對象獲取方法上的注解 Method method = clazz.getMethod("hello"); MyAnnotation annotationOnMethod = clazz.getAnnotation(MyAnnotation.class); System.out.println(annotationOnMethod.getValue()); } }
--------------程序運行結果--------------------------
annotation on class
2
10
annotation on field
annotation on class
2 .使用動態代理和注解,靈活實現類的功能增強。下面模擬給一個普通類的方法增加事務功能來說明問題
1)定義接口
public interface UserService { void login(String userName,String password); void register(); }
2)定義注解:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Transactional { }
3)在實現類中去使用注解,很簡單,只需要你想代理的方法上打上注解,
/** * 被代理的對象 */ public class UserServiceImpl implements UserService,UserLife{ /** *模擬開啟事務 */ @Override @Transactional public void login(String userName, String password) { System.out.println(userName + " login");
//int i = 9 /0;你可以在這個方法中制造異常,觀察程序運行結果 } @Override public void register() { System.out.println("register()....."); // login("tom","12345");是為了模擬在一個沒有注解標記的方法中去調用帶注解的方法,方法調用時不會走代理的 } }
4)代理邏輯
/** * 代理的業務 * 只為帶有 Transactional 注解的方法開事務 */ public class TransactionInvocationHandler implements InvocationHandler { //被代理類對象 private UserService userService; public TransactionInvocationHandler(UserService userService) { this.userService = userService; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //通過真實的代理對象 找到需要代理的方法 就可以不必在接口中增加注解,此處的UserService對象取決於你在類的構造方法中傳入的是哪個對象 Method targetMethod = userService.getClass().getMethod(method.getName(), method.getParameterTypes());
//這種方式是獲取接口中的方法的注解信息,不會識別具體顯現類的方法上的注解信息 // Annotation[] annotations1 = method.getAnnotations();
//獲取被代理類的當前調用方法上所有注解 Annotation[] annotations = targetMethod.getAnnotations();
//判斷方法是否需要被代理 boolean isProxy = false;
//遍歷注解, for(Annotation annotation : annotations){
//判斷注解類型是否和自定的注解時同一類型,如果是,將isProxy置為ture if(annotation.annotationType() == Transactional.class){ isProxy = true; } } //如果不需要代理,則直接調用目標方法 if(!isProxy){ Object result = method.invoke(userService, args); return result; } //以下是需要代理的業務 System.out.println(">>>>>>>開啟事務...........");
//method.invoke()方法調用時又返回值的 Object result = null; try{ result = method.invoke(userService, args); System.out.println(">>>>>>>>>>事務提交"); } catch (Exception e){ e.printStackTrace(); System.out.println(">>>>>>> 事務回滾 rollback >>>>>>>>>>>>>"); }finally { System.out.println(">>>>>>>>關閉連接 connection close"); } return result; } }
5)測試代碼:
public class Test2 { public static void main(String[] args) { UserService service = (UserService)Proxy.newProxyInstance(Test2.class.getClassLoader(),//獲取類加載器 new Class[]{UserService.class},//傳入的接口 new TransactionInvocationHandler(new UserServiceImpl()));//承載了代理邏輯的類
//調用方法 service.login("sx","123455"); System.out.println("---------在無事務的方法register()中調用有事務的方法login()---------");
//在一個沒有事務的方法調用一個沒有注解的方法中調用一個有注解的方法,注解是會被忽略的,這也是spring中事務失效的原因之一 service.register(); System.out.println("代理類的名字>>>>>" + service.getClass().getName());//com.sun.proxy.$Proxy0 } }
--------------程序運行結果--------------------
>>>>>>>開啟事務...........
sx login
>>>>>>>>>>事務提交
>>>>>>>>關閉連接 connection close
---------在無事務的方法register()中調用有事務的方法login()---------
register().....
代理類的名字>>>>>com.sun.proxy.$Proxy0
總結:通過java中的動態代理,配合注解,就能靈活的控制在程序執行到某些特殊點的地方動態的加上一些代碼,這就是織入,一個簡單單的類,就是通過這樣的方式增加了功能,變得更加強大,行到此處,勉強算是摸到spring AOP的腳后跟了。