主页 >
新闻资讯 >
软件开发资讯 >
Java编程Java软件开发 >
Java注解与自定义注解处理器
更新时间:2016-08-14 14:39
发布者:周老师
动机
最近在看ButterKnife源码的时候,居然发现有一个类叫做AbstractProcessor
,并且ButterKnife的View绑定不是依靠反射来实现的,而是使用了编译时的注解,自动生成的.class文件。真是一个神奇的东西啊!!
所以本文就注解与自定义的注解处理器来学习注解。项目Github地址
基础知识
大家应该知道元注解@Retention
吧,它表示注解在什么时候存在,可以分为3个时期:
-
RetentionPolicy.SOURCE
:表示注解只在java文件里面才有,在编译的时候,这部分注解就会被擦出。类似于@Override
只是给程序员看的。
-
RetentionPolicy.CLASS
:注解在编译的时候会存在,在运行的时候就会擦除。
-
RetentionPolicy.RUNTIME
:在运行的时候会存在,这里就要用到了反射来实现了。
Annotation Processor
注解处理器,在编译期间,JVM会自动运行注解处理器(当然,我们需要将其注册)。虽然我们写的Java代码被编译成class就不能被改动了,但是注解处理器会重新生成其他的java代码,我们可以通过反射来调用新生成的java文件里面的类或方法。然后JVM再对这些生成的java代码进行编译。这是一个递归的过程。
Java API为我们提供了注解处理器的接口,我们只要实现它就可以了。这个类就是AbstractProcessor
,下面就一起来实现它。这个例子我是从【Java二十周年】Java注解处理器,参考过来的。实现功能如下:对于一个类,只要给它的某个加上一个注解,就会自动生成其接口,接口中的方法就是加注解的方法。
public class Man {
@Interface("ManInterface")
public void eat() {
System.out.println("Eat");
}
}
eat
方法加上了@Interface("ManInterface")
,上述就会生成:
public interface ManInterface {
void eat();
}
就是这么粗暴。 然后我们看看注解是怎么定义的:
package wqh.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created on 2016/8/10.
*
* @author 王启航
* @version 1.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Interface {
String value();
}
注意上面@Target(ElementType.METHOD)
表示该方法只能加在方法上面;@Retention(RetentionPolicy.CLASS)
表示是在编译时存在的。
好了接下来就是重点了:
public class InterfaceProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion() { }
}
定义自己的注解处理器
-
init
对一些工具进行初始化。
-
process
就是真正生成java代码的地方。
-
getSupportedAnnotationTypes
表示该注解处理器可以出来那些注解。
-
getSupportedSourceVersion
可以出来java版本
我们首先看看除去process
的其他三个方法:
public class InterfaceProcessor extends AbstractProcessor {
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
filer = env.getFiler();
typeUtils = env.getTypeUtils();
messager = env.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new TreeSet<>();
types.add(Interface.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
getSupportedSourceVersion
返回最近的版本。 getSupportedAnnotationTypes
返回了我们定义的Interface
注解。 init
初始化了了4个工具类,下面稍微介绍一哈:
Elements
:Element代表程序的元素,例如包、类或者方法。每个Element代表一个静态的、语言级别的构件。它只是结构化的文本,他不是可运行的.可以理解为一个签名 (public class Main
或者 private void fun(int a) {}
),大家可以联系到XML的解析,或者抽象语法树 (AST)。 给出以下例子:
package com.example;
public class Foo {
private int a;
private Foo other;
public Foo () {}
public void setA ( // ExecuteableElement
int newA // TypeElement
) {}
}
而且我们可以通过
aType.getEnclosingElement()
得到其父标签(没有父标签就返回null
).可以联系Class.getEnclosingClass
这个方法。
上面例子大家都懂了吧!! 本文主要就是对ExecuteableElement
进行操作。
下面看看Messager
: 因为在注解处理器里面不可以抛出Exception
!为什么了?因为在编译阶段抛出错误后,注解处理器就不会运行完,也就没什么用了。 所以Message
就是为了输出错误信息。
关于Filter
与Types
本文就不进行讲解了。。。。
好!!下面看看process
方法
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Map<String, AnnotatedClass> classMap = new HashMap<>();
Set<? extends Element> elementSet = env.getElementsAnnotatedWith(Interface.class);
for (Element e : elementSet) {
if (e.getKind() != ElementKind.METHOD) {
error(e, "错误的注解类型,只有函数能够被该 @%s 注解处理", Interface.class.getSimpleName());
return true;
}
ExecutableElement element = (ExecutableElement) e;
AnnotatedMethod annotatedMethod = new AnnotatedMethod(element);
String classname = annotatedMethod.getSimpleClassName();
AnnotatedClass annotatedClass = classMap.get(classname);
if (annotatedClass == null) {
PackageElement pkg = elementUtils.getPackageOf(element);
annotatedClass = new AnnotatedClass(pkg.getQualifiedName().toString(), element.getAnnotation(Interface.class).value());
annotatedClass.addMethod(annotatedMethod);
classMap.put(classname, annotatedClass);
} else
annotatedClass.addMethod(annotatedMethod);
}
for (AnnotatedClass annotatedClass : classMap.values()) {
annotatedClass.generateCode(elementUtils, filer);
}
return false;
}
首先看看第一部分:
Map<String, AnnotatedClass> classMap = new HashMap<>();
这里就是存储整个工程注释过getSupportedAnnotationTypes
返回的注解的Map
。 在这里我们定义了如下的数据结构:
classMap:
key -> 一个类的类名
value -> AnnotatedClass
AnnotatedClass
key -> 一个类的类名
value -> 该类所有加上注解的方法。1
第二部分:
// 得到所有注解@Interface的Element集合
Set<? extends Element> elementSet = env.getElementsAnnotatedWith(Interface.class);
如注释所说,得到所有注解@Interface
的Element
集合。注意这里得到了是Element
的集合,也就生成了一个树结构。
第三部分:
for (Element e : elementSet) {
if (e.getKind() != ElementKind.METHOD) {
error(e, "错误的注解类型,只有函数能够被该 @%s 注解处理", Interface.class.getSimpleName())
return true
}
ExecutableElement element = (ExecutableElement) e
AnnotatedMethod annotatedMethod = new AnnotatedMethod(element)
String classname = annotatedMethod.getSimpleClassName()
AnnotatedClass annotatedClass = classMap.get(classname)
if (annotatedClass == null) {
PackageElement pkg = elementUtils.getPackageOf(element)
annotatedClass = new AnnotatedClass(pkg.getQualifiedName().toString(), element.getAnnotation(Interface.class).value())
annotatedClass.addMethod(annotatedMethod)
classMap.put(classname, annotatedClass)
} else
annotatedClass.addMethod(annotatedMethod)
}
首先所有加上@Interface
的Element
进行变量,第一步判断注解是否加在Method
之上,如果不是就输出错误信息。 上文说到可以用Messager
来输出:
private void error(Element e, String msg, Object... args) {
messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), e);
}
然后
ExecutableElement element = (ExecutableElement) e;
AnnotatedMethod annotatedMethod = new AnnotatedMethod(element);
将其转型为ExecutableElement
。因为在上面判断所有的Element
都是加在方法上的。这里不会报错。 然后构造AnnotatedMethod
,因为面向对象,AnnotatedMethod
表示一个加了注解的方法。 在这里我们看看什么是AnnotatedMethod
:
package wqh.core;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
/**
* Created on 2016/8/10.
*
* @author 王启航
* @version 1.0
*/
public class AnnotatedMethod {
private ExecutableElement annotatedMethodElement;
private String simpleMethodName;
private String simpleClassName;
public AnnotatedMethod(ExecutableElement annotatedMethodElement) {
this.annotatedMethodElement = annotatedMethodElement;
simpleMethodName = annotatedMethodElement.getSimpleName().toString();
TypeElement parent = (TypeElement) annotatedMethodElement.getEnclosingElement();
simpleClassName = parent.getQualifiedName().toString();
}
public ExecutableElement getAnnotatedMethodElement() {
return annotatedMethodElement;
}
public String getSimpleMethodName() {
return simpleMethodName;
}
public String getSimpleClassName() {
return simpleClassName;
}
}
还是比较简单的哈!!
OK,回到 process
:
String classname = annotatedMethod.getSimpleClassName();
AnnotatedClass annotatedClass = classMap.get(classname);
构造AnnotatedClass
。上面也说到过AnnotatedClass
,AnnotatedClass
可以表示一个类,里面存储了许多的AnnotatedMethod
。 下面看看其代码:
package wqh.core
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import javax.annotation.processing.Filer
import javax.lang.model.element.Modifier
import javax.lang.model.element.VariableElement
import javax.lang.model.util.Elements
import java.io.IOException
import java.util.LinkedList
import java.util.List
public class AnnotatedClass {
private String className
private String packageName
private List<AnnotatedMethod> methods = new LinkedList<>()
public AnnotatedClass(String packageName, String generateClassName) {
this.className = generateClassName
this.packageName = packageName
}
// 生成Java代码部分,现在可以不看
public void generateCode(Elements elementUtils, Filer filer) {
TypeSpec.Builder typeBuilder = TypeSpec.interfaceBuilder(className).addModifiers(Modifier.PUBLIC)
for (AnnotatedMethod m : methods) {
MethodSpec.Builder methodBuilder =
MethodSpec.methodBuilder(m.getSimpleMethodName())
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.returns(TypeName.get(m.getAnnotatedMethodElement().getReturnType()))
int i = 1
for (VariableElement e : m.getAnnotatedMethodElement().getParameters()) {
methodBuilder.addParameter(TypeName.get(e.asType()), "param" + String.valueOf(i))
++i
}
typeBuilder.addMethod(methodBuilder.build())
}
JavaFile javaFile = JavaFile.builder(packageName, typeBuilder.build()).build()
try {
javaFile.writeTo(filer)
} catch (IOException e) {
e.printStackTrace()
}
}
public void addMethod(AnnotatedMethod annotatedMethod) {
methods.add(annotatedMethod)
}
}
OK,在process
里面:
if (annotatedClass == null) {
PackageElement pkg = elementUtils.getPackageOf(element)
annotatedClass = new AnnotatedClass(pkg.getQualifiedName().toString(), element.getAnnotation(Interface.class).value())
annotatedClass.addMethod(annotatedMethod)
classMap.put(classname, annotatedClass)
} else
annotatedClass.addMethod(annotatedMethod)
就是将加了注解的方法,在同一个类的加入到annotatedClass
里面去,然后构成classMap
。
到这里,所有的结构都已生成。也就是:
classMap:
key -> 一个类的类名
value -> AnnotatedClass
AnnotatedClass
key -> 一个类的类名
value -> 该类所有加上注解的方法。
下一步就是生成Java
代码了,这里用了JavaWriter
的改进版本JavaPoet
,可以自己去下这个包
for (AnnotatedClass annotatedClass : classMap.values()) {
annotatedClass.generateCode(elementUtils, filer);
}
return false;
这部分不做详细讲解。
好了,所有的InterfaceProcessor
讲解完毕。
注册打包
接下来将其注册到JVM中去:在Intelilj IDEA,有如下的目录结构: 
在javax.annotation.processing.Processor
中定义了注解处理器:
wqh.core.InterfaceProcessor
这些都做了之后,就回去生成jar包!!!可以用Maven,但是我们这个项目加了依赖项,所以有点麻烦。 这里直接用Intelilj IDEA打包! 在File->Project Structure可以配置这样
。将右侧的目录加到左侧就好。 
Build一哈,就可以,可以生成jar包了!!
测试
在其他项目中按照下面配置:

package test;
import wqh.core.Interface;
/**
* Created on 2016/8/10.
*
* @author 王启航
* @version 1.0
*/
public class Man {
@Interface("ManInterface")
public void eat() {
System.out.println("Eat");
}
}
运行就可以在generated
文件夹看到生成的接口
package test;
public interface ManInterface {
void eat();
}
那么怎么使用MainInterface
呢?
package test;
/**
* Created on 2016/8/10.
*
* @author 王启航
* @version 1.0
*/
public class Main {
public static void main(String args[]) throws ClassNotFoundException {
Class<?> c = Class.forName("test.ManInterface");
System.out.println(c.getName());
}
}
上一篇:
Java web文件上传下载 下一篇:
Java数组与函数的结合