简单介绍 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.什么是注解 用一个词就可以 ... 
随机推荐
- git pull的时候提示git pull <remote> <branch>
			yuanqiao@yuanqiao-PC MINGW64 /h/WorkSpace/git/dadeTest (dev)$ git pullremote: Enumerating objects: 7 ... 
- 【解决篇】映美FP-530K+打印发票卡纸,色带安装问题
			之前由于色带变浅了,而换了色带,后来出现了发票经常中间卡纸的状况,不过也不是不能打,只是偶尔出现,发现每次打,发票纸会撞击一下色带,导致有时候发票会被色带挡一下,导致中间卡纸,有时候又能过去.后来通过 ... 
- css几个优先级测试和!important
			css样式不加!important情况下的有默认优先级 ,用!important提高优先级,最常见的css样式选择器的优先级测试.之前博文里也用到了提升优先级的方法: 测试结果:加了!importan ... 
- centos7升级openssh
			注意: openssl版本(openssl版本要大于1.0.1,zlib版本要大于1.1.4) 一.安装依赖包 yum -y install gcc make perl # zlib zlib-dev ... 
- Spring boot后台搭建二集成Shiro权限控制
			上一篇文章,实现了用户验证 查看,接下来实现下权限控制 权限控制,是管理资源访问的过程,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等 Apache Shir ... 
- 【视频开发】OpenCV中Mat,图像二维指针和CxImage类的转换
			在做图像处理中,常用的函数接口有OpenCV中的Mat图像类,有时候需要直接用二维指针开辟内存直接存储图像数据,有时候需要用到CxImage类存储图像.本文主要是总结下这三类存储方式之间的图像数据的转 ... 
- nodepad++格式化html代码
			如果没有安装插件 
- php imagick 获取psd图层信息
			php imagick 获取psd图层信息<pre><?php$projectname = 'test';$im = new Imagick("test.psd" ... 
- IP通信学习心得02
			1.7层模型 2.OSI的应用 3.如何辨别.数清冲突域和广播域 1)首先,须知第一层不能隔离冲突域和广播域.例如集线器或者直接连PC 2)其次,第二层可以隔离冲突域,但不能隔离广播域.例如,二层交换 ... 
- Office系列常用快捷键
			office三件套,常用的快捷键. Word常用快捷键 查找文字.格式和特殊项 Ctrl+G 使字符变为粗体 Ctrl+B 为字符添加下划线 Ctrl+U 删除段落格式 Ctrl+Q 复制所选文本或对 ... 
