摘要:例子首先来看一个例子这里用了目的是告诉编译器这个方法重写了父类的方法如果编译器发现父类中没有这个方法就会报错这个注解的作用大抵是防止手滑写错方法同时增强了程序的可读性这里需要指出一点去掉并不会影响程序的执行只是起到标记的作用找到的实现关注点
1. 例子
首先来看一个例子:
@Override public String toString() { return "xxxxx"; }
这里用了 @Override, 目的是告诉编译器这个方法重写了父类的方法, 如果编译器发现父类中没有这个方法就会报错. 这个注解的作用大抵是防止手滑写错方法, 同时增强了程序的可读性. 这里需要指出一点, @Override 去掉并不会影响程序的执行, 只是起到标记的作用
找到 @Override 的实现
package java.lang; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
关注点有三个: @Target, @Retention, @interface. 从形状可以看出, 前两个也是注解. 它们用于描述注解, 称为 元注解 . @interface 用于定义一个注解, 类似于 public class/interface XXX 中的 class/interface
参照 @Override, 我们可以试着实现自己的注解.
2. 自定义注解@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { }
这个注解与 @Override 类似, 但是把 ElememtType.METHOD 改成了 ElementType.FIELD, 意思是在成员变量上使用. 把 RetentionPolicy.SOURCE 改成了 RetentionPolicy.RUNTIME, 表示注解一直持续到代码运行时.
这样就定义好了一个注解, 可以这样使用
public class Game { @Player private String A; }
当然这个注解太简单了, 我们可以加点料, 比如这样
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { String name() default "PT"; int ATK() default 1; int DEF() default 0; }
使用的时候就可以定义一些值了
public class Game { @Player(name = "CC", ATK = 2, DEF = 1) private String A; @Player(DEF = 2) private String B; }
注解元素必须有特定的值, 不指定值的话就会使用默认的 default 值.
注解里有一个相对特殊的方法 value(), 使用注解时只给 value 赋值时, 可以只写值. 例子如下
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { String name() default "PT"; int ATK() default 1; int DEF() default 0; double value(); }
public class Game { @Player(1.0) private String A; @Player(value = 3.0, DEF = 0) private String B; }
自定义注解大致如上. 给代码打上注解相当于做了标记, 只有搭配上相应的注解处理流程, 才能算是真正发挥作用. 接下来介绍如何处理注解
3. 注解处理器这里使用反射获取注解信息. 只有标注为 RetentionPolicy.RUNTIME 的注解可以这么提取信息.
/** * 注解处理器 */ public class PlayerProcessor { public static void process() { // 获取成员变量 Field[] fields = Game.class.getDeclaredFields(); for (Field field : fields) { // 判断是否是 Player 注解 if (field.isAnnotationPresent(Player.class)) { Player annotation = field.getAnnotation(Player.class); System.out.println("name = " + annotation.name()); System.out.println("attack = " + annotation.ATK()); System.out.println("defence = " + annotation.DEF()); System.out.println("type = "+ annotation.annotationType() + " "); } } } public static void main(String[] args) { process(); } }
输出如下
name = CC attack = 2 defence = 2 type = interface Player name = PT attack = 1 defence = 10 type = interface Player
这样粗略地介绍完了创建注解到处理注解的流程. 下面讲一下注解中的几个概念.
4. 概念 1. 元注解 1. 作用描述注解的注解, 在创建新注解的时候使用
2. 分类注解的作用范围
分类
FIELD : 成员变量, 包括枚举常量
LOCAL_VARIABLE : 局部变量
METHOD : 方法
PARAMETER : 参数
TYPE : 类、接口(包括注解类型) 或 enum 声明
ANNOTATION_TYPE : 注解类型
等等
实现
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
ElementType[] 是枚举类型的数组, 定义了上面的分类( FIELD, METHOD 等 ). @Target(ElementType.ANNOTATION_TYPE) 表示 @Target 只能用于修饰注解
注解的生命周期, 即注解到什么时候有效
分类
SOURCE
注解只保留在源文件, 当 Java 文件编译成 class 文件的时候, 注解被遗弃
CLASS
注解被保留到 class 文件, jvm 加载 class 文件时候被遗弃. 这是默认的生命周期
RUNTIME
注解不仅被保存到 class 文件中, jvm 加载 class 文件之后, 仍然存在, 保存到 class 对象中, 可以通过反射来获取
实现
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }
RetentionPolicy 是枚举类型( SOURCE, CLASS, RUNTIME )
上述代码表示 @Retention 是运行时注解, 且只能用于修饰注解
表示注解是否能被 javadoc 处理并保留在文档中
实现
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
表明它自身也会被文档化, 是运行时注解, 且只能用于修饰注解类型
例子
注解类没有加 @Document
public @interface Doc { }
被文档化的类
public class DocExample { @Doc public void doc() {} }
生成的 javadoc
加上 @Document 后
@Document public @interface Doc { }
生成的 javadoc
表示注解能否被继承, 不常见
实现
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
例子
public class TestInherited { @Inherited @Retention(RetentionPolicy.RUNTIME) // 设成 RUNTIME 才能输出信息 @interface Yes {} @Retention(RetentionPolicy.RUNTIME) @interface No {} @Yes @No class Father {} class Son extends Father {} public static void main(String[] args) { System.out.println(Arrays.toString(Son.class.getAnnotations())); } }
输出: [@TestInherited$Yes()]
说明注解被继承了, 也就是用反射的时候子类可以得到父类的注解的信息
就是 jdk 自带的注解
1. @Override作用是告诉编译器这个方法重写了父类中的方法
2. @Deprecated表明当前的元素已经不推荐使用
3. @SuppressWarnings用于让编译器忽略警告信息
5. 什么是注解现在对注解的了解应该更深一些了. 下面概括一下什么是注解.
注解的定义如下
注解是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。是一种由 JSR-175 标准选择用来描述元数据的一种工具
简单来说, 注解就是代码的 元数据 metadata , 包含了代码自身的信息, 即 描述代码的代码 . 我们可以使用注解给代码打上"标记", 通过解析 class 文件中相应的标记, 就可以做对应的处理.
需要注意的是, 注解 没有行为, 只有数据 , 是一种被动的信息, 不会对代码的执行造成影响, 需要配套的工具进行处理.
我们再来看一下 @Override 的声明
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
这是一个源码级别的注解, 不会保留到 class 文件中.
这里有一个问题, @Override 这里并没有实现, 那是怎们实现对方法名称的检查的 ?
显然, 这里能看到注解的只有编译器, 所以编译器是这段注解的 "消费者", 也就是编译器实现了这部分业务逻辑.
标记, 用于告诉编译器一些信息
编译时动态处理, 如动态生成代码
运行时动态处理, 如得到注解信息
后面两个主要是用于一些框架中
说到注解的话不得不提到 xml, 许多框架是结合使用这两者的.
xml 的优点是容易编辑, 配置集中, 方面修改, 缺点是繁琐==, 配置文件过多的时候难以管理.如果只是简单地配置参数, xml 比较适合
注解的优点是配置信息和代码放在一起, 增强了程序的内聚性, 缺点是分布在各个类中, 不宜维护.
如果要把某个方法声明为服务, 注解是更优的选择
现在我们知道注解是 元数据, 也知道注解需要配合处理器使用, 那注解本质上是什么呢.
我们回到自定义注解
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Player { String name() default "PT"; int ATK() default 1; int DEF() default 0; }
编译后对字节码做一些处理: javap -verbose Player.class
可以看到
Last modified 2017-5-26; size 498 bytes MD5 checksum 4ca03164249758f784827b6d103358de Compiled from "Player.java" public interface Player extends java.lang.annotation.Annotation minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION Constant pool: #1 = Class #23 // Player #2 = Class #24 // java/lang/Object #3 = Class #25 // java/lang/annotation/Annotation #4 = Utf8 name #5 = Utf8 ()Ljava/lang/String; #6 = Utf8 AnnotationDefault #7 = Utf8 PT #8 = Utf8 ATK #9 = Utf8 ()I #10 = Integer 1 #11 = Utf8 DEF #12 = Integer 0 #13 = Utf8 SourceFile #14 = Utf8 Player.java #15 = Utf8 RuntimeVisibleAnnotations #16 = Utf8 Ljava/lang/annotation/Target; #17 = Utf8 value #18 = Utf8 Ljava/lang/annotation/ElementType; #19 = Utf8 FIELD #20 = Utf8 Ljava/lang/annotation/Retention; #21 = Utf8 Ljava/lang/annotation/RetentionPolicy; #22 = Utf8 RUNTIME #23 = Utf8 Player #24 = Utf8 java/lang/Object #25 = Utf8 java/lang/annotation/Annotation { public abstract java.lang.String name(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: s#7 public abstract int ATK(); descriptor: ()I flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: I#10 public abstract int DEF(); descriptor: ()I flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: I#12} SourceFile: "Player.java" RuntimeVisibleAnnotations: 0: #16(#17=[e#18.#19]) 1: #20(#17=e#21.#22)
这里需要注意的是这个
public interface Player extends java.lang.annotation.Annotation
意思已经很明显了, 注解是继承了 Annotation 类的 接口.
那么通过反射获得的实例是哪来的呢 ? 通过看源码可以发现, 在用 XXX.class.getAnnotation(XXX.class) 创建一个注解实例时, 用到了 AnnotationParser.parseAnnotations 方法.
在 openjdk 8 的 sun.reflect.annotation.AnnotationParser.java 文件中, 有方法
public static Annotation annotationForMap(final Class extends Annotation> type, final MapmemberValues) { return AccessController.doPrivileged(new PrivilegedAction () { public Annotation run() { return (Annotation) Proxy.newProxyInstance( type.getClassLoader(), new Class>[] { type }, new AnnotationInvocationHandler(type, memberValues)); }}); }
这里的 AnnotationInvocationHandler 实现了 InvocationHandler 接口, 所以运行期间获得的实例其实是通过 动态代理 生成的.
8. 实际项目举例这里实现一个类似于 ORM 的功能, 加深一下对运行时注解的理解
注解类
表
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String name(); }
列
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String name(); }
实体类
/** * Created by away on 2017/5/27. */ @Table(name = "city") public class City { @Column(name = "city_id") private int id; @Column(name = "city_name") private String name; // getset }
SQL 方法类
/** * Created by away on 2017/5/27. */ public class ORMSupport{ public void save(T entity) { StringBuffer sql = new StringBuffer(30); sql.append("insert into "); processTable(entity, sql); processField(entity, sql); System.out.println(sql); } private void processTable(T entity, StringBuffer sql) { Table table = entity.getClass().getDeclaredAnnotation(Table.class); if (table != null) { sql.append(table.name()).append(" ("); } } private void processField(T entity, StringBuffer sql) { Field[] fields = entity.getClass().getDeclaredFields(); StringBuilder val = new StringBuilder(); val.append("("); String comma = ""; for (Field field : fields) { if (field.isAnnotationPresent(Column.class)) { String name = field.getAnnotation(Column.class).name(); sql.append(comma).append(name); val.append(comma).append(getColumnVal(entity, field.getName())); } comma = ", "; } sql.append(") values ") .append(val) .append(");"); } private Object getColumnVal(T entity, String field) { StringBuilder methodName = new StringBuilder(); String firstLetter = (field.charAt(0) + "").toUpperCase(); methodName.append("get") .append(firstLetter) .append(field.substring(1)); try { Method method = entity.getClass().getMethod(methodName.toString()); return method.invoke(entity); } catch (Exception e) { e.printStackTrace(); return ""; } } }
DAO
/** * Created by away on 2017/5/27. */ public class CityRepository extends ORMSupport{ }
测试类
/** * Created by away on 2017/5/27. */ public class ORMTest { public static void main(String[] args) { City city = new City(); city.setId(1); city.setName("nanjing"); CityRepository cityRepository = new CityRepository(); cityRepository.save(city); } }
输出
insert into city (city_id, city_name) values (1, nanjing);
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/67114.html
摘要:第章元编程与注解反射反射是在运行时获取类的函数方法属性父类接口注解元数据泛型信息等类的内部信息的机制。本章介绍中的注解与反射编程的相关内容。元编程本质上是一种对源代码本身进行高层次抽象的编码技术。反射是促进元编程的一种很有价值的语言特性。 第12章 元编程与注解、反射 反射(Reflection)是在运行时获取类的函数(方法)、属性、父类、接口、注解元数据、泛型信息等类的内部信息的机...
摘要:如果在中没有找到该错误请通过报告页建立该编译器。请在报告中附上您的程序和以下诊断信息。 1. 前言 上一篇 主要介绍了什么是 注解 (Annotation) 以及如何读取 运行时注解 中的数据, 同时用注解实现了简单的 ORM 功能. 这次介绍另一部分: 如何读取 编译时注解 ( RetentionPolicy.SOURCE ) 2. 作用 编译时注解可以用来动态生成代码. 使用 S...
摘要:上一篇博客介绍了如何基于配置文件在运行时创建实例对象,这篇博客将介绍基于注解方式怎样实现对象的创建。方便测试,该类型分别创建两个单例和多例的类型。注意这种为对象注入属性值的方式耦合度较高,可根据情况使用。 上一篇博客介绍了如何基于xml配置文件在运行时创建实例对象,这篇博客将介绍基于注解方式怎样实现对象的创建。 废话不多说,直接上代码。 首先还是创建项目,由于这次不需要使用第三方的AP...
阅读 793·2019-08-30 15:55
阅读 1392·2019-08-30 13:55
阅读 1955·2019-08-29 17:13
阅读 2814·2019-08-29 15:42
阅读 1286·2019-08-26 14:04
阅读 999·2019-08-26 13:31
阅读 3240·2019-08-26 11:34
阅读 807·2019-08-23 18:25