0.参考


注解(上)-bravo1988 的文章
注解(下)-bravo1988 的文章

java之元数据(metadata)
12-注解基础(重要-框架基础).xmind


1.概述

1.1基本知识

  1. - 简介
  2. - 注解(Annotation)也被称为元数据(Metadata: 描述数据的数据),用于修饰解释 包、类、构造器、方法、属性、局部变量等数据信息
  3. - 和注释一样,注解不影响程序逻辑,但**注解可以被编译或运行**,相当于嵌入在代码中的补充信息
  4. - 使用位置
  5. - 实际开发中,注解常常出现在类、方法、成员变量、形参等位置。具体见枚举类 ElementType
  6. - 生效时机
  7. - 注解可以保留到 源码.java文件, 编译后.class文件, 运行时内存中. 即(_SOURCE, CLASS, RUNTIME_)
  8. - 如何使用
  9. - 注解的三角关系:定义注解,使用注解,读取注解
  10. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628080474871-2f4e983b-6458-49f7-ab8e-ba05d8b4d429.png#clientId=ud78a6599-311a-4&from=paste&id=u4867ef0a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=349&originWidth=465&originalType=url&ratio=1&size=14434&status=done&style=none&taskId=u6efc519c-11dd-4683-a41a-7789b671f49)
  11. - 作用
  12. - JavaSE 中,注解的使用目的比较简单,例如**标记过时的功能,忽略警告**等。在 JavaEE 中注解占据了更重要的角色,例如用来配置应用程序的任何切面(AOP),代替 java EE 旧版中所遗留的繁冗代码和 XML 配置等。
  13. - 如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是 **为当前读取该注解的程序提供判断依据**。比如**程序只要读到加了@Test的方法,就知道该方法是待测试方法,又比如@Before注解,程序看到这个注解,就知道该方法要放在@Test方法之前执行**。
  14. - 级别
  15. - **注解和类、接口、枚举是同一级别的.(其声明使用 @interface)**

1.2格式

  1. public @interface 注解名称{
  2. 属性列表;
  3. }

1.3分类

  1. - JDK内置注解
  2. - @Override: 检验方法重载
  3. - @Deprecated: 标识类/方法等元素过时
  4. - @SuppressWarnings: 抑制编译器警告
  5. - 第三方框架注解
  6. - Junit的单元测试 @Test, @Before, @After
  7. - SpringMVC@Controller
  8. - 自定义注解

2.注解的本质

2.1反编译查看注解本质

  1. 1. 自定义一个注解类: MyAnnotation.Java
  1. package com.ryze.myAnnotation;
  2. public @interface MyAnnotation {
  3. String value();
  4. }
  1. 2. 编译后得到字节码 MyAnnotation.class 文件, 再对其进行反编译(通过XJad软件)得到 MyAnnotation.java:
  1. // Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
  2. // Jad home page: http://kpdus.tripod.com/jad.html
  3. // Decompiler options: packimports(3) fieldsfirst ansi space
  4. // Source File Name: MyAnnotation.java
  5. package com.ryze.myAnnotation;
  6. import java.lang.annotation.Annotation;
  7. public interface MyAnnotation extends Annotation{
  8. public abstract String value();
  9. }
  1. 3. 其中 java.lang.annotation.Annotation 接口源码如下:
  1. package java.lang.annotation;
  2. public interface Annotation {
  3. boolean equals(Object obj);
  4. int hashCode();
  5. String toString();
  6. // 返回此注解的注解类型
  7. Class<? extends Annotation> annotationType();
  8. }

2.2结论

  1. 1. 编码上使用 @interface, **反编译后底层是: 继承了 Annotation 接口的 interface, **. 故也叫**声明式接口**
  2. - 类比 public enum MyEnum{...} .
  3. - 编码上使用 enum, 反编译后底层是 继承了 Enum class
  4. - public final class MyEnum extends Enum{...}
  5. 2. JDK java.lang.annotation.Annotation 本身是接口,而不是注解. 当使用关键字@interface 定义一个注解时,该注解隐含的继承了Annotation接口;如果我们定义一个 TestAnnotation,并且让 TestAnnotation 继承Annotation,那么我们定义的接口依然是接口而不是注解。综上,定义注解只能依靠@interface实现。

3.注解的属性

3.1引入

  1. - 可以如下使用注解
  1. public @interface MyAnnotation {
  2. String value();
  3. }
  4. /*=========================*/
  5. @MyAnnotation(value = "MyValue")
  6. public class TestAnnotation {
  7. }
  1. - 形式上如同 value()方法赋值. 底层??
  2. - 注解中类似于 String value(); 被称为属性

3.2属性的类型

  1. - 八种基本数据类型
  2. - String
  3. - 枚举
  4. - Class
  5. - 注解类型
  6. - 以上类型的一维数组

3.3属性的赋值

  1. - **若注解的属性只有一个,且叫value,那使用该注解时,可不用指定属性名,默认给value赋值**
  2. - 若注解的属性如果有多个,无论是否叫value,都**必须写明属性的对应关系**
  3. - **如果数组的元素只有一个,可以省略{}**

3.4属性的默认(返回)值

  1. - **(在注解类中)使用 default 关键词以声明属性默认值**
  1. public @interface MyAnnotation {
  2. String value() default "My Default value...";
  3. }

4.保留策略与@Retention元注解

4.0概述

  1. - 保留策略: 定义的注解可以在哪个时间段起作用
  2. - 类比JSP中,<!-- -->和<%-- -->的区别:前者可以在浏览器检查网页源代码时看到,后者在服务器端输出时就被抹去了。同样的,注解通过保留策略,控制自己可以保留到哪个阶段。保留策略也是通过注解(@Retention中属性值)实现,它属于元注解.
  3. - **元注解: 加在注解上的注解**

4.1引入: 反射获取注解信息出现的问题

  1. - 前提
  2. - **要用到注解,必然有三角关系:定义注解,使用注解,读取注解**
  3. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628086253733-85e147c2-3d71-45a8-b7d3-9bf59d59a3a3.png#clientId=ud6f0581e-aa34-4&from=paste&id=uf58be32a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=349&originWidth=465&originalType=url&ratio=1&size=14447&status=done&style=none&taskId=uf006a201-2699-4555-a839-1193fdbabf4)
  4. - 读取注解的方式选择:
  5. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628086292695-808b2884-c53e-4736-8474-f6c04731a888.png#clientId=ud6f0581e-aa34-4&from=paste&id=u08e65e88&margin=%5Bobject%20Object%5D&name=image.png&originHeight=365&originWidth=460&originalType=url&ratio=1&size=16565&status=done&style=none&taskId=u28785c3f-6bc2-43f6-8451-a5f0970a634)
  6. - 反射
  7. - ![temp.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628081716737-97c11522-cd11-409b-a997-4033782613ae.png#clientId=ud78a6599-311a-4&from=ui&id=ub323cd44&margin=%5Bobject%20Object%5D&name=temp.png&originHeight=711&originWidth=961&originalType=binary&ratio=1&size=58462&status=done&style=none&taskId=u099779c9-3cd7-435c-8da5-7d07610130f)
  8. - 返回 null值, 异常提示: **注解类 MyAnnotation 没有为反射保留. 原因: 反射是基于JVM运行时行为, 保留策略默认为**_CLASS**, 运行时候即不存在, 所以不会被反射程序所检测.**_

4.2简单使用

  1. - 定义注解时
  2. 1. 标记@Retention()元注解,
  3. 1. 并进行 RetentionPolicy 类型的 value 值设定
  4. 1. @Retention() 的默认值为 _RetentionPolicy.CLASS_
  5. 1. 对于该默认值JDK中的注释: _CLASS_ 属性将由编译器记录在类文件中,但不需要在运行时由VM保留。这是默认行为。
  6. - 效果: 该默认值无法反射获取注解信息. 需要手动设为:_RetentionPolicy.RUNTIME_
  1. package com.ryze.myAnnotation;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. // 将保留策略设为 RUNTIME
  5. @Retention(value = RetentionPolicy.RUNTIME)
  6. public @interface MyAnnotation {
  7. String value() default "My Default value...";
  8. }
  1. - 再次运行反射代码:
  1. public class TestGetAnnot {
  2. public static void main(String[] args) {
  3. Class<TestAnnotation> clazz = TestAnnotation.class;
  4. MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
  5. System.out.println(annotation);
  6. System.out.println(annotation.value());
  7. }
  8. }
  1. - **修改保留策略(为RUNTIME)后成功获取注解信息:**
  1. @com.ryze.myAnnotation.MyAnnotation(value=My Default value...)
  2. My Default value...
  1. - // 其中 "My Default value..." 为 MyAnnotation类 value属性的default值

4.3Retention 元注解源码分析

4.3.1Retention 元注解类源码

  1. package java.lang.annotation;
  2. @Documented
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Target(ElementType.ANNOTATION_TYPE)
  5. public @interface Retention {
  6. /**
  7. Returns the retention policy.
  8. 返回保留策略
  9. */
  10. RetentionPolicy value();
  11. }

4.3.2@RetentionPolicy() 保留策略取值(注解的生命周期)

  1. package java.lang.annotation;
  2. public enum RetentionPolicy {
  3. /**
  4. * Annotations are to be discarded by the compiler.
  5. // 编译器将丢弃注解。
  6. */
  7. SOURCE,
  8. /**
  9. * Annotations are to be recorded in the class file by the compiler
  10. * but need not be retained by the VM at run time. This is the default
  11. * behavior.
  12. //注解将由编译器记录在类文件中,但在运行时不需要由 VM 保留。
  13. 这是默认行为。
  14. */
  15. CLASS,
  16. /**
  17. * Annotations are to be recorded in the class file by the compiler and
  18. * retained by the VM at run time, so they may be read reflectively.
  19. //注解将被编译器记录在类文件中,并在运行时由 VM 保留,
  20. 因此它们可以被反射读取。
  21. *
  22. * @see java.lang.reflect.AnnotatedElement
  23. */
  24. RUNTIME
  25. }

4.4@Retention 元注解

  1. - 它是被定义在一个注解类的前面,用来说明该注解的生命周期。
  2. - 参数 @Retention( RetentionPolicy )
  3. - RetentionPolicy._SOURCE_:指定注解只保留在源文件当中。  
  4. - 当编译器将源文件编译成class文件时,它不会将源文件中定义的注解保留在class文件中。
  5. - RetentionPolicy._CLASS_:指定注解只保留在class文件中。(默认值)
  6. - 当加载class文件到内存时,虚拟机会将注解去掉,从而在程序中不能访问。
  7. - RetentionPolicy._RUNTIME_:指定注解可以保留在程序运行期间。
  8. - 此时,我们可以通过反射来获得定义在某个类上的该类注解。

image.png

Retention元注解小结

  1. 1. 注解的使用主要被反射读取
  2. 1. 反射只能读取内存中的字节码信息
  3. 1. _RetentionPolicy.CLASS _注解保留到字节码文件
  4. - (字节码)在磁盘内,而不是内存中。虚拟机将字节码文件加载进内存后注解会消失
  5. 4. **注解类的默认保留策略为 **_**RetentionPolicy****.CLASS**_
  6. 4. 要想被反射读取,即运行时仍可读取, 保留策略只能用 _**RetentionPolicy.**RUNTIME_

5.JDK中的元注解

@Documented

:::info @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) //唯一值: 限于注解上
public @interface Documented {
}

:::

  1. - 表明在生成JavaDoc文档时,使用该注解的注解类也会出现在javaDoc文档中。 不重要, 可忽略

@Target

:::info @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)//唯一值: 限定于注解上
public @interface Target {
/*
Returns an array of the kinds of elements an annotation type
can be applied to.
@return an array of the kinds of elements an annotation type
* can be applied to

返回一个可以应用注解类型的元素类型数组__ */
_ElementType[] value();
}

:::

  1. - 定义在一个注解类的前面,用来说明**使用该注解的注解类可以被声明在哪些元素前**。(默认可以放在任何元素之前)
  2. - @Target( ElementType[] ) 参数:
  3. - ElementType枚举类源码:
  1. package java.lang.annotation;
  2. public enum ElementType {
  3. /** Class, interface (including annotation type), or enum declaration
  4. 类、接口(包括注解类型)或枚举声明 */
  5. TYPE,
  6. /** Field declaration (includes enum constants) */
  7. FIELD,
  8. /** Method declaration */
  9. METHOD,
  10. /** Formal parameter declaration */
  11. PARAMETER,
  12. /** Constructor declaration */
  13. CONSTRUCTOR,
  14. /** Local variable declaration
  15. 局部变量声明*/
  16. LOCAL_VARIABLE,
  17. /** Annotation type declaration */
  18. ANNOTATION_TYPE,
  19. /** Package declaration */
  20. PACKAGE,
  21. /**
  22. * Type parameter declaration
  23. * 泛型
  24. * @since 1.8
  25. */
  26. TYPE_PARAMETER,
  27. /**
  28. * Use of a type
  29. *
  30. * @since 1.8
  31. */
  32. TYPE_USE
  33. }

@Retention

  1. - [注解的保留策略](#hERDS)

@Inherited: 继承

:::info @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

:::

  1. - 被它修饰的 注解 将具有继承性.
  2. - 若某类使用了被 @Inherited 元注解修饰的 Annotation, 则其子类将自动具有该 Annotation.

6.自定义Junit框架

6.1结构

  1. - 基于注解的三角关系下的代码结构:![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628087361320-12e48b35-3234-46d8-833c-ab6ae9dc8dc3.png#clientId=ud0652fcc-0fca-4&from=paste&id=u0c078d4b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=273&originWidth=693&originalType=url&ratio=1&size=24082&status=done&style=none&taskId=u9a007fde-b098-48bc-a318-bd879ba74c1)

6.2代码

  1. 1. 定义注解. (需要运行时反射获取注解信息, 故需保留策略设置为_RUNTIME_)
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. public @interface MyTest {
  4. }
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. public @interface MyBefore {
  4. }
  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. public @interface MyAfter {
  4. }
  1. 2. 使用注解
  1. public class DataController {
  2. @MyTest
  3. public void save(){
  4. System.out.println("save...");
  5. }
  6. @MyBefore
  7. public void init(){
  8. System.out.println("init...");
  9. }
  10. @MyAfter
  11. public void destroy(){
  12. System.out.println("destroy...");
  13. }
  14. }
  1. 3. 自定义Junit框架: 读取注解, 完成操作
  1. package com.ryze.myJunit;
  2. import com.ryze.useMyJunit.DataController;
  3. import java.lang.reflect.Method;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. public class MyJunitFrameWork {
  7. public static void main(String[] args) throws Exception {
  8. // 1.先找到测试类的字节码:DataController
  9. Class clazz = DataController.class;
  10. // 2.获取DataController类中所有的公共方法
  11. Method[] methods = clazz.getMethods();
  12. //3.迭代出每一个Method对象, 判断哪些方法上使用了@MyBefore/@MyAfter/@MyTest注解
  13. List<Method> myBeforeList = new ArrayList();
  14. List<Method> myAfterList = new ArrayList();
  15. List<Method> myTestList = new ArrayList();
  16. for (Method method : methods){
  17. // isAnnotationPresent() : boolean 返回是否存在制定类型的注解
  18. if (method.isAnnotationPresent(MyBefore.class)){
  19. myBeforeList.add(method);
  20. }
  21. if (method.isAnnotationPresent(MyTest.class)){
  22. myTestList.add(method);
  23. }
  24. if (method.isAnnotationPresent(MyAfter.class)){
  25. myAfterList.add(method);
  26. }
  27. }
  28. // 找到目标@MyTest. 前后置执行@MyBefore()和My@After单元测试方法组的实际执行(反射调用)
  29. Object obj = clazz.newInstance();
  30. int count = 0;
  31. for (Method myTestMethod : myTestList){
  32. System.out.println("---------" + ++count + "---------");
  33. // 前置
  34. for (Method myBeforeMethod : myBeforeList){
  35. myBeforeMethod.invoke(obj);
  36. }
  37. // @MyTest
  38. myTestMethod.invoke(obj);
  39. // 后置
  40. for (Method myAfterMethod : myAfterList){
  41. myAfterMethod.invoke(obj);
  42. }
  43. System.out.println('\n');
  44. }
  45. }
  46. }
  1. 4. 控制台成功输出
  1. ---------1---------
  2. init...
  3. write...
  4. destroy...
  5. ---------2---------
  6. init...
  7. save...
  8. destroy...