Java 的 annotation 自 JDK1.5就拥有了,主要作用就是给代码打标注,这个系列文章将从头到尾进行一个梳理,当然不仅仅是 Annotation,也包括他的解析;一些比较常见的做法,百度谷歌一搜一大把的就再赘述。
还是直奔主题,我们以一个目标进行,比如我们需要做个一个文档生成器,使用Annotation来对 API 进行标注, 然后结合 Maven 生成文档;或者在编译的时候生成文档,先结合 Maven 来使用。
构建一些 Annotation
首先,我们需要准备一些 Annotation,这个 Annotation 将会给我们的其他项目使用,建议单独是一个项目,按照思路,某些类比如 UserController需要标注, 以确定这个类是我们需要扫描的,然后这个类的若干方法也需要标注,方法里面包括参数,目前先整这基础的3个
,先看类标注
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)//对类接口进行注解
@Retention(RetentionPolicy.RUNTIME)//在运行时也保留该注解
@Documented//可以被 javadoc 文档化
public @interface DocAction {
String value() default"";//功能描述
String[] author() default "";//作者,可能有多个
String name() default "";//名称
String date() default "";//开始日期
String update() default "";//结束日期
}
Target
Target标识注解到什么地方,这里的ElementType
是一个枚举类,源码如下
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
//类、接口(包括注解类型),或者枚举
TYPE,
/** Field declaration (includes enum constants) */
//字段,包括枚举
FIELD,
/** Method declaration */
//方法
METHOD,
/** Parameter declaration */
//参数
PARAMETER,
/** Constructor declaration */
//构造方法
CONSTRUCTOR,
/** Local variable declaration */
//本地变量
LOCAL_VARIABLE,
/** Annotation type declaration */
//注解
ANNOTATION_TYPE,
/** Package declaration */
//包
PACKAGE
}
上面就是所有的 target 注解地方,也顺便看下@Target
的源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
他的 target 就是ANNOTATION_TYPE
,也就是只能在 Annotation
上使用,而里面 value()
方法返回值是一个数组,所以,target
里面可以是多个地方,比如@Target({ElementType.FIELD, ElementType.METHOD})
;
Retention
Retention是保存期,也就是该 Annotation 是存活时间,他有3个值,如下源码解释
public enum RetentionPolicy {
/**
* 编译的时候就没了,但是在预编译的时候还是存在的
*/
SOURCE,
/**
* 注解被保存在 class 文件中,但是在 VM 运行时候并不能获得
*/
CLASS,
/**
* 和上个 CLASS 的区别是在运行时候也能获得
*/
RUNTIME
}
接下来继续把 DocMethod
和 DocParam
的注解也写出来
DocMethod
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DocMethod {
String value() default "";//方法描述
String[] author() default "";//作者
String[] version() default "1.0.0";//版本,可以存在多个版本
String url() default "";//接口
Method method() default Method.GET;//请求方式
String date() default "";//日期
String update() default "";//更新日期
Type returnType();//返回类型
DocParam[] params();
public enum Method{
GET,POST,CALL
}
}
DocParam
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value={ElementType.METHOD,ElementType.PARAMETER})//方法和参数中都可以使用
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DocParam {
String value();//参数描述
Type type() default Type.String;//参数类型
public enum Type{
String,Long,Integer,Date,FLOAT,DOUBLE,OBJECT
}
}
到这里基本的注解都已经完成,实际情况根据自己项目的需要自行定制。
Maven 插件
如何写一个插件,搜索一下都有,不再赘述,按照流程做个
1、插件POM
按照普通 maven 项目骨架构建一个 maven 项目,项目的名称位doc-maven-plugin
,按照 XXX-maven-plugin的格式来,这样在运行 maven 时候比较方便,修改 package 形式为maven-plugin
,具体如下
<groupId>cn.ts</groupId>
<artifactId>doc-maven-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
然后需要依赖如下的包
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.4</version>
<scope>provided</scope>
</dependency>
为了插件编写的方便,下面连个依赖也一并加入
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.0.8</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
<version>2.2.1</version>
</dependency>
如果上面的 annotation 是单独一个项目,也需要引入,否则解析 Annotation 找不到
2、写个 Mojo
Mojo
必须继承AbstractMojo
,具体情况如下代码详述
@Mojo(name = "gen",
requiresProject=true,
requiresDependencyResolution=ResolutionScope.COMPILE_PLUS_RUNTIME,
requiresDependencyCollection=ResolutionScope.COMPILE_PLUS_RUNTIME)
public class DocMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {}
}
说明下@Mojo
:name表示 MVN doc:XXX
这个 XXX,这里就是mvn doc:gen
,requiresProject需要项目支持,我们的注解是注解到项目上的,需要扫描项目代码;requiresDependencyResolution表示需要项目在编译运行时的 CLASSPATH,方便我们获取项目的 CLASS,从而获得 CLASS 上面的注解。
项目参数和配置参数
@Parameter(defaultValue = "${project}",required = true,readonly=true)
private MavenProject project;
@Parameter(property="port",defaultValue="3306")
private String port;
第一个获取项目对应 POM 构造,第二个是自己定义的一个参数,比如我们需要获取数据库连接参数等,注解的含义一目了然。
核心解析方法
execute
是整个 Mojo 的核心方法,我们是业务实现也在这里,这里把伪代码写出
//根据配置参数,构造数据库连接
Connection c=getConnByConfig(url,driver,user,pswd)
//从配置参数中获得项目中的 CLASS 路径
String targetPath=project.getBuild().getOutputDirectory();
//获取路径下所有类文件
List<File>classFile=scanClassFile(targetPath);
//根据类文件获得类
List<Class<?>> classes=classForClassFile(classFile);
//解析类
//分析类的 Annotation
if(c==null)continue;
DocAction docAction = c.getAnnotation(DocAction.class);
if(docAction==null)continue;//没有标注文档输出的过滤掉
Method[] methods = c.getDeclaredMethods();
for(Method method:methods){
DocMethod docMethod = method.getAnnotation(DocMethod.class);
if(docMethod==null)continue;
DocParam[] params = docMethod.params();
for(DocParam param:params){
String name = param.value();
Type type=param.type();
//TODO 收集需要的Annotation
//信息进行存储到数据库,根据类名+方法名称作为主键,不存在插入,存在就更新
}
}
使用
插件写好了,我们需要在项目中使用,建立一个maven项目,依赖annotation,插件依赖上面的插件,同时插件里面依赖当前项目。如下所示
<plugin>
<groupId>cn.ts</groupId>
<artifactId>doc-maven-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<configuration>
<port>3309</port><!-- 自定义参数 -->
</configuration>
<dependencies><!-- 依赖本身项目,为了获取 classpath -->
<dependency>
<groupId>cn.ts</groupId>
<artifactId>myrpc</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
对项目使用注解
import cn.ts.annotation.DocAction;
import cn.ts.annotation.DocMethod;
import cn.ts.annotation.DocMethod.Method;
import cn.ts.annotation.DocParam;
import cn.ts.annotation.DocParam.Type;
@DocAction(value="Test",author={"老唐"},date="2017-01-10",name="用户信息")
public class UserAnnotation {
@DocMethod(value="测试用户信息",
author={"Tangshun"},
method=Method.GET,
url="user",
version="1.0.0",
params={
@DocParam(value="用户姓名",type=Type.String),
@DocParam(value="用户年龄",type=Type.Integer)
}, returnType = Type.NONE)
public void test( String name,Integer age){}
}
执行命令
mvn doc:gen
,在 eclipse 环境配置如下
不出意外,你能获得需要的东西
上面是基于 Maven 插件的形式,我们需要把 Annotation 存活时间设置为 RunTime,如果设置为 Source 或者 CLASS 又怎样解析?
Annotation CLASS解析
首先把注解的存活时间设置为 CLASS。这里我构建三个 maven 项目来进行
先看下annotation 部分
重点是 annotation-process,先看他的 pom 文件,把依赖加入
<dependency>
<groupId>cn.ts</groupId>
<artifactId>x-annotation</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 可选插件 不再自己增加 META-INF/service/插件处理 -->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc2</version>
</dependency>
接下来,我们需要编写自己的 Annotation 处理器,该处理器需要实现AbstractProcessor
,并且复写核心方法process
public class ClassAnnotationProcess extends AbstractProcessor{
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {}
}
参数说明:
输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素,比如roundEnv.getElementsAnnotatedWith(FirstAnotation.class)
,获得注解了FirstAnotation
的元素,元素有
PACKAGE, ENUM, CLASS, ANNOTATION_TYPE, INTERFACE, ENUM_CONSTANT, FIELD, PARAMETER, LOCAL_VARIABLE, EXCEPTION_PARAMETER, METHOD, CONSTRUCTOR, STATIC_INIT, INSTANCE_INIT, TYPE_PARAMETER, OTHER, RESOURCE_VARIABLE;
根据元素类型枚举值转换对应的元素类型,进一步根据typeElement.getAnnotation(FirstAnotation.class)
获得注解的详细情况,做进一步操作。
还有一些方法也需要override
,如下
/**
* init()方法会被注解处理工具调用,并输入ProcessingEnviroment参数。
* ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer
* @param processingEnv 提供给 processor 用来访问工具框架的环境
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// Filer是个接口,支持通过注解处理器创建新文件
filer = processingEnv.getFiler();
}
/**
* 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称
* @return 注解器所支持的注解类型集合,如果没有这样的类型,则返回一个空集合
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add(FirstAnotation.class.getCanonicalName());
return annotataions;
}
/**
* 指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),默认返回SourceVersion.RELEASE_6
* @return 使用的Java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
写完 annotation 处理器,打包,在demo项目依赖中引用
<dependencies>
<dependency>
<groupId>cn.ts</groupId>
<artifactId>x-annotation</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.ts</groupId>
<artifactId>x-annotation-process</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
可能的问题:
在处理类上注解@AutoService(Processor.class) ,可能没有作用,需要在 src/main/resources/目录下构建 META_INF/service 子目录,并且建立文件javax.annotation.processing.Processor,并且在里面输入插件的具体名称,如cn.ts.x_annotation_process.ClassAnnotationProcess
版权印为您的作品印上版权32805660