一、创建注解
1.1 @Override 注解的定义
我们通过一些例子来说明,先看@Override
的定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
定义注解与定义接口有点类似,都用了interface
,不过注解的interface
前多了@
,另外,它还有两个元注解@Targe
t和@Retention
,这两个注解专门用于定义注解本身。
1.2 @Target
@Target
表示注解的目标,@Override
的目标是方法(ElementType.METHOD)
,ElementType
是一个枚举,其他可选值有:
TYPE
:表示类、接口(包括注解),或者枚举声明FIELD
:字段,包括枚举常量METHOD
:方法PARAMETER
:方法中的参数CONSTRUCTOR
:构造方法LOCAL_VARIABLE
:本地变量ANNOTATION_TYPE
:注解类型PACKAGE
:包
目标可以有多个,用{}
表示,比如@SuppressWarnings
的@Target
就有多个,定义为:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
如果没有声明@Target
,默认为适用于所有类型。
1.3 @Retention
@Retention
表示注解信息保留到什么时候,取值只能有一个,类型为RetentionPolicy
,它是一个枚举,有三个取值:
SOURCE
:只在源代码中保留,编译器将代码编译为字节码文件后就会丢掉CLASS
:保留到字节码文件中,但 Java 虚拟机将 class 文件加载到内存时不一定会在内存中保留RUNTIME
:一直保留到运行时
如果没有声明@Retention
,默认为CLASS
。
@Override
和@SuppressWarnings
都是给编译器用的,所以@Retention
都是RetentionPolicy.SOURCE
。
1.4 定义参数
可以为注解定义一些参数,定义的方式是在注解内定义一些方法,比如@SuppressWarnings
内定义的方法value
,返回值类型表示参数的类型,这里是String[]
,使用@SuppressWarnings
时必须给value
提供值,比如:
@SuppressWarnings(value={"deprecation","unused"})
当只有一个参数,且名称为value
时,提供参数值时可以省略"value=
",即上面的代码可以简写为:
@SuppressWarnings({"deprecation","unused"})
注解内参数的类型不是什么都可以的,合法的类型有基本类型、String
、Class
、枚举、注解、以及这些类型的数组。
参数定义时可以使用default
指定一个默认值,比如,Guice
中Inject
注解的定义:
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {
boolean optional() default false;
}
它有一个参数optional
,默认值为false
。如果类型为String
,默认值可以为""
,但不能为null
。如果定义了参数且没有提供默认值,在使用注解时必须提供具体的值,不能为null
。
@Inject
多了一个元注解@Documented
,它表示注解信息包含到Javadoc
中。
1.5 @Inherited
与接口和类不同,注解不能继承。不过注解有一个与继承有关的元注解@Inherited
,它是什么意思呢?我们看个例子:
public class InheritDemo {
@Inherited
@Retention(RetentionPolicy.RUNTIME)
static @interface Test {
}
@Test
static class Base {
}
static class Child extends Base {
}
public static void main(String[] args) {
System.out.println(Child.class.isAnnotationPresent(Test.class));
}
}
Test是一个注解,类Base
有该注解,Child
继承了Base
但没有声明该注解,main
方法检查Child
类是否有Test
注解,输出为true
,这是因为Test
有注解@Inherited
,如果去掉,输出就变成false
了。
二、查看注解信息
创建了注解,就可以在程序中使用,注解指定的目标,提供需要的参数,但这还是不会影响到程序的运行。要影响程序,我们要先能查看这些信息。我们主要考虑@Retention
为RetentionPolicy.RUNTIME
的注解,利用反射机制在运行时进行查看和利用这些信息。
我们知道反射相关类中有与注解有关的方法,这里汇总说明下,Class
、Field
、Method
、Constructor
中都有如下方法:
//获取所有的注解
public Annotation[] getAnnotations()
//获取所有本元素上直接声明的注解,忽略inherited来的
public Annotation[] getDeclaredAnnotations()
//获取指定类型的注解,没有返回null
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
//判断是否有指定类型的注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
Annotation
是一个接口,它表示注解,具体定义为:
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
//返回真正的注解类型
Class<? extends Annotation> annotationType();
}
实际上,所有的注解类型,内部实现时,都是扩展的Annotation
。
对于Method
和Contructor
,它们都有方法参数,而参数也可以有注解,所以它们都有如下方法:
public Annotation[][] getParameterAnnotations()
返回值是一个二维数组,每个参数对应一个一维数组,我们看个简单的例子:
public class MethodAnnotations {
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
static @interface QueryParam {
String value();
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
static @interface DefaultValue {
String value() default "";
}
public void hello(@QueryParam("action") String action,
@QueryParam("sort") @DefaultValue("asc") String sort){
// ...
}
public static void main(String[] args) throws Exception {
Class<?> cls = MethodAnnotations.class;
Method method = cls.getMethod("hello", new Class[]{String.class, String.class});
Annotation[][] annts = method.getParameterAnnotations();
for(int i=0; i<annts.length; i++){
System.out.println("annotations for paramter " + (i+1));
Annotation[] anntArr = annts[i];
for(Annotation annt : anntArr){
if(annt instanceof QueryParam){
QueryParam qp = (QueryParam)annt;
System.out.println(qp.annotationType().getSimpleName()+":"+ qp.value());
}else if(annt instanceof DefaultValue){
DefaultValue dv = (DefaultValue)annt;
System.out.println(dv.annotationType().getSimpleName()+":"+ dv.value());
}
}
}
}
}
代码比较简单,就不赘述了。
定义了注解,通过反射获取到注解信息,但具体怎么利用这些信息呢?我们看一个简单的示例,定制序列化。
三、应用注解 - 定制序列化
3.1 定义注解
本节演示如何对输出格式进行定制化。我们实现一个简单的类SimpleFormatter
,它有一个方法:
public static String format(Object obj)
我们定义两个注解,@Label
和@Format
,@Label
用于定制输出字段的名称,@Format
用于定义日期类型的输出格式,它们的定义如下:
@Retention(RUNTIME)
@Target(FIELD)
public @interface Label {
String value() default "";
}
@Retention(RUNTIME)
@Target(FIELD)
public @interface Format {
String pattern() default "yyyy-MM-dd HH:mm:ss";
String timezone() default "GMT+8";
}
3.2 使用注解
可以用这两个注解来修饰要序列化的类字段,比如:
static class Student {
@Label("姓名")
String name;
@Label("出生日期")
@Format(pattern="yyyy/MM/dd")
Date born;
@Label("分数")
double score;
public Student() {
}
public Student(String name, Date born, Double score) {
super();
this.name = name;
this.born = born;
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", born=" + born + ", score=" + score + "]";
}
}
我们可以这样来使用SimpleFormatter
:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Student zhangsan = new Student("张三", sdf.parse("1990-12-12"), 80.9d);
System.out.println(SimpleFormatter.format(zhangsan));
输出为:
姓名:张三
出生日期:1990/12/12
分数:80.9
3.3 利用注解信息
可以看出,输出使用了自定义的字段名称和日期格式,SimpleFormatter.format()
是怎么利用这些注解的呢?我们看代码:
public static String format(Object obj) {
try {
Class<?> cls = obj.getClass();
StringBuilder sb = new StringBuilder();
for (Field f : cls.getDeclaredFields()) {
if (!f.isAccessible()) {
f.setAccessible(true);
}
Label label = f.getAnnotation(Label.class);
String name = label != null ? label.value() : f.getName();
Object value = f.get(obj);
if (value != null && f.getType() == Date.class) {
value = formatDate(f, value);
}
sb.append(name + ":" + value + "\n");
}
return sb.toString();
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
对于日期类型的字段,调用了formatDate
,其代码为:
private static Object formatDate(Field f, Object value) {
Format format = f.getAnnotation(Format.class);
if (format != null) {
SimpleDateFormat sdf = new SimpleDateFormat(format.pattern());
sdf.setTimeZone(TimeZone.getTimeZone(format.timezone()));
return sdf.format(value);
}
return value;
}
这些代码都比较简单,我们就不解释了。
标签:String,自定义,value,class,如何,注解,public,Retention From: https://www.cnblogs.com/lin546/p/16926369.html