上一篇文章簡單講了下Java注解的學習之元注解說明,學習了Java注解是如何定義的,怎么使用的,但是并沒有介紹Java的注解是怎么起作用的,像Spring Boot里面的那些注解,到底是怎么讓程序這樣子運行起來的?特別是講到RetentionPolicy這一塊,到底在SOURCE階段,在CLASS階段,在RUNTIME階段有什么差別,注解是如何在這三個階段起作用的呢?而且,在SOURCE階段,在CLASS階段,程序又不運行,那注解到底會用來做些什么呢?
帶著這些疑問,我就去了解下了如何讓注解起作用,發現RUNTIME階段的介紹到處都是,但是SOURCE和CLASS階段就很少文章講解到了,真的得搜刮好幾十篇文章才能成功的把程序跑起來,幾乎每一篇文章都少講了些東西。
本文優先講的是SOURCE階段注解如何發揮作用,發現這一塊涉及的知識非常多且難,資料還少,另外還發現,Java服務器端編程的人用這個反而不如Android開發的人用得多。對我學習SOURCE階段的注解幫助最大的是Pluggable Annotation Processing API,JSR269插件化注解API以及JVM進階 -- 淺談注解處理器。搜索這方面的資料用“插件化注解處理API”這個關鍵詞能搜得更全。
這幾篇文章基本上把SOURCE階段的注解實現和使用講了,但是具體細節,比如用javac直接編譯運行代碼,javac使用jar包,使用maven等三個方式如何運行注解處理器,倒是基本上蜻蜓點水,摸索了我好久才搞定了。關于注解處理器的知識,可以從如上三篇文章了解,本文主要講注解的定義和運行。
我用的代碼是摘抄至 JVM進階 -- 淺談注解處理器 ,它定義了一個CheckGetter的注解,用來檢查一個類里面的字段,哪個沒有Getter方法,沒有的話編譯就報錯。不過我的和他的稍稍不同,他的代碼定義沒有放到package里面,我的放到package里面了,這樣子的使用和執行又有了點不同。
首先,定義CheckGetter注解:
package com.shahuwang.processor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface CheckGetter {
}
注意上面的代碼,是放到 package com.shahuwang.processor 里面的,因此,先創建如下結構的文件夾
Processor
—— com
—— shuhuwang
—— processor
—— CheckGetter.java
注解已經定義好了,現在先來用一下這個注解:
package com.shahuwang.processor;
@CheckGetter
public class TestGetter {
String name;
String first;
public String getName(){
return this.name;
}
}
這個類有兩個字段,但是有一個字段沒有設置getter。
下面才是真正重要的代碼,就是讓CheckGetter這個注解真正能運行起來,發揮作用:
package com.shahuwang.processor;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import javax.lang.model.element.Element;
import java.util.Set;
// 這個地方換了包名就需要改過來,否則processor就不會執行了, 這里是最需要注意的地方,千萬注意!!!!!
@SupportedAnnotationTypes("com.shahuwang.processor.CheckGetter")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class CheckGetterProcessor extends AbstractProcessor {
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotatedClass : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(CheckGetter.class))) {
for (VariableElement field : ElementFilter.fieldsIn(annotatedClass.getEnclosedElements())) {
if(!containsGetter(annotatedClass, field.getSimpleName().toString())){
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
String.format("getter not found for '%s.%s'.", annotatedClass.getSimpleName(), field.getSimpleName()));
}
}
}
return false;
}
private static boolean containsGetter(TypeElement typeElement, String name){
String getter = "get" + name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
for(ExecutableElement executableElement: ElementFilter.methodsIn(typeElement.getEnclosedElements())){
if(!executableElement.getModifiers().contains(Modifier.STATIC) &&
executableElement.getSimpleName().toString().equals(getter) &&
executableElement.getParameters().isEmpty()){
return true;
}
}
return false;
}
}
上面這段代碼要理解的概念有點兒多,實際上我現在也不是很懂,但是本文的目標是先讓注解處理器跑起來,所以先不管。這里最重要也是折磨我最慘的地方,就是這一句@SupportedAnnotationTypes("com.shahuwang.processor.CheckGetter"),你定義的注解在哪個package下,這里就要寫完整了,如果寫錯了,注解處理器就不起作用了。
現在在Processor目錄下,打開命令行,先編譯CheckGetterProcessor.java: javac com/shahuwang/processor/CheckGetterProcessor.java,編譯好之后就可以使用了,再執行命令:javac -processor com.shahuwang.processor.CheckGetterProcessor com/shahuwang/processor/TestGetter.java,便可以看到這樣的提示:
錯誤: getter not found for 'TestGetter.first'.
上面這種方式,需要每次執行編譯的時候,指定一下processor才能做到檢查作用,那如果在團隊開發中,希望自動執行一些檢查,那可以用SPI 服務提供者發現機制或者maven來實現。
SPI 服務提供者發現機制就是配置META-INF文件夾和里面的文件,告訴Java你要執行某個東西,這個東西的路徑在哪里。再把編譯好的processor和META-INF打包成jar包,就可以很方便使用了。文件夾和文件結構如下:
processor
-com
-shahuwang
- processor
- CheckGetterProcessor.class
-META-INF
-services
- javax.annotation.processing.Processor
編譯方式和上述一樣。
javax.annotation.processing.Processor是一個普通的文本,就是告知java一些關于注解處理器的配置,里面的內容如下:
com.shahuwang.processor.CheckGetterProcessor
就是告知這個注解處理器要用哪些個,如果有多個的話,可以一行一個。
然后在processor目錄下,執行指令:jar -cvf processor.jar com META-INF, 形成了一個 processor.jar 的包,此時可以執行指令:
javac -cp processor.jar com/shahuwang/processor/TestGetter.java
就會自動執行注解處理器檢查字段有沒有getter方法了。
jar包的這個方法,其實是把注解和注解的實現單獨放一塊作為一個插件來使用了,maven也是如此的。
現實的開發中,還是用maven最多,先用指令創建一個maven項目:mvn archetype:generate -DgroupId=com.shahuwang -DartifactId=Processor -Dpackage=com.shahuwang.processor
然后在pom.xml的層級下添加如下配置:
UTF-8
UTF-8
1.8
1.8
1.8
org.apache.maven.plugins
maven-compiler-plugin
3.5.1
1.8
1.8
UTF-8
com.shahuwang.processor.CheckGetterProcessor
代碼目錄結構如下:
Processor
- src
- main
- java
-com
-shahuwang
-processor
-CheckGetter.java
-CheckGetterProcessor.java
- pom.xml
然后Processor目錄下執行: mvn clean install, 這個項目就會被安裝到本地的maven庫里面。
再用maven新建一個TestProcessor項目,項目結構如下:
TestProcessor
- src
- main
- java
-com
-shahuwang
-TestGetter.java
- pom.xml
TestGetter.java 的代碼上面有。
修改pom.xml, 先在 下添加:
UTF-8
UTF-8
1.8
1.8
1.8
org.apache.maven.plugins
maven-compiler-plugin
3.5.1
1.8
1.8
UTF-8
com.shahuwang.processor.CheckGetterProcessor
再在dependencies添加:
com.shahuwang
Processor
1.0-SNAPSHOT
也就是引入上面編譯好的處理器的包。
現在,在TestProcessor目錄下執行:
mvn compile
就會看到熟悉的提示:
錯誤: getter not found for 'TestGetter.first'.
三種主流的注解處理器使用方式現在都搞懂怎么使用了。下一篇文章將會關注注解處理器的使用方法,各個注解處理器API的使用