相信搞Java开发的没有没见过java注解的,形如@XXXX这种形式。特别是在最近的web开发框架中,注解表达形式更是大行其道,大有取代原来xml文件配置方式的趋势。在平常应用中,只知道加上这个注解起什么作用,但是怎么起作用就不太清楚,也就是知其然而不知其所以然,所以在闲暇之余就有了这篇注解学习篇。
本文参考《秒懂,java注解你可以这样学》,这篇文章真的写的很好,也是看了这篇文章才催发我写下学习博客的动力。首先这篇文章的前沿就给了我极大的冲击,它告诉了我如何去学习一门新东西,我记得里面最精辟的一句话就是“用专业名词去解释专业名词是及其糟糕的”,这句话给了我极大的共鸣,因为自己在读技术手册时经常就受到此种描述方式的困扰,此文作者用标签来比喻注解非常形象非常容易理解,使我受益斐然,让我明白了一个道理:学习新事物是需要实物化的过程,总结提炼是需要抽象化的一个过程。好了,开始我们的注解之旅吧。
java注解的官方解释“注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释”。这是百度百科的定义,这样的定义相信大家看得都是一只半解,那如何才能让别人能够直观的感受注解,这里我借用引文里的比喻【标签】,可以将注解理解为生活中所用的标签,一个人可以为其贴上诚实、勇敢、聪明的标签,一台设备可以为其贴上设备编号、设备名称的标签,总之注解就是类、属性、方法上的标签,作为它们的一个扩展信息。
注解
注解的定义:注解是在java SE5.0版本引入的,定义同接口类相似也是属于一种类型,通过关键字@interface定义
public @interface TestAnnotation{ }
TestAnnotation就是我们定义好的标签,这个标签就可以用在类、属性和方法上了
@TestAnnotation() public class Test{ }
既然我们是标签,那么标签信息放在哪里呢,自然是放在标签里面,信息就是标签的属性,通过属性方法定义:
public @interface TestAnnotation{ int id(); String name(); }
id和name就是我们的注解属性,相当于我们为一张标签规划好了消息结构,如设备的名称、设备编号等,当我们将某张标签贴到某台设备上时,就在标签上填写设备的信息,形如
@TestAnnotation(id = 12,name = "xiaowang") public class Test{ }
通过属性 = 值的方式进行赋值,对于特殊属性value属性,且只有其一个属性时可以简写为@Annotation("value值")
上面我们已经讲了可以将注解标签贴在类上、方法上和属性上,就像机器标签用于贴在机器上,书本标签用来贴在书本上,水果标签用来贴上水果上的,那么如何分类这些标签,限定他们只能用在想要被用到的地方呢?这就需要用到【元注解】,这又是一个难懂的词汇哈,所谓元注解就是java定义好的注解,是用于注解上的注解。
【元注解】
元注解是注解的注解,是对注解进行分类、解释说明的,它主要包括@Retention、@Target、@Documented、@Inherited、@Repeatable
@Retention
Retention译为保留的意思,它是用于限定这个注解的实效的,也就是这个标签可以保留到什么阶段,在java程序里面可以分为源码阶段、编译阶段、运行阶段,其值有下面几个枚举
- RetentionPolicy.SOURCE 源码阶段,此注解只存在于源码阶段,在代码编译期会被擦除掉
- RetentionPolicy.CLASS 编译阶段,此注解存活于编译阶段,不会被加载到JVM中
- RetentionPolicy.RUNTIME 运行阶段,注解可以保留在程序整个运行期间
@Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation{ int id(); String name(); }
如上定义了TestAnnotation注解可以在程序运行期间存在。
@Target
Target目标用于对注解进行分类,表明注解可以用在哪个地方,其取值有
- ElementType.ANNOTATION_TYPE 可以用于为注解标注注解
- ElementType.CONSTRUCTOR 可以给构造方法进行注解
- ElementType.FIELD 可以给属性进行注解
- ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
- ElementType.METHOD 可以给方法进行注解
- ElementType.PACKAGE 可以给包进行注解
- ElementType.PARAMETER 可以给方法内的参数进行注解
- ElementType.TYPE 可以给一个类型进行注解,如类、接口、枚举
其属性值基本涵盖了一个java类的所有成员,也就是说可以在一个类的任意地方加上注解标签。
@Documented
Document 文档,用于将注解中的元素包含到javadoc中去
@Inherited
inherited 继承,被此标记注解标注的类,其子类如果没有被其它注解应用则自动继承此注解,如下所示
@Inherited public @interface test{ } @test public class A{} public class B extends A{} //相当于有一个@test注解
test注解被标注为可继承,将其应用于A超类上,那么子类B上没有任何它注解则默认相当于加了@test注解
@Repeatable
Repeatable 可重复的,顾名思义其表示注解可以重复使用,也就是此类标签可以贴多张,就像人的技能标签一样,会唱歌,会做饭,应用如下:
public @interface Skills{ Skill[] value(); } @Repeatable(Skills.class) public @interface Skill{ String skill(); } @Skill(skill = "sing") @Skill(skill = "dangce") public class B {}
上面对注解的限定与规则做了一个简要介绍,下面就讲一讲如何去获取标签的内容。
【注解内容提取】
大家在逛超市选东西的时候,一眼就能看到货架上商品的标签信息。但是咋们的java程序没有长眼睛,它如何能够查看我们为某一类、方法或者是属性贴注的标签信息呢,这肯定是有办法的,如果不能看内容那注解也就没有任何意义了,那就得依靠java的反射大法了,通过java反射我们能够获取到类、方法、属性等等上面依附的注解标签,拿到标签就能查看其内容了,我们通过一个例子来说明
public class AnnotionTest { @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation{ int id(); String name(); } @Target(ElementType.FIELD) public @interface Check{ String value(); } @Retention(RetentionPolicy.RUNTIME) public @interface Perform{ } @TestAnnotation(id = 12,name = "xiaowang") public class Test{ @Check("xiaoh") int n; @Perform public void testMethod(){} @SuppressWarnings("deprecation") public void test(){ System.out.println("this is annotationTest"); } } public static void main(String[] args){ boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class); if(hasAnnotation){ //获取注解 TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class); System.out.println("id:"+ testAnnotation.id()); System.out.println("name" + testAnnotation.name()); } try { Field n = Test.class.getDeclaredField("n");//获取已定义的属性 n.setAccessible(true);//设置私有属性可访问 Check check = n.getAnnotation(Check.class); if(null != check){ System.out.println("check value is" + check.value()); } Method method = Test.class.getDeclaredMethod("testMethod"); Annotation[] annotations = method.getAnnotations(); for(int i = 0;i < annotations.length;i++){ System.out.println("method testMethod annotation is:" + annotations[i].annotationType().getSimpleName()); } }catch (Exception e){ e.printStackTrace(); } } }
程序输出如下:id:12 namexiaowang method testMethod annotation is:Perform Process finished with exit code 0
可以看到,我们可以通过反射获取其身上依附的标签,并且拿到标签内的内容,需要注意的是反射是在程序加载运行时发生的,所以其只能获取到注解生命周期@Retention中RetentionPolicy.RUNTIME标注的注解,文章演示示例是通过反射来处理注解的,也可在程序编译期来处理注解。
【注解的应用】
介绍了注解的内容,让我们谈谈注解的应用,目前注解的应用比较灵活广泛,可以用于信息配置,输入参数校验,方法测试,方法拦截、方法测试输出javadoc文档等,用得着的地方你就用,不要拘泥于它现有的使用场景,咋们就方法测试来举例吧。
咋们首先定义一个注解标签用于检测我们的算术方法
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Calculate { }
随后我们的测试目标类,提供了加减除三种算术方法,咋们分别打上@Calculate计算处理标签
public class MyCalculate { @Calculate public void add(){ int n = 2 + 1; System.out.println("加法结果是:" + n); } @Calculate public void subtracation(){ int n = 5 - 1; System.out.println("减法的结果是:"+ n); } @Calculate public void division(){ int a = 3; int b = 0; int c = a/b; System.out.println("除法的结果是:" + c); } }
注解标签处理类:
public class CalTest { public static void main(String[] args){ MyCalculate calculateObj = new MyCalculate(); Method[] methods = MyCalculate.class.getDeclaredMethods(); for(int i = 0;i < methods.length;i++){ boolean hasAnnotation = methods[i].isAnnotationPresent(Calculate.class); if(hasAnnotation){ try{ methods[i].invoke(calculateObj,null); }catch (Exception e){ System.out.println("出错方法:" + methods[i].getName()); e.printStackTrace(); } } } } }
通过反射调用,获取被计算测试注解标注的方法,执行方法检测是否有异常抛出,得到结果如下
java.lang.reflect.InvocationTargetException
加法结果是:3
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
减法的结果是:4
出错方法:division
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at basejava.annotation.CalTest.main(CalTest.java:22)
Caused by: java.lang.ArithmeticException: / by zero
at basejava.annotation.MyCalculate.division(MyCalculate.java:30)
... 5 more
通过结果可以看出成功定位到了错误方法。
以上大致就是注解的基本内容,我们首先要明白注解的一个基本概念,了解其使用场景,知道注解的处理方式(反射,编译期),然后在以后具体的使用场景中在来设计注解的应用目标,下面一篇接着讲《java反射》