框架基础知识——注解
一、创建注解
注解是一种手段,它可以给程序添加一些信息,用字符@开头,这些信息用于修饰它后面紧挨着的其他代码元素,比如类、接口、字段、方法、参数、构造方法等。注解本身只是一种标记,不会改变当前代码的行为,类似标记接口的作用,但是它可以被编译器、程序运行时和其他工具使用,可以被用于增强或修改代码行为等。
我们可以像定义一个接口一样定义注解:
@Target(ElementType.METHOD) //声明注解的目标 @Retention(RetentionPolicy.SOURCE) //声明注解信息保留到什么时候 @interface TestAnnotation{ //定义注解 String value(); //定义注解的参数,可以有多个 }
@interface 为定义注解的关键字,和定义接口相似;
@Target和@Retention是两个元注解,其中@Target声明该注解的作用目标,有以下几种:
- TYPE——表示适用于类、接口或枚举
- FIELD——字段,包括枚举常量
- METHOD——方法
- PARAMETER——方法中的参数
- CONSTRUCTOR——构造方法
- LOCAL_VARIABLE——本地变量
- MODULE——模块(java 9引入)
目标可以有多种类型,用{,}隔开,默认是所有类型。
@Retention表示注解信息保留到什么时候,有以下三种:
- SOURCE——只在源代码中保留,编译器将代码编译成字节码文件后就会丢掉
- CLASS——保留到字节码文件中,但Java虚拟机将class文件加载到内存是不一定在内存中保留
- RUNTIME——一直保留到运行时
只能选择一种,默认是CLASS。
可以为注解定义一些参数,如上例中的Sting value(),其中String 是参数类型,value是参数名,也可以指定默认值(如没有声明默认值,使用时必须赋值):
String value() default "v0";
上面注解的使用方法如下:
@TestAnnotation(value = "v1") public void operation() {...}
如果只有一个参数,且名称是value时可以省略参数名:
@TestAnnotation("v1") public void operation() {...}
二、查看注解
创建了注解并在代码中使用,并不会影响代码的行为。要影响代码行为首先要能够查看注解信息,我们一般使用反射机制在运行时查看和利用注解信息。反射中有很多与注解相关的方法:
//判断是否有指定类型的注解 public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {} //获取该元素所有注解 public Annotation[] getAnnotations() {} //获取该元素指定注解 public <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {} //获取参数类型注解,仅Method和Constructor元素有此方法 public Annotation[][] getParameterAnnotations() {}
下面利用反射查看并调用带有特定类型注解的方法:
public static <T> void parseMethod(Class<T> clazz) { try { T obj = clazz.newInstance(); //创建反射类实例 for (Method method : clazz.getDeclaredMethods()) { //获取所有声明的方法 MyMethodAnnotation methodAnnotation = method.getAnnotation(MyMethodAnnotation.class); //获取每个方法的MyMethodAnnotation类型注解信息 if (methodAnnotation != null) { // 通过反射调用带有此注解的方法,并引用注解参数uri method.invoke(obj, methodAnnotation.uri()); } } } catch (Exception e) { e.printStackTrace(); } }
三、应用注解
下面以格式化输出为例讲解注解的实战用法。
如果我们需要打印一个对象的变量信息,通常是调用它的toString方法,但是往往不能达到我们想要的信息展示效果,我们需要定制toString方法,如果有多个不同对象,则需要定制每个对象对应类的toString方法。
使用注解可以简化这一过程,首先定义两个注解:
//表示变量名称 @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"; }
然后使用这两个注解:
static class Student { @Label("姓名") String name; @Label("出生日期") @Format(pattern = "yyyy/MM/dd") Date born; @Label("分数") double score; }
定义一个SimpleFormatter类在运行时查看并使用注解信息对输出格式化:
public class SimpleFormatter { public static String format(Object obj) { try { Class<?> cls = obj.getClass(); //通过实例获取Class类 StringBuilder sb = new StringBuilder(); for (Field f : cls.getDeclaredFields()) { //获取所有变量 if (!f.isAccessible()) { //取消访问限制 f.setAccessible(true); } Label label = f.getAnnotation(Label.class); //获取Lable注解 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); } } }
使用方法如下:
public static void main(String[] args) throws ParseException { 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
四、总结
注解提升了Java语言的表达能力,有效地实现了应用功能和底层功能的分离,框架/库的程序员可以专注于底层实现,借助反射实现通用功能,提供注解给应用程序员;应用程序员可以专注于应用功能开发,通过简单的声明式注解与框架/库进行协作。