Java注解(一):注解介绍及自定义注解入门

Java注解(一):注解介绍及自定义注解入门

目录

前言

现如今框架开发不断的增加注解去替代许多原本的XML配置,打开任意一个框架的源码库都能看见聆郎满目、各式各样的注解。你是否也和我一样,对spring中一个@EnableScheduling便可开启对计划任务的支持等等神秘的注解充满了疑惑和好奇?带着对注解这门技术的敬畏。我决定在学习的过程中写下这篇博客。

对于注解这项技术,我将打算写下三篇博客来进行讲解,第一篇博客将会介绍注解的来由及在Java中自定义注解的基本知识第二篇将会讲解如何编写一个简单的注解处理类从而为我们的自定义注解附上他的价值(重点!)第三篇将会仿写一个扫描包路径,找到该路径下注解了指定注解的类。(就像扫描@Controller、@Service等那样)

什么是注解(Annotation)?

注解就是一个与源代码紧耦合的元数据,在JDK1.5时引入。

为什么要引入注解

在这里首先要提出一个被广泛应用于描述元数据的技术[XML(EXtensible Markup Language)][1],相信各位Javaer都有集成框架(如spring)等的体验,XML在框架中的表现便是代码与配置进行分离。随着XML技术的不断普及人们渐渐开始抱怨XML难以维护,有时候程序员可能需要将一些元数据与代码进行紧耦合。这点与XML的初衷是相矛盾的。于是乎,Annotation便应运而生。

对于项目中的一些全局常量及参数的配置,可以通过XML将这些配置与代码进行分离。而对于一些类、方法、变量的功能描述(如spring mvc的@Controller将该类定义为控制器层)则可以使用Annotation。

目前,许多框架将XML和Annotation两种方式结合使用,平衡两者之间的利弊。

正式开始学习自定义注解

要想写出自己的自定义注解,我们需要了解到JDK为我们提供的元注解以及定义注解参数的语法。

元注解

元注解就是注解在注解上的注解(好吧,好拗口,就是可以描述自定义注解的注解。)在JDK1.8中共提供了五种元注解

1.@Target(JDK1.5中引入,用于描述注解的注解范围)
2.@Retention(JDK1.5中引入,用于描述注解被保存的范围)
3.@Documented(JDK1.5中引入,用于标记生成文档)
4.@Inherited(JDK1.5中引入,定义该注解可被子类继承)
5.@@Repeatable(JDK1.8中引入,重复注解)


1. @Target

作用:定义了自定义注解可以作用的位置,比如注解在字段上、方法参数上、类上、方法上等。
通过源代码可以得到@Target包含一个名为value的参数,参数的类型是一个枚举型ElementType。其中包括10个枚举值,含义分别如下:

ElementType.TYPE:注解可作用于类、接口、枚举类型
ElementType.FIELD:注解可作用于实例变量
ElementType.METHOD注解可作用于方法
ElementType.PARAMETER注解可作用于方法参数
ElementType.CONSTRUCTOR注解可作用于构造方法
ElementType.LOCAL_VARIABLE注解可作用于局部变量
ElementType.ANNOTATION_TYPE 元注解,也就是作用于其他Annotation上
ElementType.PACKAGE 用于记录java文件的package信息
ElementType.TYPE_USE 类型使用声明,JDK1.8中引入
ElementType.TYPE_PARAMETER 类型参数声明,JDK1.8中引入

//定义了一个可以作用于类、接口、枚举类型以及实例变量的自定义注解
@Target({ElementType.TYPE,ElementType.FIELD})
public @interface FirstAnnotation {
    public String name() default "cjl";
}

@FirstAnnotation(name="world")
public class AnnotationTest {
    @FirstAnnotation
    private String name;

}

注:当自定义注解不使用@Target时,默认为支持全部作用范围。


@2.Retention

作用:用于描述该注解保存的时间(范围)。
通过源代码可以看出@Retention包含一个value参数,其中参数的类型是RetentionPolicy枚举型,其中包含了三个值,含义分别如下:

RetentionPolicy.SOURCE:注解仅仅在文件中保留,当JAVA文件被编译器编辑时将丢弃该注解。(也就是说通过反编译class文件是无法还原注解的。)
RetentionPolicy.CLASS:注解会被编译器保留在class文件中,但是不会存于JVM内,所以通过反射是无法获取的,这是默认级别。
RetentionPolicy.RUNTIME:注解会被编译器保留在class文件中并且会被读取进JVM内,所以通过反射是可以获取的。

那么接下来简单的演示一下,首先编写一个通过反射,读取一个Class上的@FirstAnnotation注解的属性的工具类(比较粗糙的简单例子,第二章写注解处理器时会修改。)

public abstract class AnnotationResolveUtils{
    public static String resolveByClass(Class<?> obj){
        if(obj == null)
            return null;
        //判断是否包含FirstAnnotation注解
        if(obj.isAnnotationPresent(FirstAnnotation.class)){
            FirstAnnotation ann = obj.getAnnotation(FirstAnnotation.class);
            return ann.name();
        }else{
            return null;
        }
    }
}

那么现在把@FirstAnnotation修改一下并读取

//保留范围为CLASS,说明不会在JVM运行时保存注解,也就是说反编译无法得到注解。
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface FirstAnnotation {
    public String name() default "cjl";
}

@FirstAnnotation(name="world")
public class AnnotationTest {
    public static void main(String[] args) {
        String name = AnnotationResolveUtils.resolveByClass(AnnotationTest.class);
        System.out.println(name);
    }
}

执行上面的代码会发现,main中打印出来的name是个null,原因是因为我们的注解保存范围是Class所以无法通过反射获得。

那么现在在将@FirstAnnotation修改为保存于JVM的范围。

@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FirstAnnotation {
    public String name() default "cjl";
}

此时再次执行主方法会发现,能够成功打印出注解上所配置的属性值。


3.@Documented

作用:标记该注解将会被记录在javaDoc文档中。

该注解是个标记性注解,没有任何的参数。使用这个注解来标记自定义注解以后,会使得javaDoc命令将你的注解也写入javaDoc文档中。

修改@FirstAnnotation代码如下:

@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented//说明@FirstAnnotation将会被javaDOC命令记录。
public @interface FirstAnnotation {
    public String name() default "cjl";
}

执行javaDoc命令得到API文档……
javaDoc生成的API文档截图
由图片可以看出,我们的自定义注解被保存了下来。


4.@Inherited

作用:使注解可以被子类继承。

该注解是一个标记注解,没有任何的参数,使用该注解来标记自定义注解后。自定义注解可以被子类继承。

修改@FirstAnnotation

@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FirstAnnotation {
    public String name() default "cjl";
}

//父类
@FirstAnnotation(name="world")
public class AnnotationTest {

    private String name;

    public static void main(String[] args) {
    //读取子类@FirstAnnotation的属性值
        String name = AnnotationResolveUtils.resolveByClass(Son.class);
        System.out.println(name);
    }
}
//子类
class Son extends AnnotationTest{

}

执行代码后会成功打印出”world”,证明注解确实被子类所继承。

注:当注解作用于字段、方法等元素时,只要该字段、方法不是private访问修饰符(private修饰的无法被继承),注解将伴随着字段、方法继承给子类。


5.@Repeatable

作用:通过这个定义重复注解。

该注解由JDK1.8引入,其中包含一个Class

//该注解用于存储重复注解@FirstAnnotation,以数组的形式储存。
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface FirstAnnotations{
    FirstAnnotation[] value();
}

//那么现在 我们的@FirstAnnotation 只需要通过元注解@Repeatable定义用于存储它的注解.class即可。

@Repeatable(FirstAnnotations.class)
public @interface FirstAnnotation {
    public String name() default "cjl";
}

//重复注解的使用
public class AnnotationTest {

    @FirstAnnotation(name="one")
    @FirstAnnotation(name="two")
    private String name;

    @FirstAnnotations({@FirstAnnotation(name = "one"),@FirstAnnotation(name = "two")})
    private String name2;
}
注解参数定义

首先我们来定义一个自定义注解@FirstAnnotation,其中包含一个参数为String类型,名字为name,默认值是”cjl”的参数。(稍后会详细讲解@Target,@Target现在在这里的意思是,让我的自定义注解@FirstAnnotation能够注解在类上)

@Target(ElementType.TYPE)
public @interface FirstAnnotation {//自定义注解在被解析后会默认继承Annotation接口,这点与enum枚举会默认继承Enum类似。
    public String name() default "cjl";
}

可以看出,注解参数的定义有些类似于Java类中的方法,只是没有了”{}”这个方法体,多了default XXX 默认值关键字。
注解参数的构造分别有:访问修饰符(只可为public或default默认)、返回值(定义了该参数的数据类型,只可为基本类型、String 、enum以及Annotation类型)、方法名(等同于参数名)、默认值(定义的值必须与返回值类型一致。)

那么现在我们便可以将@FirstAnnotation注解在类上了

//@FirstAnnotation 等价于 @FirstAnnotation(name = "cjl") 因为设置了name的默认值。
@FirstAnnotation(name="world")
public class AnnotationTest {

}

总结

在本篇博客中,我们了解到了什么是注解,为什么要使用注解,并且介绍了Java中提供的元注解的含义以及基本语法。相信大家现在都可以很轻松的写出一些能标记在你想要标记的位置的自定义注解了吧?但是呢!注解实际上只是一个元数据,一个躯壳!只有通过注解处理器才能为你的注解赋予灵魂!没有注解处理器的注解和普通注释并没有什么区别,那么在下一篇博客中,我们将开始编写简单的注解处理器!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值