Java自定义注解详解+实例
在很多框架中都使用了自定义注解,之前在项目中也用到了自定义注解来对代码做一些解耦,今天就给大家介绍下自定义注解的使用方法,对于自定义注解其实很简单,大家只要搞清楚如何定义自定义注解和如何获取定义的自定义注解内容就基本掌握了自定义注解,后续就可以在自己的项目中去使用自定义注解完成一些功能。
1. 元注解
JDK 1.5开始jdk就定义了元注解,用来定义其他的自定义注解,目前提供的元注解主要有4个:
- @Target
- @Retention
- @Documented
- .@Inherited
@Target
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。
取值(ElementType)有:
- 1.CONSTRUCTOR:用于描述构造器
- 2.FIELD:用于描述域
- 3.LOCAL_VARIABLE:用于描述局部变量
- 4.METHOD:用于描述方法
- 5.PACKAGE:用于描述包
- 6.PARAMETER:用于描述参数
- 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
我们在日常自定义注解中常用的类型有FIELD、METHOD、PARAMETER、TYPE。
@Retention
@Retention定义了该Annotation被保留的时间长短。
取值(RetentionPoicy)有:
- 1.SOURCE:在源文件中有效(即源文件保留)
- 2.CLASS:在class文件中有效(即class保留)
- 3.RUNTIME:在运行时有效(即运行时保留)
我们在自定义注解用用的比较多的自然是RUNTIME了,这样保证注解在运行时是有效的,我们在其他框架中遇到的也大部分都是RUNTIME的。
@Documented
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@Inherited
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
2.自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
注解参数支持的类型如下:
- 1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- 2.String类型
- 3.Class类型
- 4.enum类型
- 5.Annotation类型
- 6.以上所有类型的数组
下面我们写个例子,定义几个自定义注解,并获取这些自定义注解中的值。
定义了一个可以作用于类、接口、枚举上的注解MyType。
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @Author: feiweiwei
* @Description:
* @Created Date: 16:10 17/9/22.
* @Modify by:
*/
@Documented
@Target({ ElementType.TYPE})
@Retention(RUNTIME)
public @interface MyType {
String value() default "";
String className() default "";
}
定义了一个可以作用于类的方法上的注解MyMethod。
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @Author: feiweiwei
* @Description:
* @Created Date: 16:12 17/9/22.
* @Modify by:
*/
@Documented
@Target({ ElementType.METHOD})
@Retention(RUNTIME)
public @interface MyMethod {
String value() default "";
String methodName() default "";
}
定义了一个可以作用于类内部变量的注解MyField。
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @Author: feiweiwei
* @Description:
* @Created Date: 16:07 17/9/22.
* @Modify by:
*/
@Documented
@Target({ ElementType.FIELD})
@Retention(RUNTIME)
public @interface MyField {
String value() default "";
String name() default "";
String type() default"String";
}
下面我们写个例子,定义一个类使用这些注解。
import com.monkey01.annotation.MyField;
import com.monkey01.annotation.MyMethod;
import com.monkey01.annotation.MyType;
/**
* @Author: feiweiwei
* @Description:
* @Created Date: 16:14 17/9/22.
* @Modify by:
*/
@MyType(value = "test", className = "TestClass")
public class TestClass {
@MyField(value = "testNum", name="num", type = "int")
private int num;
@MyField(value = "testName", name="name", type = "String")
private String name;
@MyMethod(value = "print ")
public String print(){
return "hello annotation";
}
}
从例子中可以看到,在类名上面使用了MyType的注解,在内部申明的变量上都定义了MyField,在内部方法上使用了MyMethod注解,java也能自动识别这些自定义注解。
写好了测试类后,我们下一步要做的就是如何获取这些自定义注解上定义的属性值,因为定义自定义注解的目的是为了将一些公共的功能,能够比较优雅的与实际业务代码隔离开。获取注解上的属性使用的方法实际上就是利用反射来获取其中的注解。
下面我们写一个类来实现读取注解属性并打印出来。
import org.reflections.Reflections;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;
/**
* @Author: feiweiwei
* @Description:
* @Created Date: 16:14 17/9/22.
* @Modify by:
*/
public class PrintAnnotation {
private String allAnnotation = new String();
public String printAllAnnotation(){
Set<Class<?>> clazzes = new Reflections("com.monkey01").getTypesAnnotatedWith(MyType.class);
for (Class<?> clazz : clazzes) {
printMyType(clazz);
}
System.out.println(allAnnotation);
return allAnnotation;
}
private void printMyType(Class<?> clazz) {
MyType myType = clazz.getAnnotation(MyType.class);
allAnnotation = allAnnotation + clazz.getName() + ": " + myType.value() + "-" + myType.className() + "\n";
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
MyField myField = field.getAnnotation(MyField.class);
if(myField != null) {
allAnnotation = allAnnotation + myField.value() + "-" + myField.name() + "-" + myField.type() + "\n";
}
}
Method[] methods = clazz.getMethods();
for(Method method : methods){
MyMethod myMethod = method.getAnnotation(MyMethod.class);
if(myMethod != null) {
allAnnotation = allAnnotation + myMethod.methodName() + "-" + myMethod.value() + "\n";
}
}
}
}
从代码可以看到首先通过反射来获取com.monkey01包下的使用了MyType注解的类。
Set<Class<?>> clazzes = new Reflections("com.monkey01").getTypesAnnotatedWith(MyType.class);
再循环取出使用了MyType注解的类,并打印其中的FIELD和METHOD,同样是使用反射获取MyField和MyMethod注解。
要获取一个注解定义的属性值,需要先获取到这个注解类对象,所有的注解类对象获取方法都是一样的,调用class的getAnnotation方法,入参为这个注解类。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
MyType myType = clazz.getAnnotation(MyType.class);
获取到注解类对象后就可以调用定义的方法,获取这些方法属性值了,是不是很简单。
allAnnotation = allAnnotation + clazz.getName() + ": " + myType.value() + "-" + myType.className() + "\n";
要获取Field和Method的属性需要先通过反射获取到获取这个类的Field和Method。
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getMethods();
对于MyField和MyMethod获取注解属性的方法和获取MyType是一样的。
总体看下来是不是很简单,源码已经传到git上,没看懂的同学跑下例子就秒懂了,也可以在评论区留言提问。