前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Annotation 使用案例

Annotation 使用案例

作者头像
breezedancer
发布2018-09-12 15:45:05
9670
发布2018-09-12 15:45:05
举报
文章被收录于专栏:技术与生活技术与生活

Java 的 annotation 自 JDK1.5就拥有了,主要作用就是给代码打标注,这个系列文章将从头到尾进行一个梳理,当然不仅仅是 Annotation,也包括他的解析;一些比较常见的做法,百度谷歌一搜一大把的就再赘述。

还是直奔主题,我们以一个目标进行,比如我们需要做个一个文档生成器,使用Annotation来对 API 进行标注, 然后结合 Maven 生成文档;或者在编译的时候生成文档,先结合 Maven 来使用。

构建一些 Annotation

首先,我们需要准备一些 Annotation,这个 Annotation 将会给我们的其他项目使用,建议单独是一个项目,按照思路,某些类比如 UserController需要标注, 以确定这个类是我们需要扫描的,然后这个类的若干方法也需要标注,方法里面包括参数,目前先整这基础的3个 ,先看类标注

代码语言:javascript
复制
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是一个枚举类,源码如下

代码语言:javascript
复制
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的源码:

代码语言:javascript
复制
@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个值,如下源码解释

代码语言:javascript
复制
public enum RetentionPolicy {    /**
     * 编译的时候就没了,但是在预编译的时候还是存在的
     */
    SOURCE,    /**
     * 注解被保存在 class 文件中,但是在 VM 运行时候并不能获得
     */
    CLASS,    /**
     * 和上个 CLASS 的区别是在运行时候也能获得
     */
    RUNTIME
}

接下来继续把 DocMethodDocParam 的注解也写出来

DocMethod
代码语言:javascript
复制
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)@Documentedpublic @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
代码语言:javascript
复制
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)@Documentedpublic @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,具体如下

代码语言:javascript
复制
  <groupId>cn.ts</groupId>
    <artifactId>doc-maven-plugin</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>maven-plugin</packaging>

然后需要依赖如下的包

代码语言:javascript
复制
    <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>

为了插件编写的方便,下面连个依赖也一并加入

代码语言:javascript
复制
    <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,具体情况如下代码详述

代码语言:javascript
复制
@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 上面的注解。

项目参数和配置参数
代码语言:javascript
复制
  @Parameter(defaultValue = "${project}",required = true,readonly=true)    private MavenProject project;    @Parameter(property="port",defaultValue="3306")    private String port;

第一个获取项目对应 POM 构造,第二个是自己定义的一个参数,比如我们需要获取数据库连接参数等,注解的含义一目了然。

核心解析方法

execute是整个 Mojo 的核心方法,我们是业务实现也在这里,这里把伪代码写出

代码语言:javascript
复制
//根据配置参数,构造数据库连接Connection c=getConnByConfig(url,driver,user,pswd)//从配置参数中获得项目中的 CLASS 路径String targetPath=project.getBuild().getOutputDirectory();//获取路径下所有类文件List<File>classFile=scanClassFile(targetPath);//根据类文件获得类List<Class<?>> classes=classForClassFile(classFile);//解析类//分析类的 Annotationif(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,插件依赖上面的插件,同时插件里面依赖当前项目。如下所示

代码语言:javascript
复制
<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>

对项目使用注解

代码语言:javascript
复制
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 文件,把依赖加入

代码语言:javascript
复制
<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

代码语言:javascript
复制
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,如下

代码语言:javascript
复制
/** 
 * 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项目依赖中引用

代码语言:javascript
复制
<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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 技术与生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 构建一些 Annotation
    • Target
      • Retention
        • DocMethod
          • DocParam
          • Maven 插件
            • 1、插件POM
              • 2、写个 Mojo
              • 使用
                • 执行命令
                • 不出意外,你能获得需要的东西
                  • Annotation CLASS解析
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档