一天写一点,天天学一点,今天来扒拉扒拉JAVA的反射以及自定义注解的事儿,其实就是前面做项目的时候自定义了一个注解,然后在struts2的拦截器里面通过反射的方式获取到注解的内容,通过注解的内容来进行权限判断,就是这么个事儿,反射是个很XX(此处省略两个字)的问题,用的也多,但是学着却很纠结,会的也不多,就随便写点我会的,还有自定义注解,也是那么回事,下面就简单记录一下吧,省的以后忘了还得去查资料,说了一堆废话,下面开始。
要自定义注解就得知道元注解,什么是元注解呢?元注解就是注解其他注解的注解,说的比较绕,其实就是在定义其他注解的时候,需要用到的注解就是元注解。Java 5.0定义了4个元注解,用来对其他的注解类型进行说明,这4个元注解分别是:@Target,@Retention,@Documented,@Inherited,在API文档中的位置如下所示:
指示注解修饰的对象范围,如果注释类型声明中不存在 Target 元注释,则声明的类型可以用在任一程序元素上;如果存在 Target 元注释,那么就要标明作用范围,参数为一个数组,数组元素为ElementType的属性值。
ElementType的属性值主要有以下几种:
1.CONSTRUCTOR:用于描述构造器;
2..FIELD:用于描述域;
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
下面我们定义一个作用在方法上的注解:
@Target({ElementType.METHOD}) public @interface Exam { String name() default ""; int age() default 0; }
指示注释类型的注释要保留多久。如果注释类型声明中不存在 Retention 注释,则保留策略默认为 RetentionPolicy.CLASS。 有些注释出现在源代码中,有些注释出现在class文件中,有些注释则出现在class被装载时被读取,主要的就这三个,Retention注释的参数也有三种,对应源码、class文件和运行时,表示需要在什么级别保存该注释信息,用于描述注解的声明周期,取值如下:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
例子:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Exam { String name() default ""; int age() default 0; }
指示某一类型的注释将通过javadoc和类似的默认工具进行文档化。应使用此类型来注释这些类型的声明:其注释会影响由其客户端注释的元素的使用。如果类型声明是由@Documented来注释的,则其注释将成为注释元素的公共API的一部分。
上面是JDK文档上的解释,什么意思呢?我的理解是使用@Documented注解的注解应该被javadoc工具记录,默认情况下javadoc是不会记录注解的,但是声明注解时如果指定了@Documented,那么这个注解就会被javadoc处理,所以注解类型信息也会包括在生成的文档中。如果注解信息不需要包括在文档中,那么就可以不加这个注解。使用方式如下:
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.METHOD})
public @interface Exam {
String name() default "";
int age() default 0;
}
指示注释类型被自动继承,如果在注释类型声明中存在@Inherited元注释,并且用户在某一类声明中查询该注释类型,同时该类声明中没有此类型的注释,则将在该类的超类中自动查询该注释类型。此过程会重复进行,直到找到此类型的注释或到达了该类层次结构的顶层(Object)为止,如果没有超类具有该类型的注释,则查询将指示当前类没有这样的注释。注意,如果使用注释类型注释类以的任何事物,此元注释类型是无效的,还要注意,此元注释仅促成从超类继承注释,对已实现接口的注释无效。
上述呢是文档中的解释,我自己说也说不太清楚,我直接举个例子来说可能更容易理解:
首先定义一个自定义注解,上面没有说定义注解时的注意事项,在这还是写一下吧:
注意:在使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,编译程序会自动处理具体的细节。在定义注解时,不可以再继承其他的注解或者接口。
注解的定义格式: public @interface 注解名称 { 注解的参数 }
注解的参数主要有一下几种类型:1.八中基本数据类型;
2.String类型;Class类型;enum类型;Annotations类型;
3.以上类型的数组;
注解的参数设定:
只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
如果只有一个参数成员,最好把参数名称设为"value";
说完了定义注解的注意事项,下面继续说@Inherited的事情,先定义一个注解:
@Inherited @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface Exam { String name() default ""; int age() default 0; }
注意红色的部分,具体原因下面会说到。然后是一个父类:
@Exam(name="张三",age=10) public class User { public static String getUserAddress(){ String address = "北京市"; return address; } }
再来个子类,子类中不加注解:
public class Son extends User{ public static int getAge(){ int age = 20; return age; } }
看测试类和测试结果:
public static void main(String[] args) { Class<?> cla = null; try { cla = Class.forName("modal.Son"); boolean flag = cla.isAnnotationPresent(Exam.class); System.out.println("flag==="+flag); if(flag){ Exam exam = cla.getAnnotation(Exam.class); String name = exam.name(); System.out.println(name); Integer age = exam.age(); System.out.println(age); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } }
测试结果:
flag===true 张三 10
测试类中使用反射的方式拿到子类,然后判断子类中是否有注解,子类中我们是没有写注解的,但是isAnnotationPresent方法却返回了true,我们取到注解的值后发现这个注解的值是父类的注解的值,这就是@Inherited元注解的作用,就是子类可以继承父类的注解,当我把@Inherited注解去掉之后isAnnotationPresent返回false。
上边留的红字部分没有解释,什么原因呢?这是因为我在测试的发现只有把注解放在类级别上才可以让子类继承,如果放到方法上是不能继承的,因此我用了@Target({ElementType.TYPE})注解,而没有用@Target({ElementType.METHOD})注解。
自定义注解部分暂时就说到这,本来想写反射的,但是又感觉无从下手,暂时就先不写了,再研究研究,等熟练了在写,不然写出来的东西我都不知道什么意思,以后看的时候也是浪费时间。