有部分小伙伴反饋說前面基于注解的Spring中大量使用注解,由于對Java的注解不熟悉,有點難受,建議總結一篇的Java注解的基礎知識,那么,它來了!
本文內容
- 什么是注解?
- 如何定義注解
- 如何使用注解
- 如何獲取注解資訊
- Spring 中對注解做了什么增強?
什么是注解?
什么是代碼中寫的注釋?那是給開發者看的,但是編譯之后的位元組碼檔案中是沒有注釋資訊的,也就是說注釋對于java編譯器和JVM來說是沒有意義的,看不到!
類比注釋是給人看的,注解則是給java編譯器和JVM看的一些標識,編譯器和虛擬機在運行的程序中可以獲取注解資訊來做一些處理,
如何定義注解
注解定義的語法如下:
public @interface 注解類名{
引數型別 引數名稱1() [default 引數默認值];
引數型別 引數名稱2() [default 引數默認值];
}
引數名稱可以沒有,也可以定義多個,定義細節如下:
- 引數型別只能是基本型別、String、Class、列舉型別、注解型別以及對應的一維陣列
- 如果注解引數只有1個,建議定義名稱為value,方便使用時預設引數名
- default 可以指定默認值,如果沒有默認值使用注解時必須給定引數值
定義注解時候需要考慮2個主要問題:
- 注解可以使用在哪里也就是使用范圍?
- 注解保留到什么階段,原始碼階段,還是運行階段?
java提供了一部分的元注解來解決上面的問題,
@Target指定注解的使用范圍
來看下原始碼,主要是指定了可以應用注釋型別的元素種類的陣列引數,
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
// 回傳可以應用注釋型別的元素種類的陣列
ElementType[] value();
}
/*注解的使用范圍*/
public enum ElementType {
/*類、介面、列舉、注解上面*/
TYPE,
/*欄位上*/
FIELD,
/*方法上*/
METHOD,
/*方法的引數上*/
PARAMETER,
/*建構式上*/
CONSTRUCTOR,
/*本地變數上*/
LOCAL_VARIABLE,
/*注解上*/
ANNOTATION_TYPE,
/*包上*/
PACKAGE,
/*型別引數上 1.8之后*/
TYPE_PARAMETER,
/*型別名稱上 1.8之后*/
TYPE_USE
}
@Retention指定注解的保留策略
指示要保留帶注釋型別的注釋多長時間,如果注釋型別宣告中不存在保留注釋,則保留策略默認為 RetentionPolicy.CLASS
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
public enum RetentionPolicy {
// 原始碼階段,注解被編譯器丟棄,
SOURCE,
// 注釋將由編譯器記錄在類檔案中,但不需要在運行時由 VM 保留,這是默認行為,
CLASS,
// 注釋將由編譯器記錄在類檔案中,并在運行時由 VM 保留,因此可以反射性地讀取它們,
RUNTIME
}
綜合上面2個注解,自定義一個保留到運行期的僅用在方法上的注解如下,
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DemoAnnotation {
String name() default "";
Class targetClazz();
}
如何使用注解
使用語法
@注解類(引數1=值1,引數2=值2,引數3=值3,引數n=值n)
目標物件
使用前一節的注解來個簡單的案例
public class MyBean {
@DemoAnnotation(name = "xxx", targetClazz = MyBean.class)
public void m() {
}
}
來一個綜合案例,注解位置包括類上、方法上、建構式上、方法引數上、欄位上、本地變數上、泛型型別引數和型別名稱上,
/**
* 綜合案例
* @author zfd
* @version v1.0
* @date 2022/1/24 13:31
* @關于我 請關注公眾號 螃蟹的Java筆記 獲取更多技術系列
*/
@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/用在類上", elementType = ElementType.TYPE)
public class UseStrongAnnotation<@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/用在型別引數上T0", elementType = ElementType.TYPE_PARAMETER) T0,
@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/用在型別名稱上T1", elementType = ElementType.TYPE_USE) T1> {
@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/用在欄位上", elementType = ElementType.FIELD)
private String field;
@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/構造方法上", elementType = ElementType.CONSTRUCTOR)
public UseStrongAnnotation(@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/用在方法引數上", elementType = ElementType.PARAMETER) String field) {
this.field = field;
}
@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/用在普通方法上", elementType = ElementType.METHOD)
public void m(@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/方法引數上", elementType = ElementType.PARAMETER) String name) {
@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/用在本地變數上", elementType = ElementType.LOCAL_VARIABLE)
String prefix = "hello ";
System.out.println(prefix + name);
}
public <@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/方法的型別引數T2上", elementType = ElementType.TYPE_PARAMETER) T2> void m1() {
}
public <@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/方法的型別名稱T3上", elementType = ElementType.TYPE_USE) T3> void m2() {
}
private Map<@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/Map后面的尖括號也是型別名稱", elementType = ElementType.TYPE_USE) String ,
@StrongAnnotation(value = "https://www.cnblogs.com/kongbubihai/p/Map后面的尖括號也是型別名稱", elementType = ElementType.TYPE_PARAMETER)Object> map;
}
如何獲取注解資訊
java.lang.reflect.AnnotatedElement介面表示當前在此 VM 中運行的程式的注解元素, 該介面允許以反射方式讀取注解, 此介面中方法回傳的所有注解都是不可變的和可序列化的, 該介面的方法回傳的陣列可以被呼叫者修改,而不影響回傳給其他呼叫者的陣列,其獲取注解的主要方法如下,見名知意,

主要的實作類或介面圖如下

對應的實作的含義也很明確:
- Package:用來表示包的資訊
- Class:用來表示類的資訊
- Constructor:用來表示構造方法資訊
- Field:用來表示類中屬性資訊
- Method:用來表示方法資訊
- Parameter:用來表示方法引數資訊
- TypeVariable:用來表示型別變數資訊,如:類上定義的泛型型別變數,方法上面定義的泛型型別變數
綜合案例
來一個綜合案例來決議上一節的注解使用UseStrongAnnotation,測驗用例和結果如下,建議多實戰敲敲代碼,
package com.crab.spring.ioc.demo17;
import com.sun.xml.internal.bind.v2.model.core.ID;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Arrays;
import static org.junit.Assert.*;
/**
* @author zfd
* @version v1.0
* @date 2022/1/24 13:52
* @關于我 請關注公眾號 螃蟹的Java筆記 獲取更多技術系列
*/
public class UseStrongAnnotationTest {
@Test
public void test_annotated_class() {
System.out.println("決議類上注解:");
Arrays.stream(UseStrongAnnotation.class.getAnnotations())
.forEach(System.out::println);
}
// 決議類上注解:
// @com.crab.spring.ioc.demo17.StrongAnnotation(value=https://www.cnblogs.com/kongbubihai/p/用在類上, elementType=TYPE)
@Test
public void test_annotated_class_type_parameter() {
TypeVariable>[] typeParameters = UseStrongAnnotation.class.getTypeParameters();
for (TypeVariable> typeParameter : typeParameters) {
System.out.println(typeParameter.getName() +" 1.8變數引數或變數名稱注解資訊:");
Annotation[] annotations = typeParameter.getAnnotations();
Arrays.stream(annotations).forEach(System.out::println);
}
}
// T0 1.8變數引數或變數名稱注解資訊:
// @com.crab.spring.ioc.demo17.StrongAnnotation(value=https://www.cnblogs.com/kongbubihai/p/用在型別引數上T0, elementType=TYPE_PARAMETER)
// T1 1.8變數引數或變數名稱注解資訊:
// @com.crab.spring.ioc.demo17.StrongAnnotation(value=用在型別名稱上T1, elementType=TYPE_USE)
@Test
public void test_annotated_field() throws NoSuchFieldException {
Field field = UseStrongAnnotation.class.getDeclaredField("field");
Arrays.stream(field.getAnnotations()).forEach(System.out::println);
}
// @com.crab.spring.ioc.demo17.StrongAnnotation(value=https://www.cnblogs.com/kongbubihai/p/用在欄位上, elementType=FIELD)
@Test
public void test_annotated_constructor() {
Constructor<?> constructor = UseStrongAnnotation.class.getDeclaredConstructors()[0];
for (Annotation annotation : constructor.getAnnotations()) {
System.out.println(annotation);
}
}
// @com.crab.spring.ioc.demo17.StrongAnnotation(value=構造方法上, elementType=CONSTRUCTO
@Test
public void test_annotated_normal_method() throws NoSuchMethodException {
Method method = UseStrongAnnotation.class.getDeclaredMethod("m", String.class);
System.out.println("方法注解:");
for (Annotation annotation : method.getAnnotations()) {
System.out.println(annotation);
}
System.out.println("方法引數注解:");
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter.getName() + " 引數注解:");
for (Annotation annotation : parameter.getAnnotations()) {
System.out.println(annotation);
}
}
}
// 方法注解:
// @com.crab.spring.ioc.demo17.StrongAnnotation(value=https://www.cnblogs.com/kongbubihai/p/用在普通方法上, elementType=METHOD)
// 方法引數注解:
// name 引數注解:
// @com.crab.spring.ioc.demo17.StrongAnnotation(value=方法引數上, elementType=PARAMETER)
@Test
public void test_annotated_map_type() throws NoSuchFieldException {
Field field = UseStrongAnnotation.class.getDeclaredField("map");
// 回傳一個 Type 物件,該物件表示此 Field 物件表示的欄位的宣告型別,
// 如果 Type 是引數化型別,則回傳的 Type 物件必須準確反映源代碼中使用的實際型別引數,
Type genericType = field.getGenericType();
// 獲取回傳表示此型別的實際型別引數的 Type 物件陣列
Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
// 回傳一個 AnnotatedType 物件,該物件表示使用一個型別來指定此 Field 表示的欄位的宣告型別,
AnnotatedType annotatedType = field.getAnnotatedType();
// 獲取此引數化型別的可能帶注釋的實際型別引數陣列
AnnotatedType[] annotatedActualTypeArguments =
((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments();
int index = 0;
for (AnnotatedType annotatedActualTypeArgument : annotatedActualTypeArguments) {
Type actualTypeArgument = actualTypeArguments[index++];
System.out.println(annotatedActualTypeArgument.getType());
System.out.println(actualTypeArgument.getTypeName() + " 型別上的注解:");
for (Annotation annotation : annotatedActualTypeArgument.getAnnotations()) {
System.out.println(annotation);
}
}
}
// T0 1.8變數引數或變數名稱注解資訊:
// @com.crab.spring.ioc.demo17.StrongAnnotation(value=https://www.cnblogs.com/kongbubihai/p/用在型別引數上T0, elementType=TYPE_PARAMETER)
// T1 1.8變數引數或變數名稱注解資訊:
// @com.crab.spring.ioc.demo17.StrongAnnotation(value=用在型別名稱上T1, elementType=TYPE_USE)
}
@Inherited實作子類繼承父類的注解
@Inherited指示注解型別是自動繼承的,注意針對的父類的注解,介面是無效的
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
來看一個案例,父類和介面上都有可繼承的注解,觀察下子類的上的注解情況,
/**
* 測驗父類注解的繼承
* 注意:是類,不是介面,介面無效
* @author zfd
* @version v1.0
* @date 2022/1/24 17:15
* @關于我 請關注公眾號 螃蟹的Java筆記 獲取更多技術系列
*/
public class TestInherited {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Annotation1{}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Annotation2{}
@Annotation1
interface Interface1{}
@Annotation2
static class SupperClass{}
// 繼承 SupperClass 實作 Interface1,觀察其注解繼承情況
static class SubClass extends SupperClass implements Interface1{}
public static void main(String[] args) {
for (Annotation annotation : SubClass.class.getAnnotations()) {
System.out.println(annotation);
}
}
// 輸出
// @com.crab.spring.ioc.demo17.TestInherited$Annotation2()
// 只繼承了父類注解 無法繼承介面上的注解
}
@Repeatable重復注解
常規情況下同一個目標上是無法使用同一個注解多個重復標記的,如果自定義注解需要實作可重復注解,則在定義的時候可以使用 @Repeatable來宣告的注解型別是可重復的,
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
// 指定容器注解型別
Class<? extends Annotation> value();
}
模擬 @ComponentScan @ComponentScans來提供一個案例,
/**
* 測驗 @Repeatable 注解重復使用
* @author zfd
* @version v1.0
* @date 2022/1/24 17:30
* @關于我 請關注公眾號 螃蟹的Java筆記 獲取更多技術系列
*/
public class TestRepeatable {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ComponentScans.class)
@interface ComponentScan{}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ComponentScans{
// 注意: 必須定義value引數,其型別是子重復注解的陣列型別
ComponentScan[] value();
}
// 重復注解方式1
@ComponentScan
@ComponentScan
static class MyComponent{}
// 重復注解方式2
@ComponentScans({@ComponentScan, @ComponentScan})
static class MyComponentB{}
public static void main(String[] args) {
for (Annotation annotation : MyComponent.class.getAnnotations()) {
System.out.println(annotation);
}
for (Annotation annotation : MyComponentB.class.getAnnotations()) {
System.out.println(annotation);
}
}
// 輸出
// @com.crab.spring.ioc.demo17.TestRepeatable$ComponentScans(value=https://www.cnblogs.com/kongbubihai/p/[@com.crab.spring.ioc.demo17
// .TestRepeatable$ComponentScan(), @com.crab.spring.ioc.demo17.TestRepeatable$ComponentScan()])
// @com.crab.spring.ioc.demo17.TestRepeatable$ComponentScans(value=[@com.crab.spring.ioc.demo17
// .TestRepeatable$ComponentScan(), @com.crab.spring.ioc.demo17.TestRepeatable$ComponentScan()])
}
Spring 中@AliasFor對注解的增強
注解的定義引數是不能繼承,如注解A上面有注解B,但是實際在使用B注解在目標類C的程序中想要設定A的引數是做不到的,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationA {
String name() default "";
int value() default -1;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@AnnotationA
public @interface AnnotationB {
String name() default "";
int value() default 1;
String aliasForName() default "";
}
@AnnotationB(name = "xxx", value = https://www.cnblogs.com/kongbubihai/p/1) // 無法設定AnnotiaonA的引數值
public class ClassC {
}
Spring 中 提供了@AliasFor 元注解,用于宣告注解屬性的別名,主要的使用場景:
- 注解中的顯式別名:在單個注解中, @AliasFor可以在一對屬性上宣告,以表明它們是彼此可互換的別名
- 元注解中屬性的顯式別名:如果@AliasFor的annotation屬性設定為與宣告它的注解不同的注解,則該attribute被解釋為元注解中屬性的別名(即顯式元注解屬性覆寫), 這可以精確控制注解層次結構中覆寫的屬性,
- 注解中的隱式別名:如果注解中的一個或多個屬性被宣告為相同元注解屬性的屬性覆寫(直接或傳遞),則這些屬性將被視為彼此的一組隱式別名,從而導致類似于注解中顯式別名的行為,
原始碼簡單過一下
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AliasFor {
@AliasFor("attribute")
String value() default "";
@AliasFor("value")
String attribute() default "";
// 宣告別名屬性的注解型別,默認為 Annotation,這意味著別名屬性在與此屬性相同的注解中宣告,
Class<? extends Annotation> annotation() default Annotation.class;
}
綜合案例
來使用@AliasFor 改造下 AnnotationB,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@AnnotationA
public @interface AnnotationB {
// 注解AnnotationB內部顯式別名
@AliasFor(value = "https://www.cnblogs.com/kongbubihai/p/aliasForName")
String name() default "";
int value() default 1;
// 注解AnnotationB內部顯式別名
@AliasFor(annotation = AnnotationB.class, attribute = "name")
String aliasForName() default "";
// 元注解AnnotationA屬性name顯式別名
@AliasFor(annotation = AnnotationA.class, value = "https://www.cnblogs.com/kongbubihai/p/name")
String aliasForAnnotationAName() default "";
// 元注解AnnotationA屬性name顯式別名2
@AliasFor(annotation = AnnotationA.class, value = "https://www.cnblogs.com/kongbubihai/p/name")
String aliasForAnnotationAName2() default "";
// 元注解AnnotationA屬性value顯式別名
@AliasFor(annotation = AnnotationA.class, https://www.cnblogs.com/kongbubihai/p/value = "value")
int aliasForAnnotationAValue() default -1;
}
使用AnnotationB 注解,注意:互為別名的屬性設定時只能設定其中一個,否則設定多個會報錯,
@AnnotationB(value = https://www.cnblogs.com/kongbubihai/p/100,
name ="xx",
aliasForAnnotationAName = "a1",
aliasForAnnotationAValue = https://www.cnblogs.com/kongbubihai/p/-100
)
public class ClassC2 {
public static void main(String[] args) {
//spring提供一個查找注解的工具類AnnotatedElementUtils
System.out.println(AnnotatedElementUtils.getMergedAnnotation(ClassC2.class, AnnotationB.class));
System.out.println(AnnotatedElementUtils.getMergedAnnotation(ClassC2.class, AnnotationA.class));
}
}
輸出結果顯示AnnotationB 通過別名設定AnnotationA中屬性成功,
@com.crab.spring.ioc.demo17.AnnotationB(aliasForAnnotationAName=a1, aliasForAnnotationAName2=a1, aliasForAnnotationAValue=https://www.cnblogs.com/kongbubihai/p/-100, aliasForName=xx, name=xx, value=100)
@com.crab.spring.ioc.demo17.AnnotationA(name=a1, value=-100)
總結
本文詳解了注解的概念,如何定義注解、使用注解、獲取注解;并介紹了元注解@Target、@Retention、@Inherited、@Repeatable 的使用;重點講解了Spring 中 @AliasFor 注解來為元注解屬性設定別名的增強處理,
本篇原始碼地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo17
知識分享,轉載請注明出處,學無先后,達者為先!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/440484.html
標籤:Java
