一、创建注解

1.1 @Override 注解的定义

我们通过一些例子来说明,先看@Override的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

定义注解与定义接口有点类似,都用了interface,不过注解的interface前多了@,另外,它还有两个元注解@Target和@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"})

注解内参数的类型不是什么都可以的,合法的类型有基本类型、StringClass、枚举、注解、以及这些类型的数组。

参数定义时可以使用default指定一个默认值,比如,GuiceInject注解的定义:

@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了。

二、查看注解信息

创建了注解,就可以在程序中使用,注解指定的目标,提供需要的参数,但这还是不会影响到程序的运行。要影响程序,我们要先能查看这些信息。我们主要考虑@RetentionRetentionPolicy.RUNTIME的注解,利用反射机制在运行时进行查看和利用这些信息。

我们知道反射相关类中有与注解有关的方法,这里汇总说明下,ClassFieldMethodConstructor中都有如下方法:

//获取所有的注解
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

对于MethodContructor,它们都有方法参数,而参数也可以有注解,所以它们都有如下方法:

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;
}

这些代码都比较简单,我们就不解释了。

原文地址:http://www.cnblogs.com/lin546/p/16926369.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性