Java注解--Java深度历险(转)
在開(kāi)發(fā)Java程序,尤其是Java EE應(yīng)用的時(shí)候,總是免不了與各種配置文件打交道。以Java EE中典型的S(pring)S(truts)H(ibernate)架構(gòu)來(lái)說(shuō),Spring、Struts和Hibernate這 三個(gè)框架都有自己的XML格式的配置文件。這些配置文件需要與Java源代碼保存同步,否則的話就可能出現(xiàn)錯(cuò)誤。而且這些錯(cuò)誤有可能到了運(yùn)行時(shí)刻才被發(fā) 現(xiàn)。把同一份信息保存在兩個(gè)地方,總是個(gè)壞的主意。理想的情況是在一個(gè)地方維護(hù)這些信息就好了。其它部分所需的信息則通過(guò)自動(dòng)的方式來(lái)生成。JDK 5中引入了源代碼中的注解(annotation)這一機(jī)制。注解使得Java源代碼中不但可以包含功能性的實(shí)現(xiàn)代碼,還可以添加元數(shù)據(jù)。注解的功能類(lèi)似 于代碼中的注釋,所不同的是注解不是提供代碼功能的說(shuō)明,而是實(shí)現(xiàn)程序功能的重要組成部分。Java注解已經(jīng)在很多框架中得到了廣泛的使用,用來(lái)簡(jiǎn)化程序 中的配置。
使用注解
在一般的Java開(kāi)發(fā)中,最常接觸到的可能就是@Override和@SupressWarnings這 兩個(gè)注解了。使用@Override的時(shí)候只需要一個(gè)簡(jiǎn)單的聲明即可。這種稱(chēng)為標(biāo)記注解(marker annotation ),它的出現(xiàn)就代表了某種配置語(yǔ)義。而其它的注解是可以有自己的配置參數(shù)的。配置參數(shù)以名值對(duì)的方式出現(xiàn)。使用 @SupressWarnings的時(shí)候需要類(lèi)似@SupressWarnings({"uncheck", "unused"})這樣的語(yǔ)法。在括號(hào)里面的是該注解可供配置的值。由于這個(gè)注解只有一個(gè)配置參數(shù),該參數(shù)的名稱(chēng)默認(rèn)為value,并且可以省略。而花 括號(hào)則表示是數(shù)組類(lèi)型。在JPA中的@Table注解使用類(lèi)似@Table(name = "Customer", schema = "APP")這樣的語(yǔ)法。從這里可以看到名值對(duì)的用法。在使用注解時(shí)候的配置參數(shù)的值必須是編譯時(shí)刻的常量。
從某種角度來(lái)說(shuō),可以把注解看成是一個(gè)XML元素,該元素可以有不同的預(yù)定義的屬性。而屬性的值是可以在聲明該元素的時(shí)候自行指定的。在代碼中使用注解,就相當(dāng)于把一部分元數(shù)據(jù)從XML文件移到了代碼本身之中,在一個(gè)地方管理和維護(hù)。
開(kāi)發(fā)注解
在一般的開(kāi)發(fā)中,只需要通過(guò)閱讀相關(guān)的API文檔來(lái)了解每個(gè)注解的配置參數(shù)的含義,并在代碼中正確使用即可。在有些情況下,可能會(huì)需要開(kāi)發(fā)自己的注 解。這在庫(kù)的開(kāi)發(fā)中比較常見(jiàn)。注解的定義有點(diǎn)類(lèi)似接口。下面的代碼給出了一個(gè)簡(jiǎn)單的描述代碼分工安排的注解。通過(guò)該注解可以在源代碼中記錄每個(gè)類(lèi)或接口的 分工和進(jìn)度情況。
1 @Retention(RetentionPolicy.RUNTIME) 2 @Target(ElementType.TYPE) 3 public @interface Assignment { 4 String assignee(); 5 int effort(); 6 double finished() default 0; 7 }?
@interface用來(lái)聲明一個(gè)注解,其中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù)。方法的名稱(chēng)就是參數(shù)的名稱(chēng),返回值類(lèi)型就是參數(shù)的類(lèi)型。可以通過(guò)default來(lái)聲明參數(shù)的默認(rèn)值。在這里可以看到@Retention和@Target這樣的元注解,用來(lái)聲明注解本身的行為。@Retention用來(lái)聲明注解的保留策略,有CLASS、RUNTIME和SOURCE這三種,分別表示注解保存在類(lèi)文件、JVM運(yùn)行時(shí)刻和源代碼中。只有當(dāng)聲明為RUNTIME的時(shí)候,才能夠在運(yùn)行時(shí)刻通過(guò)反射API來(lái)獲取到注解的信息。@Target用來(lái)聲明注解可以被添加在哪些類(lèi)型的元素上,如類(lèi)型、方法和域等。
處理注解
在程序中添加的注解,可以在編譯時(shí)刻或是運(yùn)行時(shí)刻來(lái)進(jìn)行處理。在編譯時(shí)刻處理的時(shí)候,是分成多趟來(lái)進(jìn)行的。如果在某趟處理中產(chǎn)生了新的Java源文 件,那么就需要另外一趟處理來(lái)處理新生成的源文件。如此往復(fù),直到?jīng)]有新文件被生成為止。在完成處理之后,再對(duì)Java代碼進(jìn)行編譯。JDK 5中提供了apt工具用來(lái)對(duì)注解進(jìn)行處理。apt是一個(gè)命令行工具,與之配套的還有一套用來(lái)描述程序語(yǔ)義結(jié)構(gòu)的Mirror API。Mirror API(com.sun.mirror.*)描述的是程序在編譯時(shí)刻的靜態(tài)結(jié)構(gòu)。通過(guò)Mirror API可以獲取到被注解的Java類(lèi)型元素的信息,從而提供相應(yīng)的處理邏輯。具體的處理工作交給apt工具來(lái)完成。編寫(xiě)注解處理器的核心是AnnotationProcessorFactory和AnnotationProcessor兩個(gè)接口。后者表示的是注解處理器,而前者則是為某些注解類(lèi)型創(chuàng)建注解處理器的工廠。
以上面的注解Assignment為例,當(dāng)每個(gè)開(kāi)發(fā)人員都在源代碼中更新進(jìn)度的話,就可以通過(guò)一個(gè)注解處理器來(lái)生成一個(gè)項(xiàng)目整體進(jìn)度的報(bào)告。 首先是注解處理器工廠的實(shí)現(xiàn)。
1 public class AssignmentApf implements AnnotationProcessorFactory { 2 public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds,? AnnotationProcessorEnvironment env) { 3 if (atds.isEmpty()) { 4 return AnnotationProcessors.NO_OP; 5 } 6 return new AssignmentAp(env); //返回注解處理器 7 } 8 public Collection<String> supportedAnnotationTypes() { 9 return Collections.unmodifiableList(Arrays.asList("annotation.Assignment")); 10 } 11 public Collection<String> supportedOptions() { 12 return Collections.emptySet(); 13 } 14 }?
AnnotationProcessorFactory接口有三個(gè)方法:getProcessorFor是根據(jù)注解的類(lèi)型來(lái)返回特定的注解處理 器;supportedAnnotationTypes是返回該工廠生成的注解處理器所能支持的注解類(lèi)型;supportedOptions用來(lái)表示所支 持的附加選項(xiàng)。在運(yùn)行apt命令行工具的時(shí)候,可以通過(guò)-A來(lái)傳遞額外的參數(shù)給注解處理器,如-Averbose=true。當(dāng)工廠通過(guò) supportedOptions方法聲明了所能識(shí)別的附加選項(xiàng)之后,注解處理器就可以在運(yùn)行時(shí)刻通過(guò)AnnotationProcessorEnvironment的getOptions方法獲取到選項(xiàng)的實(shí)際值。注解處理器本身的基本實(shí)現(xiàn)如下所示。
1 public class AssignmentAp implements AnnotationProcessor { 2 private AnnotationProcessorEnvironment env; 3 private AnnotationTypeDeclaration assignmentDeclaration; 4 public AssignmentAp(AnnotationProcessorEnvironment env) { 5 this.env = env; 6 assignmentDeclaration = (AnnotationTypeDeclaration) env.getTypeDeclaration("annotation.Assignment"); 7 } 8 public void process() { 9 Collection<Declaration> declarations = env.getDeclarationsAnnotatedWith(assignmentDeclaration); 10 for (Declaration declaration : declarations) { 11 processAssignmentAnnotations(declaration); 12 } 13 } 14 private void processAssignmentAnnotations(Declaration declaration) { 15 Collection<AnnotationMirror> annotations = declaration.getAnnotationMirrors(); 16 for (AnnotationMirror mirror : annotations) { 17 if (mirror.getAnnotationType().getDeclaration().equals(assignmentDeclaration)) { 18 Map<AnnotationTypeElementDeclaration, AnnotationValue> values = mirror.getElementValues(); 19 String assignee = (String) getAnnotationValue(values, "assignee"); //獲取注解的值 20 } 21 } 22 } 23 }?
注解處理器的處理邏輯都在process方法中完成。通過(guò)一個(gè)聲明(Declaration)的getAnnotationMirrors方法就可以獲取到該聲明上所添加的注解的實(shí)際值。得到這些值之后,處理起來(lái)就不難了。
在創(chuàng)建好注解處理器之后,就可以通過(guò)apt命令行工具來(lái)對(duì)源代碼中的注解進(jìn)行處理。 命令的運(yùn)行格式是apt -classpath bin -factory annotation.apt.AssignmentApf src/annotation/work/*.java,即通過(guò)-factory來(lái)指定注解處理器工廠類(lèi)的名稱(chēng)。實(shí)際上,apt工具在完成處理之后,會(huì)自 動(dòng)調(diào)用javac來(lái)編譯處理完成后的源代碼。
JDK 5中的apt工具的不足之處在于它是Oracle提供的私有實(shí)現(xiàn)。在JDK 6中,通過(guò)JSR 269把自定義注解處理器這一功能進(jìn)行了規(guī)范化,有了新的javax.annotation.processing這個(gè)新的API。對(duì)Mirror API也進(jìn)行了更新,形成了新的javax.lang.model包。注解處理器的使用也進(jìn)行了簡(jiǎn)化,不需要再單獨(dú)運(yùn)行apt這樣的命令行工具,Java編譯器本身就可以完成對(duì)注解的處理。對(duì)于同樣的功能,如果用JSR 269的做法,只需要一個(gè)類(lèi)就可以了。
1 @SupportedSourceVersion(SourceVersion.RELEASE_6) 2 @SupportedAnnotationTypes("annotation.Assignment") 3 public class AssignmentProcess extends AbstractProcessor { 4 private TypeElement assignmentElement; 5 public synchronized void init(ProcessingEnvironment processingEnv) { 6 super.init(processingEnv); 7 Elements elementUtils = processingEnv.getElementUtils(); 8 assignmentElement = elementUtils.getTypeElement("annotation.Assignment"); 9 } 10 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 11 Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(assignmentElement); 12 for (Element element : elements) { 13 processAssignment(element); 14 } 15 } 16 private void processAssignment(Element element) { 17 List<? extends AnnotationMirror> annotations = element.getAnnotationMirrors(); 18 for (AnnotationMirror mirror : annotations) { 19 if (mirror.getAnnotationType().asElement().equals(assignmentElement)) { 20 Map<? extends ExecutableElement, ? extends AnnotationValue> values = mirror.getElementValues(); 21 String assignee = (String) getAnnotationValue(values, "assignee"); //獲取注解的值 22 } 23 } 24 } 25 }?
仔細(xì)比較上面兩段代碼,可以發(fā)現(xiàn)它們的基本結(jié)構(gòu)是類(lèi)似的。不同之處在于JDK 6中通過(guò)元注解@SupportedAnnotationTypes來(lái) 聲明所支持的注解類(lèi)型。另外描述程序靜態(tài)結(jié)構(gòu)的javax.lang.model包使用了不同的類(lèi)型名稱(chēng)。使用的時(shí)候也更加簡(jiǎn)單,只需要通過(guò)javac -processor annotation.pap.AssignmentProcess Demo1.java這樣的方式即可。
上面介紹的這兩種做法都是在編譯時(shí)刻進(jìn)行處理的。而有些時(shí)候則需要在運(yùn)行時(shí)刻來(lái)完成對(duì)注解的處理。這個(gè)時(shí)候就需要用到Java的反射API。反射API提供了在運(yùn)行時(shí)刻讀取注解信息的支持。不過(guò)前提是注解的保留策略聲明的是運(yùn)行時(shí)。Java反射API的AnnotatedElement接口提供了獲取類(lèi)、方法和域上的注解的實(shí)用方法。比如獲取到一個(gè)Class類(lèi)對(duì)象之后,通過(guò)getAnnotation方法就可以獲取到該類(lèi)上添加的指定注解類(lèi)型的注解。
實(shí)例分析
下面通過(guò)一個(gè)具體的實(shí)例來(lái)分析說(shuō)明在實(shí)踐中如何來(lái)使用和處理注解。假定有一個(gè)公司的雇員信息系統(tǒng),從訪問(wèn)控制的角度出發(fā),對(duì)雇員的工資的更新只能由具有特定角色的用戶才能完成。考慮到訪問(wèn)控制需求的普遍性,可以定義一個(gè)注解來(lái)讓開(kāi)發(fā)人員方便的在代碼中聲明訪問(wèn)控制權(quán)限。
1 @Retention(RetentionPolicy.RUNTIME) 2 @Target(ElementType.METHOD) 3 public @interface RequiredRoles { 4 String[] value(); 5 }?
下一步則是如何對(duì)注解進(jìn)行處理,這里使用的Java的反射API并結(jié)合動(dòng)態(tài)代理。下面是動(dòng)態(tài)代理中的InvocationHandler接口的實(shí)現(xiàn)。
1 public class AccessInvocationHandler<T> implements InvocationHandler { 2 final T accessObj; 3 public AccessInvocationHandler(T accessObj) { 4 this.accessObj = accessObj; 5 } 6 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 7 RequiredRoles annotation = method.getAnnotation(RequiredRoles.class); //通過(guò)反射API獲取注解 8 if (annotation != null) { 9 String[] roles = annotation.value(); 10 String role = AccessControl.getCurrentRole(); 11 if (!Arrays.asList(roles).contains(role)) { 12 throw new AccessControlException("The user is not allowed to invoke this method."); 13 } 14 } 15 return method.invoke(accessObj, args); 16 } 17 }?
在具體使用的時(shí)候,首先要通過(guò)Proxy.newProxyInstance方法創(chuàng)建一個(gè)EmployeeGateway的接口的代理類(lèi),使用該代理類(lèi)來(lái)完成實(shí)際的操作。
參考資料
- JDK 5和JDK 6中的apt工具說(shuō)明文檔
- Pluggable Annotation Processing API
-
APT: Compile-Time Annotation Processing with Java
轉(zhuǎn)載于:https://www.cnblogs.com/WayneZeng/archive/2012/09/24/2699737.html
總結(jié)
以上是生活随笔為你收集整理的Java注解--Java深度历险(转)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 多线程的概念
- 下一篇: HDU 4292 Food (成都赛区网