1、概述
通俗的说是将我们需要的数据通过注解存储起来,当我们需要的时候(编译时或者是运行时)就去获取存储的数据。
(1)注解是一种对程序元素设置元数据的方法。
(2)元数据是指添加在类,方法,字段,包上的额外信息。
(3)注解是一种分散式的设置元数据的方法,而xml配置是一种集中式的设置元数据的方法
(4)注解不能直接干扰程序的运行,即添加和删除注解后程序都应当是可运行的
2、java.lang.Annotation
通过反编译可以知道所有的注解都是接口且继承自java.lang.Annotation
package annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DemoAnnotation {
}
3、注解的作用
(1)作用于编译阶段:
a、作为特定的标记,用于告诉编译器一些信息。如@Override。
b、编译时动态处理,如生成动态代码,lombok中的@Data会在编译时被动态处理生成代码
(2)作用于运行阶段:运行时动态处理,作为额外信息的载体。
4、注解的分类
(1)标准注解:如@Override @Deprecated @SuppressWarnings
(2) 元注解:用于标记注解的注解,包括:@Target @Retention @Inherited @Documented
(3)自定义注解:自己定义的作为载体来存储额外信息。
5、元注解
元注解是用于标记(定义)注解的注解,用来定义注解的生命周期,目标等信息,有以下四种
(1)@Target:用于标记注解作用的目标,可同时作用到多种目标上如@Autowired
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
取值的范围:
public enum ElementType {
/** 类、接口、枚举*/
TYPE,
/** 字段*/
FIELD,
/** 方法 */
METHOD,
/** 方法的参数定义上*/
PARAMETER,
/** 构造方法 */
CONSTRUCTOR,
/** 本地局部变量 */
LOCAL_VARIABLE,
/** 注解 */
ANNOTATION_TYPE,
/** 包 */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
(2)@Retention:用于定义注解的生命周期,即什么范围内有效
取值范围:
public enum RetentionPolicy {
/**
* 在源代码中保存,编译后的class文件中该注解不存在
*/
SOURCE,
/**
* 在源代码及编译后生成的class文件中保存,但在运行中无效
*/
CLASS,
/**
* 表示在运行时有效,能够获取通过注解的获取到额外的信息
*/
RUNTIME
}
如@Autowired的生命周期为:@Retention(RetentionPolicy.RUNTIME),即在运行时能将容器中对应的Bean实例注入到如修饰的成员变量上。
(3)@Inherited:@Inherited()标记的注解能被子类所继承。
(4)@Documented:注解是否应当被包含在JavaDoc文档中。
6、自定义注解的实现
(1)格式:
package annotation;
import java.lang.annotation.*;
//注解不能继承其他注解或类,也不能实现接口
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited()
public @interface PersonInfoAnnotation {
public String name() default "小明";
public int age() default 10;
public String[] hobbies() default {"吃饭","睡觉","打豆豆"};
}
(2)注解中允许的数据类型:基本数据类型(不允许为基本数据类型的包装类),Class类,
String类,枚举,注解,及以上类型的数组,不能是自定义的类
7、通过反射来获取注解中的信息
(1)Class,Filed,Constructor,Method类都实现了AnnotatedElement接口,通过AnnotatedElement接口可以获取对应的类、字段、构造函数、方法上的注解,然后再从注解中获取到额外的信息。
(2)代码实例,通过反射获取注解中的信息
package annotation;
import java.lang.annotation.*;
//注解不能继承其他注解或类,也不能实现接口
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited()
public @interface PersonInfoAnnotation {
public String name() default "小明";
public int age() default 10;
public String[] hobbies() default {"吃饭","睡觉","打豆豆"};
}
package annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@PersonInfoAnnotation(name="大明",age=1,hobbies = {"游泳"})
public class PerSonTest {
@PersonInfoAnnotation(name="二明",age=2,hobbies = {"看书"})
private String personInfo;
@PersonInfoAnnotation(age=3)
private void printInfo() throws NoSuchFieldException, NoSuchMethodException {
printClazzAnnotation();
printFieldAnnotation();
printMethodAnnotation();
}
//获取类上的注解
private void printClazzAnnotation(){
Class calzz=this.getClass();
PersonInfoAnnotation personInfoAnnotation=(PersonInfoAnnotation)calzz.getAnnotation(PersonInfoAnnotation.class);
System.out.println("------------------打印类上的注解-------------------------");
print(personInfoAnnotation);
}
//获取字段上的注解
private void printFieldAnnotation() throws NoSuchFieldException {
Class calzz=this.getClass();
Field field=calzz.getDeclaredField("personInfo");
System.out.println("------------------打印成员变量上的注解-------------------------");
PersonInfoAnnotation personInfoAnnotation=(PersonInfoAnnotation)field.getAnnotation(PersonInfoAnnotation.class);
print(personInfoAnnotation);
}
//获取方法上的注解
private void printMethodAnnotation() throws NoSuchMethodException {
Class calzz=this.getClass();
Method method=calzz.getDeclaredMethod("printInfo");
PersonInfoAnnotation personInfoAnnotation=(PersonInfoAnnotation)method.getAnnotation(PersonInfoAnnotation.class);
System.out.println("------------------打印方法上的注解-------------------------");
print(personInfoAnnotation);
}
private static void print(PersonInfoAnnotation personInfoAnnotation){
System.out.println("姓名:"+personInfoAnnotation.name()
+" 年龄:"+personInfoAnnotation.age()
+" 爱好:");
for (String e:personInfoAnnotation.hobbies()) {
System.out.println(e+" ");
}
}
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
PerSonTest perSonTest=new PerSonTest();
perSonTest.printInfo();
}
}
运行结果:
------------------打印类上的注解-------------------------
姓名:大明 年龄:1 爱好:
游泳
------------------打印成员变量上的注解-------------------------
姓名:二明 年龄:2 爱好:
看书
------------------打印方法上的注解-------------------------
姓名:小明 年龄:3 爱好:
吃饭
睡觉
打豆豆
Process finished with exit code 0
注意:@Retention的生命周期必须是Runtime,否则无法获取到数据。
8、注解的原理
(1)通过键值对的形式为注解的属性赋值
(2)编译器在编译时会检查注解使用的范围(即@Target),并将注解的内容(键值对)保存到对应元素(类、方法、成员变量等)的属性表中
(3)运行时,如果元素对应注解的@Retention为Runtime,则JVM会将属性表中的内容生成一个Map
(4)创建一个AnnotationInvocationHandler实例对象,将上面生成的Map传入,保存在成员变量,memberValues中
(5)JVM使用JDK动态代理生成代理类,并将AnnotationInvocationHandler实例对象传入生成一个对应的代理类对象,如下图所示:
a、代理类继承自Proxy且实现了对应的注解接口,可以看出注解本质上是一个接口
b、初始化时传入一个AnnotationInvocationHandler对象
c、代码中注解方法name()的调用,实际调用的是代理类中的name()方法
d、代理类中的name()调用了AnnotationInvocationHandeler的invoke方法。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import annotation.PersonInfoAnnotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//代理类继承自Proxy且实现了对应的注解接口
//可以看出注解本质上是一个接口
public final class $Proxy2 extends Proxy implements PersonInfoAnnotation {
private static Method m1;
private static Method m3;
private static Method m5;
private static Method m2;
private static Method m4;
private static Method m6;
private static Method m0;
//初始化时传入一个AnnotationInvocationHandler对象
public $Proxy2(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//代码中注解方法name()的调用,实际调用的是这里
public final String name() throws {
try {
//调用了AnnotationInvocationHandeler的invoke方法。
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String[] hobbies() throws {
try {
return (String[])super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int age() throws {
try {
return (Integer)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Class annotationType() throws {
try {
return (Class)super.h.invoke(this, m6, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("annotation.PersonInfoAnnotation").getMethod("name");
m5 = Class.forName("annotation.PersonInfoAnnotation").getMethod("hobbies");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("annotation.PersonInfoAnnotation").getMethod("age");
m6 = Class.forName("annotation.PersonInfoAnnotation").getMethod("annotationType");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
e、AnnotationInvocationHandeler的invoke就是在键值对memberValues中获取方法名name对应的值。
注:查看生成代理类的方法:
JDK1.8及之前的版本:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
JDK1.8之后的版本: -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
设置后直接运行,可获取生成的动态代理类