java注解机制详解

注解也被称为元数据,它是我们在代码中添加信息的一种形式化方法,使我们可以在稍后某个时刻非常方便地使用这些数据。注解是java se5中众多引入的变化之一。他们可以提供用来完整的描述程序所需的信息。而这些信息我们现在不能通过java的基本语法来表达。实际上,相比较使用java提供的注解,在使用中定义自己的新注解是更加常用的。

(一)java内置标准注解

java在它最核心的lang包中定义了三种标准的注解,这三个注解通过@符号就可以简单的使用了。

1.@Override

表示当前方法定义将覆盖超类中的方法。如果将方法拼写错误,或者签名对不上被覆盖的方法,编译器会报错。使用的方法非常的简单,我们就在覆盖的那个新方法上面一行添加这个注释就可以。增加了可读性。

2.@Deprecated

如果程序员使用了注解为他的元素,那么编译器会发出警告信息。就是说,被这个注解标记的方法是要被废弃的方法,我们不建议去使用它。我们下面用一个在Date中的构造器方法来举例(毕竟Date类已经基本全都是废弃方法,我们不需要注意下面方法中实际的作用,举例只是为了说明注解的用处)。和重写一样,在方法上一行写出。

 @Deprecated
    public Date(int year, int month, int date, int hrs, int min, int sec) {
        int y = year + 1900;
        // month is 0-based. So we have to normalize month to support Long.MAX_VALUE.
        if (month >= 12) {
            y += month / 12;
            month %= 12;
        } else if (month < 0) {
            y += CalendarUtils.floorDivide(month, 12);
            month = CalendarUtils.mod(month, 12);
        }
        BaseCalendar cal = getCalendarSystem(y);
        cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.getDefaultRef());
        cdate.setNormalizedDate(y, month + 1, date).setTimeOfDay(hrs, min, sec, 0);
        getTimeImpl();
        cdate = null;
    }

3.@SuppressWarnings

关闭不当的编译器警告信息。在java se5之前这个注解是没作用的,现在我们使用他来表示忽略下面的警告。理论上使用频率并不是很高,我们的代码出现一些警告很正常,很少会为每个警告打上这个注解。

(二)java中的四种元注解

元注解专门负责注解其他的注解。看到这里,或许我们对这种概念还不太熟悉。但是没关系,我们先来看一下这四种元注解的作用,在介绍了之后,我们会去描述他们是怎么样使用的。

1.@Target

@Target表示该注解可以用于什么地方,可能的ElementType参数有以下这些种:

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

需要注意的是,如果我们在自定义的注解中不使用这个元注解,那么会默认该注解可以用于任何地方

2.@Retention

@Retention表示需要在什么级别保存该注解信息。可选的RetentionPoilcy参数包括:

  1. SOURCE:注解将被编译器抛弃
  2. CLASS:注解在class文件中可用,但是在jvm中会被抛弃
  3. RUNTIME:jvm运行期间也会保留注解,因此可以通过反射机制读取注解信息

3.@Documented

@Documented将该注解包含在javadoc中。

4.@Inherited

@Inherited允许子类继承父类中的注解。

(三)自定义类型注解

每当你创建描述符性质的类或接口时,一旦其中包含了一些重复性的工作,那我们就可以考虑使用注解来简化与自动化这个过程,就像是spring中大量采用的那种方法。而且注解是真正的语言级别的概念,一旦构造出来,就享有编译期的类型检查保护。

1.一个自定义类型注解的例子

package Annotation;

import java.lang.annotation.*;
//几乎所有和注解有关的内容都在这个包内

@Target(ElementType.METHOD)
// 这里使用Target注解,并且我们设置为METHOD,表示CustomAnn这个注解作用于方法

@Retention(RetentionPolicy.RUNTIME)
// 这里使用Retention注解,设置为RUNTIME,表示这个注解的有效期为jvm中可存在

/**
 * 
 * @author QuinnNorris
 *
 * 我们自定义一个叫做UseCase的注解类
 */
public @interface UseCase {
    //这里不声明为class,而是使用@interface来表示注解

    public int id();
    //这是注解的属性,我们写成这种类似方法的写法。返回值表示了这个属性的类型。

    public String description() default " no description";
    //这个是另外一个属性,这个属性有默认值。
}

上面的这个例子已经很详细的诠释了一个注解应该如何定义。上面的例子中注解内有两个属性,我们也可以直接写一对大括号而不写任何的属性,我们将这样不写任何属性的注解称之为标记注解

2.在相应的位置使用注解

我们既然已经给出了这个注解的实现情况,那么我们接下来继续看一下使用的情况:

package Annotation;

public class UsingAnno {

    @UseCase(id=1,description="this is 1")
    //用我们自定义创建的UseCase注解来标注one方法
    public void one(){
        //此处写方法中逻辑代码
    }

    @UseCase(id=2,description="this is 2")
    //用我们自定义创建的UseCase注解来标注two方法
    public void two(){
        //此处写方法中逻辑代码
    }

}

就是这样,我们可以为自己写的代码打上自定义的注解。

那么我们这样自定义注解,然后自己给自己写的代码打上注解有什么用呢?我们可以通过UseCase注解来跟踪一个项目中所有的用例(UseCase),如果一个方法或者一组方法实现了某个用例,那么程序员就可以为此方法加上这个注解。于是项目经理通过计算已经打上的注解数量,就知道了已经实现的用例数量,就可以很好的掌握项目的进展。如果要更新或者修改系统只要找到对应的用例即可。总而言之,注解将标注的作用达到了极致。

(四)注解处理器

我们在上面说到,注解可以被计算、可以用代码来统计和做更复杂的事情。这正是注解优于注释的地方。在使用注解的时候,最关键的就是创建和使用注解处理器。我们下面要实现一个注解处理器,处理我们上面创建和使用的UseCase注解。我们会需要用到java的反射机制。

java反射机制传送门:http://blog.csdn.net/quinnnorris/article/details/54809297

1.注解处理器实例

package Annotation;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 
 * @author QuinnNorris
 * 
 *         注解处理器
 */
public class UseCaseTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        List<Integer> useCases = new ArrayList<Integer>();
        Collections.addAll(useCases, 1, 2);
        TestUseCase(useCases, UsingAnno.class);
    }

    private static void TestUseCase(List<Integer> useCases, Class<?> cl) {
        // TODO Auto-generated method stub
        for (Method m : cl.getDeclaredMethods()) {
            // 通过反射的方法,获取这个类中所有的方法
            UseCase uc = m.getAnnotation(UseCase.class);
            // 实例化一个UseCase对象,将方法的注解存入
            if (uc != null) {
                System.out.println("useCase id = " + uc.id()
                        + " ; description = " + uc.description());
                useCases.remove(new Integer(uc.id()));
            }
        }
    }

}

这是一种注解处理器,可能功能并没有想象中的那么复杂,但是对于只是想把功能展示一下已经足够了。这个方法中通过调用getDeclaredMethods方法获取了所有方法(包括由private修饰的方法),再去实例化一个注解类的对象,将获取这些方法的注解赋值给这个引用,我们再使用这个对象的属性进行我们想要进行的操作。这就完成了一个基本的注解处理器的功能。

2.注解元素的限制

在上例中,@UseCase注解由UseCase.java类来定义,其中包括int类型和String类型的元素,那么这些元素的类型有限制么?答案是:有的。

注解元素可用的类型:
1. 所有基本类型(int、float、boolean等)
2. String
3. Class
4. enum
5. Annotation
6. 以上所有类型的数组

理论上不可以使用包装类,但是有自动装箱拆箱的存在,使用包装类并不是问题。注解也可以作为元素的类型,也就是说注解可以嵌套。这是一个很有趣的技巧。

3.默认值设置

在自定义的注解中,java规定——元素不能有不确定的值。就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。对于那些非基本类型的元素,java规定——不能用null作为其值。就是说,有的时候,我们为了表示出一个元素不存在值,往往会先在定义的时候定义成空字符串或者负数。
也就是说,我们最开始的UseCase注解类,习惯上,我们要这样去书写:

package Annotation;

import java.lang.annotation.*;
//几乎所有和注解有关的内容都在这个包内

@Target(ElementType.METHOD)
// 这里使用Target注解,并且我们设置为METHOD,表示CustomAnn这个注解作用于方法

@Retention(RetentionPolicy.RUNTIME)
// 这里使用Retention注解,设置为RUNTIME,表示这个注解的有效期为jvm中可存在

/**
 * 
 * @author QuinnNorris
 *
 * 我们自定义一个叫做UseCase的注解类
 */
public @interface UseCase {
    //这里不声明为class,而是使用@interface来表示注解

    public int id() default -1;
    //这是注解的属性,我们写成这种类似方法的写法。返回值表示了这个属性的类型。

    public String description() default "";
    //这个是另外一个属性,这个属性有默认值。
}

4.注解不可被继承

不能使用关键字extends来继承某个@interface。或许有很多情况下,如果我们能这么做会带来很多的好处,但是事实上,这样做并不被允许,而且以后也可能一直不会被允许(没有人向java提出这种建议)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值