0.参考
注解(上)-bravo1988 的文章
注解(下)-bravo1988 的文章
java之元数据(metadata)
12-注解基础(重要-框架基础).xmind
1.概述
1.1基本知识
- 简介- 注解(Annotation)也被称为元数据(Metadata: 描述数据的数据),用于修饰解释 包、类、构造器、方法、属性、局部变量等数据信息- 和注释一样,注解不影响程序逻辑,但**注解可以被编译或运行**,相当于嵌入在代码中的补充信息- 使用位置- 实际开发中,注解常常出现在类、方法、成员变量、形参等位置。具体见枚举类 ElementType- 生效时机- 注解可以保留到 源码.java文件, 编译后.class文件, 运行时内存中. 即(_SOURCE, CLASS, RUNTIME_)- 如何使用- 注解的三角关系:定义注解,使用注解,读取注解- - 作用- 在 JavaSE 中,注解的使用目的比较简单,例如**标记过时的功能,忽略警告**等。在 JavaEE 中注解占据了更重要的角色,例如用来配置应用程序的任何切面(AOP),代替 java EE 旧版中所遗留的繁冗代码和 XML 配置等。- 如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是 **为当前读取该注解的程序提供判断依据**。比如**程序只要读到加了@Test的方法,就知道该方法是待测试方法,又比如@Before注解,程序看到这个注解,就知道该方法要放在@Test方法之前执行**。- 级别- **注解和类、接口、枚举是同一级别的.(其声明使用 @interface)**
1.2格式
public @interface 注解名称{属性列表;}
1.3分类
- JDK内置注解- @Override: 检验方法重载- @Deprecated: 标识类/方法等元素过时- @SuppressWarnings: 抑制编译器警告- 第三方框架注解- Junit的单元测试 @Test, @Before, @After- SpringMVC的@Controller等- 自定义注解
2.注解的本质
2.1反编译查看注解本质
1. 自定义一个注解类: MyAnnotation.Java
package com.ryze.myAnnotation;public @interface MyAnnotation {String value();}
2. 编译后得到字节码 MyAnnotation.class 文件, 再对其进行反编译(通过XJad软件)得到 MyAnnotation.java:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.// Jad home page: http://kpdus.tripod.com/jad.html// Decompiler options: packimports(3) fieldsfirst ansi space// Source File Name: MyAnnotation.javapackage com.ryze.myAnnotation;import java.lang.annotation.Annotation;public interface MyAnnotation extends Annotation{public abstract String value();}
3. 其中 java.lang.annotation.Annotation 接口源码如下:
package java.lang.annotation;public interface Annotation {boolean equals(Object obj);int hashCode();String toString();// 返回此注解的注解类型Class<? extends Annotation> annotationType();}
2.2结论
1. 编码上使用 @interface, **反编译后底层是: 继承了 Annotation 接口的 interface, **. 故也叫**声明式接口**- 类比 public enum MyEnum{...} .- 编码上使用 enum, 反编译后底层是 继承了 Enum 的 class- public final class MyEnum extends Enum{...}2. JDK中 java.lang.annotation.Annotation 本身是接口,而不是注解. 当使用关键字@interface 定义一个注解时,该注解隐含的继承了Annotation接口;如果我们定义一个 TestAnnotation,并且让 TestAnnotation 继承Annotation,那么我们定义的接口依然是接口而不是注解。综上,定义注解只能依靠@interface实现。
3.注解的属性
3.1引入
- 可以如下使用注解
public @interface MyAnnotation {String value();}/*=========================*/@MyAnnotation(value = "MyValue")public class TestAnnotation {}
- 形式上如同 给value()方法赋值. 底层??- 注解中类似于 String value(); 被称为属性
3.2属性的类型
- 八种基本数据类型- String- 枚举- Class- 注解类型- 以上类型的一维数组
3.3属性的赋值
- **若注解的属性只有一个,且叫value,那使用该注解时,可不用指定属性名,默认给value赋值**- 若注解的属性如果有多个,无论是否叫value,都**必须写明属性的对应关系**- **如果数组的元素只有一个,可以省略{}**
3.4属性的默认(返回)值
- **(在注解类中)使用 default 关键词以声明属性默认值**
public @interface MyAnnotation {String value() default "My Default value...";}
4.保留策略与@Retention元注解
4.0概述
- 保留策略: 定义的注解可以在哪个时间段起作用- 类比JSP中,<!-- -->和<%-- -->的区别:前者可以在浏览器检查网页源代码时看到,后者在服务器端输出时就被抹去了。同样的,注解通过保留策略,控制自己可以保留到哪个阶段。保留策略也是通过注解(@Retention中属性值)实现,它属于元注解.- **元注解: 加在注解上的注解**
4.1引入: 反射获取注解信息出现的问题
- 前提- **要用到注解,必然有三角关系:定义注解,使用注解,读取注解**- - 读取注解的方式选择:- - 反射- - 返回 null值, 异常提示: **注解类 MyAnnotation 没有为反射保留. 原因: 反射是基于JVM运行时行为, 保留策略默认为**_CLASS**, 运行时候即不存在, 所以不会被反射程序所检测.**_
4.2简单使用
- 定义注解时1. 标记@Retention()元注解,1. 并进行 RetentionPolicy 类型的 value 值设定1. @Retention() 的默认值为 _RetentionPolicy.CLASS_1. 对于该默认值JDK中的注释: _CLASS_ 属性将由编译器记录在类文件中,但不需要在运行时由VM保留。这是默认行为。- 效果: 该默认值无法反射获取注解信息. 需要手动设为:_RetentionPolicy.RUNTIME_
package com.ryze.myAnnotation;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;// 将保留策略设为 RUNTIME@Retention(value = RetentionPolicy.RUNTIME)public @interface MyAnnotation {String value() default "My Default value...";}
- 再次运行反射代码:
public class TestGetAnnot {public static void main(String[] args) {Class<TestAnnotation> clazz = TestAnnotation.class;MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);System.out.println(annotation);System.out.println(annotation.value());}}
- **修改保留策略(为RUNTIME)后成功获取注解信息:**
@com.ryze.myAnnotation.MyAnnotation(value=My Default value...)My Default value...
- // 其中 "My Default value..." 为 MyAnnotation类 value属性的default值
4.3Retention 元注解源码分析
4.3.1Retention 元注解类源码
package java.lang.annotation;@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Retention {/**Returns the retention policy.返回保留策略*/RetentionPolicy value();}
4.3.2@RetentionPolicy() 保留策略取值(注解的生命周期)
package java.lang.annotation;public enum RetentionPolicy {/*** Annotations are to be discarded by the compiler.// 编译器将丢弃注解。*/SOURCE,/*** Annotations are to be recorded in the class file by the compiler* but need not be retained by the VM at run time. This is the default* behavior.//注解将由编译器记录在类文件中,但在运行时不需要由 VM 保留。这是默认行为。*/CLASS,/*** Annotations are to be recorded in the class file by the compiler and* retained by the VM at run time, so they may be read reflectively.//注解将被编译器记录在类文件中,并在运行时由 VM 保留,因此它们可以被反射读取。** @see java.lang.reflect.AnnotatedElement*/RUNTIME}
4.4@Retention 元注解
- 它是被定义在一个注解类的前面,用来说明该注解的生命周期。- 参数 @Retention( RetentionPolicy )- RetentionPolicy._SOURCE_:指定注解只保留在源文件当中。- 当编译器将源文件编译成class文件时,它不会将源文件中定义的注解保留在class文件中。- RetentionPolicy._CLASS_:指定注解只保留在class文件中。(默认值)- 当加载class文件到内存时,虚拟机会将注解去掉,从而在程序中不能访问。- RetentionPolicy._RUNTIME_:指定注解可以保留在程序运行期间。- 此时,我们可以通过反射来获得定义在某个类上的该类注解。
Retention元注解小结
1. 注解的使用主要被反射读取1. 反射只能读取内存中的字节码信息1. _RetentionPolicy.CLASS _注解保留到字节码文件- (字节码)在磁盘内,而不是内存中。虚拟机将字节码文件加载进内存后注解会消失4. **注解类的默认保留策略为 **_**RetentionPolicy****.CLASS**_4. 要想被反射读取,即运行时仍可读取, 保留策略只能用 _**RetentionPolicy.**RUNTIME_
5.JDK中的元注解
@Documented
:::info
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) //唯一值: 限于注解上
public @interface Documented {
}
:::
- 表明在生成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();
}
:::
- 定义在一个注解类的前面,用来说明**使用该注解的注解类可以被声明在哪些元素前**。(默认可以放在任何元素之前)- @Target( ElementType[] ) 参数:- ElementType枚举类源码:
package java.lang.annotation;public enum ElementType {/** Class, interface (including annotation type), or enum declaration类、接口(包括注解类型)或枚举声明 */TYPE,/** Field declaration (includes enum constants) */FIELD,/** Method declaration */METHOD,/** Formal parameter declaration */PARAMETER,/** Constructor declaration */CONSTRUCTOR,/** Local variable declaration局部变量声明*/LOCAL_VARIABLE,/** Annotation type declaration */ANNOTATION_TYPE,/** Package declaration */PACKAGE,/*** Type parameter declaration* 泛型* @since 1.8*/TYPE_PARAMETER,/*** Use of a type** @since 1.8*/TYPE_USE}
@Retention
- [注解的保留策略](#hERDS)
@Inherited: 继承
:::info
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
:::
- 被它修饰的 注解 将具有继承性.- 若某类使用了被 @Inherited 元注解修饰的 Annotation, 则其子类将自动具有该 Annotation.
6.自定义Junit框架
6.1结构
- 基于注解的三角关系下的代码结构:
6.2代码
1. 定义注解. (需要运行时反射获取注解信息, 故需保留策略设置为_RUNTIME_)
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyTest {}
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyBefore {}
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyAfter {}
2. 使用注解
public class DataController {@MyTestpublic void save(){System.out.println("save...");}@MyBeforepublic void init(){System.out.println("init...");}@MyAfterpublic void destroy(){System.out.println("destroy...");}}
3. 自定义Junit框架: 读取注解, 完成操作
package com.ryze.myJunit;import com.ryze.useMyJunit.DataController;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;public class MyJunitFrameWork {public static void main(String[] args) throws Exception {// 1.先找到测试类的字节码:DataControllerClass clazz = DataController.class;// 2.获取DataController类中所有的公共方法Method[] methods = clazz.getMethods();//3.迭代出每一个Method对象, 判断哪些方法上使用了@MyBefore/@MyAfter/@MyTest注解List<Method> myBeforeList = new ArrayList();List<Method> myAfterList = new ArrayList();List<Method> myTestList = new ArrayList();for (Method method : methods){// isAnnotationPresent() : boolean 返回是否存在制定类型的注解if (method.isAnnotationPresent(MyBefore.class)){myBeforeList.add(method);}if (method.isAnnotationPresent(MyTest.class)){myTestList.add(method);}if (method.isAnnotationPresent(MyAfter.class)){myAfterList.add(method);}}// 找到目标@MyTest. 前后置执行@MyBefore()和My@After单元测试方法组的实际执行(反射调用)Object obj = clazz.newInstance();int count = 0;for (Method myTestMethod : myTestList){System.out.println("---------" + ++count + "---------");// 前置for (Method myBeforeMethod : myBeforeList){myBeforeMethod.invoke(obj);}// @MyTestmyTestMethod.invoke(obj);// 后置for (Method myAfterMethod : myAfterList){myAfterMethod.invoke(obj);}System.out.println('\n');}}}
4. 控制台成功输出
---------1---------init...write...destroy...---------2---------init...save...destroy...
