一起来学Java注解(Annotation)
一. 什么是annotation
我们在平时的开发过程中看到很多如@override,@suppresswarnings,@test等样式的代码就是注解,注解是放到类、构造器、方法、属性、参数前的标记。
二. annotation的作用
给某个类、方法..添加了一个注解,这个环节仅仅是做了一个标记,对代码本身并不会造成任何影响,需要后续环节的配合,需要其他方法对该注解赋予业务逻辑处理。就如同我们在微信上发了一个共享定位,此时并没有什么用,只有当后面其他人都进入了这个共享定位,大家之间的距离才能明确,才知道该怎么聚在一起。
注解分为三类:
2.1 编译器使用到的注解
如@override,@suppresswarnings都是编译器使用到的注解,作用是告诉编译器一些事情,而不会进入编译后的.class文件。
@override:告诉编译器检查一下是否重写了父类的方法;
@suppresswarnings:告诉编译器忽略该段代码产生的警告;
对于开发人员来说,都是直接使用,无需进行其他操作
2.2 .class文件使用到的注解
需要通过工具对.class字节码文件进行修改的一些注解,某些工具会在类加载的时候,动态修改用某注解标注的.class文件,从而实现一些特殊的功能,一次性处理完成后,并不会存在于内存中,都是非常底层的工具库、框架会使用,对于开发人员来说,一般不会涉及到。
2.3 运行期读取的注解
一直存在于jvm中,在运行期间可以读取的注解,也是最常用的注解,如spring的@controller,@service,@repository,@autowired,mybatis的@mapper,junit的@test等,这类注解很多都是工具框架自定义在运行期间发挥特殊作用的注解,一般开发人员也可以自定义这类注解。
三. 定义annotation
我们使用@interface来定义一个注解
/** * 定义一个table注解 */ public @interface table { string value() default ""; }
/** * 定义一个colum注解 */ public @interface colum { string value() default ""; string name() default ""; string dicttype() default ""; }
这样就简单地将一个注解定义好了
我们上面定义的注解主要用到了string类型,但实际上还可以是基本数据类型(不能为包装类)、枚举类型。
注解也有一个约定俗成的东西,最常用的参数应该命名为value,同时一般情况下我们都会通过default参数设置一个默认值。
但这样是不是就满足于我们的使用了呢,我想把@table
注解仅用于类上,@colum
注解仅用于属性上,怎么办?而且开始提到的三类注解,一般开发人员用的都是运行期的注解,那我们定义的是吗?
要回答这些问题,就需要引入一个概念“元注解”。
3.1 元注解
可以修饰注解的注解即为元注解,java已经定义了一些元注解,我们可以直接使用。
3.1.1 @target
顾名思义指定注解使用的目标对象,参数为elementtype[]
public @interface target { /** * returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ elementtype[] value(); }
而下面是elementtype枚举中定义的属性,不设置target的时候,除了type_parameter,type_use,其他地方都相当于配置上了。
public enum elementtype { /** 通过elementtype.type可以修饰类、接口、枚举 */ type, /** 通过elementtype.field可以修饰类属性 */ field, /** 通过elementtype.method可以修饰方法 */ method, /** 通过elementtype.parameter可以修饰参数(如构造器或者方法中的) */ parameter, /** 通过elementtype.constructor可以修改构造器 */ constructor, /** 通过elementtype.local_variable可以修饰方法内部的局部变量 */ local_variable, /** 通过elementtype.annotation_type可以修饰注解 */ annotation_type, /** 通过elementtype.package可以修饰包 */ package, /** * 可以用在type的声明式前 * * @since 1.8 */ type_parameter, /** * 可以用在所有使用type的地方(如泛型、类型转换等) * * @since 1.8 */ type_use }
我们主要说一下elementtype.package和1.8添加的elementtype.type_parameter和elementtype.type_use
elementtype.package
@target(elementtype.package) public @interface table { string value() default ""; }
含义是用来修饰包,但我们用来修饰包的时候却提示错误
我们按照提示创建package-info.java文件,这里需要注意一下,通过ide 进行new --> java class是创建不了的,需要通过new file文件创建
@table package annotation; class packageinfo { public void hello() { system.out.println("hello"); } }
elementtype.type_parameter和elementtype.type_use
这两个一起说,因为它们有相似之处。都是java1.8后添加的
@target(elementtype.type_use) public @interface noneempty { string value() default ""; }
@target(elementtype.type_parameter) public @interface noneblank { string value() default ""; }
很明显使用elementtype.type_parmeter修饰的注解@noneblank无法在泛型使用的时候编译通过,仅能用于类的泛型声明,而通过elementtype.type_use修饰的注解@noneempty可以。
3.1.2 @retention
可以用于定义注解的生命周期,参数为枚举retentionpolicy,包括了source,class,runtime
@retention(retentionpolicy.runtime) @target(elementtype.annotation_type) public @interface retention { /** * returns the retention policy. * @return the retention policy */ retentionpolicy value(); }
public enum retentionpolicy { /** * 仅存在于源代码中,编译阶段会被丢弃,不会包含于class字节码文件中. */ source, /** * 【默认策略】,在class字节码文件中存在,在类加载的时被丢弃,运行时无法获取到 */ class, /** * 始终不会丢弃,可以使用反射获得该注解的信息。自定义的注解最常用的使用方式。 */ runtime }
3.1.3 @documented
表示是否将此注解的相关信息添加到javadoc文档中
3.1.4 @inherited
定义该注解和子类的关系,使用此注解声明出来的自定义注解,在使用在类上面时,子类会自动继承此注解,否则,子类不会继承此注解。注意,使用@inherited声明出来的注解,只有在类上使用时才会有效,对方法,属性等其他无效。
@target(elementtype.type) @retention(retentionpolicy.runtime) @inherited public @interface person { string value() default "man"; }
@person public class parent { } //子类也拥有@person注解 class son extends parent { }
3.2 定义注解小结
用@interface定义注解
可以添加多个参数,核心参数按约定用value,为每个参数可以设置默认值,参数类型包括基本类型、string和枚举
可以使用元注解来修饰注解,元注解包括多个,必须设置@target
和@retention
,@retention
一般设置为runtime
。
四. annotation处理
我们前面已经提到光配置了注解,其实没有作用,需要通过相应的代码来实现该注解想要表达的逻辑。
注解定义后也是一种class,所有的注解都继承自java.lang.annotation.annotation
,因此,读取注解,需要使用反射api。
//定义的注解 @target(elementtype.field) @retention(retentionpolicy.runtime) public @interface colum { string value() default ""; //用于表示某个属性代表的中文含义 string name() default ""; }
用注解@colum来修饰某个类的属性
public class person { @colum(name = "姓名") private string name; @colum(name = "性别") private string gender; @colum(name = "年龄") private int age; @colum(name = "住址") private string address; public string getname() {return name;} public void setname(string name) {this.name = name;} public string getgender() {return gender;} public void setgender(string gender) {this.gender = gender;} public int getage() {return age;} public void setage(int age) {this.age = age;} public string getaddress() {return address;} public void setaddress(string address) {this.address = address;} }
通过反射读取这个类的所有字段的中文含义,并保存到list中,然后打印出来
public static void main(string[] args) throws classnotfoundexception { list<string> columnames = new arraylist<>(); class clazz = class.forname("annotation.person"); //获取person类所有属性 field[] fields = clazz.getdeclaredfields(); for (field field : fields){ //获取该属性的colum注解 colum colum = field.getannotation(colum.class); //或者可以先判断有无该注解 field.isannotationpresent(colum.class); //将该属性通过注解配置好的中文含义取出来放到集合中 columnames.add(colum.name()); } //打印集合 columnames.foreach((columname) -> system.out.println(columname)); }
结果如下:
姓名 性别 年龄 住址
比如我们有一些常见的应用场景,需要把网站上的列表导出成excel表格,我们通过注解的方式把列名配置好,再通过反射读取实体需要导出(是否需要导出,也可通过注解配置)的每个字段的值,从而实现excel导出的组件。
五. 总结
本文只是抛砖引玉地讲解了注解的基本概念,注解的作用,几种元注解的功用以及使用方法,并通过一个简单的例子讲解了一下注解的处理,并不全面,文中通过field讲解了注解的基本api,但注解还可以修饰类、构造器、方法等,也有相对应的注解处理方法,大家可自行查一下api手册相关内容,大同小异,有不对之处,请批评指正,望共同进步,谢谢!
上一篇: Python 入门之 文件
下一篇: jvm虚拟机栈的作用