什么是注解 注解(Annotation,有的地方也翻译为注释,但是我觉得这样会和我们一般理解的注释混淆) 是 JDK 1.5 引入的新特性。注解是一种接口 。其实,这并不好理解。但我们可以从它的声明方式中看出一二:
1 2 3 public  @interface  AnnotationName {    String value ()  default  "" }  
实际上,我更喜欢这样理解:注解是一种元数据工具,或者说是我们给代码打的一个标记。 
注解的作用 如今,在基于 Java 的应用程序开发中,注解已经被广泛使用。注解的主要好处在于:提供编译器检查和代码分析功能。 java.lang 包中的:
@Deprecated :表明这个元素已经被弃用,如果使用它,编译器会发出警告。@Override :被标记的方法必须是父类或接口中已定义的方法,否则编译器会生成一条错误消息。@SuppressWarnings :可以用来消除显示指定的编译器警告。 
这类注解,我们主要用它们来执行一些的编译器的检查。
而另外一类注解,我们则是用它们来给代码添加一些信息,以便实现额外的功能。例如:
javax.annotation 中的:@Resource 。JPA 中的:@Entity 、@Table(name = "user") 。 
Spring 中的:@Component 、 @RequestMapping(value = "/index", method = RequestMethod.GET) 。 
 
就像前面所说的,注解只是一个标记,它本身并不会做任何事情,往往需要借助其它帮手才能发挥作用。注解可以选择只在源代码中有效,也可以供编译器使用,或者是在运行期再通过程序读取。
自定义注解 如果 JDK 内置的注解不能满足我们的使用需求,就要考虑引入第三方的注解,或是定义自己的注解类型。
首先,确定注解的名称。 
考虑是否需要属性,如果需要配置属性,则进一步考虑属性的类型,以及默认值。 
指定 Target ,也就是确定该注解适用于哪种程序元素(如果不指定,则可以用于任意元素)。可选择的元素有:
ElementType.TYPE :类、接口(包括注解)或枚举声明。ElementType.FIELD :字段(包括枚举常量)声明。ElementType.METHOD :方法声明。ElementType.PARAMETER :方法参数声明。ElementType.CONSTRUCTOR :构造方法声明。ElementType.LOCAL_VARIABLE :局部变量声明。ElementType.ANNOTATION_TYPE :注解声明。ElementType.PACKAGE :包声明。ElementType.TYPE_PARAMETER :类型参数声明(例如:class Ccc<@Xxx T> {}),JDK 1.8 新增。ElementType.TYPE_USE :类型使用(例如:new @Xxx Object() 或者是 void method() throws @Xxx Exception {}),JDK 1.8 新增。TYPE_USE 包含 TYPE_PARAMETER 。 
 
确认该注解的 Retention ,我理解为作用范围(或者是生命周期),也就是要保留多久。可选择的范围有:
RetentionPolicy.SOURCE :编译器会丢弃的注解,也就是在 class 文件中不会保留它们的信息。RetentionPolicy.CLASS :编译器会将注解保留在 class 文件中,但在运行时 虚拟机不会加载它们。这是默认的行为。RetentionPolicy.RUNTIME :编译器会将注解保留在 class 文件中,并且在运行时虚拟机也会加载。因此可以通过反射 API 读取它们。 
 
最后,确认是否需要将注解的信息加入到 Java Doc 中。如果需要加上 @Documented 即可。 
 
具体可以参考后面的代码。
需要注意的是:注解中属性声明实际上就是方法声明。 注解中的方法声明不能有任何参数,不能使用泛型,也不能抛出异常。 
让注解发挥作用 单单定义注解是没有什么实际意义的,它只不过是给我们的代码添加了一点点元数据而已。
假设我们正在开发一个简单的 ORM 组件,其中涉及自动生成 SQL 语句的功能。
1 2 3 4 5 6 7 8 9 10 11 12 import  java.util.Date;public  class  User      private  long  id;     private  String name;     private  String password;     private  String email;     private  Date createTime;      } 
其对应的数据库表是:
id 
username 
password 
email 
create_time 
 
 
1 
franky 
6d17840a 
franky@codinglike.com 
2015-08-01 15:06:56 
 
 
二者映射关系为:
类名/表名 
User 
user 
 
 
属性名/字段名 
id 
id 
 
属性名/字段名 
name 
username 
 
属性名/字段名 
password 
password 
 
属性名/字段名 
email 
email 
 
属性名/字段名 
createTime 
create_time 
 
 
这种映射关系,就是一种元数据。
1 2 3 4 5 6 7 <class  name ="User"  table ="user" >     <id  name ="id"  column ="id"  />      <property  name ="name"  column ="username"  />      <property  name ="password"  column ="password"  />      <property  name ="email"  column ="email"  />      <property  name ="createTime"  column ="create_time"  />  </class > 
但是,现在,我们更倾向于使用注解来解决这个问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  java.lang.annotation.Documented;import  java.lang.annotation.ElementType;import  java.lang.annotation.Retention;import  java.lang.annotation.RetentionPolicy;import  java.lang.annotation.Target;@Target (ElementType.TYPE)@Retention (RetentionPolicy.RUNTIME)@Documented public  @interface  Table {    String name ()  ; } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  java.lang.annotation.Documented;import  java.lang.annotation.ElementType;import  java.lang.annotation.Retention;import  java.lang.annotation.RetentionPolicy;import  java.lang.annotation.Target;@Target (ElementType.FIELD)@Retention (RetentionPolicy.RUNTIME)@Documented public  @interface  Column {         String name ()  ;      } 
1 2 3 4 5 6 7 8 9 10 11 import  java.lang.annotation.Documented;import  java.lang.annotation.ElementType;import  java.lang.annotation.Retention;import  java.lang.annotation.RetentionPolicy;import  java.lang.annotation.Target;@Target (ElementType.FIELD)@Retention (RetentionPolicy.RUNTIME)@Documented public  @interface  Id {} 
标记后的实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Table (name = "user" )public  class  User      @Id      private  long  id;     @Column (name = "username" )     private  String name;     private  String password;     private  String email;     @Column (name = "create_time" )     private  Date createTime;      } 
这样,元数据就有了,接下来就是如何去读取并利用它们:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 import  java.lang.reflect.Field;public  class  SqlBuilder                private  static  final  String DEFAULT_ID = "id" ;               * 构建查询一个实体对象的 SELECT 语句      *       * @param  clazz      * @return  SQL 语句      */      public  static  String buildSelect (Class<?> clazz)           StringBuilder sql = new  StringBuilder("SELECT " );                  StringBuilder id = new  StringBuilder(" FROM " ).append(                 getTableName(clazz)).append(" WHERE 1 = 1" );         String fieldName = null ;         String columnName = null ;         int  idCount = 0 ;         int  columnCount = 0 ;         Field[] fields = clazz.getDeclaredFields();         for  (Field field : fields) {             fieldName = field.getName();             columnName = getColumnName(field);                          boolean  isId = field.isAnnotationPresent(Id.class);             if  (isId) {                 id.append(" AND " ).append(columnName).append(" = ?" );                 idCount++;             }             if  (columnCount == 0 ) {                 sql.append(columnName);             } else  {                 sql.append(", " ).append(columnName);             }             columnCount++;         }                  if  (idCount == 0 ) {             id.append(" AND " ).append(DEFAULT_ID).append(" = :" )                     .append(DEFAULT_ID);         }         return  sql.append(id).toString();     }               * 判断实体属性是否标注了列名,如果已标注,返回标注的名称,否则直接返回属性名。      *       * @param  field      *            实体类属性      * @return  列名      */      private  static  String getColumnName (Field field)           String columnName = null ;         boolean  isColumn = field.isAnnotationPresent(Column.class);         if  (isColumn) {             Column column = field.getAnnotation(Column.class);             columnName = column.name();         } else  {             columnName = field.getName();         }         return  columnName;     }          * 判断实体是否标注了表名,如果已标注,返回标注的名称,否则直接返回类名。      *       * @param  clazz      *            实体类      * @return  表名      */      private  static  String getTableName (Class<?> clazz)           String tableName = null ;         boolean  isTable = clazz.isAnnotationPresent(Table.class);         if  (isTable) {             Table table = clazz.getAnnotation(Table.class);             tableName = table.name();         } else  {             tableName = clazz.getSimpleName();         }         return  tableName;     } } 
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  static  org.assertj.core.api.Assertions.assertThat;import  org.junit.Test;public  class  SqlBuilderTest      @Test      public  void  testBuildSelectById ()           String sql = SqlBuilder.buildSelectById(User.class);         assertThat(sql)                 .isEqualTo(                         "SELECT id, username, password, email, create_time FROM user WHERE 1 = 1 AND id = ?" );     } } 
从上面的代码可以看到,使用注解来表示元数据,可以精简配置。这就是它相对于配置文件的优势之一。除此之外,在使用注解的时候编译器还能够进行校验,减少出错,并且,由于注解是和代码放在一起的,这也有助于增强程序的内聚性,在一些情况更便于理解和维护。
Java Doc 注释 在我们编写 Java Doc 注释的时候,也会看到一些跟注解长得很像的家伙,例如:
@author@param@return@throws@deprecated@see@since 
但是它们并不是注解,这些只是 Java Doc 工具内置的一些标签而已。
总结 
一般来说,在实际的开发中,我们更多的只是去使用注解,这时候只需要认真阅读相关的 API 文档,搞清楚每个注解的含义,就可以了。 
在有些情况下(例如,开发组件或框架),我们可能需要开发自己的注解。这时候不妨参考一下本文上述内容,相信这也难不倒我们。 
虽然现在非常流行使用注解,但是,最终是选择使用注解还是配置文件,这不是由技术驱动的,而是要取决于我们的团队和具体的项目。