1 理解注解
Java注解是JDK5时引入的特性,目前大部分框架(如Spring)都使用了注解简化代码并提高编码的效率。注解不支持继承,因此不能用关键字extends继承某个@interface,但是在编译之后,编译器会自动继承java.lang.annotation.Annotation接口。
实际上Java注解与普通修饰符(public、static、void等)的使用方式并没有多大区别,下面的例子是常见的注解:
public class AnnotationDemo {
//@Test注解修饰方法A,在运行该方法时,测试框架会自动识别该方法并单独调用,@Test实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。
@Test
public static void A(){
System.out.println("Test.....");
}
//一个方法上可以拥有多个不同的注解
//对于@Deprecated和@SuppressWarnings(“uncheck”),则是Java本身内置的注解,在代码中,可以经常看见它们,但这并不是一件好事,毕竟当方法或是类上面有@Deprecated注解时,说明该方法或是类都已经过期不建议再用,@SuppressWarnings 则表示忽略指定警告,比如@SuppressWarnings(“uncheck”),这就是注解的最简单
@Deprecated
@SuppressWarnings("uncheck")
public static void B(){
}
}
2 基本语法
2.1 声明注解
//声明Test注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
1、使用了@interface声明了Test注解
2、使用@Target注解传入ElementType.METHOD参数来标明@Test只能用于方法上
3、@Retention(RetentionPolicy.RUNTIME)则用来表示该注解生存期是运行时
4、从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成Test.class文件
5、对于@Target和@Retention是由Java提供的元注解,所谓元注解就是标记其他注解的注解。
2.2 注解分类
2.2.1 JDK基本注解
(1)@Override
重写
(2)@Deprecated
已过时
(3)@SuppressWarning
压制编辑器警告
(4)@SafeVarargs
用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全的问题。
2.2.2 JDK元注解
作用:元注解用于修饰其他的注解
(1)@Retention
定义注解的保留策略。
@Retention(RetentionPolicy.SOURCE)//注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS)//默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME)//注解会在class字节码文件中存在,在运行时可以通过反射获取到
(2)@Target
指定被修饰的Annotation可以放置的位置(被修饰的目标)。
@Target(ElementType.TYPE) //接口、类
@Target(ElementType.FIELD) //属性
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE) //局部变量
@Target(ElementType.ANNOTATION_TYPE) //注解
@Target(ElementType.PACKAGE) //包
注:可以指定多个位置。
例如:@Target({ElementType.METHOD, ElementType.TYPE})
此注解可以在方法和类上面使用。
(3)@Inherited
指定被修饰的Annotation将具有继承性。
(4)@Documented
指定被修饰的该Annotation可以被javadoc工具提取成文档。
2.2.3 自定义注解
注解分类(根据Annotation是否包含成员变量,可以把Annotation分为两类):
(1)标记Annotation
没有成员变量的Annotation。仅利用自身存在与否提供信息。
(2)元数据Annotation
包含成员变量的Annotation。可以接受(提供)更多的元数据。
自定义注解过程:
- 使用@interface关键字, 其定义过程与定义接口非常类似。
- Annotation的成员变量在Annotation定义中是以无参的方法形式声明的,其方法名和返回值类型定义了该成员变量的名称和类型。
- 可以是以default关键位成员变量设置默认值。
- 名字为value属性,赋值时可以省略属性名。
样例:获取类和方法上的注解值
package com.ssm.yuan.p1;
public enum TranscationModel {
Read, Write, ReadWrite
}
package com.ssm.yuan.p1;
import java.lang.annotation.*;
/**
*
* MyAnnotation3注解可以用在方法上
* 注解运行期也保留
* 可继承
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyAnnotation3 {
TranscationModel[] models() default TranscationModel.ReadWrite;
}
package com.ssm.yuan.p1;
import java.lang.annotation.*;
/**
*
* MyAnnotation2注解可以用在方法上
* 注解运行期也保留
* 不可继承
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation2 {
TranscationModel model() default TranscationModel.ReadWrite;
}
package com.ssm.yuan.p1;
import java.lang.annotation.*;
/**
*
* MyAnnotation1注解可以用在类、接口、属性、方法上
* 注解运行期也保留
* 不可继承
*/
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation1 {
String name();
}
package com.ssm.yuan.p1;
import org.junit.Test;
/**
* 获取类和方法上的注解名
*/
public class Demo1Test {
@Test
public void list() throws Exception {
// 获取类上的注解
MyAnnotation1 annotation1 = Demo1.class.getAnnotation(MyAnnotation1.class);
System.out.println(annotation1.name());//abc
// 获取方法上的注解
MyAnnotation2 myAnnotation2 = Demo1.class.getMethod("list").getAnnotation(MyAnnotation2.class);
System.out.println(myAnnotation2.model());//Read
}
@Test
public void edit() throws Exception {
MyAnnotation3 myAnnotation3 = Demo1.class.getMethod("edit").getAnnotation(MyAnnotation3.class);
for (TranscationModel model : myAnnotation3.models()) {
System.out.println(model);//Read,Write
}
}
}
package com.ssm.yuan.p1;
/**
*
* 获取类与方法上的注解值
*/
@MyAnnotation1(name = "abc")
public class Demo1 {
@MyAnnotation1(name = "xyz")
private Integer age;
@MyAnnotation2(model = TranscationModel.Read)
public void list() {
System.out.println("list");
}
@MyAnnotation3(models = {TranscationModel.Read, TranscationModel.Write})
public void edit() {
System.out.println("edit");
}
}
3 注解与反射机制
Java所有注解都继承了Annotation接口,也就是说 Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。
同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。
Class | 类的Class对象定义 |
---|---|
Constructor | 代表类的构造器定义 |
Filed | 代表类的成员变量定义 |
Method | 代表类的方法定义 |
Package | 代表类的包定义 |
AnnotatedElement中相关的API方法,以上5个类都实现以下的方法:
返回值 | 方法名称 | 说明 |
---|---|---|
getAnnotation(ClassannotationClass) | 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。 | |
Annotation[] | getAnnotations() | 返回此元素上存在的所有注解,包括从父类继承的 |
boolean | isAnnotationPresent(Class<?extends Annotation> annotationClass) | 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。 |
Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组 |
import java.lang.annotation.Annotation;
import java.util.Arrays;
@DocumentA
class A{ }
//继承了A类
@DocumentB
public class DocumentDemo extends A{
public static void main(String... args){
Class<?> clazz = DocumentDemo.class;
//根据指定注解类型获取该注解
DocumentA documentA=clazz.getAnnotation(DocumentA.class);
System.out.println("A:"+documentA);
//获取该元素上的所有注解,包含从父类继承
Annotation[] an= clazz.getAnnotations();
System.out.println("an:"+ Arrays.toString(an));
//获取该元素上的所有注解,但不包含继承!
Annotation[] an2=clazz.getDeclaredAnnotations();
System.out.println("an2:"+ Arrays.toString(an2));
//判断注解DocumentA是否在该元素上
boolean b=clazz.isAnnotationPresent(DocumentA.class);
System.out.println("b:"+b);
/**
* 执行结果:
A:@com.zejian.annotationdemo.DocumentA()
an:[@com.zejian.annotationdemo.DocumentA(), @com.zejian.annotationdemo.DocumentB()]
an2:@com.zejian.annotationdemo.DocumentB()
b:true
*/
}
}
4 注解处理器
如果没有处理注解的工具,那么注解也不会有太大的作用。对于不同的注解有不同的注解处理器。虽然注解处理器的编写千变万化,但是也有处理标准。
- 针对运行时注解会采用反射机制处理。
针对编译时注解会采用 AbstractProcessor 来处理。
4.1 运行时注解处理器
定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Get {
String value() default "";
}
使用注解:
public class AnnotationTest {
@Get(value = "http://ip.taobao.com/59.108.54.37")
public String getIpMsg() {
return "";
}
@Get(value = "http://ip.taobao.com/")
public String getIp() {
return "";
}
}
获取注解值:
public class AnnotationProcessor {
public static void main(String[] args) {
Method[] methods = AnnotationTest.class.getDeclaredMethods();
for (Method m : methods) {
Get get = m.getAnnotation(Get.class);
System.out.println(get.value());
}
}
}
输出:
http://ip.taobao.com/59.108.54.37
http://ip.taobao.com/
4.2 编译时注解处理器
定义注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default 1;
}
编写注解处理器: ```java public class ClassProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager = processingEnv.getMessager();
for (Element ele : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
if (ele.getKind() == ElementKind.FIELD) {
messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + ele.toString());
}
}
return true;
}
@Override public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override public Set
getSupportedAnnotationTypes() { Set<String> annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
} }
- init 被注解处理工具调用,并输入 processingEnvironment 参数。processingEnvironment 提供了很多工具类,如 Elements、Types、Filer 和 Messenger 等。
- process 注解处理的主函数,这里处理扫描、评估和处理注解的代码,以及生产 Java 文件。
- getSupportedAnnotationTypes 指明注解处理器是处理哪些注解的。
- getSupportedSourceVersion 指明使用的 Java 版本,通常返回 SourceVersion.latestSupported。
在Java7以后,也可以用注解的形式代替getSupportedAnnotationTypes和getSupportedSourceVersion。即@SupportedAnnotationTypes和@SupportedSourceVersion注解。
```java
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.caoshen.annotations.BindView")
public class ClassProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
...
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
}
并在 processor 的 build.gradle 配置依赖:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':annotations')
}
5 Java8中注解增强
5.1 新增元注解@Repeatable
在同一个位置重复相同的注解。
之前这样表示同一个位置使用相同注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
String [] value();
}
//使用
@FilterPath({"/update","/add"})
public class A { }
现在可以这样表示:
@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}
5.2 新增的两种ElementType
在Java8中 ElementType 新增两个枚举成员,TYPE_PARAMETER 和 TYPE_USE ,在Java8前注解只能标注在一个声明(如字段、类、方法)上,Java8后,新增的TYPE_PARAMETER可以用于标注类型参数,而TYPE_USE则可以用于标注任意类型(不包括class)。