Java自定义注解详解+实例

在很多框架中都使用了自定义注解,之前在项目中也用到了自定义注解来对代码做一些解耦,今天就给大家介绍下自定义注解的使用方法,对于自定义注解其实很简单,大家只要搞清楚如何定义自定义注解和如何获取定义的自定义注解内容就基本掌握了自定义注解,后续就可以在自己的项目中去使用自定义注解完成一些功能。

1. 元注解

JDK 1.5开始jdk就定义了元注解,用来定义其他的自定义注解,目前提供的元注解主要有4个:

  • @Target
  • @Retention
  • @Documented
  • .@Inherited

@Target

@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。

取值(ElementType)有:

  • 1.CONSTRUCTOR:用于描述构造器
  • 2.FIELD:用于描述域
  • 3.LOCAL_VARIABLE:用于描述局部变量
  • 4.METHOD:用于描述方法
  • 5.PACKAGE:用于描述包
  • 6.PARAMETER:用于描述参数
  • 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

我们在日常自定义注解中常用的类型有FIELD、METHOD、PARAMETER、TYPE。

@Retention

@Retention定义了该Annotation被保留的时间长短。
  
取值(RetentionPoicy)有:

  • 1.SOURCE:在源文件中有效(即源文件保留)
  • 2.CLASS:在class文件中有效(即class保留)
  • 3.RUNTIME:在运行时有效(即运行时保留)

我们在自定义注解用用的比较多的自然是RUNTIME了,这样保证注解在运行时是有效的,我们在其他框架中遇到的也大部分都是RUNTIME的。

@Documented

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

@Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

2.自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

注解参数支持的类型如下:

  • 1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
  • 2.String类型
  • 3.Class类型
  • 4.enum类型
  • 5.Annotation类型
  • 6.以上所有类型的数组

下面我们写个例子,定义几个自定义注解,并获取这些自定义注解中的值。

定义了一个可以作用于类、接口、枚举上的注解MyType。


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

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @Author: feiweiwei
 * @Description:
 * @Created Date: 16:10 17/9/22.
 * @Modify by:
 */
@Documented
@Target({ ElementType.TYPE})
@Retention(RUNTIME)
public @interface MyType {

    String value() default "";
    String className() default "";
}

定义了一个可以作用于类的方法上的注解MyMethod。

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

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @Author: feiweiwei
 * @Description:
 * @Created Date: 16:12 17/9/22.
 * @Modify by:
 */
@Documented
@Target({ ElementType.METHOD})
@Retention(RUNTIME)
public @interface MyMethod {
    String value() default "";
    String methodName() default "";

}

定义了一个可以作用于类内部变量的注解MyField。


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

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @Author: feiweiwei
 * @Description:
 * @Created Date: 16:07 17/9/22.
 * @Modify by:
 */
@Documented
@Target({ ElementType.FIELD})
@Retention(RUNTIME)
public @interface MyField {

    String value() default "";
    String name() default "";
    String type() default"String";

}

下面我们写个例子,定义一个类使用这些注解。

import com.monkey01.annotation.MyField;
import com.monkey01.annotation.MyMethod;
import com.monkey01.annotation.MyType;

/**
 * @Author: feiweiwei
 * @Description:
 * @Created Date: 16:14 17/9/22.
 * @Modify by:
 */
@MyType(value = "test", className = "TestClass")
public class TestClass {

    @MyField(value = "testNum", name="num", type = "int")
    private int num;

    @MyField(value = "testName", name="name", type = "String")
    private String name;

    @MyMethod(value = "print ")
    public String print(){
        return "hello annotation";
    }
}

从例子中可以看到,在类名上面使用了MyType的注解,在内部申明的变量上都定义了MyField,在内部方法上使用了MyMethod注解,java也能自动识别这些自定义注解。

写好了测试类后,我们下一步要做的就是如何获取这些自定义注解上定义的属性值,因为定义自定义注解的目的是为了将一些公共的功能,能够比较优雅的与实际业务代码隔离开。获取注解上的属性使用的方法实际上就是利用反射来获取其中的注解。

下面我们写一个类来实现读取注解属性并打印出来。

import org.reflections.Reflections;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * @Author: feiweiwei
 * @Description:
 * @Created Date: 16:14 17/9/22.
 * @Modify by:
 */
public class PrintAnnotation {

    private String allAnnotation = new String();

    public String printAllAnnotation(){
        Set<Class<?>> clazzes = new Reflections("com.monkey01").getTypesAnnotatedWith(MyType.class);

        for (Class<?> clazz : clazzes) {
            printMyType(clazz);
        }
        System.out.println(allAnnotation);
        return allAnnotation;
    }

    private void printMyType(Class<?> clazz) {
        MyType myType = clazz.getAnnotation(MyType.class);
        allAnnotation = allAnnotation + clazz.getName() + ": " + myType.value() + "-" + myType.className() + "\n";
        Field[] fields = clazz.getDeclaredFields();
        for(Field field : fields){
            MyField myField = field.getAnnotation(MyField.class);
            if(myField != null) {
                allAnnotation = allAnnotation + myField.value() + "-" + myField.name() + "-" + myField.type() + "\n";
            }
        }
        Method[] methods = clazz.getMethods();
        for(Method method : methods){

            MyMethod myMethod = method.getAnnotation(MyMethod.class);
            if(myMethod != null) {
                allAnnotation = allAnnotation + myMethod.methodName() + "-" + myMethod.value() + "\n";
            }
        }

    }

}

从代码可以看到首先通过反射来获取com.monkey01包下的使用了MyType注解的类。

        Set<Class<?>> clazzes = new Reflections("com.monkey01").getTypesAnnotatedWith(MyType.class);

再循环取出使用了MyType注解的类,并打印其中的FIELD和METHOD,同样是使用反射获取MyField和MyMethod注解。

要获取一个注解定义的属性值,需要先获取到这个注解类对象,所有的注解类对象获取方法都是一样的,调用class的getAnnotation方法,入参为这个注解类。

public <A extends Annotation> A getAnnotation(Class<A> annotationClass) 
MyType myType = clazz.getAnnotation(MyType.class);

获取到注解类对象后就可以调用定义的方法,获取这些方法属性值了,是不是很简单。

        allAnnotation = allAnnotation + clazz.getName() + ": " + myType.value() + "-" + myType.className() + "\n";

要获取Field和Method的属性需要先通过反射获取到获取这个类的Field和Method。

Field[] fields = clazz.getDeclaredFields();

Method[] methods = clazz.getMethods();

对于MyField和MyMethod获取注解属性的方法和获取MyType是一样的。

总体看下来是不是很简单,源码已经传到git上,没看懂的同学跑下例子就秒懂了,也可以在评论区留言提问。

源码地址:https://github.com/feiweiwei/java-monkey01-sample

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容