Java核心知識體系2:註解機制詳解

2022-07-20 15:00:25

1 Java註解基礎

註解是JDK1.5版本開始引入的一個特性,用於對程式程式碼的說明,可以對包、類、介面、欄位、方法引數、區域性變數等進行註解。
它主要的作用有以下四方面:

  • 生成javadoc檔案,通過在程式碼裡面標識後設資料生成javadoc檔案。
  • 編譯期的檢查,通過標識的後設資料讓編譯器在編譯期間對程式碼進行驗證。
  • 編譯時動態處理,編譯時通過程式碼中標識的後設資料動態處理,比如動態生成程式碼。
  • 執行時動態處理,執行時通過程式碼中標識的後設資料動態處理,比如使用反射技術注入範例。

註解的常見分類有三種:

  • Java自帶的標準註解,包括 @Override、@Deprecated和@SuppressWarnings,分別代表 方法重寫、某個類或方法過時、以及忽略警告,用這些註解標明後編譯器就會進行檢查。
  • 元註解,元註解是用於定義註解的註解,包括@Retention、@Target、@Inherited、@Documented 等6種
    • @Retention:指定其所修飾的註解的保留策略
    • @Document:該註解是一個標記註解,用於指示一個註解將被檔案化
    • @Target:用來限制註解的使用範圍
    • @Inherited:該註解使父類別的註解能被其子類繼承
    • @Repeatable:該註解是Java8新增的註解,用於開發重複註解
    • 型別註解(Type Annotation):該註解是Java8新增的註解,可以用在任何用到型別的地方
  • 自定義註解,可以根據自己的需求定義註解,並可用元註解對自定義註解進行註解。

接下來我們通過這三種分類來逐一理解註解。

1.1 Java內建註解

我們先從Java內建註解開始說起,先看下下面的程式碼:

class Parent {
    public void rewriteMethod() {
        
    }
}

class Child extends Parent {

    /**
        * 過載父類別的 rewriteMethod() 方法
        */
    @Override
    public void rewriteMethod() {
    }

    /**
        * 被棄用的過時方法
        */
    @Deprecated
    public void oldMethod() {
    }

    /**
        * 忽略告警
        * 
        * @return
        */
    @SuppressWarnings("keep run")
    public List infoList() {
        List list = new ArrayList();
        return list;
    }
}

Java 1.5開始自帶的標準註解,包括@Override、@Deprecated和@SuppressWarnings:

  • @Override:表示當前類中的方法定義將覆蓋父類別中的方法
  • @Deprecated:表示該程式碼段被棄用,但是可以使用,只是編譯器會發出警告而已
  • @SuppressWarnings:表示關閉編譯器的警告資訊
    我們再具體看下這幾個內建註解,同時通過這幾個內建註解中的元註解的定義來引出元註解。

1.1.1 內建註解 - @Override

我們先來看一下這個註解型別的定義:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

從它的定義我們可以看到,這個註解可以被用來修飾方法,並且它只在編譯時有效,在編譯後的class檔案中便不再存在。這個註解的作用我們大家都不陌生,那就是告訴編譯器被修飾的方法是重寫的父類別的中的相同簽名的方法,編譯器會對此做出檢查,
若發現父類別中不存在這個方法或是存在的方法簽名不同,則會報錯。

1.1.2 內建註解 - @Deprecated

這個註解的定義如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

從它的定義我們可以知道,它會被檔案化,能夠保留到執行時,能夠修飾構造方法、屬性、區域性變數、方法、包、引數、型別。這個註解的作用是告訴編譯器被修飾的程式元素已被「廢棄」,不再建議使用者使用。

1.1.3 內建註解 - @SuppressWarnings

這個註解我們也比較常用到,先來看下它的定義:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

它能夠修飾的程式元素包括型別、屬性、方法、引數、構造器、區域性變數,只能存活在原始碼時,取值為String[]。它的作用是告訴編譯器忽略指定的警告資訊,它可以取的值如下所示:

引數 作用 原描述
all 抑制所有警告 to suppress all warnings
boxing 抑制裝箱、拆箱操作時候的警告 to suppress warnings relative to boxing/unboxing operations
cast 抑制對映相關的警告 to suppress warnings relative to cast operations
dep-ann 抑制啟用註釋的警告 to suppress warnings relative to deprecated annotation
deprecation 抑制過期方法警告 to suppress warnings relative to deprecation
fallthrough 抑制確在switch中缺失breaks的警告 to suppress warnings relative to missing breaks in switch statements
finally 抑制finally模組沒有返回的警告 to suppress warnings relative to finally block that don’t return
hiding 抑制與隱藏變數的區域變數相關的警告 to suppress warnings relative to locals that hide variable()
incomplete-switch 忽略沒有完整的switch語句 to suppress warnings relative to missing entries in a switch statement (enum case)
nls 忽略非nls格式的字元 to suppress warnings relative to non-nls string literals
null 忽略對null的操作 to suppress warnings relative to null analysis
rawtype 使用generics時忽略沒有指定相應的型別 to suppress warnings relative to un-specific types when using
restriction 抑制與使用不建議或禁止參照相關的警告 to suppress warnings relative to usage of discouraged or
serial 忽略在serializable類中沒有宣告serialVersionUID變數 to suppress warnings relative to missing serialVersionUID field for a serializable class
static-access 抑制不正確的靜態存取方式警告 to suppress warnings relative to incorrect static access
synthetic-access 抑制子類沒有按最優方法存取內部類的警告 to suppress warnings relative to unoptimized access from inner classes
unchecked 抑制沒有進行型別檢查操作的警告 to suppress warnings relative to unchecked operations
unqualified-field-access 抑制沒有許可權存取的域的警告 to suppress warnings relative to field access unqualified
unused 抑制沒被使用過的程式碼的警告 to suppress warnings relative to unused code

1.2 元註解

上述內建註解的定義中使用了一些元註解(註解型別進行註解的註解類),在JDK 1.5中提供了4個標準的元註解:@Target,@Retention,@Documented,@Inherited, 在JDK 1.8中提供了兩個新的元註解 @Repeatable和@Native。

1.2.1 元註解 - @Target

Target註解的作用是:描述註解的使用範圍(即:被修飾的註解可以用在什麼地方) 。

Target註解用來說明那些被它所註解的註解類可修飾的物件範圍:

  • packages、types(類、介面、列舉、註解類)
  • 類成員(方法、構造方法、成員變數、列舉值)
  • 方法引數和本地變數(如迴圈變數、catch引數)
    在定義註解類時使用了@Target 能夠更加清晰的知道它能夠被用來修飾哪些物件,它的取值範圍定義在ElementType 列舉中。列舉資訊如下:
public enum ElementType { 
    TYPE, // 類、介面、列舉類 
    FIELD, // 成員變數(包括:列舉常數) 
    METHOD, // 成員方法 
    PARAMETER, // 方法引數 
    CONSTRUCTOR, // 構造方法 
    LOCAL_VARIABLE, // 區域性變數 
    ANNOTATION_TYPE, // 註解類 
    PACKAGE, // 可用於修飾:包 
    TYPE_PARAMETER, // 型別引數,JDK 1.8 新增 
    TYPE_USE // 使用型別的任何地方,JDK 1.8 新增 
}

1.2.2 元註解 - @Retention & @RetentionTarget

Reteniton註解的作用是:描述註解保留的時間範圍(即:被描述的註解在它所修飾的類中可以被保留到何時) 。

Reteniton註解用來限定那些被它所註解的註解類在註解到其他類上以後,可被保留到何時,一共有三種策略,定義在RetentionPolicy列舉中。列舉如下:

public enum RetentionPolicy {
 
    SOURCE,    // 原始檔保留
    CLASS,       // 編譯期保留,預設為該值,CLASS
    RUNTIME   // 執行期保留,可通過反射去獲取註解資訊
}

我們測試下這三種策略,在定義註解類的時候什麼區別:

@Retention(RetentionPolicy.SOURCE)
public @interface SourcePolicy {
  // 原始檔保留策略
}

@Retention(RetentionPolicy.CLASS)
public @interface ClassPolicy {
  // 編譯器保留策略
}

@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimePolicy {
  // 執行期保留策略
}

上面已經定義好了三個註解類,我們再用這三個註解類再去註解方法,如下:

public class RetentionTest {
 
	@SourcePolicy
	public void sourcePolicy() {
	}
 
	@ClassPolicy
	public void classPolicy() {
	}
 
	@RuntimePolicy
	public void runtimePolicy() {
	}
}

通過執行 javap -verbose RetentionTest命令獲取到的RetentionTest 的 class 位元組碼內容如下。

{
  public retention.RetentionTest();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial   #1            // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public void sourcePolicy();
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 7: 0

  public void classPolicy();
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 11: 0
    RuntimeInvisibleAnnotations:
      0: #11()

  public void runtimePolicy();
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 15: 0
    RuntimeVisibleAnnotations:
      0: #14()
}

從 RetentionTest 的位元組碼內容我們可以得出以下兩點結論:

  • 編譯器並沒有記錄下 sourcePolicy() 方法的註解資訊
  • 編譯器使用 RuntimeInvisibleAnnotations 去記錄 classPolicy()方法的註解資訊
  • 編譯器使用 RuntimeVisibleAnnotations 去記錄 runtimePolicy()方法的註解資訊

1.2.3 元註解 - @Documented

Documented註解的作用如下:使用 javadoc 工具為類生成幫助檔案,並確認是否保留註解資訊。

以下程式碼在使用Javadoc工具可以生成 @DocAnnotation註解資訊。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
 
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DocAnnotation {
 
	public String value() default "default";
}

@DocAnnotation("some method doc")
public void testMethod() {
  // 測試方法的檔案註解
}

1.2.4 元註解 - @Inherited

Inherited註解的作用:被它修飾的Annotation將具有繼承特性。父類別使用了被@Inherited修飾的Annotation,則子類將自動具備該註解。

我們來測試下這個註解:

  • 定義@Inherited註解:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface InheritedAnnotation {
    String [] values();
    int number();
}

  • 使用這個註解
@InheritedAnnotation(values = {"brand"}, number = 100)
public class UserInfo {
}

class Customer extends UserInfo {
	@Test
    public void testMethod(){
        Class clazz = Student.class;
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.toString());
        }
    }
}

  • 輸出
xxx.InheritedAnnotation(values=[brand], number=100)

雖然Customer類沒有顯示地被註解@InheritedAnnotation,但是它的父類別UserInfo被註解,而且@InheritedAnnotation被@Inherited註解,因此Customer類自動繼承註解

1.2.4 元註解 - @Repeatable (Java8)

Repeatable是可重複使用的意思,允許在同一宣告的型別(類,屬性,或方法)中,可以多次使用同一個註解

JDK8之前要想實現註解重複使用,需要組合模式,編寫和可讀性都不是很好

public @interface Pet {
     String myPet();
}

public @interface Pets {
    Pet[] value();
}

public class RepeatAnnotationOV {
    @Pets({@Pet(myPet="dog"),@Pet(myPet="cat")})
    public void workMethod(){
    }
}

由另一個註解來儲存重複註解,在使用時候,用儲存註解Authorities來擴充套件重複註解。

Java 8中的做法:

@Repeatable(Pets.class)
public @interface Pet {
     String myPet();
}

public @interface Pets {
    Pet[] value();
}

public class RepeatAnnotationNV {
    @Pet(role="dog")
    @Pet(role="cat")
    public void workMethod(){ }
}

不同的地方是,建立重複註解Authority時,加上@Repeatable,指向儲存註解Authorities,在使用時候,直接可以重複使用Authority註解。從上面例子看出,java 8裡面做法更適合常規的思維,可讀性強一點

1.2.5 元註解 - @Native (Java8)

使用 @Native 註解修飾成員變數,則表示這個變數可以被原生程式碼參照,常常被程式碼生成工具使用。對於 @Native 註解不常使用,瞭解即可

1.3 註解與反射介面

定義註解後,如何獲取註解中的內容呢?反射包java.lang.reflect下的AnnotatedElement介面提供這些方法。這裡注意:只有註解被定義為RUNTIME後,該註解才能是執行時可見,當class檔案被裝載時被儲存在class檔案中的Annotation才會被虛擬機器器讀取。

AnnotatedElement 介面是所有程式元素(Class、Method和Constructor)的父介面,所以程式通過反射獲取了某個類的AnnotatedElement物件之後,程式就可以呼叫該物件的方法來存取Annotation資訊。我們看下具體的先關介面

  • boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)
    判斷該程式元素上是否包含指定型別的註解,存在則返回true,否則返回false。注意:此方法會忽略註解對應的註解容器。
  • T getAnnotation(Class annotationClass)
    返回該程式元素上存在的、指定型別的註解,如果該型別註解不存在,則返回null。
  • Annotation[] getAnnotations()
    返回該程式元素上存在的所有註解,若沒有註解,返回長度為0的陣列。
  • T[] getAnnotationsByType(Class annotationClass)
    返回該程式元素上存在的、指定型別的註解陣列。沒有註解對應型別的註解時,返回長度為0的陣列。該方法的呼叫者可以隨意修改返回的陣列,而不會對其他呼叫者返回的陣列產生任何影響。getAnnotationsByType方法與 getAnnotation的區別在於,getAnnotationsByType會檢測註解對應的重複註解容器。若程式元素為類,當前類上找不到註解,且該註解為可繼承的,則會去父類別上檢測對應的註解。
  • T getDeclaredAnnotation(Class annotationClass)
    返回直接存在於此元素上的所有註解。與此介面中的其他方法不同,該方法將忽略繼承的註釋。如果沒有註釋直接存在於此元素上,則返回null
  • T[] getDeclaredAnnotationsByType(Class annotationClass)
    返回直接存在於此元素上的所有註解。與此介面中的其他方法不同,該方法將忽略繼承的註釋
  • Annotation[] getDeclaredAnnotations()
    返回直接存在於此元素上的所有註解及註解對應的重複註解容器。與此介面中的其他方法不同,該方法將忽略繼承的註解。如果沒有註釋直接存在於此元素上,則返回長度為零的一個陣列。該方法的呼叫者可以隨意修改返回的陣列,而不會對其他呼叫者返回的陣列產生任何影響。

1.4 自定義註解

當我們理解了內建註解, 元註解和獲取註解的反射介面後,我們便可以開始自定義註解了。這個例子我把上述的知識點全部融入進來, 程式碼很簡單:

  • 定義自己的註解
package com.helenlyn.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * <p>Description: 水果供應者註解        </p>
 * <p>Copyright: Copyright (c) 2021 </p>
 * <p>Company: helenlyn Co., Ltd.             </p>
 *
 * @author brand
 * @date 2021/5/16 16:35
 * <p>Update Time:                      </p>
 * <p>Updater:                          </p>
 * <p>Update Comments:                  </p>
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供應商編號
     * @return
     */
    public int id() default -1;

    /**
     * 供應商名稱
     * @return
     */
    public String name() default "";

    /**
     * 供應商地址
     * @return
     */
    public String address() default "";
}
  • 使用註解
package com.helenlyn.common.dto;

import com.helenlyn.common.annotation.FruitColor;
import com.helenlyn.common.annotation.FruitName;
import com.helenlyn.common.annotation.FruitProvider;

/**
 * <p>Description:               </p>
 * <p>Copyright: Copyright (c) 2021 </p>
 * <p>Company: helenlyn Co., Ltd.             </p>
 *
 * @author brand
 * @date 2021/5/16 16:28
 * <p>Update Time:                      </p>
 * <p>Updater:                          </p>
 * <p>Update Comments:                  </p>
 */
public class AppleDto {
   @FruitName("Apple")
    private String appleName;

    @FruitColor(fruitColor=FruitColor.Color.RED)
    private String appleColor;

    @FruitProvider(id=1,name="helenlyn 貿易公司",address="福州xx路xxx大樓")
    private String appleProvider;
}

  • 用反射介面獲取註解資訊
    在 FruitInfoUtil 中進行測試:
/**
 * <p>Description: FruitInfoUtil註解實現 </p>
 * <p>Copyright: Copyright (c) 2021 </p>
 * <p>Company: helenlyn Co., Ltd.             </p>
 *
 * @author brand
 * @date 2021/5/16 16:37
 * <p>Update Time:                      </p>
 * <p>Updater:                          </p>
 * <p>Update Comments:                  </p>
 */
public class FruitInfoUtil {
    public static String getFruitInfo(Class<?> clazz) {
        String strFruitName = " 水果名稱:";
        String strFruitColor = " 水果顏色:";
        String strFruitProvicer = "供應商資訊:";

        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(FruitName.class)) {
                FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
                strFruitName += fruitName.value();
                System.out.println(strFruitName);
            } else if (field.isAnnotationPresent(FruitColor.class)) {
                FruitColor fruitColor = (FruitColor) field.getAnnotation(FruitColor.class);
                strFruitColor += fruitColor.fruitColor().toString();
                System.out.println(strFruitColor);
            } else if (field.isAnnotationPresent(FruitProvider.class)) {
                FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class);
                strFruitProvicer = " 供應商編號:" + fruitProvider.id() + " 供應商名稱:" + fruitProvider.name() + " 供應商地址:" + fruitProvider.address();
                System.out.println(strFruitProvicer);
            }
        }
        return String.format("%s;%s;%s;", strFruitName, strFruitColor, strFruitProvicer);
    }
}

  • 測試的輸出
2022-07-09 11:33:41.688  INFO 5895 --- [TaskExecutor-35] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: cl-debug-rabbitmq-erp-service-7w0cpa.docker.sdp:9146
Hibernate: update UserBasicInfo set personName=? where personCode=?
 水果名稱:Apple
 水果顏色:RED
 供應商編號:1 供應商名稱:helenlyn 貿易公司 供應商地址:福州xx路xxx大樓

2 理解註解的原理

2.1 Java 8 提供了哪些新的註解

  • @Repeatable
  • ElementType.TYPE_USE
  • ElementType.TYPE_PARAMETER

ElementType.TYPE_USE(此型別包括型別宣告和型別引數宣告,是為了方便設計者進行型別檢查)包含了ElementType.TYPE(類、介面(包括註解型別)和列舉的宣告)和ElementType.TYPE_PARAMETER(型別引數宣告), 可以看下面這個例子:

// 自定義ElementType.TYPE_PARAMETER註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_PARAMETER)
public @interface MyNotEmpty {
}

// 自定義ElementType.TYPE_USE註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface MyNotNull {
}

// 測試類
public class TypeParameterAndTypeUseAnnotation<@MyNotEmpty T>{

  //使用TYPE_PARAMETER型別,會編譯不通過
//		public @MyNotEmpty T test(@MyNotEmpty T a){
//			new ArrayList<@MyNotEmpty String>();
//				return a;
//		}

  //使用TYPE_USE型別,編譯通過
  public @MyNotNull T test2(@MyNotNull T a){
    new ArrayList<@MyNotNull String>();
    return a;
  }
}

2.2 註解支援繼承嗎?

註解是不支援繼承的
不能使用關鍵字extends來繼承某個@interface,但註解在編譯後,編譯器會自動繼承java.lang.annotation.Annotation介面。 雖然反編譯後發現註解繼承了Annotation介面,請記住,即使Java的介面可以實現多繼承,但定義註解時依然無法使用extends關鍵字繼承@interface。 區別於註解的繼承,被註解的子類繼承父類別註解可以用@Inherited: 如果某個類使用了被@Inherited修飾的Annotation,則其子類將自動具有該註解。

3 註解的應用場景

自定義註解多喝AOP - 通過切面實現解耦

筆者曾經在 《基於AOP的動態資料來源切換》 這篇文章中有個典型的例子,就是使用AOP切面來對多資料來源進行使用場景的切換,下面展示下如何通過註解實現解耦的。

  • 自定義Annotation,對映的目標範圍為 型別和方法。
/**
 * @author brand
 * @Description: 資料來源切換註解
 * @Copyright: Copyright (c) 2021
 * @Company: Helenlyn, Inc. All Rights Reserved.
 * @date 2021/12/15 7:36 下午
 */
@Target({ ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "";
}
  • 編寫AOP實現,切面程式碼,以實現對註解的PointCut,切點攔截
/**
 * @author brand
 * @Description:
 * @Copyright: Copyright (c) 2021
 * @Company: Helenlyn, Inc. All Rights Reserved.
 * @date 2021/12/15 7:49 下午
 */
@Aspect
@Component
public class DataSourceAspect implements Ordered  {
    /**
     * 定義一個切入點,匹配到上面的註解DataSource
     */
    @Pointcut("@annotation(com.helenlyn.dataassist.annotation.DataSource)")
    public void dataSourcePointCut() {
    }

    /**
     * Around 環繞方式做切面注入
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource ds = method.getAnnotation(DataSource.class);
        String routeKey = ds.name();  // 從頭部中取出註解的name(basic 或 cloudoffice 或 attend),用這個name進行資料來源查詢。
        String dataSourceRouteKey = DynamicDataSourceRouteHolder.getDataSourceRouteKey();
        if (StringUtils.isNotEmpty(dataSourceRouteKey)) {
            // StringBuilder currentRouteKey = new StringBuilder(dataSourceRouteKey);
            routeKey = ds.name();
        }
        DynamicDataSourceRouteHolder.setDataSourceRouteKey(routeKey);
        try {
            return point.proceed();
        } finally { // 最後做清理,這個步驟很重要,因為我們的設定中有一個預設的資料來源,執行完要回到預設的資料來源。
            DynamicDataSource.clearDataSource();
            DynamicDataSourceRouteHolder.clearDataSourceRouteKey();
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

  • 測試,在Control中寫三個測試方法
/**
     * 無註解預設情況:資料來源指向basic
     * @return
     */
    @RequestMapping(value = "/default/{user_code}", method = RequestMethod.GET)
    public UserInfoDto getUserInfo(@PathVariable("user_code") String userCode) {
        return userInfoService.getUserInfo(userCode);
    }

    /**
     * 資料來源指向attend
     * @return
     */
    @DataSource(name= Constant.DATA_SOURCE_ATTEND_NAME)
    @RequestMapping(value = "/attend/{user_code}", method = RequestMethod.GET)
    public UserInfoDto getUserInfoAttend(@PathVariable("user_code") String userCode) {
        return userInfoService.getUserInfo(userCode);
    }

    /**
     * 資料來源指向cloud
     * @return
     */
    @DataSource(name= Constant.DATA_SOURCE_CLOUD_NAME)
    @RequestMapping(value = "/cloud/{user_code}", method = RequestMethod.GET)
    public UserInfoDto getUserInfoCloud(@PathVariable("user_code") String userCode) {
        return userInfoService.getUserInfo(userCode);
    }

  • 執行效果


除此之外,我們可以看到很多紀錄檔管理、許可權管理,也都是也是通過類似的註解機制來實現的,通過註解+AOP來最終實現模組之間的解耦,以及業務與系統層面的解耦 。