相信大部分的開發者都用過註解,尤爲是對使用過Spring的開發者來講,註解是現代Spring中不可獲取的一部分。Spring從最開始的xml配置到後面的註解配置,不管是從編程習慣仍是項目的構建,都對咱們程序員產生了很是重要的影響。 java
除了使用Spring自帶的註解以外,咱們還能夠自定義註解。而後經過AOP來對註解進行攔截從而處理相應的業務邏輯。git
除了Spring以外,其實JDK自己自帶註解,本文將會深刻探討註解的起源和兩種不一樣的使用方式。程序員
更多精彩內容且看:github
更多內容請訪問 www.flydean.com
先看一個最簡單的註解:apache
@CustUserAnnotation public class CustUser { }
上面咱們將CustUser標記爲一個自定義的註解@CustUserAnnotation。編程
註解實際上是在JDK 5中引入的。那麼在JDK 5以前,註解是用什麼方式來表示的呢?答案就是marker interfaces。數組
marker interfaces中文翻譯叫作標記接口,標記接口就是說這個接口使用來作標記用的,內部並無提供任何方法或者字段。app
在java中有不少標記接口,最多見的就是Cloneable,Serializable,還有java.util包中的EventListener和RandomAccess。dom
以Cloneable爲例:maven
/* * @since 1.0 */ public interface Cloneable { }
該接口從java1.0就開始有了。實現該接口的類纔可以調用Object中的clone方法。
咱們在代碼中如何判斷類是否實現了Cloneable接口呢?
public Object clone() throws CloneNotSupportedException { if (this instanceof Cloneable) { return super.clone(); } else { throw new CloneNotSupportedException(); } }
很簡單,經過instanceof來判斷是不是Cloneable便可。
marker interfaces好用是好用,可是有一些缺點,好比沒有額外的元數據信息,功能太過單一,而且會和正常的interface混淆。實現起來也比通常的interface複雜。
正式因爲這些緣由,在JDK5中,引入了註解Annotation。
註解是由@interface來定義的。建立一個annotation須要指定其target和retention,並能夠自定義參數。
咱們舉個例子:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface CustUserAnnotation { int value(); String name(); String[] addresses(); }
上面是我自定義的一個註解。
Retention表示註解將會在什麼階段可見。它有三個可選值:
SOURCE 表示只在源代碼可見,編譯的時候就會被丟棄。
CLASS 表示在class可見,也就是說編譯的時候可見,可是運行時候不可見。
RUNTIME 表示運行時候可見。何時才須要運行時可見呢?那就是使用到反射的時候。咱們會在後面的例子中具體的描述這種狀況。
Retention自己也是一個註解:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
Target表示這個註解將會用到什麼地方。它有12個值。
TYPE 表示用在Class,interface,enum或者record上。
FIELD 表示用在class的字段上。
METHOD 表示用在方法上。
PARAMETER 表示用在方法上面。
CONSTRUCTOR 用在構造函數上。
LOCAL_VARIABLE 用在本地變量上。
ANNOTATION_TYPE 用在註解上。
PACKAGE 用在package上。
TYPE_PARAMETER 用在類型參數上。
TYPE_USE 用在任何TYPE使用上。
TYPE_PARAMETER和TYPE_USE有什麼區別呢?
TYPE_USE用在任何類型的使用上面,好比申明,泛型,轉換:
@Encrypted String data List<@NonNull String> strings MyGraph = (@Immutable Graph) tmpGraph;
而TYPE_PARAMETER用在類型參數上:
class MyClass<T> {...}
MODULE 用在module上。
RECORD_COMPONENT 預覽功能,和records相關。
Target和Retention同樣也是一個註解。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
註解也能夠自定參數,參數能夠是下的類型:
上面咱們的自定義類型定義了三個參數:
int value(); String name(); String[] addresses();
咱們看下怎麼使用:
@CustUserAnnotation(value = 100, name="jack ma",addresses = {"人民路","江西路"}) public class CustUser { }
在使用中,咱們須要傳入自定義的參數,固然你也可使用default在註解中提供默認值,這樣就不須要從外部傳入。
在運行時,咱們可使用反射的API來得到註解,並獲取註解中的自定義變量,從而進行相應的業務邏輯處理。
CustUser custUser= new CustUser(); Annotation[] annotations= custUser.getClass().getAnnotations(); Stream.of(annotations).filter(annotation -> annotation instanceof CustUserAnnotation) .forEach(annotation -> log.info(((CustUserAnnotation) annotation).name()));
仍是剛纔的例子,咱們經過getAnnotations方法獲取到註解的值。
在運行時是用註解固然是個不錯的主意,可是反射用的太多的話其實會影響程序的性能。
那麼咱們能夠不能夠將運行時的註解提早到編譯時呢?答案是確定的。
要想在編譯時使用註解,就要介紹今天咱們的最後一部份內容annotation processors。
自定義processors須要實現javax.annotation.processing.Processor接口。
接下來咱們自定義一個Processor:
@SupportedAnnotationTypes("com.flydean.*") @SupportedSourceVersion(SourceVersion.RELEASE_14) public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { System.out.println("process annotation!"); annotations.forEach(annotation -> { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation); elements.stream() .filter(TypeElement.class::isInstance) .map(TypeElement.class::cast) .map(TypeElement::getQualifiedName) .map(name -> "Class " + name + " is annotated with " + annotation.getQualifiedName()) .forEach(System.out::println); }); return true; } }
SupportedAnnotationTypes表示支持的註解類型。
SupportedSourceVersion表示支持的源代碼版本。
最後咱們在process方法中,獲取了註解類的一些信息。
有了processor咱們怎麼在maven環境中使用呢?
最簡單的辦法就是在maven的maven-compiler-plugin插件中添加annotationProcessors,以下所示:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>14</source> <target>14</target> <annotationProcessors> <annotationProcessor>com.flydean.MyProcessor</annotationProcessor> </annotationProcessors> </configuration> </plugin> </plugins> </build>
若是不添加,默認狀況下編譯器會從classpath中去尋找META-INF/services/javax.annotation.processing.Processor文件,這個文件裏面列出了對外提供的註解處理器。編譯器會加載這些註解處理器去處理當前項目的註解。
lombok應該你們都用過吧,它實際上爲咱們提供了兩個註解處理器:
很不幸的是,由於我在CustUser中使用了lombok中的log,若是像上面同樣顯示指定annotationProcessor則會將覆蓋默認的查找路徑,最後會致使lombok失效。
那應該怎麼處理才能兼容lombok和自定義的processor呢?
咱們能夠把自定義processor單獨成一個模塊,也作成lombok這樣的形式:
這個processor的模塊編譯參數須要加上一個proc none的參數:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>14</source> <target>14</target> <proc>none</proc> </configuration> </plugin> </plugins> </build>
proc是設置是否須要在本項目中啓用processor。對於processor項目來講,它自己尚未編譯,若是啓用就會出現找不到類的錯誤。因此這裏咱們須要將proc設置爲none。
最後咱們的annotation-usage項目能夠不須要annotationProcessors的配置就能夠自動從classpath中讀取到自定義的processor了。
本文介紹了marker interface,annotation和annotation processor,並詳細講解了如何在maven程序中使用他們。
本文的例子[https://github.com/ddean2009/
learn-java-base-9-to-20](https://github.com/ddean2009/...
本文做者:flydean程序那些事本文連接:http://www.flydean.com/marker-interface-annotation-processor/
本文來源:flydean的博客
歡迎關注個人公衆號:程序那些事,更多精彩等着您!