1. 注解概念

(1) 注解格式

modifiers @interface AnnotationName {
	type elementName();
	type elementName() default value;
}

示例


public @interface AnnotationExample {
    String assignedTo() default "value";
    int severity();
}

(2) 概念

Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。


元数据

Java与元数据
元数据是关于数据的数据。在编程语言上下文中,元数据是添加到程序元素如方法、字段、类和包上的额外信息。
元数据的作用
  一般来说,元数据可以用于创建文档,跟踪代码中的依赖性,执行编译时检查,代码分析。
元数据还可用于协助程序元素与框架或者EJB、EMF 和 TestNG这样的工具之间的通信。EJB 3.0就广泛地应用了Java元数据,通过元数据来实现声明性请求企业服务,依赖性以及资源注入,消除了严格的EJB组件模型约束,并且取代了复杂的XML配置文件。
元数据甚至使我们可以不用修改核心语言,就能够在 Java 语言中添加新功能,使核心语言成为一种开放式语言。在纯面向对象的语言中实现AOP就是使用元数据进行语言扩展的一个很好的例子。AspectWerkz、JBoss AOP以及AspectJ5 使用元数据将类的语义转换为一个aspect、将数据字段转换为一个pointcut、将方法转换为一个advice,等等。
Java平台的元数据
Java 元数据(Annotation)是 J2SE 5.0 (研发历时近三年,于2004年9月30日正式发布,代号为“Tiger”)新增加的功能之一,它在JSR-175规范中有详细定义。该机制允许在 Java 代码中添加自定义注释,并允许通过反射(reflection),以编程方式访问元数据注释。通过提供为程序元素附加额外数据的标准方法,元数据功能具有简化和改进许多应用程序开发领域的潜在能力,其中包括配置管理、框架实现和代码生成。

Annotation不直接影响程序的语义。然而,开发和部署工具可以读取这些注释,并以某种形式处理这些注释,可能生成其他 Java源程序、XML配置文件或者要与包含注释的程序一起使用的其他组件,从而影响运行状态的程序的语义。注释可以从源代码中读取,从编译后的.class文件中读取,也可以通过反射机制在运行时读取。

Annotation具有以下的一些特点:
元数据以标签的形式存在于Java代码中。
元数据描述的信息是类型安全的,即元数据内部的字段都是有明确类型的。
元数据需要编译器之外的工具额外的处理用来生成其它的程序部件。
元数据可以只存在于Java源代码级别,也可以存在于编译之后的Class文件内部。
事实上,早在JDK5.0推出语言级的元数据机制Annotation以前,就一直存在对元数据的需求。但是由于没有提供表达元数据的标准机制,出现了各种解决方案。下面罗列了一些例子
transient 关键字
Serializable 标记接口
xml 部署描述文件
manifest.mf 文件    
Javadoc 标记(将文档直接写在源程序里,极大的方便了文档的编写)
XDoclet(使用类似于JavaDoc的语法撰写描述信息,并使用工具生成描述文件)
这些方法都存在一定的局限性,比如使用关键字不具有扩展性,用户自定义新的关键字;标记接口没有提供额外的信息,它们不能带有参数,并且只能处理类,而不能处理字段或方法或包。Javadoc和XDoclet标记不会被编译器检查。

最后,我们再详细的对比一下Annotation和XML部署描述文件的优劣
XML配置文件与代码文件分离,不利于一致性维护,缺乏在运行时的反射机制。而Annotation与代码一起被编译器处理,并能够在运行时访问。
通常XML配置文件都很复杂而且冗长,为了配置代码,XML文件必须复制许多信息:比如代码中类名字和方法名字。Java注释则不同,它是代码的一部分,不需要额外的引用就可以指明配置信息。
XML配置文件是文本文件,没有显式的类型支持,需要到运行时刻才能发现隐藏的错误。而Annotation是类型安全的,它会被编译器检查。  
XML文件可以表达复杂的关系,但是在注释中我们却很难表达复杂的或层次的结构。
XML配置文件是在代码之外被单独处理的,也就是说基于XML的配置信息不是硬编码的,可以部署的时候进行修改。而修改Annotation则需要进行重新编译,不过我们可以利用AOP提供的机制为已有的代码添加Annotation。通过部署不同的AOP模块,就能使代码具有不同的Annotation,但比起直接修改XML显得复杂。
总而言之,注释是简单易用的,并且对大多数应用来说已经足够了。而XML文件更复杂,但具有部署的灵活性,因而被用来处理与部署相关的决策。注释与XML配置文件可以一起使用。由于注释只能保存相当少的配置信息,只有预先集成的框架组件(类似在框架组件中已经完成了大多数预备工作)可以广泛地把注释作为配置选项。而XML配置文件作为一个可选的重载机制,可以用于改变注释的默认行为。


http://blog.csdn.net/vebasan/article/details/4794699



  • 没带成员变量的Annotation被称为标记,这种注解仅利用自身的存在与否来提供信息,如@Override等。
  • 包含成员变量的Annotation称为元数据Annotation,因为他们提供更多元数据

(3)使用

1) 使用注解时,元素的值未指定,就使用默认的default值;

@AnnotationExample(severity = 1)
String example;

2) 均指定

@AnnotationExample(severity = 1, assignedTo="xyz")
String example;

3) type

注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组

注解中属性可以有默认值,默认值需要用 default 关键值指定

(4) 标记注解

1) 注解中没有任何元素

public @interface AnnotationExample {
}
@AnnotationExample
String example;

2) 注解中所有元素都使用默认值

public @interface AnnotationExample {
    String assignedTo() default "value";
    int severity() default 0;
}
@AnnotationExample
String example;

(5) 单值注解

元素具有特殊名字value(), 没有指定其它元素; 可以忽略这个元素名及等号

public @interface AnnotationExample {
	String value();
}
@AnnotationExample("the value")
String example;

(6) 注解约束

所有注解的元素值必须是编译期常量;

一个项可以有多个注解;

注解可以被重复使用;


2. 注解this

???

public boolean equals(@AnnotationExample AnnotationTest AnnotationTest.this, @AnnotationExample Object obj) {
    return false;
}


3. 元注解

可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
它的作用和目的就是给其他普通的标签进行解释说明的。

元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种.


@Documented

给javadoc这样的归档工具提示;被其注解过的方法的归档就会含有这条注解。

没有加@Documented

package annotation;

public @interface AnnotationExample {
    String assignedTo() default "value";
    int severity();
}

package annotation;

public class AnnotationTest {
	@AnnotationExample(severity = 1, assignedTo = "xyz")
	public String example;
	
	@AnnotationExample(severity = 2)
	public void foo() {
		
	}
}

加@Documented

package annotation;

import java.lang.annotation.Documented;

@Documented
public @interface AnnotationExample {
    String assignedTo() default "value";
    int severity();
}



package annotation;

public class AnnotationTest {
    @AnnotationExample(severity = 1, assignedTo = "xyz")
    public String example;
    
    @AnnotationExample(severity = 2)
    public void foo() {
        
    }
}


@Target

没有@Target限制的注解可以用于任何项上;


指定了注解运用的地方(场景)

元素类型

注解适用场合

ElementType.ANNOTATION_TYPE

注解类型声明

ElementType.PACKAGE

ElementType.TYPE

类型,比如类、接口、枚举

ElementType.TYPE_PARAMETER
参数类型

ElementType.TYPE_USE

类型用法

ElementType.FIELD

属性

ElementType.CONSTRUCTOR

构造方法

ElementType.METHOD

方法

ElementType.LOCAL_VARIABLE

局部变量

ElementType.PARAMETER

方法或构造器内的参数

举例

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
public @interface AnnotationExample {
    String assignedTo() default "value";
    int severity();
}
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.METHOD})
public @interface AnnotationExample {
    String assignedTo() default "value";
    int severity();
}

java为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值。@Target(TYPE_USE)修饰的注解称为Type Annotation(类型注解),Type Annotation可用在任何用到类型的地方

package annotation;

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_USE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
    String value() default "";
}
package annotation;

import java.io.Serializable;
import java.util.List;

public class SerializableAnnotation implements @NotNull(value = "Serializable") Serializable {
	// 泛型中使用Type Annotation 、 抛出异常中使用Type Annotation
	public void foo(List<@NotNull String> list) throws @NotNull(value = "ClassNotFoundException") ClassNotFoundException {
		// 创建对象中使用Type Annotation
		Object obj = new @NotNull String("annotation.Test");
		
		// 强制类型转换中使用Type Annotation
		String str = (@NotNull String) obj;
	}
}

@Retention

说明了这个注解的的存活时间。


它的取值如下:
- RetentionPolicy.SOURCE      注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视,编译器直接丢弃这种Annotation。
- RetentionPolicy.CLASS         注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。运行java程序时,JVM不可获取Annotation信息。(默认值
- RetentionPolicy.RUNTIME    注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。 当运行java程序时,JVM也可获取Annotation信息,程序可以通过反射获取该Annotation信息

举例

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationExample {
    String assignedTo() default "value";
    int severity();
}


@Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解

只能应用于对类的注解,所有的子类自动具有相同的注解。

举例

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}


@Test
public class A {}


public class B extends A {}
注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。


@Repeatable

可重复的,注解的值可以同时取多个


容器注解

用来存放其它注解的地方,它本身也是一个注解

里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。

@interface Persons {
    Person[]  value();
}

属性Person必须被 @Repeatable注解

@Repeatable(Persons.class)
@interface Person{
    String role default "";
}

定义多个角色

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{

}

4. 预置注解/系统注解

@Deprecated

用于表示某个程序元素(类、方法等)已过时。编译时读取,编译器编译到过时元素会给出警告。

public class Hero {

    @Deprecated
    public void say(){
        System.out.println("Noting has to say!");
    }

    public void speak(){
        System.out.println("I have a dream!");
    }
}

需要注意@Deprecated和@deprecated这两者的区别,前者被javac识别和处理,而后者则是被javadoc工具识别和处理.因此当我们需要在源码标记某个方法已经过时应该使用@Deprecated,如果需要在文档中说明则使用@deprecated.


@Override

用来指定方法覆载的,它可以强制一个子类必须覆盖父类的方法。写在子类的方法上,在编译期,编译器检查这个方法,保证父类包含被该方法重写的方法,否则编译出错。该注解只能修饰方法,在编译期被读取。


@SuppressWarnings

抑制编译警告/忽视某类编译警告,被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译警告。

比如:取消如果程序使用没有泛型限制的集合会引起编译器警告,为了避免这种警告使用该注解。

关闭编译器对类,方法,成员变量即变量初始化的警告.


该注解可接受以下参数:

含义
deprecated 使用已过时类,方法,变量
unchecked 执行了未检查的转告时的警告,如使用集合是为使用泛型来制定集合保存时的类型
fallthrough 使用switch,但是没有break时
path 类路径,源文件路径等有不存在的路径
serial 可序列化的类上缺少serialVersionUID定义时的警告
finally 任何finally字句不能正常完成时的警告
all 以上所有情况的警告


unchecked异常:运行时异常。是RuntimeException的子类,不需要在代码中显式地捕获unchecked异常做处理。

Java异常  http://blog.csdn.net/kingzone_2008/article/details/8535287


checked和unchecked异常之间的区别是

Checked异常必须被显式地捕获或者传递,如Basic try-catch-finally Exception Handling一文中所说。而unchecked异常则可以不必捕获或抛出
Checked异常继承java.lang.Exception类。Unchecked异常继承自java.lang.RuntimeException


@SafeVarargs

参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。


java7的“堆污染”警告与@SafeVarargs

堆污染:把一个不带泛型的对象赋给一个带泛型的变量是,就会发生堆污染。


例如:下面代码引起堆污染,会给出警告

		List l2 = new ArrayList<Number>();
		List<String> ls = l2;
  • 3中方式去掉这个警告

    • 使用注解@SafeVarargs修饰引发该警告的方法或构造器。
    • 使用@SuppressWarnings("unchecked") 修饰。
    • 使用编译器参数命令:-Xlint:varargs


	@SuppressWarnings("unchecked")
	public void foo() {
		List l2 = new ArrayList<Number>();
		List<String> ls = l2;
	}


@FunctionalInterface

函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。

使用该注解修饰的接口必须是函数式接口,不然编译会出错。那么什么是函数式接口?

答:如果接口中只有一个抽象方法(可以包含多个默认default方法或static方法, 还有equals方法),就是函数式接口。


函数式接口标记有什么用,这个原因是函数式接口可以很容易转换为 Lambda 表达式



@Generated

供代码生成工具使用


@PostConstruct

控制对象生命周期中,对象被构建后调用


@PreDestroy

控制对象生命周期中,对象被移除后调用


@Resource

资源注入


5. 自定义注解

public @interface 注解名 {
   定义体
}

需要注意:

  • 此处只能使用public或者默认的defalt两个权限修饰符
  • 配置参数的类型只能使用基本类型(byte,boolean,char,short,int,long,float,double)和String,Enum,Class,annotation
  • 对于只含有一个配置参数的注解,参数名建议设置中value,即方法名为value
  • 配置参数一旦设置,其参数值必须有确定的值,要不在使用注解的时候指定,要不在定义注解的时候使用default为其设置默认值,对于非基本类型的参数值来说,其不能为null.
  • 以无形参的方法形式来声明Annotation的成员变量,方法名和返回值定义了成员变量名称和类型。使用default关键字设置初始值。没设置初始值的变量则使用时必须提供,有初始值的变量可以设置也可以不设置.


举例

package beans.validation.annotation;

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;

import javax.validation.Constraint;
import javax.validation.ConstraintTarget;
import javax.validation.Payload;
import javax.validation.constraints.Pattern;

import validation.EmailValidator;

@Pattern.List({
	@Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+"+"(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*"+"@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
})
@Constraint(validatedBy={EmailValidator.class})
@Documented
@Target({
	ElementType.METHOD,ElementType.FIELD,ElementType.TYPE,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER
})
@Retention(RetentionPolicy.RUNTIME)
public @interface Email {
	String message() default "invalid email";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
	String[] value() default {};
	
	//设置约束
//	ConstraintTarget validationAppliesTo() default ConstraintTarget.PARAMETERS;
	
	
	@Target({
		ElementType.METHOD,ElementType.FIELD,ElementType.TYPE,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER
	})
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@interface List{
		Email[] value();
	}
}


6. 注解的提取

运用反射

提供了获取Annotation的方法,它的所有实现类也便拥有了这些方法。常见的实现类:

  • Class:类定义。
  • Constructor:构造器定义
  • Field:类的成员变量定义
  • Method:类的方法定义。
  • Package:类的包定义。


例如, 调用Class对象的方法

    @SuppressWarnings("unchecked")
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }

    /**
     * {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     * @since 1.5
     */
    @Override
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }

    /**
     * @throws NullPointerException {@inheritDoc}
     * @since 1.8
     */
    @Override
    public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        AnnotationData annotationData = annotationData();
        return AnnotationSupport.getAssociatedAnnotations(annotationData.declaredAnnotations,
                                                          this,
                                                          annotationClass);
    }

    /**
     * @since 1.5
     */
    public Annotation[] getAnnotations() {
        return AnnotationParser.toArray(annotationData().annotations);
    }

    /**
     * @throws NullPointerException {@inheritDoc}
     * @since 1.8
     */
    @Override
    @SuppressWarnings("unchecked")
    public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().declaredAnnotations.get(annotationClass);
    }

    /**
     * @throws NullPointerException {@inheritDoc}
     * @since 1.8
     */
    @Override
    public <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return AnnotationSupport.getDirectlyAndIndirectlyPresent(annotationData().declaredAnnotations,
                                                                 annotationClass);
    }

    /**
     * @since 1.5
     */
    public Annotation[] getDeclaredAnnotations()  {
        return AnnotationParser.toArray(annotationData().declaredAnnotations);
    } 

调用Field, Method, Constructor的 annotation方法


7. 注解处理器(Annotation Processor)

概念

注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容。

用途

由于注解处理器可以在程序编译阶段工作,所以我们可以在编译期间通过注解处理器进行我们需要的操作。比较常用的用法就是在编译期间获取相关注解数据,然后动态生成.java源文件(让机器帮我们写代码),通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率。


有过Hibernate开发经验的朋友可能知道每写一个Java文件,还必须额外地维护一个Hibernate映射文件(一个名为*.hbm.xml的文件,当然可以有一些工具可以自动生成)下面将使用Annotation来简化这步操作。思路:自定义修饰类的注解,在实体类上使用注解,编写注解处理器:根据源文件中的类上的注解,生成*.hbm.xml文件,使用java提供的编译命令javac执行注解处理器。关键:编写注解处理器。

我们知道前面的注解处理器处理的都是@Retention(RetentionPolicy.RUNTIME)的注解,使用的是反射技术。而生成的*hbm.xml文件是需要在编译阶段完成。为此java在java7之前提供了apt工具及api,在java7及之后提供了JSR269 api。

  • APT是一种处理注释的工具,它对源代码文件进行检测,并找出源文件中所包含的Annotation信息,然后针对Annotation信息进行额外的处理。
  • APT处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件.使用APT主要的目的是简化开发者的工作量。
  • 因为APT可以编译程序源代码的同时,生成一些附属文件(比如源文件、类文件、程序发布描述文件等),这些附属文件的内容也都是与源代码相关的,换句话说,使用APT可以代替传统的对代码信息和附属文件的维护工作。
  • APT的相关api都在com.sun.mirror 包下,在jdk7及之后,apt的相关api就被废除了,代替的是JSR269。JSR269API文档下载。JSR269的api在 javax.annotation.processing and javax.lang.model包下。
    所以以后开发注解处理器使用jsr269提供的api就可以了。

JSR269描述



==>

http://blog.csdn.net/xiang__liu/article/details/79372431



在 Java8中,已经移除了 APT 工具

JDK工具-apt命令

http://www.javacui.com/Theory/367.html

https://blog.zenfery.cc/archives/78.html




在编译期获取Java代码文件中的Annotation


https://www.jianshu.com/p/d7567258ae85

http://blog.csdn.net/nupt123456789/article/details/51018352

https://juejin.im/entry/58abebf12f301e006c3c8832

https://juejin.im/entry/585fe4e61b69e600562147fa

jar 打包命令详解

http://blog.csdn.net/marryshi/article/details/50751764




8. 注解的使用场景


注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取

值得注意的是,注解不是代码本身的一部分。
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool, item 6)。

注解什么时候用?取决于你想利用它干什么用。


比如可以根据反射(item 5) 拿到注解后,针对特定的字段,方法做特定的事情。


http://blog.csdn.net/J080624/article/details/78269504


  • 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等
  • 跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都 使用了这种配置来减少配置文件的数量
  • 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查 出















posted on 2018-02-26 16:18  刘达人186  阅读(443)  评论(0编辑  收藏  举报