简单介绍 Java 中的注解 (Annotation)
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() + "\n");
}
}
}
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. 分类
1. @Target
注解的作用范围
分类
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
只能用于修饰注解
2. @Retention
注解的生命周期, 即注解到什么时候有效
分类
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
是运行时注解, 且只能用于修饰注解
3. @Document
表示注解是否能被 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
4. @Inherited
表示注解能否被继承, 不常见
实现
@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()]
说明注解被继承了, 也就是用反射的时候子类可以得到父类的注解的信息
2. 标准注解 (内建注解)
就是 jdk 自带的注解
1. @Override
作用是告诉编译器这个方法重写了父类中的方法
2. @Deprecated
表明当前的元素已经不推荐使用
3. @SuppressWarnings
用于让编译器忽略警告信息
5. 什么是注解
现在对注解的了解应该更深一些了. 下面概括一下什么是注解.
注解的定义如下
注解是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。是一种由 JSR-175 标准选择用来描述元数据的一种工具
简单来说, 注解就是代码的 元数据 metadata
, 包含了代码自身的信息, 即 描述代码的代码 . 我们可以使用注解给代码打上"标记", 通过解析 class
文件中相应的标记, 就可以做对应的处理.
需要注意的是, 注解 没有行为, 只有数据 , 是一种被动的信息, 不会对代码的执行造成影响, 需要配套的工具进行处理.
我们再来看一下 @Override
的声明
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这是一个源码级别的注解, 不会保留到 class
文件中.
这里有一个问题, @Override
这里并没有实现, 那是怎们实现对方法名称的检查的 ?
显然, 这里能看到注解的只有编译器, 所以编译器是这段注解的 "消费者", 也就是编译器实现了这部分业务逻辑.
6. 为什么要用注解
标记, 用于告诉编译器一些信息
编译时动态处理, 如动态生成代码
运行时动态处理, 如得到注解信息
后面两个主要是用于一些框架中
说到注解的话不得不提到 xml
, 许多框架是结合使用这两者的.xml
的优点是容易编辑, 配置集中, 方面修改, 缺点是繁琐==, 配置文件过多的时候难以管理.如果只是简单地配置参数, xml
比较适合
注解的优点是配置信息和代码放在一起, 增强了程序的内聚性, 缺点是分布在各个类中, 不宜维护.
如果要把某个方法声明为服务, 注解是更优的选择
7. 对注解底层实现的探讨
现在我们知道注解是 元数据
, 也知道注解需要配合处理器使用, 那注解本质上是什么呢.
我们回到自定义注解
@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 Map<String, Object> memberValues) {
return AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
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<T> {
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<City> {
}
测试类
/**
* 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);
简单介绍 Java 中的注解 (Annotation)的更多相关文章
- java中的注解(Annotation)
转载:https://segmentfault.com/a/1190000007623013 简介 注解,java中提供了一种原程序中的元素关联任何信息.任何元素的途径的途径和方法. 注解是那些插入到 ...
- java中的注解详解和自定义注解
一.java中的注解详解 1.什么是注解 用一个词就可以描述注解,那就是元数据,即一种描述数据的数据.所以,可以说注解就是源代码的元数据.比如,下面这段代码: @Override public Str ...
- [转]详细介绍java中的数据结构
详细介绍java中的数据结构 本文介绍的是java中的数据结构,本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类.一起来看本文吧! 也许你已经熟练使用了java.util包里面的各 ...
- 详细介绍java中的数据结构
详细介绍java中的数据结构 http://developer.51cto.com/art/201107/273003.htm 本文介绍的是java中的数据结构,本文试图通过简单的描述,向读者阐述各个 ...
- (转)简单介绍java Enumeration
简单介绍java Enumeration 分类: java技术备份 java数据结构objectstringclass存储 Enumeration接口 Enumeration接口本身不是一个数据结构 ...
- 简单聊聊java中的final关键字
简单聊聊java中的final关键字 日常代码中,final关键字也算常用的.其主要应用在三个方面: 1)修饰类(暂时见过,但是还没用过); 2)修饰方法(见过,没写过); 3)修饰数据. 那么,我们 ...
- java中元注解
java中元注解有四个: @Retention @Target @Document @Inherited: @Retention:注解的保留位置 @Retention(RetentionPolicy ...
- 详细介绍Java中的堆、栈和常量池
下面主要介绍JAVA中的堆.栈和常量池: 1.寄存器 最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈 存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在 ...
- 【java】详解java中的注解(Annotation)
目录结构: contents structure [+] 什么是注解 为什么要使用注解 基本语法 4种基本元注解 重复注解 使用注解 运行时处理的注解 编译时处理的注解 1.什么是注解 用一个词就可以 ...
随机推荐
- vs中调试程序查看变量在内存中的内容的方法
vs中调试程序 查看变量在内存中的内容的方法 https://blog.csdn.net/guojg1988/article/details/42922149 原文链接:http://www.sows ...
- hexo 博客如何更换电脑
如何在更换电脑后继续使用Hexo部署博客 重要目录 _config.yml package.json scaffolds/ source/ themes/ 在新电脑上配置hexo环境:安装node.j ...
- 第07组 Beta冲刺(1/4)
队名:秃头小队 组长博客 作业博客 组长徐俊杰 过去两天完成的任务:学习了很多东西 Github签入记录 接下来的计划:继续学习 还剩下哪些任务:后端部分 燃尽图 遇到的困难:自己太菜了 收获和疑问: ...
- 百度地图jsapi 自定义大头针的方法
百度地图jsapi 自定义大头针的方法<pre> var myIcon = new BMap.Icon("http://developer.baidu.com/map/jsdem ...
- 如何在运行时更改JMeter的负载
在某些情况下,能够在不停止测试的情况下更改性能测试产生的负载是有用的或必要的.这可以通过使用Apache JMeter™的恒定吞吐量计时器和Beanshell服务器来完成.在这篇文章中,我们将介绍如何 ...
- c# 用XmlWriter写xml序列化
using System.Text; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using ...
- 嵌入式02 STM32 实验04跑马灯
开学STM32 跑马灯的实验主要就是了解GPIO口的配置及使用,我这里是使用库函数进行编程,主要需要设置以下两方面: 1.使能需要使用的IO口的时钟,一共有A.B.C.D.E.F.G七组IO口 2.初 ...
- 用python写一个简单的文件上传
用Pycharm创建一个django项目.目录如下: <!DOCTYPE html> <html lang="en"> <head> <m ...
- WITH AS学习
一.WITH AS的含义 WITH AS短语,也叫做子查询部分(subquery factoring),可以让你做很多事情,定义一个SQL片断,该SQL片断会被整个SQL语句所用到.有的时候, ...
- jwt 0.9.0 系列目录
jwt官网地址:https://jwt.io/ PS: 写此系列的时候,jjwt jar包版本是0.9.0 <dependency> <groupId>io.jsonwe ...