Java的注解機制——Spring自動裝配的實現原理


  JDK1.5加入了對注解機制的支持,實際上我學習Java的時候就已經使用JDK1.6了,而且除了@Override和@SuppressWarnings(后者還是IDE給生成的……)之外沒接觸過其他的。

  進入公司前的面試,技術人員就問了我關於注解的問題,我就說可以生成chm手冊……現在想起來真囧,注釋和注解被我搞得完全一樣了。

  

  使用注解主要是在需要使用Spring框架的時候,特別是使用SpringMVC。因為這時我們會發現它的強大之處:預處理。

  注解實際上相當於一種標記,它允許你在運行時(源碼、文檔、類文件我們就不討論了)動態地對擁有該標記的成員進行操作。

  實現注解需要三個條件(我們討論的是類似於Spring自動裝配的高級應用):注解聲明、使用注解的元素、操作使用注解元素的代碼

  

  首先是注解聲明,注解也是一種類型,我們要定義的話也需要編寫代碼,如下:

 1 package annotation;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 /**
 9  * 自定義注解,用來配置方法
10  * 
11  * @author Johness
12  *
13  */
14 @Retention(RetentionPolicy.RUNTIME) // 表示注解在運行時依然存在
15 @Target(ElementType.METHOD) // 表示注解可以被使用於方法上
16 public @interface SayHiAnnotation {
17     String paramValue() default "johness"; // 表示我的注解需要一個參數 名為"paramValue" 默認值為"johness"
18 }

 

  然后是使用我們注解的元素:

 1 package element;
 2 
 3 import annotation.SayHiAnnotation;
 4 
 5 /**
 6  * 要使用SayHiAnnotation的元素所在類
 7  * 由於我們定義了只有方法才能使用我們的注解,我們就使用多個方法來進行測試
 8  * 
 9  * @author Johness
10  *
11  */
12 public class SayHiEmlement {
13 
14     // 普通的方法
15     public void SayHiDefault(String name){
16         System.out.println("Hi, " + name);
17     }
18     
19     // 使用注解並傳入參數的方法
20     @SayHiAnnotation(paramValue="Jack")
21     public void SayHiAnnotation(String name){
22         System.out.println("Hi, " + name);
23     }
24     
25     // 使用注解並使用默認參數的方法
26     @SayHiAnnotation
27     public void SayHiAnnotationDefault(String name){
28         System.out.println("Hi, " + name);
29     }
30 }

  最后,是我們的操作方法(值得一提的是雖然有一定的規范,但您大可不必去浪費精力,您只需要保證您的操作代碼在您希望的時候執行即可):

 1 package Main;
 2 
 3 import java.lang.reflect.InvocationTargetException;
 4 import java.lang.reflect.Method;
 5 
 6 import element.SayHiEmlement;
 7 import annotation.SayHiAnnotation;
 8 
 9 public class AnnotionOperator {
10     public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
11         SayHiEmlement element = new SayHiEmlement(); // 初始化一個實例,用於方法調用
12         Method[] methods = SayHiEmlement.class.getDeclaredMethods(); // 獲得所有方法
13         
14         for (Method method : methods) {
15             SayHiAnnotation annotationTmp = null;
16             if((annotationTmp = method.getAnnotation(SayHiAnnotation.class))!=null) // 檢測是否使用了我們的注解
17                 method.invoke(element,annotationTmp.paramValue()); // 如果使用了我們的注解,我們就把注解里的"paramValue"參數值作為方法參數來調用方法
18             else
19                 method.invoke(element, "Rose"); // 如果沒有使用我們的注解,我們就需要使用普通的方式來調用方法了
20         }
21     }
22 }

  結果為:Hi, Jack
      Hi, johness
      Hi, Rose

  可以看到,注解是進行預處理的很好方式(這里的預處理和編譯原理有區別)!

 

  接下來我們看看Spring是如何使用注解機制完成自動裝配的:

  

    首先是為了讓Spring為我們自動裝配要進行的操作,無外乎兩種:繼承org.springframework.web.context.support.SpringBeanAutowiringSupport類或者添加@Component/@Controller等注解並(只是使用注解方式需要)在Spring配置文件里聲明context:component-scan元素。

    我說說繼承方式是如何實現自動裝配的,我們打開Spring源代碼查看SpringBeanAutowiringSupport類。我們會發現以下語句:

1 public SpringBeanAutowiringSupport() {
2         processInjectionBasedOnCurrentContext(this);
3     }

 

    眾所周知,Java實例構造時會調用默認父類無參構造方法,Spring正是利用了這一點,讓"操作元素的代碼"得以執行!(我看到第一眼就震驚了!真是奇思妙想啊。果然,高手都要善於用Java來用Java)

    后面的我就不就不多說了,不過還是要糾正一些人的觀點:說使用注解的自動裝配來完成注入也需要setter。這明顯是錯誤的嘛!我們看Spring注解裝配(繼承方式)的方法調用順序: org.springframework.web.context.support.SpringBeanAutowiringSupport#SpringBeanAutowiringSupport=>

        org.springframework.web.context.support.SpringBeanAutowiringSupport#processInjectionBasedOnCurrentContext=>

      org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#processInjection=>

               org.springframework.beans.factory.annotation.InjectionMetadata#Injection(繼承,方法重寫)。最后看看Injection方法的方法體:

 1 /**
 2          * Either this or {@link #getResourceToInject} needs to be overridden.
 3          */
 4         protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
 5             if (this.isField) {
 6                 Field field = (Field) this.member;
 7                 ReflectionUtils.makeAccessible(field);
 8                 field.set(target, getResourceToInject(target, requestingBeanName));
 9             }
10             else {
11                 if (checkPropertySkipping(pvs)) {
12                     return;
13                 }
14                 try {
15                     Method method = (Method) this.member;
16                     ReflectionUtils.makeAccessible(method);
17                     method.invoke(target, getResourceToInject(target, requestingBeanName));
18                 }
19                 catch (InvocationTargetException ex) {
20                     throw ex.getTargetException();
21                 }
22             }
23         }

      雖然不完全,但可以基本判定此種自動裝配是使用了java放射機制。

 

 歡迎您移步我們的交流群,無聊的時候大家一起打發時間:Programmer Union

 或者通過QQ與我聯系:點擊這里給我發消息

 (最后編輯時間2013-04-19 09:52:27)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM