神奇的Annotation
来源:互联网 发布:淘宝打假赚钱 编辑:程序博客网 时间:2024/05/21 18:42
什么是元数据(metadata)
元数据由metadata译来,所谓的元数据就是“关于数据的数据”,更通俗的说就是描述数据的数据,对数据及信息资源的描述性信息.比如说一个文本文件,有创建时间,创建人,文件大小等数据,这都可以理解为是元数据.
在java中,元数据以标签的形式存在java代码中,它的存在并不影响程序代码的编译和执行,通常它被用来生成其它的文件或运行时知道被运行代码的描述信息。java当中的javadoc和注解都属于元数据.
什么是注解(Annotation)?
注解是从java 5.0开始加入,可以用于标注包,类,方法,变量等.比如我们常见的@Override,再或者Android源码中的@hide,@systemApi,@privateApi等
对于@Override,多数人往往都是知其然而不知其所以然,今天我就来聊聊Annotation背后的秘密,开始正文.
元注解
元注解就是定义注解的注解,是java提供给我们用于定义注解的基本注解.在java.lang.annotation包中我们可以看到目前元注解共有以下几个:
- @Retention
- @Target
- @Inherited
- @Documented
- @interface
下面我们将集合@Override注解来解释着5个基本注解的用法.
@interface
@interface是java中用于声明注解类的关键字.使用该注解表示将自动继承java.lang.annotation.Annotation类,该过程交给编译器完成.
因此我们想要定义一个注解只需要如下做即可,以@Override注解为例
public @interface Override {}
需要注意:在定义注解时,不能继承其他注解或接口.
@Retention
@Retention:该注解用于定义注解保留策略,即定义的注解类在什么时候存在(源码阶段 or 编译后 or 运行阶段).该注解接受以下几个参数: RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME
,其具体使用及含义如下:
来看一下@Override注解的保留策略:
@Retention(RetentionPolicy.SOURCE)public @interface Override {}
这表明@Override注解只在源码阶段存在,javac在编译过程中去去掉该注解.
@Target
该注解用于定义注解的作用目标,即注解可以用在什么地方,比如是用于方法上还是用于字段上,该注解接受以下参数:
以@Override为例,不难看出其作用目标为方法:
@Target(ElementType.METHOD)public @interface Override {}
到现在,通过@interface,@Retention,@Target已经可以完整的定义一个注解,来看@Override完整定义:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}
@Inherited
默认情况下,我们自定义的注解用在父类上不会被子类所继承.如果想让子类也继承父类的注解,即注解在子类也生效,需要在自定义注解时设置@Inherited.一般情况下该注解用的比较少.
@Documented
该注解用于描述其它类型的annotation应该被javadoc文档化,出现在api doc中.
比如使用该注解的@Target会出出现在api说明中.
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Target { ElementType[] value();}
借助@Interface,@Target,@Retention,@Inherited,@Documented这五个元注解,我们就可以自定义注解了,其中前三个注解是任何一个注解都必备具备的.
你以为下面会直接来将如何自定义注解嘛?不,你错了,我们还是来聊聊java自带的几个注解.
系统注解
java设计者已经为我们自定义了几个常用的注解,我们称之为系统注解,主要是这三个:
如果你已经完全知道这三者的用途,跳过这一小节,直接往下看.
@Override
它用作标注方法,说明被标注的方法重写了父类的方法,其功能类似断言.如果在一个没有重写父类方法的方法上使用该注解,java编译器将会以一个编译错误提示:
@Deprecated
当某个类型或者成员使用该注解时意味着
编译器不推荐开发者使用被标记的元素.另外,该注解具有”传递性”,子类中重写该注解标记的方法,尽管子类中的该方法未使用该注解,但编译器仍然报警.
public class SimpleCalculator { @Deprecated public int add(int x, int y) { return x+y; }}public class MultiplCalculator extends SimpleCalculator { // 重写SimpleCalculator中方法,但不使用@Deprecated public int add(int x, int y) { return Math.abs(x)+Math.abs(y); }}//test codepublic class Main { public static void main(String[] args) { new SimpleCalculator().add(3, 4); new MultiplCalculator().add(3,5); }}
对于像new SimpleCalculator().add(3,4)这种直接调用的,Idea会直接提示,而像第二种则不是直接提示:
但是在编译过程中,编译器都会警告:
需要注意@Deprecated和@deprecated这两者的区别,前者被javac识别和处理,而后者则是被javadoc工具识别和处理.因此当我们需要在源码标记某个方法已经过时应该使用@Deprecated,如果需要在文档中说明则使用@deprecated,因此可以这么:
public class SimpleCalculator { /** * @param x * @param y * @return * * @deprecated deprecated As of version 1.1, * replace by <code>SimpleCalculator.add(double x,double y)</code> */ @Deprecated public int add(int x, int y) { return x+y; } public double add(double x,double y) { return x+y; }}
@SuppressWarnning
该注解被用于有选择的关闭编译器对类,方法,成员变量即变量初始化的警告.该注解可接受以下参数:
滋溜一下,我们飞过了2016年,不,是看完了上一节.继续往下飞.
自定义注解
了解完系统注解之后,现在我们就可以自己来定义注解了,通过上面@Override的实例,不难看出定义注解的格式如下:
public @interface 注解名 {定义体}
定义体就是方法的集合,每个方法实则是声明了一个配置参数.方法的名称作为配置参数的名称,方法的返回值类型就是配置参数的类型.和普通的方法不一样,可以通过default关键字来声明配置参数的默认值.
需要注意:
- 此处只能使用public或者默认的defalt两个权限修饰符
- 配置参数的类型只能使用基本类型(byte,boolean,char,short,int,long,float,double)和String,Enum,Class,annotation
- 对于只含有一个配置参数的注解,参数名建议设置中value,即方法名为value
- 配置参数一旦设置,其参数值必须有确定的值,要不在使用注解的时候指定,要不在定义注解的时候使用default为其设置默认值,对于非基本类型的参数值来说,其不能为null.
像@Override这样,没有成员定义的注解称之为标记注解.
现在我们来自定义个注解@UserMeta,这个注解目前并没啥用,就是为了演示一番:
@Documented@Target(ElementType.CONSTRUCTOR)@Retention(RetentionPolicy.RUNTIME)public @interface UserMeta { public int id() default 0; public String name() default ""; public int age() default ;}
有了米饭,没有筷子没法吃啊(手抓饭的走开),下面来看看如何处理注解.
注解处理器
上面我们已经学会了如何定义注解,要想注解发挥实际作用,需要我们为注解编写相应的注解处理器.根据注解的特性,注解处理器可以分为运行时注解处理和编译时注解处理器.运行时处理器需要借助反射机制实现,而编译时处理器则需要借助APT来实现.
无论是运行时注解处理器还是编译时注解处理器,主要工作都是读取注解及处理特定注解,从这个角度来看注解处理器还是非常容易理解的.
先来看看如何编写运行时注解处理器.
运行时注解处理器
熟悉java反射机制的同学一定对java.lang.reflect包非常熟悉,该包中的所有api都支持读取运行时Annotation的能力,即属性为@Retention(RetentionPolicy.RUNTIME)的注解.
在java.lang.reflect中的AnnotatedElement接口是所有程序元素的(Class,Method)父接口,我们可以通过反射获取到某个类的AnnotatedElement对象,进而可以通过该对象提供的方法访问Annotation信息,常用的方法如下:
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
返回该元素上存在的制定类型的注解Annotation[] getAnnotations()
返回该元素上存在的所有注解default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
返回该元素指定类型的注解default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
返回直接存在与该元素上的所有注释default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
返回直接存在该元素岸上某类型的注释Annotation[] getDeclaredAnnotations()
返回直接存在与该元素上的所有注释编写运行时注解大体就需要了解以上知识点,下面来做个小实验.
简单示例
首先我们用一个简单的实例来介绍如何编写运行时注解处理器:我们的系统中存在一个User实体类:
public class User { private int id; private int age; private String name; @UserMeta(id=1,name="dong",age = 10) public User() { } public User(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } //...省略setter和getter方法 @Override public String toString() { return "User{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; }}
我们希望可以通过 @UserMeta(id=1,name="dong",age = 10)
(这个注解我们在上面提到了)来为设置User实例的默认值。
自定义注解类如下:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.CONSTRUCTOR)public @interface UserMeta { public int id() default 0; public String name() default ""; public int age() default 0;}
该注解类作用于构造方法,并在运行时存在,这样我们就可以在运行时通过反射获取注解进而为User实例设值,看看如何处理该注解吧.
运行时注解处理器:
public class AnnotationProcessor { public static void init(Object object) { if (!(object instanceof User)) { throw new IllegalArgumentException("[" + object.getClass().getSimpleName() + "] isn't type of User"); } Constructor[] constructors = object.getClass().getDeclaredConstructors(); for (Constructor constructor : constructors) { if (constructor.isAnnotationPresent(UserMeta.class)) { UserMeta userFill = (UserMeta) constructor.getAnnotation(UserMeta.class); int age = userFill.age(); int id = userFill.id(); String name = userFill.name(); ((User) object).setAge(age); ((User) object).setId(id); ((User) object).setName(name); } } }}
测试代码:
public class Main { public static void main(String[] args) { User user = new User(); AnnotationProcessor.init(user); System.out.println(user.toString()); }}
运行测试代码,便得到我们想要的结果:
User{id=1, age=10, name=’dong’}
这里通过反射获取User类声明的构造方法,并检测是否使用了@UserMeta注解。然后从注解中获取参数值并将其赋值给User对象。
正如上面提到,运行时注解处理器的编写本质上就是通过反射获取注解信息,随后进行其他操作。编译一个运行时注解处理器就是这么简单。运行时注解通常多用于参数配置类模块。
- 神奇的Annotation
- Annotation(1)------------annotation的位置
- Annotation-JDK的元Annotation
- 神奇的的调色板
- 神奇的*号
- 一组神奇的图片
- 神奇的一天
- 神奇的预言
- 神奇的jsfl!
- 神奇的VIM
- 神奇的猴子Mono
- 神奇的xx宏
- 神奇的视觉艺术
- 神奇的上班路上
- 神奇的视觉艺术
- 神奇的 DataGrid
- 神奇的食物
- 神奇的 DataGrid
- 上海启动千亿级集成电路项目
- Redis/Sentinel各配置项详细说明
- 欢迎使用CSDN-markdown编辑器
- C#基础:Equals()与运算符==的区别分析
- lua的包库
- 神奇的Annotation
- struts.xml配置详解
- https连接都发生了什么
- MySql Workbench的E-R图设计小技巧
- 解决 ajax请求 跨域问题
- 嵌入式学习笔记之gcc编译
- screen capture using CGDisplayStreamCreateWithDispatchQueue
- Redis数据库的备份与恢复
- layer中type=2的一些使用方法(添加成功后才关闭路由、刷新页面列表)