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就可以了。
==>
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 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查 出