Java自定义注解实现

一、注解的定义和作用

1、定义
  注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

2、作用
(1)编写文档:
  通过代码里标识的元数据生成文档【生成文档doc文档】
(2)代码分析:
  通过代码里标识的元数据对代码进行分析【使用反射】
(3)编译检查:
  通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】

二、元注解

注解说明
@Target(JDK1.5)定义注解的作用目标
@Retention(JDK1.5)定义注解的保留策略
@Document(JDK1.5)注解是否应当被包含在 JavaDoc 文档中(生成说明文档,添加类的解释 )
@Inherited(JDK1.5)说明子类可以继承父类中的该注解
@Repeatable(JDK1.8)允许在相同的程序元素中重复注解,在需要对同一注解多次使用时,往往需要借助@Repeatable注解。
@Native(JDK1.8)修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。

1、Target类型说明

Target类型说明
ElementType.TYPE类、接口(包括注释类型)或枚举声明
ElementType.FIELD字段声明(包括枚举常量)
ElementType.METHOD方法声明
ElementType.PARAMETER正式的参数声明
ElementType.CONSTRUCTOR构造函数声明
ElementType.LOCAL_VARIABLE局部变量声明
ElementType.ANNOTATION_TYPE注解类型声明
ElementType.PACKAGE程式包说明

2、Retention类型说明

Target类型说明
RetentionPolicy.SOURCE注解仅存在于源码中,在class字节码文件中不包含
RetentionPolicy.CLASS默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
RetentionPolicy.RUNTIME注解会在class字节码文件中存在,在运行时可以通过反射获取到

3、@Documented和@Inherited的比较
  (1)@Documented注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。
  (2)@Inherited注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。

4、注意
  (1)@Repeatable 所声明的注解,其元注解@Target的使用范围要比@Repeatable的值声明的注解中的@Target的范围要大或相同,否则编译器错误,显示@Repeatable值所声明的注解的元注解@Target不是@Repeatable声明的注解的@Target的子集。
  (2)@Repeatable注解声明的注解的元注解@Retention的周期要比@Repeatable的值指向的注解的@Retention得周期要小或相同。
周期长度为:SOURCE(源码) < CLASS (字节码) < RUNTIME(运行)

5、实例

//Java 8 之前的做法
public @interface Roles {
    Role[] roles();
}

public @interface Role {
    String roleName();
}

public class RoleTest {
    @Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")})
    public String doString(){
        return "这是C语言中国网Java教程";
    }
}

//java 8 之后增加了重复注解
public @interface Roles {
    Role[] value();
}

@Repeatable(Roles.class)
public @interface Role {
    String roleName();
}

public class RoleTest {
    @Role(roleName = "role1")
    @Role(roleName = "role2")
    public String doString(){
        return "这是C语言中文网Java教程";
    }
}

三、自定义注解实现

1、运行时处理的注解(反射机制)
(1)定义注解

package com.newtouch.springboot.examples.annotation.runningAnnotation;

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)//保留策略使用RUNTIME:注解会在class字节码文件中存在,在运行时可以通过反射获取到
public @interface Reflect {
    String name() default "liuyifei";
}

(2)注解处理器

package com.newtouch.springboot.examples.annotation.runningAnnotation;

import java.lang.reflect.Method;

/**
 * 注解处理器
 */
public class ReflectProcessor {
    public void parseMethod(final Class<?> clazz) throws Exception {
        final Object obj = clazz.getConstructor(new Class[] {}).newInstance(new Object[] {});
        final Method[] methods = clazz.getDeclaredMethods();
        for (final Method method : methods) {
            final Reflect my = method.getAnnotation(Reflect.class);
            if (null != my) {
                System.out.println("方法名:");
                System.out.println(method.getName());
                method.invoke(obj, my.name());
            }
        }
    }
}

(3)测试注解

package com.newtouch.springboot.examples.annotation.runningAnnotation;

/**
 * 测试注解
 */
public class ReflectTest {
    @Reflect
    public static void sayHello1(final String name) {
        System.out.println("==>> Hi, " + name + " , nest to meet you!");
    }

    @Reflect(name = "yangmi")
    public static void sayHello2(final String name) {
        System.out.println("==>> Hi, " + name + " , nest to meet you!");
    }

    public static void main(final String[] args) throws Exception {
        final ReflectProcessor reflectProcessor = new ReflectProcessor();
        reflectProcessor.parseMethod(ReflectTest.class);
    }
}

(4)测试结果

方法名:
sayHello1
==>> Hi, liuyifei , nest to meet you!
方法名:
sayHello2
==>> Hi, yangmi , nest to meet you!

2、编译时处理的注解(虚处理器方式AbstractProcessor)
(1)定义注解

package com.newtouch.springboot.examples.annotation.compileAnnotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定义注解
 * 编译时处理的注解(虚处理器方式AbstractProcessor)
 */
@Target({ElementType.TYPE, ElementType.METHOD,ElementType.FIELD})//类、接口(包括注释类型)或枚举声明;方法声明;字段声明(包括枚举常量)
@Retention(RetentionPolicy.SOURCE)//保留策略使用SOURCE:注解仅存在于源码中,在class字节码文件中不包含
public @interface NameScanner {
}

(2)注解处理器

package com.newtouch.springboot.examples.annotation.compileAnnotation;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

/**
 * 注解处理器
 * 将每一个被注解的元素名称打印出来
 */
public class NameScannerProcessor extends AbstractProcessor {

    /**
     * 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一系列有用的工具类。
     * @param processingEnv
     */
    @Override
    public void init(ProcessingEnvironment processingEnv){
        super.init(processingEnv);
    }

    /**
     * 注解处理器的核心方法,处理具体的注解。主要功能基本可以理解为两个:
     *  1、获取同一个类中的所有指定注解修饰的Element;
     *      a、set参数,存放的是支持的注解类型,一般无用。
     *      b、RoundEnvironment参数,可以通过遍历获取代码中所有通过指定注解(例如在ButterKnife中主要就是@BindeView等)修饰的Element对象。通过Element对象可以获取字段名称,字段类型以及注解元素的值。
     *  2、创建Java文件;
     *      将同一个类中通过指定注解修饰的所有Element在同一个Java文件中实现初始化,这样做的目的是让在最终依赖注入时便于操作。
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){
        if(!roundEnv.processingOver()){
            for(Element element : roundEnv.getElementsAnnotatedWith(NameScanner.class)){
                String name = element.getSimpleName().toString();
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "element name: " + name);
            }
        }
        return false;
    }
}

(3)测试注解

package com.newtouch.springboot.examples.annotation.compileAnnotation;

@NameScanner
public class NameScannerTest {
    @NameScanner
    private String name;

    @NameScanner
    private int age;

    @NameScanner
    public String getName(){
        return this.name;
    }

    @NameScanner
    public void setName(String name){
        this.name = name;
    }

    public static void main(String[] args){
        System.out.println("--finished--");
    }
}

(4)手动编译

javac NameScanner.java
javac NameScannerProcessor.java
javac -processor NameScannerProcessor NameScannerTest.java

编译输出:
element name: NameScannerTest
element name: name
element name: age
element name: getName
element name: setName

3、AbstractProcessor方法作用解析
(1)void init(ProcessingEnvironment processingEnv)
  该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一系列有用的工具类
(2)SourceVersion getSupportedSourceVersion()
  返回此注释 Processor 支持的最新的源版本,该方法可以通过注解@SupportedSourceVersion指定。
(3)Set getSupportedAnnotationTypes()
  返回此 Processor 支持的注释类型的名称。结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 ” name.” 形式的名称,表示所有以 ” name.” 开头的规范名称的注释类型集合。最后,自身表示所有注释类型的集合,包括空集。
注意:
  Processor不应声明 “*”,除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。
(4)boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
  注解处理器的核心方法,处理具体的注解。主要功能基本可以理解为两个:
1)获取同一个类中的所有指定注解修饰的Element
  set参数,存放的是支持的注解类型,一般无用。
  RoundEnvironment参数,可以通过遍历获取代码中所有通过指定注解(例如在ButterKnife中主要就是@BindeView等)修饰的Element对象。通过Element对象可以获取字段名称,字段类型以及注解元素的值。
2)创建Java文件
  将同一个类中通过指定注解修饰的所有Element在同一个Java文件中实现初始化,这样做的目的是让在最终依赖注入时便于操作。

4、Java代码编译过程与注解处理器的注解处理过程
(1)Java代码编译过程
1)解析与填充符号表过程;
2)插入式注解处理器的注解处理过程;
3)语义分析与字节码生成过程。
在这里插入图片描述
(2)注解处理器的注解处理过程
  插入式注解处理器可以在编译器时读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,那么编译器将回到解析及填充符号表的过程重新处理,直到所有的插入式注解处理器都没有再对语法树进行修改为止。

参考文献:
https://blog.csdn.net/qq_16268979/article/details/108251227
https://blog.csdn.net/doc_sgl/article/details/50367083
https://www.jianshu.com/p/1acb3cd1fc8f

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值