动机

近期在看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;    // PackageElement

public class Foo {        // TypeElement

    private int a;      // VariableElement
private Foo other; // VariableElement public Foo () {} // ExecuteableElement public void setA ( // ExecuteableElement
int newA // TypeElement
) {}
}

并且我们能够通过

aType.getEnclosingElement();

得到其父标签(没有父标签就返回null).能够联系Class.getEnclosingClass这种方法。

上面样例大家都懂了吧!!

本文主要就是对ExecuteableElement进行操作。

以下看看Messager

由于在注解处理器里面不能够抛出Exception!为什么了?由于在编译阶段抛出错误后,注解处理器就不会执行完。也就没什么用了。

所以Message就是为了输出错误信息。

关于FilterTypes本文就不进行解说了。。。。

好!!以下看看process方法

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Map<String, AnnotatedClass> classMap = new HashMap<>(); // 得到全部注解@Interface的Element集合
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<>();

这里就是存储整个project凝视过getSupportedAnnotationTypes返回的注解的Map

在这里我们定义了例如以下的数据结构:

classMap:
key -> 一个类的类名
value -> AnnotatedClass // 这个等下会说到,这里知道是这种关系就好
AnnotatedClass
key -> 一个类的类名
value -> 该类全部加上注解的方法。

第二部分:

// 得到全部注解@Interface的Element集合
Set<? extends Element> elementSet = env.getElementsAnnotatedWith(Interface.class);

如凝视所说,得到全部注解@InterfaceElement集合。

注意这里得到了是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); }

首先全部加上@InterfaceElement进行变量,第一步推断注解是否加在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();
/*
* getEnclosingElement() 能够理解为该标签的父标签.
* {@see Class.getEnclosingClass}
*/
TypeElement parent = (TypeElement) annotatedMethodElement.getEnclosingElement();
/*
* Return the fully qualified name of this class or interface.
* {@code java.util.Set<E>} is "java.util.Set"
* {@code java.util.Map.Entry}is "java.util.Map.Entry"
*/
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; /**
* Created on 2016/8/10.
*
* @author 王启航
* @version 1.0
*/
public class AnnotatedClass {
private String className;
private String packageName;
private List<AnnotatedMethod> methods = new LinkedList<>(); /**
* @param packageName 将要生成的类的包名
* @param generateClassName 将要生成的类的类名
*/
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包了。!

这些步骤。我们也能够直接使用Google的AutoService,直接省去了这一步。

測试

在其它项目中依照以下配置:



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二十周年 Java注解处理器

Java注解处理器

我的邮箱:1906362072@qq.com

大二学生,欢迎联系。

Java注解与自己定义注解处理器的更多相关文章

  1. 深入理解Java:注解(Annotation)自己定义注解入门

    深入理解Java:注解(Annotation)自己定义注解入门 要深入学习注解.我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前.我们就必须要了解Java为我们提供的元注解和相关定义注解的 ...

  2. Java深入 - 深入 Java自己定义注解

    我们在使用Spring框架的时候,会常常使用类似:@Autowired 这种注解. 我们也能够自定义一些注解.Java的注解主要在包:java.lang.annotation中实现. 1. 元注解 什 ...

  3. 深入JAVA注解(Annotation):自定义注解 (转)

    原文出自:http://blog.csdn.net/yjclsx/article/details/52101922 一.基础知识:元注解 要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义 ...

  4. java基础知识:自定义注解

    转自 深入了解注解 要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法. 元注解的作用就是负责注解其他注解.J ...

  5. Java编程的逻辑 (85) - 注解

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  6. 廖雪峰Java4反射与泛型-2注解-2定义注解

    1.定义注解 使用@interface定义注解Annotation 注解的参数类似无参数方法 可以设定一个默认值(推荐) 把最常用的参数命名为value(推荐) 2.元注解 2.1Target使用方式 ...

  7. 怎样自己定义注解Annotation,并利用反射进行解析

    Java注解可以提供代码的相关信息,同一时候对于所注解的代码结构又没有直接影响.在这篇教程中,我们将学习Java注解,怎样编写自己定义注解.注解的使用,以及怎样使用反射解析注解. 注解是Java 1. ...

  8. java基础解析系列(六)---注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...

  9. java学习第七天注解.day19

    注解 可以使用注解来修饰类中的成员信息 "注解,可以看作是对 一个 类/方法 的一个扩展的模版 元注解 注解:用来贴在类/方法/变量等之上的一个标记,第三方程序可以通过这个标记赋予一定功能 ...

随机推荐

  1. iOS开发系列--音频播放、录音、

    音频 在iOS中音频播放从形式上可以分为音效播放和音乐播放.前者主要指的是一些短音频播放,通常作为点缀音频,对于这类音频不需要进行进度.循环等控制.后者指的是一些较长的音频,通常是主音频,对于这些音频 ...

  2. Android 按钮长按下去重复执行某个动作,放开后停止执行动作

    Android开发中,常遇到一种需求,即按钮长按下去重复执行某个动作,放开后停止执行动作.网上找了许多代码,都没有适合的,于是自己动手写了一个. 基本思路是:首先设置一个标识变量,用于标识是否处于按下 ...

  3. iOS开发经验总结——基础工程

    iOS开发经验总结--依赖库 这篇博客,我想说一下开发中经常遇到的一个问题,虚拟个场景描述一下的话,应该是这样的. 项目经理:今天我们正式开始一个新项目,iOSer你负责把苹果端的APP完成,有没有问 ...

  4. Objective-C 关于静态方法与实例方法

    objective-c中非常重要的语法知识,在此归纳总结一下. 类方法,也称静态方法,指的是用static关键字修饰的方法.此方法属类本身的方法,不属于类的某一个实例(对象).类方法中不可直接使用实例 ...

  5. Unused port adds a PWM/analog channel to a microcontroller

    Low-cost, 8-bit, single-chip microcontrollers are stingy when it comes to on-chip PWM (pulse-width-m ...

  6. PHP中var_dump

    var_dump() 能打印出类型 print_r() 只能打出值echo() 是正常输出... 需要精确调试的时候用 var_dump();一般查看的时候用 print_r() 另外 , echo不 ...

  7. 简化delegate写法

    标准的写法 空 简化后的宏 /**************************************************************/ // delegate 托付 /* #de ...

  8. Andorid之Annotation框架初使用(四)

    代替繁琐的finViewById @EActivity public class MyActivity extends Activity { // Injects R.id.myEditText @V ...

  9. Java编程兵书

    <Java编程兵书> 基本信息 作者: 桂颖 任昱衡 丛书名: 程序员藏经阁 出版社:电子工业出版社 ISBN:9787121207419 上架时间:2013-8-26 出版日期:2013 ...

  10. [Android Pro] 有关Broadcast作为内部类时注册的一些问题

    很经常Broadcast都会写成一个Activity或者Service的内部类.这时候的注册和普通有点小区别. 有两种情况 1.假如是再Manifest文件里面静态注册的话,需要注意. ex: < ...