1、注解概述
1.1、注解编写格式
public @interface 注解名称{
属性列表;
}
1.2、注解分类
1、注解大致分为三类:自定义注解、JDK内置注解、第三方框架提供的注解
1.1、自定义注解:我们自己写的注解,比如:@UserLog
1.2、JDK内置注解:
1.2.1、原生注解,比如:@Overried、@Deprecated、@FunctionalInterface、@SuppressWarnings(关闭不当的编译器警告信息)等基本注解,大多数用于「标记」和「检查」。
1.2.2、元注解:用来修饰注解的。常用的元Annotation有@Retention、@Target、@Document、@Inherited
1.3、第三方框架定义的注解,比如:SpringMVC的@Controller等
1.2.1、JDK内置注解之标准注解
1、@Override: 『标记式注解』,只能作用于方法之上,仅被编译器可知(编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。),编译结束后将被丢弃。
2、@Deprecated:『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。
3、@FunctionalInterface:接口可以接受lambda表达式作为右值,此类接口又叫函数式接口,其规定的修饰接口只能有一个抽象的方法(不包括静态方法和替代,专有方法)。
4、@SuppressWarnings:主要用来压制 java 的警告,它有一个 value 属性需要你主动的传值,该 value 代表的就是需要被压制的警告类型
1.2.2、JDK内置注解之元注解
1、@Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括:
ElemenetType.CONSTRUCTOR-----------------------------允许作用在构造器上
ElemenetType.FIELD ----------------------------------允许作用在属性字段上
ElemenetType.LOCAL_VARIABLE------------------------- 允许作用在本地局部变量上
ElemenetType.METHOD ---------------------------------允许作用在方法上
ElemenetType.PACKAGE --------------------------------允许作用在包上
ElemenetType.PARAMETER ------------------------------允许作用在方法参数上
ElemenetType.TYPE----------------------------------- 允许被修饰的注解作用在类、接口和枚举上
ElementType.ANNOTATION_TYPE-----------------------------------允许作用在注解上
2、@Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy 中,包括:
RetentionPolicy.SOURCE-------------注解仅保留在源代码级别中,将被编译器丢弃所忽略掉【对应.java】
RetentionPolicy.CLASS -------------注解在class文件中可用,但会被JVM丢弃 【对应.class】
RetentionPolicy.RUNTIME ---------JVM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。【对应JVM】
3、@Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param 等。
4、@Inherited 允许子类继承父类中的注解。
5、Java8新提供元注解-示例
5.1、@Repeatable (Java8): 允许在同一申明类型(类,属性,或方法)的多次使用同一个注解。
5.2、 @Native (Java8) 使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。
1.3、注解使用位置
注解常常出现在类、方法、成员变量、形参位置,当然还有其他位置。
1.4、注解的作用
1、注解是一种方便程序识别的一种标签,为当前读取该注解的程序提供判断依据及少量附加信息。
1.1、比如:程序只要读到加了@Test的方法,就知道该方法是待测试方法;又比如@Before注解,程序看到这个注解,就知道该方法要放在@Test方法之前执行。
1.5、注解的级别
注解和类、接口、枚举是同一级别的。
1.6、注解的本质
1、所有的注解类型都继承自这个普通的接口(Annotation)
2、@Override注解源代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
==> 反编译之后,可得到
public interface Override extends Annotation{
}
1.6.1、注解与反射
1、虚拟机规范定义了一系列和注解相关的属性表,也就是说,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:
RuntimeVisibleAnnotations:运行时可见的注解
RuntimeInVisibleAnnotations:运行时不可见的注解
RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解
RuntimeInVisibleParameterAnnotations:运行时不可见的方法参数注解
AnnotationDefault:注解类元素的默认值
2、对于一个类或者接口来说,Class 类中提供了以下一些方法用于反射注解。
2.1、AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的方法来访问Annotation信息。相关接口:
2.1.1、判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。注意:此方法会忽略注解对应的注解容器。
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)
2.1.2、返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
2.1.3、返回该程序元素上存在的所有注解,若没有注解,返回长度为0的数组。
Annotation[] getAnnotations()
2.1.4、返回该程序元素上存在的、指定类型的注解数组。没有注解对应类型的注解时,返回长度为0的数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。【getAnnotationsByType会检测注解对应的重复注解容器。若程序元素为类,当前类上找不到注解,且该注解为可继承的,则会去父类上检测对应的注解。】
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
2.1.5、返回直接存在于此元素上的所有注解。该方法将忽略继承的注释。如果没有注释直接存在于此元素上,则返回null。
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
2.1.6、返回直接存在于此元素上的所有注解。该方法将忽略继承的注释。
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
2.1.7、返回直接存在于此元素上的所有注解及注解对应的重复注解容器。该方法将忽略继承的注解。如果没有注释直接存在于此元素上,则返回长度为零的一个数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。
Annotation[] getDeclaredAnnotations()
3、示例demo
3.1、自定义注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD,ElementType.METHOD)
@Documented
@Service
public @interface SelfDemo {
String value();
}
3.2、main方法测试
public class TestDemo{
public static void main(String[] args) throws NoSuchMethodException {
Class classs = TestDemo.class;
Method method = classs.getMethod("main", String[].class);
/**
通过方法名返回注解属性值: JDK 是通过动态代理机制生成一个实现我们注(接口)的代理类。
-- 代理类实现接口 Hello 并重写其所有方法,包括 value 方法以及接口 Hello 从 Annotation 接口继承而来的方法。
-- AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler
-- 当你进行反射的时候,虚拟机将所有生命周期在 RUNTIME 的注解取出来放到一个 map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。最后,虚拟机将采用 JDK 动态代理机制生成一个目标注解的代理类,并初始化好处理器。
*/
SelfDemo selfDemo = method.getAnnotation(SelfDemo.class);
System.out.println(selfDemo.value());
}
}
3.3、demo总结
3.3.1、通过方法名返回注解属性值: JDK 是通过动态代理机制生成一个实现我们注(接口)的代理类。
-- 代理类实现接口 Hello 并重写其所有方法,包括 value 方法以及接口 Hello 从 Annotation 接口继承而来的方法。
-- AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler
-- 当你进行反射的时候,虚拟机将所有生命周期在 RUNTIME 的注解取出来放到一个 map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。最后,虚拟机将采用 JDK 动态代理机制生成一个目标注解的代理类,并初始化好处理器。
4、注解需要三要素:定义、使用、读取并执行
1.6.2、自定义注解示例-demo
********* 注解需要三要素:定义、使用、读取并执行 **********
1、定义注解
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String getValue() default "no description";
}
2、使用注解
@MyAnnotation(getValue = "annotation on class")
public class Demo {
@MyAnnotation(getValue = "annotation on field")
public String name;
@MyAnnotation(getValue = "annotation on method")
public void hello() {}
@MyAnnotation() // 故意不指定getValue
public void defaultMethod() {}
}
3、测试
public class AnnotationTest {
public static void main(String[] args) throws Exception {
// 获取类上的注解
Class<Demo> clazz = Demo.class;
MyAnnotation annotationOnClass = clazz.getAnnotation(MyAnnotation.class);
System.out.println(annotationOnClass.getValue());
// 获取成员变量上的注解
Field name = clazz.getField("name");
MyAnnotation annotationOnField = name.getAnnotation(MyAnnotation.class);
System.out.println(annotationOnField.getValue());
// 获取hello方法上的注解
Method hello = clazz.getMethod("hello", (Class<?>[]) null);
MyAnnotation annotationOnMethod = hello.getAnnotation(MyAnnotation.class);
System.out.println(annotationOnMethod.getValue());
// 获取defaultMethod方法上的注解
Method defaultMethod = clazz.getMethod("defaultMethod", (Class<?>[]) null);
MyAnnotation annotationOnDefaultMethod = defaultMethod.getAnnotation(MyAnnotation.class);
System.out.println(annotationOnDefaultMethod.getValue());
}
}
1.6.3、@Data注解分析
1、Lombok本质上就是一个实现了"JSR 269 API"的程序。在使用javac的过程中,它产生作用的具体流程如下:
1.1、javac对源代码进行分析,生成了一棵抽象语法树(AST)。
1.2、运行过程中调用实现了"JSR 269 API"的Lombok程序,此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点。
1.3、javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)。
2、Lombok插件的一些常见注解用法:
@Data : 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法
@AllArgsConstructor : 注在类上,提供类的全参构造
@NoArgsConstructor : 注在类上,提供类的无参构造
@Setter : 注在属性上,提供 set 方法
@Getter : 注在属性上,提供 get 方法
@EqualsAndHashCode : 注在类上,提供对应的 equals 和 hashCode 方法
@Log4j/@Slf4j : 注在类上,提供对应的 Logger 对象,变量名为 log
3、
1.6.4.1、ASR-269基本介绍
[https://www.yuque.com/moercheng/eig6e7/pfnm23](https://www.yuque.com/moercheng/eig6e7/pfnm23)
1.6.4、自定义注解与Spring AOP结合使用-示例
1、前序:在Spring AOP的逻辑处理中,判断是否带有自定义注解,如果有则将监控的逻辑写在方法的前后。这样,只要在方法上加上我的注解,那就可以有对方法监控的效果(RT、QPS、ERROR)。
2、demo
2.1、自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD,ElementType.METHOD)
@Documented
@Service
public @interface SelfLimitDemo {
//允许访问次数属性,默认值:MAX_VALUE
int count() default Integer.MAX_VALUE;
//访问次数间隔,单位毫秒,默认值:一分钟
int time() default 60;
}
2.2、自定义切面
@Aspect
public class LimitContract{
@Before("within(@xxx.xxx.xxx) && @annotation(limit)")
public void limit(Point ponit, SelfLimitDemo limitDemo){
//业务处理
}
}
2.3、结合使用
@SelfLimitDemo(count=5,time=2)
@RequestMapping(value="/echo", method = RequestMethod.GET)
@ResponseBody
public Map echo(HttpServletRequest request){
Map<String,String> params = new HashMap<>();
params.put("name","liyan");
return params;
}
1.7、注解实现原理[编译时期注解处理]
1.7.1、注解处理器(Annotation Processor)
1、注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。
2、通过Annotation Processor可以获取到注解和被注解对象的相关信息,然后根据注解自动生成Java代码,省去了手动编写,提高了编码效率。
3、使用注解处理器先要了解AbstractProcessor类,这个类是一个抽象类,有四个核心方法。
1.7.2、AbstractProcessor
1、.init(ProcessingEnvironment env): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。后面我们将看到详细的内容。
2、.process(Set<? extends TypeElement> annotations, RoundEnvironment env): 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。
3、.getSupportedAnnotationTypes(): 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。
4、.getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6。
1.7.3、注册自定义处理器
1、创建一个java library,其gradle配置如下:
apply plugin: 'java'
targetCompatibility = '1.7'
sourceCompatibility = '1.7'
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.google.auto.service:auto-service:1.0-rc3'
}
2、自定义Annotation Processor继承于AbstractProcessor
/**
1、自定义Annotation Processor继承于AbstractProcessor
2、@AutoService向javac注册我们这个自定义的注解处理器
2.1、主要是用来生成
META-INF/services/javax.annotation.processing.Processor文件的。如果不加上这个注解,那么,你需要自己进行手动配置进行注册,
3、具体手动注册方法如下:
3.1、创建一个
META-INF/services/javax.annotation.processing.Processor文件,
其内容是一系列的自定义注解处理器完整有效类名集合,以换行切割:
com.example.MyProcessor
com.foo.OtherProcessor
3.2、将自定义注解处理器和
META-INF/services/javax.annotation.processing.Processor打包成一个.jar文件。
3.2.1、示例:
MyProcessor.jar
- com
- example
- MyProcessor.class
- META-INF
- services
- javax.annotation.processing.Processor
*/
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){
}
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment roundEnv) { }
@Override
public Set<String> getSupportedAnnotationTypes() {
}
@Override
public SourceVersion getSupportedSourceVersion() {
}
}
1.7.4、资料
https://blog.csdn.net/qq_20009015/article/details/106038023 https://www.race604.com/annotation-processing/
2、学习资料
https://www.pdai.tech/md/java/basic/java-basic-x-annotation.html