柚子快報邀請碼778899分享:開發(fā)語言 Java-注解詳解
柚子快報邀請碼778899分享:開發(fā)語言 Java-注解詳解
1?? 注解的含義
注解(Annotation)是Java語言中一種特殊的修飾符,它可以用于類、方法、參數(shù)、變量、構(gòu)造器以及包聲明中,用于為Java代碼提供元數(shù)據(jù)。相對于其他修飾符如public、final等,注解并不直接影響代碼的語義,但卻能被某些工具軟件(如編譯器、框架)所讀取和利用。
? 注解的主要作用
編譯檢查 - 如@Override放在方法前,如果該方法不是重寫父類的方法,則編譯器會發(fā)出警告。代碼分析 - 通過代碼里標(biāo)識的注解,程序可以在編譯時進(jìn)行一些基于注解的處理。注解信息和JAVA的反射功能在一起時會使得程序的功能更加強大。編譯時動態(tài)處理 - 如常見的Java框架Spring、Hibernate、JUnit等,會在編譯時讀取注解的信息,然后根據(jù)注解的信息進(jìn)行一些其他處理。生成額外的文件 - 如Javadoc工具會根據(jù)源碼中的注解來生成API文檔。
? 注解的基本語法
注解的聲明類似于接口的聲明,但是前面多了一個@符號:
public @interface MyAnnotation {
String value() default ""; //定義value屬性
}
使用注解:
@MyAnnotation(value="This is my custom annotation")
public class MyClass {
// class body
}
? 內(nèi)置注解
Java提供了一些預(yù)定義的注解,如:
@Override: 限定重寫父類方法。@Deprecated: 表示某個程序元素(如方法)已經(jīng)過時。@SuppressWarnings: 告訴編譯器忽略指定的警告。
? 元注解
用于注解其他注解的注解稱為元注解。Java提供了以下幾種元注解:
@Target: 表明該注解可以被應(yīng)用于什么地方(如方法、類、字段等)。@Retention: 表明該注解的生命周期(僅源代碼、編譯期、運行期)。@Documented: 表示使用該注解的元素應(yīng)被Javadoc或其他工具文檔化。@Inherited: 表示該注解可以被子類繼承。
注解為Java提供了一種元級編程的方式,它允許開發(fā)者在不修改代碼邏輯的前提下,向源碼中添加一些額外的信息,這些信息可以被編譯器或其他工具所讀取并使用。
2?? 注解分類
由編譯器使用的注解
含義:這類注解不會被編譯進(jìn)入 .class 文件,在編譯后它們就被編譯器丟棄了。示例:
@Override:此注解讓編譯器檢查該方法是否正確地實現(xiàn)了覆寫。@SuppressWarnings:此注解告訴編譯器忽略此處代碼產(chǎn)生的警告。 由工具處理 .class 文件使用的注解
含義:有些工具在加載 class 的時候,會對 class 文件做動態(tài)修改,以實現(xiàn)一些特殊的功能。這類注解會被編譯進(jìn)入 .class 文件,但在加載完成后并不會存在于內(nèi)存中。這類注解主要被一些底層庫使用,一般我們不需要自己處理。示例:可以參考 lombok。 在程序運行期能夠讀取的注解
含義:這些注解在加載后會一直存在于 JVM 中,是最常用的注解。示例:配置了 @PostConstruct 的方法會在調(diào)用構(gòu)造方法后自動被調(diào)用。這是 Java 代碼通過讀取該注解實現(xiàn)的功能,JVM 本身并不會識別該注解。
3?? 元注解(java.lang.annotation下的注解:用來定義注解的)
Java 的注解為代碼添加了額外的元數(shù)據(jù),這些元數(shù)據(jù)在編譯或運行時都可以被訪問。為了定義注解的屬性和行為,Java 提供了以下一系列的元注解:
? @Retention
描述:確定注解的生命周期,即何時可用。用途:通知開發(fā)者注解在何時應(yīng)被使用或訪問。示例:@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation { }
? @Target
描述:指定注解可以應(yīng)用的源碼元素。用途:避免注解被誤用在不適當(dāng)?shù)牡胤?,限制注解的適用范圍。示例:@Target(ElementType.METHOD)
public @interface MyMethodAnnotation { }
? @Inherited
描述:允許子類繼承父類上的注解。用途:當(dāng)需要子類自動繼承父類的注解時使用。示例:@Inherited
public @interface Inheritable { }
? @Documented
描述:注解信息會被包含在 Javadoc 中。用途:確保開發(fā)者在查看 API 文檔時,可以看到注解的信息。示例:@Documented
public @interface DocumentedAnnotation { }
? @Repeatable (Java 8 新增)
描述:允許同一個注解在同一聲明上使用多次。用途:在 Java 8 之前,一個注解在同一位置只能使用一次,此元注解允許多次使用同一個注解。示例:public @interface RepeatedValues {
Value[] value();
}
@Repeatable(RepeatedValues.class)
public @interface Value {
String info() default "";
}
4?? JDK 內(nèi)置注解(都屬于由編譯器使用的注解)
在 java.lang 包中,JDK 提供了五個基本注解:
? @Override
描述:用于指定子類中的方法重寫了父類的方法。用法:
告知編譯器我們意圖重寫父類方法。如果子類的方法與父類方法簽名不匹配,編譯器會報錯。 適用范圍:只能用于方法。
? @Deprecated
描述:用于標(biāo)記已過時的程序元素,如類、方法等。用法:
提醒開發(fā)者不建議使用該程序元素。使用被此注解標(biāo)記的元素時,編譯器會給出警告。
? @SuppressWarnings
描述:用于抑制編譯器產(chǎn)生警告。用法:告知編譯器忽略特定的警告。常見參數(shù):
deprecation:使用已過時的類或方法。unchecked:未經(jīng)檢查的類型轉(zhuǎn)換。fallthrough:switch 語句缺少 break。path:類或源文件路徑不存在。serial:可序列化類缺少 serialVersionUID。finally:finally 語句未正常完成。all:上述所有情況。
? @SafeVarargs
描述:抑制堆污染警告。特點:JDK 7 新增,用于標(biāo)記可能產(chǎn)生堆污染的方法或構(gòu)造函數(shù)。
? @FunctionalInterface (Java 8 新增)
描述:標(biāo)記一個接口為函數(shù)式接口。定義:接口中只有一個抽象方法(但可以有多個 default 或 static 方法)。
?注意事項
value 特權(quán):如果注解的 value 成員變量是唯一需要賦值的,可以省略 name=value 的格式,直接在注解的括號中指定值。 推薦使用 @Override:在意圖重寫父類方法的每個方法上使用 @Override,幫助編譯器及時捕獲可能的錯誤。
5?? 自定義注解
在 Java 中,注解是一種為代碼添加元數(shù)據(jù)的方式。當(dāng)我們使用 @interface 關(guān)鍵字定義新的注解時,背后的一些細(xì)節(jié)會由編譯器自動完成,例如自動繼承 java.lang.annotation.Annotation 接口。
? 注解的定義規(guī)則:
在定義新注解時,不能繼承其他注解或接口。@interface 關(guān)鍵字用于聲明一個新的注解。在注解內(nèi),每個方法都代表一個配置參數(shù)。
方法名為參數(shù)名。返回值類型定義了參數(shù)的類型。方法可以沒有參數(shù)??梢允褂?default 關(guān)鍵字為參數(shù)設(shè)定默認(rèn)值。
? 注解參數(shù)的支持類型:
基本數(shù)據(jù)類型:如 int, float, boolean, byte, double, char, long, shortStringClass枚舉類型 (Enum)其他注解 (Annotation)以上所有類型的數(shù)組形式
package annotation.custom;
// 導(dǎo)入必要的注解處理類
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 表明這個注解是可以被子類繼承的
@Inherited
// 指明該注解的生命周期,此處指定為RUNTIME,表示注解在運行時仍然存在,并可以通過反射機(jī)制讀取
@Retention(RetentionPolicy.RUNTIME)
// 指明該注解可以被應(yīng)用于方法和類型(類、接口、枚舉、注解)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Tag {
// 定義一個名為name的元素,它的默認(rèn)值為"undefined"
String name() default "undefined";
// 定義一個名為description的元素,使用該注解時,這個元素是必須要設(shè)置的,因為它沒有默認(rèn)值
String description();
}
// 代表所有的注解類型
public interface Annotation {
// 用于比較兩個注解是否相等。注意:自定義的注解會有編譯器生成的實現(xiàn),所以使用者不需要手動實現(xiàn)此方法。
boolean equals(Object obj);
// 返回該注解的哈希碼。同樣,這個方法會由編譯器自動生成其實現(xiàn)。
int hashCode();
// 返回該注解的字符串表示形式。這也是由編譯器自動生成的方法。
String toString();
// 返回表示此注解的注解類型的Class對象。它可以用于在運行時獲取注解的信息。
Class extends Annotation> annotationType();
}
注意: Java 使用 Annotation 接口來代表程序元素前面的注解,該接口是所有 Annotation 類型的父接口。 自定義的注解繼承了 Annotation 這個接口,因此自定義注解中包含了 Annotation 接口中所有的方法
6?? 注解的提取
Java在java.lang.reflect包內(nèi)提供了強大的反射機(jī)制,其中AnnotatedElement接口扮演了關(guān)鍵角色,代表那些可以攜帶注解的程序元素。
? AnnotatedElement接口
定義:此接口表示可以接受注解的程序元素。 實現(xiàn)類:
AccessibleObject:是Field、Method和Constructor對象的基類。Executable:是Method和Constructor對象的共有基類。Method:表示類或接口中的某個方法。Constructor:提供某個類的構(gòu)造方法的信息。Field:表示類或接口中的某個字段。Class:代表應(yīng)用程序中的類和接口。Package:提供有關(guān)Java包的版本信息。Parameter:提供關(guān)于方法參數(shù)的信息,Java 8 新增。 功能:通過這些類,Java提供了一套豐富的API用于獲取運行時的注解信息。
? 如何使用
只有當(dāng)一個注解被定義為運行時注解(即使用@Retention(RetentionPolicy.RUNTIME)標(biāo)注)時,它才會在運行時被JVM讀取。這意味著,通過反射,我們可以讀取類、方法或字段上的這些注解,并據(jù)此執(zhí)行某些操作。例如,通過反射獲得某個類的Method對象后,可以調(diào)用該對象的getAnnotations()方法來獲取所有的注解。
? 方法描述
isAnnotationPresent(Class extends Annotation> annotationType):
描述:此方法用于判斷指定的對象是否應(yīng)用了某個注解。應(yīng)用:主要用于方便地訪問標(biāo)記注解。 getAnnotations():
描述:返回作用于指定對象的所有注解。注意:如果沒有,則返回長度為0的數(shù)組。 getDeclaredAnnotations():
描述:返回直接作用于指定對象的所有注解。注意:如果不存在,則返回長度為0的數(shù)組。此方法忽略繼承的注解。 getAnnotation(Class annotationClass):
描述:返回指定對象上的指定類型的注解。注意:如果注解不存在,則返回null。 getDeclaredAnnotation(Class annotationClass):
描述:返回直接作用于指定對象的指定類型的注解。注意:如果注解不存在,則返回null。此方法忽略繼承的注解。 getAnnotationsByType(Class annotationClass):
描述:返回指定對象上的指定類型的所有注解。注意:如果不存在,則返回長度為0的數(shù)組。此方法檢測其參數(shù)是否為可重復(fù)的注解類型。 getDeclaredAnnotationsByType(Class annotationClass):
描述:返回直接作用于指定對象的指定類型的所有注解。注意:如果不存在,則返回長度為0的數(shù)組。此方法檢測其參數(shù)是否為可重復(fù)的注解類型,并忽略繼承的注解。
注意: 只有當(dāng)定義 Annotation 時使用了 @Retention(RetentionPolicy.RUNTIME) 修飾, JVM 才會在裝載 class 文件時提取保存在 class 文件中的 Annotation, 該 Annotation 才會在運行時可見。否則class 文件的注解信息在執(zhí)行過程中將不可用, 從而也就不能從中得到任何和注解有關(guān)的數(shù)據(jù)。
7?? 注解處理器
? 注解處理器簡介
定義:注解處理器是用于在編譯時掃描、處理源代碼中的注解的工具。它可以分析注解并生成額外的源代碼或其他文件。歷史:
Java 5:首次引入注解,但處理注解的API不夠成熟。需要使用一個獨立的工具apt(Annotation Processor Tool)和com.sun.mirror包中的Mirror API來處理注解。Java 6:通過JSR 269,注解處理器被標(biāo)準(zhǔn)化并納入到Java標(biāo)準(zhǔn)庫中。apt工具被集成到j(luò)avac編譯工具中。Java 6提供了一個通用功能的抽象類javax.annotation.processing.AbstractProcessor和javax.lang.model包來支持注解處理。
? 如何工作
在編譯階段,javac會掃描每個源文件中的注解。對于每個找到的注解,javac會查詢是否有已注冊的注解處理器可以處理它。如果有,javac將調(diào)用該處理器來處理注解。處理器可以分析注解、讀取注解的值,并根據(jù)這些值生成額外的源代碼或其他文件。
? 注解處理器的用途
自動化代碼生成:允許開發(fā)者定義規(guī)則和模式,然后讓機(jī)器自動為我們生成代碼,避免了人為錯誤和重復(fù)勞動。編譯時檢查:通過注解處理器,我們可以在編譯時進(jìn)行更多的檢查,確保代碼的質(zhì)量和準(zhǔn)確性。性能:由于大部分工作在編譯時完成,運行時的性能開銷被最小化。代碼整潔:自動生成的代碼可以分離到另外的文件中,保持主要業(yè)務(wù)邏輯的代碼清晰和整潔。
? 應(yīng)用場景
類屬性自動賦值:
自動注入對象或值,例如Spring框架中的@Autowired和@Value。 驗證對象屬性完整性:
例如,使用Java Bean Validation API(如@NotNull, @Size, @Min, @Max等)進(jìn)行對象屬性驗證。 代替配置文件功能:
通過注解配置,如Spring的@Component, @Service, @Repository, @Controller等。 生成文檔:
Javadoc工具使用注解來生成API文檔。 編譯時檢查:
例如,@Override用于告訴編譯器子類中的某個方法是否正確地重寫了父類中的方法。 代碼生成:
編譯時注解處理器可以用于生成額外的源代碼。 運行時處理:
通過反射API在運行時讀取注解信息,如Spring AOP、JUnit等。 APT(Annotation Processing Tool):
處理和提取注解的工具或代碼。例如,Lombok庫使用注解處理器在編譯時生成getter、setter和其他常見方法。
? AbstractProcessor
AbstractProcessor 是一個提供便捷實現(xiàn)注解處理器功能的抽象類,實現(xiàn)了 Processor 接口,都位于 javax.annotation.processing 包中。
自定義注解處理器的主要步驟:
實現(xiàn) Processor 接口: 通過繼承 AbstractProcessor 類來實現(xiàn)自定義的注解處理器。主要的工作在于實現(xiàn) process 方法,進(jìn)行你想要完成的注解處理功能。 在 init() 方法中,你可以獲得:
Elements: 一個處理 Element 的工具類。Types: 用來處理 TypeMirror 的工具類。Filer: 用于創(chuàng)建文件。 在注解處理過程中,會掃描所有的 Java 源文件。每個部分都是一個特定類型的 Element,代表程序的元素,例如包、類或者方法。主要的子類有:
PackageElement: 代表一個包。TypeElement: 代表一個類或接口。VariableElement: 表示變量,如成員變量、枚舉常量、方法或構(gòu)造方法參數(shù)等。ExecutableElement: 表示類或接口的方法、構(gòu)造方法。TypeParameterElement: 表示類型參數(shù)。 注冊注解處理器: 有兩種方法:
手動注冊: 在項目的 resources/META-INF/services 目錄下新建文件 javax.annotation.processing.Processor,內(nèi)容是處理器的全稱。自動注冊: 使用 Google 提供的庫,添加 com.google.auto.service:auto-service 依賴,并在處理器上添加 @AutoService(Processor.class) 注解。這會自動在 META-INF 目錄下生成配置信息文件。
注意:在 Java 6 及以上版本中,可以使用 @SupportedAnnotationTypes 注解和 @SupportedSourceVersion 注解來分別替代 getSupportedAnnotationTypes() 方法和 getSupportedSourceVersion() 方法。
? 自定義注解處理器都需要繼承于 AbstractProcessor,如下所示:
/**
* 自定義注解處理器
*/
public class CustomProcessor extends AbstractProcessor {
private Filer mFiler;
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set
Set
annotataions.add(Tag.class.getCanonicalName());
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
Set extends Element> tagElements = roundEnvironment.getElementsAnnotatedWith(Tag.class);
for (Element element : tagElements) {
// 1.獲取包名
PackageElement packageElement = mElementUtils.getPackageOf(element);
String pkName = packageElement.getQualifiedName().toString();
printMessage(String.format("package = %s", pkName));
// 2.獲取包裝類類型
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String enclosingName = enclosingElement.getQualifiedName().toString();
printMessage(String.format("enclosindClass = %s", enclosingName));
// 3.獲取注解的成員變量名
String tagFiledName = element.getSimpleName().toString();
// 4.獲取注解的成員變量類型
String tagFiledClassType = element.asType().toString();
// 5.獲取注解元數(shù)據(jù)
Tag tag = element.getAnnotation(Tag.class);
String name = tag.name();
printMessage(String.format("%s %s = %s", tagFiledClassType, tagFiledName, name));
// 6.生成文件
createFile(enclosingElement, tagFiledClassType, tagFiledName, name);
return true;
}
return false;
}
private void createFile(TypeElement enclosingElement, String tagFiledClassType, String tagFiledName, String name) {
String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
try {
JavaFileObject javaFileObject = mFiler.createSourceFile(pkName + ".Tag");
Writer writer = javaFileObject.openWriter();
writer.write(generateCode(pkName, tagFiledClassType, tagFiledName, name));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void printMessage(String msg) {
mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
}
private String generateCode(String pkName, String tagFiledClassType, String tagFiledName, String name) {
StringBuilder builder = new StringBuilder();
builder.append("package " + pkName + ";\n\n");
builder.append("http://Auto generated by apt,do not modify!!\n\n");
builder.append("public class Tag { \n\n");
builder.append("public static void main(String[] args){ \n");
String info = String.format("%s %s = %s", tagFiledClassType, tagFiledName, name);
builder.append("System.out.println(\"" + info + "\");\n");
builder.append("}\n");
builder.append("}");
return builder.toString();
}
}
柚子快報邀請碼778899分享:開發(fā)語言 Java-注解詳解
推薦文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。