目录结构:

contents structure [+]

1.什么是注解

用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。比如,下面这段代码:

@Override
public String toString() {
return "This is String Representation of current object.";
}

上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){double r},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。

Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。

2.为什么要使用注解

使用Annotation之前(甚至在使用之后),XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。

假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。

另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。

3.基本语法

编写Annotation非常简单,可以将Annotation的定义同接口的定义进行比较。我们来看两个例子:一个是标准的注解@Override,另一个是用户自定义注解@Todo。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

对于@Override注释你可能有些疑问,它什么都没做,那它是如何检查在父类中有一个同名的函数呢。当然,不要惊讶,我是逗你玩的。@Override注解的定义不仅仅只有这么一点代码。这部分内容很重要,我不得不再次重复:Annotations仅仅是元数据,和业务逻辑无关。理解起来有点困难,但就是这样。如果Annotations不包含业务逻辑,那么必须有人来实现这些逻辑。元数据的用户来做这个事情。Annotations仅仅提供它定义的属性(类/方法/包/域)的信息。Annotations的用户(同样是一些代码)来读取这些信息并实现必要的逻辑。

当我们使用Java的标注Annotations(例如@Override)时,JVM就是一个用户,它在字节码层面工作。到这里,应用开发人员还不能控制也不能使用自定义的注解。因此,我们讲解一下如何编写自定义的Annotations。

我们来逐个讲述编写自定义Annotations的要点。上面的例子中,你看到一些注解应用在注解上。

3.1 四种基本元注解

J2SE5.0版本在 java.lang.annotation提供了四种元注解,专门注解其他的注解:
@Documented –注解是否将包含在JavaDoc中
@Retention –什么时候使用该注解
@Target? –注解用于什么地方
@Inherited – 是否允许子类继承该注解

@Documented–一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。

@Retention– 定义该注解的生命周期。
RetentionPolicy.SOURCE – 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
RetentionPolicy.CLASS – 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。
RetentionPolicy.RUNTIME– 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

@Target – 表示该注解用于什么地方。如果不明确指出,该注解可以放在任何地方。以下是一些可用的参数。需要说明的是:属性的注解是兼容的,如果你想给7个属性都添加注解,仅仅排除一个属性,那么你需要在定义target包含所有的属性。
ElementType.TYPE:用于描述类、接口或enum声明
ElementType.FIELD:用于描述实例变量
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE 另一个注释
ElementType.PACKAGE 用于记录java文件的package信息

@Inherited – 定义该注释和子类的关系

那么,注解的内部到底是如何定义的呢?Annotations只支持基本类型、String及枚举类型。注释中所有的属性被定义成方法,并允许提供默认值。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Todo {
public enum Priority {LOW, MEDIUM, HIGH}
public enum Status {STARTED, NOT_STARTED}
String author() default "Yash";
Priority priority() default Priority.LOW;
Status status() default Status.NOT_STARTED;
}

下面的例子演示了如何使用上面的注解。

@Todo(priority = Todo.Priority.MEDIUM, author = "Yashwant", status = Todo.Status.STARTED)
public void incompleteMethod1() {
//Some business logic is written
//But it’s not complete yet
}

如果注解中只有一个属性,可以直接命名为“value”,使用时无需再标明属性名。

@interface Author{
String value();
}
@Author("Yashwant")
public void someMethod() {
}

3.2 重复注解

在Java8以前,同一个程序元素前只能使用一个相同类型的Annotation;如果需要在同一个元素前使用多个类型相同的Annotation,则必须使用Annotation“容器”。
下面先介绍这种“容器”,
首先定义个MyTag注解:

//指定注解保留到运行时
@Retention(RetentionPolicy.RUNTIME)
//指定注解可以修饰类、接口、枚举
@Target(ElementType.TYPE)
@interface MyTag
{
String name() default "测试";
int age() default 20;
}

然后再定义MyTag注解的容器注解:

//指定注解保留到运行时
@Retention(RetentionPolicy.RUNTIME)
//指定注解可以修饰类、接口、枚举
@Target(ElementType.TYPE)
@interface MyTags
{
MyTag[] value();
}

然后就可以按照如下的方式来使用注解了

@MyTags({
@MyTag(name="测试1",age=21),
@MyTag(name="测试2",age=22)
})
public class Test {
public static void main(String[] args)
{
//通过反射解析注解
Class testClass= Test.class;
//获得MyTags注解
MyTags myTagsAnnotation= (MyTags) testClass.getAnnotation(MyTags.class);
//获得添加到里面的MyTag注解
MyTag[] myTags=myTagsAnnotation.value();
for(MyTag myTag : myTags)
{
System.out.println(String.format("name:%1$s,age:%2$d",myTag.name(),myTag.age()));
}
}
}

打印:
name:测试1,age:21
name:测试2,age:22

java8为上面这种繁琐的语法提供了糖语法,在java8中新增加了@Repeatable元注解,只需要在MyTag注解上添加上元注解@Repeatable(MyTags.class)。
观察可以发现,@Repeatable依然需要依赖容器注解,所以依然可以按照如下的方式来使用:

@MyTags({
@MyTag(name="测试1",age=21),
@MyTag(name="测试2",age=22)
})

又因为MyTag是重复注解,所以还可以像如下这样使用:

@MyTag(name="测试1",age=21)
@MyTag(name="测试2",age=22)

这里需要注意,重复注解只是一种简便写法,多个重复注解其实会被作为“容器”注解的value成员变量的数组元素。例如上面重复的MyTag注解会被作为@MyTags注解的value成员变量的数组元素处理。

4.使用注解

现在我们已经知道了可以通过使用@Retention注解来指定注解的生存周期,注解的生存周期有三种,分别为:RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME,这三个值分别表示注解的生存周期为源代码,字节码,运行时中。

接下来介绍注解在不同阶段中的处理:

4.1 运行时处理的注解

其实在上面的案例中,已经展示了如何使用反射获取注解的数据。如果要在程序运行时处理注解,那么必须将注解的声明周期声明为: @Retention(RetentionPolicy.RUNTIME) 。
由于注解本身是不包含任何业务逻辑的,在运行时中,我们就可以通过反射来实现具体的逻辑,
先定义一个Debug注解:

//指定注解保留到运行时
@Retention(RetentionPolicy.RUNTIME)
//指定该注解只能用于方法
@Target(ElementType.METHOD)
@interface Debug
{
boolean value() default false;
}

接下来将该注解和具体的业务逻辑关联起来:

public class DebugTest {
public static void main(String[] args) {
Class debugTestClass = DebugTest.class;
//获得所有的方法
Method[] methods = debugTestClass.getMethods();
for (Method method : methods) {
method.setAccessible(true);//禁用安全机制
if (method.isAnnotationPresent(Debug.class)) {//检查是否使用了Debug注解
Debug debug = method.getAnnotation(Debug.class);//获得注解实例
String name = method.getName();//获得方法名称
if (debug.value()) {
System.out.println("method:" + name + " should debug");
} else {
System.out.println("method:" + name + " should't debug");
}
}
}
}
@Debug(false)
public void testMethod1() {
}
@Debug(true)
public void testMethod2() {
}
@Debug(true)
public void testMethod3() {
}
@Debug(false)
public void testMethod4() {
}
@Debug(true)
public void testMethod5() {
}
}

4.2 编译时处理的注解

若是编译时需要处理的注解,那么可以把注解的声明周期声明为: @Retention(RetentionPolicy.SOURCE) 。

在这里需要先介绍一下APT,API(Annotation Processing Tool)是一种注解处理工具,他对源代码进行检测,并找出源代码所包含的Annotation信息,然后针对Annotation信息进行额外的处理。使用APT工具处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其他的文件(文件的具体内容由Annotation处理器的编写者决定),APT还会将编译生成的源代码文件和原来的源文件一起生成Class文件。

使用APT的主要目的是为了简化开发者的工作量,因为APT可以在编译程序源代码的同时生成一些附属文件(比如源文件、类文件、程序发布描述文件等),这些附属文件的内容也都与源代码相关。换句话说,使用APT可以代替传统的对代码信息和附属文件的维护工具。

Java提供的javac.exe工具有一个-processor选项,该选项可指定一个Annotation处理器,如果在编译java源文件时指定了该Annotation处理器,那么这个Annotation处理器将会在编译时提取并处理Java源文件中的Annotaion.
每一个Annoataion处理器都需要实现javax.annotataion.processor包下的Processor接口,不过实现该接口必须实现该接口下的所有的方法,因此通常会采用继承AbstractProcessor的方式来实现Annotation的处理器。一个Annotation处理器可以处理一个或多个Annotaion注解。

在Hibernate中,如果使用非注解的方式,那么每写一个Java Bean类文件,还必须额外地维护一个Hibernate映射文件(名为*.hbm.xml的文件),下面将使用APT来简化这步操作。

为了示范使用APT根据源文件中的注解来生成额外的文件,下面定义3种注解。
标识持久化类的@Persistent 注解:

//指定该注解可以修饰类,接口,枚举
@Target(ElementType.TYPE)
//指定该注解保留到编译时
@Retention(RetentionPolicy.SOURCE)
//指定该注解可以被显示在文档中(通过javadoc生成文档,便可以在被该注解修饰的元素上看到该注解信息)
@Documented
public @interface Persistent {
String table();
}

标识属性的@Id 注解:

//指定该注解只能修饰字段
@Target(ElementType.FIELD)
//指定该注解保留到编译时
@Retention(RetentionPolicy.SOURCE)
//指定该注解可以被显示在文档中(通过javadoc生成文档,便可以在被该注解修饰的元素上看到该注解信息)
@Documented
public @interface Id {
String column();
String type();
String generator();
}

标识属性的@Property 注解

//指定该注解只能修饰字段
@Target(ElementType.FIELD)
//指定该注解保留到编译时
@Retention(RetentionPolicy.SOURCE)
//指定该注解可以被显示在文档中(通过javadoc生成文档,便可以在被该注解修饰的元素上看到该注解信息)
@Documented
public @interface Property {
String column();
String type();
}

在有了三个Annotation后,我们定义一个简单的Java Bean类Person.java.

@Persistent(table="personInfo")
public class Person {
@Id(column="person_id",type="integer",generator="identity")
private int id;
@Property(column="person_name",type="string")
private String name;
@Property(column="person_age",type="integer")
private int age; public Person(){} public Person(int id,String name,int age)
{
this.id=id;
this.name=name;
this.age=age;
}
//所有属性的setter和getter.....
}

接下来写一个API工具,该API工具是根据java类中的注解来生成一个Hibernate 映射文件。

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Set; import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement; //指定该注解支持java平台的最新版本为6.0
@SupportedSourceVersion(SourceVersion.RELEASE_6)
//指定可以处理Persistent,id,Property注解
@SupportedAnnotationTypes({"Persistent","Id","Property"})
public class HibernateAnnotationProcessor extends AbstractProcessor{
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
//定义文件输出流,用于生成额外的文件
PrintStream ps=null;
try{
for(Element t:roundEnv.getElementsAnnotatedWith(Persistent.class)){
//获取正在处理的类名称
Name className=t.getSimpleName();
//获得类定义前的@Persistent Annotation
Persistent per= t.getAnnotation(Persistent.class);
//创建文件输出流
ps=new PrintStream(new FileOutputStream(new File(className+".hbm.xml")));
//执行输出
ps.println("<?xml version=\"1.0\"?>");
ps.println("<!DOCTYPE hibernate-mapping PUBLIC \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"");
ps.println("\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">");
ps.println("<hibernate-mapping>");
ps.println("<class name=\""+className+"\" table=\""+per.table()+"\" >");
for(Element f:t.getEnclosedElements())
{
//只处理成员变量上的Annotation
if(f.getKind()==ElementKind.FIELD)
{
//获取成员变量定义前的@Id Annotation
Id id=f.getAnnotation(Id.class);
//但@id注解存在时,输出<id ../>元素
if(id!=null)
{
ps.println("<id name=\""+f.getSimpleName()+"\" "+
"column=\""+id.column()+"\" "+
"type=\""+id.type()+"\">");
ps.println("<generator class=\""+id.generator()+"\" />");
ps.println("</id>");
continue;
}
//获取成员变量前的@Property Annotation
Property p=f.getAnnotation(Property.class);
if(p!=null)
{
ps.println("<property name=\""+f.getSimpleName()+"\" "+
"column=\""+p.column()+"\" "+
"type=\""+p.type()+"\" />");
continue;
}
}
}
ps.println("</class>");
ps.println("</hibernate-mapping>");
}
}catch(Exception e)
{
e.printStackTrace();
}finally{
if(ps!=null)
ps.close();
}
return true;
}
}

在编译完HibernateAnnotationProcessor.java后执行如下的命令:

javac -processor HibernateAnnotationProcessor Person.java

就可以看到在该路径下多了一个Person.cfg.xml文件

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Person" table="personInfo" >
<id name="id" column="person_id" type="integer">
<generator class="identity" />
</id>
<property name="name" column="person_name" type="string" />
<property name="age" column="person_age" type="integer" />
</class>
</hibernate-mapping>

【java】详解java中的注解(Annotation)的更多相关文章

  1. java 乱码详解_jsp中pageEncoding、charset=UTF -8"、request.setCharacterEncoding("UTF-8")

    http://blog.csdn.net/qinysong/article/details/1179480 java 乱码详解__jsp中pageEncoding.charset=UTF -8&quo ...

  2. 详解Java中的clone方法

    详解Java中的clone方法 参考:http://blog.csdn.net/zhangjg_blog/article/details/18369201/ 所谓的复制对象,首先要分配一个和源对象同样 ...

  3. 【转帖】windows命令行中java和javac、javap使用详解(java编译命令)

    windows命令行中java和javac.javap使用详解(java编译命令) 更新时间:2014年03月23日 11:53:15   作者:    我要评论 http://www.jb51.ne ...

  4. 详解javaweb中jstl如何循环List中的Map数据_java - JAVA

    文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 详解javaweb中jstl如何循环List中的Map数据 第一种方式: 1:后台代码(测试) List<Map& ...

  5. java基础(十五)----- Java 最全异常详解 ——Java高级开发必须懂的

    本文将详解java中的异常和异常处理机制 异常简介 什么是异常? 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常. Java异常的分类和类结构图 1.Java中的所 ...

  6. Java 详解 JVM 工作原理和流程

    Java 详解 JVM 工作原理和流程 作为一名Java使用者,掌握JVM的体系结构也是必须的.说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成:Java ...

  7. 详解Java GC的工作原理+Minor GC、FullGC

    详解Java GC的工作原理+Minor GC.FullGC 引用地址:http://www.blogjava.net/ldwblog/archive/2013/07/24/401919.html J ...

  8. Protocol Buffer技术详解(Java实例)

    Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发 ...

  9. 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)

    在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...

  10. 第三节:带你详解Java的操作符,控制流程以及数组

    前言 大家好,给大家带来带你详解Java的操作符,控制流程以及数组的概述,希望你们喜欢 操作符 算数操作符 一般的 +,-,*,/,还有两个自增 自减 ,以及一个取模 % 操作符. 这里的操作算法,一 ...

随机推荐

  1. 利用Docker搭建java项目开发环境

    一.需求 一台 Ubuntu 16.0.4 LTS ,安装了Docker服务,Rancher服务,也制作了Tomcat相关的image,接下来我们就来说一下如何快速的构建一个开发环境和测试环境 二.步 ...

  2. Tomcat的性能与最大并发配置

    当一个进程有 500 个线程在跑的话,那性能已经是很低很低了.Tomcat 默认配置的最大请求数是 150,也就是说同时支持 150 个并发,当然了,也可以将其改大. 当某个应用拥有 250 个以上并 ...

  3. SQL到NoSQL概览性总结之一 数据库应用场景选型

    数据库类型与实例 适合场景 不适合场景 场景举例 关系数据库 基于集合理论,具有行和列的二维表,严格使用类型 开源MySQL/MariaDB, PostgreSQL 商业:Oracle,DB2,SQL ...

  4. Volume Shadow Copy Service(VSS)如何工作

    VSS卷影拷贝服务其实不是一项新技术了,在2003年前后发布的Windows 2003和Windows XP SP1都提供了对VSS的支持.最近几年微软的一线产品对VSS支持的越来越多,包括Excha ...

  5. 深度学习-Caffe编译测试的小总结

    1. 搭建的环境和代码:win7 64bit + vs2013+CUDA7.5 http://blog.csdn.net/thesby/article/details/50880802 2. 编译,制 ...

  6. Redis问题MISCONF Redis is configured to save RDB snapshots....

    Redis问题MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on di ...

  7. GitHub如何下载clone指定的tag

    如上图,我想下载Tags标签为solution-4 的代码,如何处理呢? 命令如下: git clone --branch solution-4 git@github.com:zspo/learngi ...

  8. INT函数和ROUND

    语法:INT(number) Number 需要进行向下舍入取整的实数

  9. Servlet学习(二):ServletConfig获取参数;ServletContext应用:请求转发,参数获取,资源读取;类装载器读取文件

    转载:http://www.cnblogs.com/xdp-gacl/p/3763559.html 一.ServletConfig讲解 1.1.配置Servlet初始化参数 在Servlet的配置文件 ...

  10. Zuul Timeouts

    19.13 Zuul Timeouts 19.13.1 Service Discovery Configuration If Zuul is using service discovery there ...