概述
Java5.0注解可以看做Javadoc和Xdoclet标签的延伸和发展,在Java5.0中可以自定义这些标签,并通过Java语言的反射机制获取类中标注的注解,完成特定的功能。
注解是代码的附属信息,它遵循一个基本的原则:注解不能直接干扰程序代码的运行,无论增加或者删除注解,代码都能正常运行。
Java语言解释器会忽略这些注解,而由第三方工具负责对注解进行处理。 第三方工具可以利用代码中的注解间接控制程序代码的运行,它们通过Java反射机制读取注解的信息,并根据这些信息更改目标程序的逻辑。
元注解Meta-annotation
元注解的作用就是负责注解其他注解。
Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
Java5.0定义的元注解:
- @Target
- @Retention
- @Documented
- @Inherited
这几个类都在java.lang.annotation包中
@Target
@Target主要说明Annotation所修饰的对象范围。
Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。
在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围,即被描述的注解可以用在什么地方。
取值(ElementType)有: @Target(ElementType.XXX)取值如下
- TYPE:类、接口、注解类、Enum声明处,相应的注解称为类型注解
- FIELD:类成员变量或者常量声明处,相应的注解被称为域值注解
- METHOD:方法处声明,相应的注解称为方法注解
- PARAMETER:参数声明处,相应的注解称为参数注解
- CONSTRUCTOR:构造函数声明处,相应的注解称为构造函数注解
- LOCAL_VARIABLE:局部变量声明处,相应的注解称为局域比纳凉注解
- PACKAGE:包声明处,相应的注解被称为包注解
举例:
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * @Description: 注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上 * * * @author: Mr.Yang * @date: 2017年7月24日 下午9:59:29 */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { // 和配置文件中 dynamicDatasourceMap中的key保持一致 public static String PR_RB = "dataSourcePR"; public static String DR_RB = "dataSourceDR"; public static String PR_CC = "dataSourceCC"; /** * * * @Title: name * * @Description: 如果仅标注@DataSource 默认为PR_RB数据库实例 * * @return * * @return: String */ String name() default DataSource.PR_RB; }
@Retention
@Retention定义了该Annotation被保留的时间长短.
某些Annotation仅出现在源代码中,而被编译器丢弃;
而另一些却被编译在class文件中,编译在class文件中的Annotation可能会被虚拟机忽略,
而另一些在class被装载时将被读取(并不影响class的执行,因为Annotation与class在使用上是被分离的)。
使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即被描述的注解在什么范围内有效)
Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。
取值(RetentionPoicy)有:
SOURCE:在源文件中有效(即源文件保留),单对应的字节码文件将不再保留
CLASS:在class文件中有效(即class保留),但类家爱妻加载字节码文件时不会将注解加载到JVM中,即运行期不能获取注解信息
RUNTIME:在运行时有效(即运行时保留),注解信息在目标类加载到JVM后依然保留,在运行期可以通过反射机制读取类中的注解信息
比如
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { ....... }
@Documented
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { ....... }
@Inherited
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。
在定义注解时,不能继承其他的注解或接口。
@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。
方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。
可以通过default来声明参数的默认值。
定义注解格式:
public @interface 注解名 {定义体}
注解参数的可支持数据类型
所有基本数据类型(int,float,boolean,byte,double,char,long,short)
String类型
Class类型
enum类型
Annotation类型
以上所有类型的数组
Annotation类型里面的参数设定规则:
第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
第三,如果只有一个参数成员,最好把参数名称设为”value”,后加小括号.
实例
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster
通常情况下,第三方工具不但负责处理特定的注解,其本身还提供了这些注解的定义。 事实上,定义注解类本身并不困难,Java提供了定义注解的语法,如上所述。
编写注解类
我们着手来编写一个简单的注解类
package com.xgj.aop.spring.advisor.anno; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * * @ClassName: NeedTest * * @Description: 自定义注解 * * @author: Mr.Yang * * @date: 2017年8月23日 下午4:50:11 */ // 声明注解的保留期限 @Retention(RetentionPolicy.RUNTIME) // 声明可以使用该注解的目标类型 @Target(ElementType.METHOD) // 可以被javadoc此类的工具文档化 @Documented public @interface NeedTestAnnotation { // 定义注解 // 声明注解成员 boolean value() default false; }
Java新语法规定使用@interface修饰符定义注解类,一个注解可以拥有多个成员,成员声明和接口方法声明类似,这里仅仅定义了一个成员。
成员声明以下几点限制:
成员以无入参,无抛出异常的方式声明, 以下方式是不合法的 比如 boolean value(String xx)、boolean value()throws Exception等是非法的
可以通过default成员指定一个默认值,比如 String level() defalut “LOW_LEVEL”, int high() default 2 是合法的,当然也可以不指定默认值。
成员类型是受限制的,成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String; 而 List foo() 、ForumServer value()是不合法的。
如果注解只有一个成员,成员名建议为value().
在使用时可以忽略成员和赋值号(=),比如 @NeedTestAnnotation(true).
当注解类拥有多个成员时,如果仅仅对value成员进行赋值,则也可不使用赋值号。
如果同时对多个成员进行赋值,这必须使用赋值号,比如 @DecalreParents(value=”XXX”,defaultImpl=XXX).
注解类可以没有成员,没有成员的注解称为标注注解,解释程序以标识注解存在与否进行相应的处理
此外,所有的注解都隐式继承与java.lang.annotation.Annotation,但注解不允许显示继承其他的接口。
使用注解
我们在业务类中,使用NeedTestAnnotation注解,标注业务方法是否需要测试。
如下
package com.xgj.aop.spring.advisor.anno; /** * * * @ClassName: ForumService * * @Description: 演示使用自定义注解@NeedTestAnnotation * * @author: Mr.Yang * * @date: 2017年8月23日 下午5:15:18 */ public class ForumService { @NeedTestAnnotation public void removeTopicId(int topicId) { System.out.println("remove topicId" + topicId); } @NeedTestAnnotation(true) public void removeForumId(int forumId) { System.out.println("remove forumId" + forumId); } }
如果注解类和目标类不在同一个包中,这需要通过improt引用注解类。
在标注注解时,可以通过以下格式对注解成员进行赋值
@<注解名>(<成员名1>=<成员值1>,<成员名2>=<成员值2>,.....)
如果成员是数组类型,这可以通过{}进行赋值,比如 boolean数组的成员可以设置为{true,false,true}
访问注解
我们说过,注解不会直接影响程序的运行,但是第三方程序或者工具可以利用代码中的注解完成特殊的任务,间接控制程序的运行。 对于RetentionPolicy.RUNTIME保留期限的注解,可以通过反射机制访问类中的注解。
在Java5.0中,Package、Class、Constructor、Method以及Field 等反射对象都新增了访问注解信息的方法:<T extends Annotation>T getAnnotation(Class<T> annotationClass)该方法支持通过泛型直接返回注解对象.
下面通过反射来访问注解,得到ForumService类中通过@NeedTestAnnotation注解所承载的测试需求
package com.xgj.aop.spring.advisor.anno; import java.lang.reflect.Method; import org.junit.Test; /** * * * @ClassName: AnnotaitionSelfDefineTool * * @Description: 访问注解 * * @author: Mr.Yang * * @date: 2017年8月23日 下午5:18:26 */ public class AnnotaitionSelfDefineTool { @Test public void test() { // 得到对应的Class Class<ForumService> clazz = ForumService.class; // 因为标注在方法上,所以先得到对应的方法 Method[] methods = clazz.getDeclaredMethods(); System.out.println(clazz.getName() + " 有 " + methods.length + " 个方法"); // 遍历方法 for (Method method : methods) { // 获取方法上的注解 NeedTestAnnotation needTestAnnotation = method .getAnnotation(NeedTestAnnotation.class); // 判断是否有标注此注解 if (needTestAnnotation != null) { // 获取注解中定义的值,做处理 if (needTestAnnotation.value()) { System.out.println(method.getName() + "需要测试"); } else { System.out.println(method.getName() + "不需要测试"); } } } } }
单元测试结果:
com.xgj.aop.spring.advisor.anno.ForumService 有 2 个方法 removeTopicId不需要测试 removeForumId需要测试