1. 程式人生 > >Java的註解機制——Spring自動裝配的實現原理

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

  使用註解主要是在需要使用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         }
複製程式碼