JAVA 注解的几大作用及使用方法详解
注解(Annotation)
一、Annotation的工作原理:
JDK5.0中提供了注解的功能,允许开发者定义和使用自己的注解类型。
该功能由一个定义注解类型的语法和描述一个注解声明的语法,读取注解的API,
一个使用注解修饰的class文件和一个注解处理工具组成。
Annotation并不直接影响代码的语义,但是他可以被看做是程序的工具或者类库。
它会反过来对正在运行的程序语义有所影响。
Annotation可以从源文件、class文件或者在运行时通过反射机制多种方式被读取。
常见的作用有以下几种:
1,生成文档。
这是最常见的,也是java 最早提供的注解。
常用的有@see @param @return 等
2,跟踪代码依赖性,实现替代配置文件功能。
较常见的是spring 2.5 开始的基于注解配置。
作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量。
3,在编译时进行格式检查。
如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。
方法的名称就是参数的名称,返回值类型就是参数的类型。可以通过default来声明参数的默认值。
二、父类,接口
Java.lang.annotation
包 java.lang.annotation 中包含所有已定义或自定义注解所需用到的原注解和接口。
如接口 java.lang.annotation.Annotation 是所有注解继承的接口,并且是自动继承,不需要定义时指定,
类似于所有类都自动继承Object。
该包同时定义了四个元注解;
三、常见注解
1. @Override注解:
注释类型 Override
@Target(value=METHOD)
@Retention(value=SOURCE)
public @interface Override
@Override注解表示子类要重写父类的对应方法。
下面是一个使用@Override注解的例子:
class A {
private String id;
A(String id){
this.id = id;
}
@Override
public String toString() {
return id;
}
}
2. @Deprecated注解:
注释类型 Deprecated
@Documented
@Retention(value=RUNTIME)
public @interface Deprecated
@Deprecated注解表示方法是不被建议使用的。
下面是一个使用@Deprecated注解的例子:
class A {
private String id;
A(String id){
this.id = id;
}
@Deprecated
public void execute(){
System.out.println(id);
}
public static void main(String[] args) {
A a = new A("a123");
a.execute();
}
}
3. @SuppressWarnings注解:
注释类型 SuppressWarnings
@Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
@Retention(value=SOURCE)
public @interface SuppressWarnings
指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。
注意,在给定元素中取消显示的警告集是所有包含元素中取消显示的警告的超集。
例如,如果注释一个类来取消显示某个警告,同时注释一个方法来取消显示另一个警告,
那么将在此方法中同时取消显示这两个警告。
下面是一个使用@SuppressWarnings注解的例子:
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc");
}
四、自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。
在定义注解时,不能继承其他的注解或接口。
1,自定义最简单的注解:
public @interface MyAnnotation {
}
使用自定义注解:
public class AnnotationTest2 {
@MyAnnotation
public void execute(){
System.out.println("method");
}
}
2,添加变量:
public @interface MyAnnotation {
String value1();
}
使用自定义注解:
public class AnnotationTest2 {
@MyAnnotation(value1="abc")
public void execute(){
System.out.println("method");
}
}
添加默认值:
public @interface MyAnnotation {
String value1() default "abc";
}
3、多变量使用枚举:
public @interface MyAnnotation {
String value1() default "abc";
MyEnum value2() default MyEnum.Sunny;
}
enum MyEnum{
Sunny,Rainy
}
使用自定义注解:
public class AnnotationTest2 {
@MyAnnotation(value1="a", value2=MyEnum.Sunny)
public void execute(){
System.out.println("method");
}
}
4、数组变量:
public @interface MyAnnotation {
String[] value1() default "abc";
}
使用自定义注解:
public class AnnotationTest2 {
@MyAnnotation(value1={"a","b"})
public void execute(){
System.out.println("method");
}
}
当注解中使用的属性名为value时,对其赋值时可以不指定属性的名称而直接写上属性值接口;
除了value意外的变量名都需要使用name=value的方式赋值。
5、限定注解的使用:
限定注解使用@Target。
@Documented
@Retention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Target
指示注释类型所适用的程序元素的种类。
如果注释类型声明中不存在 Target 元注释,则声明的类型可以用在任一程序元素上。
如果存在这样的元注释,则编译器强制实施指定的使用限制。
例如,此元注释指示该声明类型是其自身,即元注释类型。它只能用在注释类型声明上:
@Target(ElementType.ANNOTATION_TYPE)
public @interface MetaAnnotationType {
...
}
此元注释指示该声明类型只可作为复杂注释类型声明中的成员类型使用。它不能直接用于注释:
@Target({})
public @interface MemberType {
...
}
这是一个编译时错误,它表明一个 ElementType 常量在 Target 注释中出现了不只一次。
例如,以下元注释是非法的:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
public @interface Bogus {
...
}
public enum ElementType
extends Enum<ElementType>
程序元素类型。此枚举类型的常量提供了 Java 程序中声明的元素的简单分类。
这些常量与 Target 元注释类型一起使用,以指定在什么情况下使用注释类型是合法的。
ANNOTATION_TYPE
注释类型声明
CONSTRUCTOR
构造方法声明
FIELD
字段声明(包括枚举常量)
LOCAL_VARIABLE
局部变量声明
METHOD
方法声明
PACKAGE
包声明
PARAMETER
参数声明
TYPE
类、接口(包括注释类型)或枚举声明
注解的使用限定的例子:
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String[] value1() default "abc";
}
元注解
元注解是指注解的注解。
包括 @Retention @Target @Document @Inherited四种。
@Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括:
@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) //包 packag注解必须在package-info.java 中声明
@Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy
中,包括:
@Retention(RetentionPolicy.SOURCE)
//注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS)
// 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME)
// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。
在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param 等。
@Inherited 允许子类继承父类中的注解
@Target
@Target用来声明注解可以被添加在哪些类型的元素上,如类型、方法和域等。
例:
@Target({TYPE,METHOD,FIELD,CONSTRUCTOR})
public @interface TestA {
//这里定义了一个空的注解。
}
这个类专门用来测试注解使用
@TestA //使用了类注解
public class UserAnnotation {
@TestA //使用了类成员注解
private Integer age;
@TestA //使用了构造方法注解
public UserAnnotation(){
}
@TestA //使用了类方法注解
public void a(){
@TestA //使用了局部变量注解
Map m = new HashMap(0);
}
@TestA //使用了方法参数注解
public void b(@TestA Integer a){
}
}
@Retention
@Retention用来声明注解的保留策略,有CLASS、RUNTIME和SOURCE这三种,
分别表示注解保存在类文件、JVM运行时刻和源代码中。
只有当声明为RUNTIME的时候,才能够在运行时刻通过反射API来获取到注解的信息。
无属性注解
@Retention 参数 RetentionPolicy。这个注解还没有特殊的属性值。 简单演示下如何使用:
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/*
* 定义注解 Test
* 首先使用ElementType.TYPE(需要在package-info.java中声明)
* 运行级别定为 运行时,以便后面测试解析
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestA {}
有属性注解
@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
代码:
/*
* 定义注解 Test
* 为方便测试:注解目标为类 方法,属性及构造方法
* 注解中含有三个元素 id ,name和 gid;
* id 元素 有默认值 0
*/
@Target({TYPE,METHOD,FIELD,CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestA {
String name();
int id() default 0;
Class<Long> gid();}
测试类
import java.util.HashMap;
import java.util.Map;
/**
* 这个类专门用来测试注解使用
*/
@TestA(name="type",gid=Long.class) //类成员注解
public class UserAnnotation {
@TestA(name="param",id=1,gid=Long.class) //类成员注解
private Integer age;
@TestA (name="construct",id=2,gid=Long.class)//构造方法注解
public UserAnnotation(){}
@TestA(name="public method",id=3,gid=Long.class) //类方法注解
public void a(){
Map<String,String> m = new HashMap<String,String>(0);
}
@TestA(name="protected method",id=4,gid=Long.class) //类方法注解
protected void b(){
Map<String,String> m = new HashMap<String,String>(0);
}
@TestA(name="private method",id=5,gid=Long.class) //类方法注解
private void c(){
Map<String,String> m = new HashMap<String,String>(0);
}
public void b(Integer a){ }
}
读取定义注解内容
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ParseAnnotation {
/**
*A 简单打印出UserAnnotation 类中所使用到的类注解
* 该方法只打印了 Type 类型的注解
* @throws ClassNotFoundException
*/
public static void parseTypeAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("com.tmser.annotation.UserAnnotation");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
TestA testA = (TestA)annotation;
System.out.println("id= \""+testA.id()+"\"; name= \""+testA.name()+"\"; gid = "+testA.gid());
}
}
//结果为id="0";name="type";gid =classjava.lang.Long
/*
* B简单打印出UserAnnotation 类中所使用到的方法注解
* 该方法只打印了 Method 类型的注解
* @throws ClassNotFoundException
*/
public static void parseMethodAnnotation(){
Method[] methods = UserAnnotation.class.getDeclaredMethods();
for (Method method : methods) {
// 判断方法中是否有指定注解类型的注解
boolean hasAnnotation = method.isAnnotationPresent(TestA.class);
if (hasAnnotation) {
//根据注解类型返回方法的指定类型注解
TestA annotation = method.getAnnotation(TestA.class);
System.out.println("method = " + method.getName()
+ " ; id = " + annotation.id() + " ; description = "
+ annotation.name() + "; gid= "+annotation.gid());
}
}
}
method = c ; id = 5 ; description = private method; gid= class java.lang.Long
method = a ; id = 3 ; description = public method; gid= class java.lang.Long
method = b ; id = 4 ; description = protected method; gid= class java.lang.Long
/**
* C简单打印出UserAnnotation 类中所使用到的构造方法注解
* 该方法只打印了 Method 类型的注解
* @throws ClassNotFoundException
*/
public static void parseConstructAnnotation(){
Constructor[] constructors = UserAnnotation.class.getConstructors();
for (Constructor constructor : constructors) {
//判断构造方法中是否有指定注解类型的注解
boolean hasAnnotation = constructor.isAnnotationPresent(TestA.class);
if (hasAnnotation) {
//根据注解类型返回方法的指定类型注解
TestA annotation =(TestA) constructor.getAnnotation(TestA.class);
System.out.println("constructor = " + constructor.getName()
+ " ; id = " + annotation.id() + " ; description = "
+ annotation.name() + "; gid= "+annotation.gid());
}
}
}
constructor=com.tmser.annotation.UserAnnotation;id=2;description =construct;gid=classjava.lang.Long
public static void main(String[] args) throws ClassNotFoundException {
parseTypeAnnotation();
parseMethodAnnotation();
parseConstructAnnotation();// field是一样的,省略之
}
}
@Documented
在帮助文档中加入注解:
要想在制作JavaDoc文件的同时将注解信息加入到API文件中,可以使用java.lang.annotation.Documented。
在自定义注解中声明构建注解文档:
@Documented
public @interface MyAnnotation {
String[] value1() default "abc";
}
使用自定义注解:
public class AnnotationTest2 {
@MyAnnotation(value1={"a","b"})
public void execute(){
System.out.println("method");
}
}
@Inherited
在注解中使用继承:
默认情况下注解并不会被继承到子类中,可以在自定义注解时加上java.lang.annotation.Inherited注解声明使用继承。
@Documented
@Retention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Inherited
指示注释类型被自动继承。
如果在注释类型声明中存在 Inherited 元注释,并且用户在某一类声明中查询该注释类型,
同时该类声明中没有此类型的注释,则将在该类的超类中自动查询该注释类型。
此过程会重复进行,直到找到此类型的注释或到达了该类层次结构的顶层 (Object) 为止。
如果没有超类具有该类型的注释,则查询将指示当前类没有这样的注释。
注意,如果使用注释类型注释类以外的任何事物,此元注释类型都是无效的。
还要注意,此元注释仅促成从超类继承注释;对已实现接口的注释无效。
五、自定义注解案例
实例1:
下面是使用反射读取RUNTIME保留策略的Annotation信息的例子:
自定义注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String[] value1() default "abc";
}
使用自定义注解:
public class AnnotationTest2 {
@MyAnnotation(value1={"a","b"})
@Deprecated
public void execute(){
System.out.println("method");
}
}
读取注解中的信息:
public static void main(String[] args) throws SecurityException,
NoSuchMethodException, IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
AnnotationTest2 annotationTest2 = new AnnotationTest2();
//获取AnnotationTest2的Class实例
Class<AnnotationTest2> c = AnnotationTest2.class;
//获取需要处理的方法Method实例
Method method = c.getMethod("execute", new Class[]{});
//判断该方法是否包含MyAnnotation注解
if(method.isAnnotationPresent(MyAnnotation.class)){
//获取该方法的MyAnnotation注解实例
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
//执行该方法
method.invoke(annotationTest2, new Object[]{});
//获取myAnnotation
String[] value1 = myAnnotation.value1();
System.out.println(value1[0]);
}
//获取方法上的所有注解
Annotation[] annotations = method.getAnnotations();
for(Annotation annotation : annotations){
System.out.println(annotation);
}
}
实例2:
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Yts {
public enum YtsType{util,entity,service,model};
public YtsType classType() default YtsType.util;
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface HelloWorld {
public String name()default "";
}
public class ParseAnnotation {
public void parseMethod(Class clazz) throws IllegalArgumentException,
IllegalAccessException,InvocationTargetException,SecurityException,
NoSuchMethodException, InstantiationException{
Object obj = clazz.getConstructor(new Class[]{}).newInstance(new Object[]{});
for(Method method : clazz.getDeclaredMethods()){
HelloWorld say = method.getAnnotation(HelloWorld.class);
String name = "";
if(say != null){
name = say.name();
method.invoke(obj, name);
}
Yts yts = (Yts)method.getAnnotation(Yts.class);
if(yts != null){
if(YtsType.util.equals(yts.classType())){
System.out.println("this is a util method");
}else{
System.out.println("this is a other method");
}
}
}
}
@SuppressWarnings("unchecked")
public void parseType(Class clazz) throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException{
Yts yts = (Yts) clazz.getAnnotation(Yts.class);
if(yts != null){
if(YtsType.util.equals(yts.classType())){
System.out.println("this is a util class");
}else{
System.out.println("this is a other class");
}
}
}
}
@Yts(classType =YtsType.util)
public class SayHell {
@HelloWorld(name = " 小明 ")
@Yts
public void sayHello(String name){
if(name == null || name.equals("")){
System.out.println("hello world!");
}else{
System.out.println(name + "say hello world!");
}
}
}
public static void main(String[] args) throws IllegalArgumentException,
IllegalAccessException, InvocationTargetException, SecurityException,
NoSuchMethodException, InstantiationException {
ParseAnnotation parse = new ParseAnnotation();
parse.parseMethod(SayHell.class);
parse.parseType(SayHell.class);
}
总结
1. 要用好注解,必须熟悉java 的反射机制,从上面的例子可以看出,注解的解析完全依赖于反射。
2. 不要滥用注解。平常我们编程过程很少接触和使用注解,只有做设计,且不想让设计有过多的配置时。