Java注解原理与使用

参考:

  1. @SuppressWarnings注解用法详解 - 掘金 (juejin.cn)
  2. 面试官:聊聊Java注解的实现原理? - 知乎 (zhihu.com)
  3. java注解和灵活的动态代理 - 指路为码 - 博客园 (cnblogs.com)
  4. 注解和动态代理 - 掘金 (juejin.cn)
  5. Java 代理模式详解 | JavaGuide(Java面试 + 学习指南)

介绍

注解是JDK5引入的,用于标注类属性方法原信息的特性。Annotation提供对类注释,用于将任何信息与类元素属性进行绑定的方法,可以应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句等。

原注解

@Documented

是否包含在JavaDoc中

是否将注解信息加入Java文档中

@Retention

什么时候使用该注解

  • RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。如@Override@SuppressWarnings
  • RetentionPolicy.CLASS : (默认)在类加载的时候丢弃。在字节码文件的处理中有用。
  • RetentionPolicy.RUNTIME : (自定义注解使用)始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。
    声明周期从长到短分别为:RUNTIME > CLASS > SOURCE

@Target

注解用于什么地方

  • ElementType.CONSTRUCTOR: 用于描述构造器
  • ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
  • ElementType.LOCAL_VARIABLE: 用于描述局部变量
  • ElementType.METHOD: 用于描述方法
  • ElementType.PACKAGE: 用于描述包
  • ElementType.PARAMETER: 用于描述参数
  • ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

@Inherited

是否允许子类继承该注解

如果在类ClassA上使用了这个注解,那么继承了ClassA的ClassB中,该注解也将应用于该子类。

基本注解

@SuppressWarnings

可选值:

  • deprecation“:用于取消有关已弃用方法或类的警告。
  • unchecked“:用于取消有关未经检查的转换的警告。
  • fallthrough“:用于取消关于case分支缺少break语句的警告。
  • serial“:用于取消有关可序列化类缺少serialVersionUID字段的警告。
  • rawtypes“:用于取消使用原始类型而不是泛型类型的警告。
  • all“:用于取消所有的警告信息。

java.lang.SuppressWarnings是J2SE 5.0中标准的Annotation之一。可以标注在类、字段、方法、参数、构造方法,以及局部变量上。作用:告诉编译器忽略指定的警告,不用在编译完成后出现警告信息。

  • @SuppressWarnings("unchecked") 告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息
1
2
1. //非参数化(会提示要求)  
2. ArrayList stringList3 = new ArrayList();
  • @SuppressWarnings("serial") 如果编译器出现这样的警告信息:The serializable class WmailCalendar does not declare a static final serialVersionUID field of type long,使用这个注释将警告信息去掉。
  • @SuppressWarnings("deprecation") 如果使用了使用@Deprecated注释的方法,编译器将出现警告信息。使用这个注释将去掉警告信息
  • @SuppressWarnings("unchecked", "deprecation") 或者 @SuppressWarnings(value={"unchecked", "deprecation"}) 告诉编译器同时忽略unchecked和deprecation的警告信息

@Deprecated

表示当前被标记的类或者对象成员,被弃用(编译器不推荐使用),如果子类调用这个方法,编译器依然会进行报错,此时可以使用@SuppressWarningsJava 9及更高版本中,如果一个已经被弃用的类或方法被继承或者覆盖了,编译器可能会产生警告信息,可以使用@Deprecated注解来标记子类或子方法也是弃用的。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Documented  
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
/**
* Returns the version in which the annotated element became deprecated. * The version string is in the same format and namespace as the value of * the {@code @since} javadoc tag. The default value is the empty
* string. * * @return the version string
* @since 9
*/ String since() default "";

/**
* Indicates whether the annotated element is subject to removal in a * future version. The default value is {@code false}.
* * @return whether the element is subject to removal
* @since 9
*/ boolean forRemoval() default false;
}

@Override

java.lang.Override它说明了被标注的方法重载了父类的方法,在一个没有覆盖父类方法的方法时,java 编译器将以一个编译错误来警示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package java.lang;  

import java.lang.annotation.*;

/**
* Indicates that a method declaration is intended to override a * method declaration in a supertype. If a method is annotated with * this annotation type compilers are required to generate an error * message unless at least one of the following conditions hold: * * <ul><li> * The method does override or implement a method declared in a * supertype. * </li><li> * The method has a signature that is override-equivalent to that of * any public method declared in {@linkplain Object}.
* </li></ul> * * @author Peter von der Ah&eacute;
* @author Joshua Bloch
* @jls 8.4.8 Inheritance, Overriding, and Hiding
* @jls 9.4.1 Inheritance and Overriding
* @jls 9.6.4.4 @Override
* @since 1.5
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

原理

注解本质上是一种标记,提供给Java编译器,或者内置程序,标记完成之后,结合Java的反射获取对应的属性进行后续动态获取注解的标记信息,做相应的其他逻辑处理。

Java编译器编译Java源代码时,它将注解替换为Java类文件中的元数据。在运行时,程序可以使用反射机制来获取该元数据并访问注解中的各种属性和元素。通过反射机制,程序可以在运行时修改自身行为,从而实现更好的灵活性和扩展性。

自定义注解

  1. 声明注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package org.baiyz;  

    /**
    * <p> * Description: 自定义注解
    * </p>
    * <p>PackageName: org.baiyz</p> * <p>ClassName: TempAnno</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:06:06
    */
    public @interface TempAnno {

    }
  2. 标记目标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package org.baiyz;  

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Target;

    /**
    * <p> * Description: 自定义注解
    * </p>
    * <p>PackageName: org.baiyz</p> * <p>ClassName: TempAnno</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:06:06
    */
    @Target(value = {ElementType.METHOD,ElementType.FIELD})
    public @interface TempAnno {

    }
  3. 标记声明周期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package org.baiyz;  

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    /**
    * <p> * Description: 自定义注解
    * </p>
    * <p>PackageName: org.baiyz</p> * <p>ClassName: TempAnno</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:06:06
    */
    @Target(value = {ElementType.METHOD,ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface TempAnno {

    }
  4. 标记注解属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package org.baiyz;  

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    /**
    * <p> * Description: 自定义注解
    * </p>
    * <p>PackageName: org.baiyz</p> * <p>ClassName: TempAnno</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:06:06
    */
    @Target(value = {ElementType.METHOD,ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface TempAnno {

    int data();
    }

注意:注解中方法的返回值类型可以是任何基本类型以及其封装类、枚举类型、String类型、Class类型、注解类型、以及以上类型的数组类型。需要注意的是,方法的返回值类型不能是void类型或抛出异常类型

示例

实例代码仓库:learn-template-java/src/main/java/org/baiyz/annoTest at master · baiyz0825/learn-template-java · GitHub

  1. 设计下述Class对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package org.baiyz;  

    /**
    * <p> * Description: 被校验的类
    * </p>
    * <p>PackageName: org.baiyz</p> * <p>ClassName: BeVerified</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:11:44
    */public class BeVerified {
    @SelfAnno(length = 10)
    private String Name;

    public BeVerified(String name) {
    Name = name;
    }

    public String getName() {
    return Name;
    }

    public void setName(String name) {
    Name = name;
    }
    }
  2. 声明注解信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package org.baiyz;  

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    /**
    * <p> * Description: 自定义长度校验
    * </p>
    * <p>PackageName: org.baiyz</p> * <p>ClassName: SelfAnno</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 13:47:52
    */@Target(value = ElementType.FIELD)
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface SelfAnno {

    // 检查属性name的长度
    int length();
    }
  3. 声明注解调用的方法(反射类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    package org.baiyz;  

    import java.lang.reflect.Field;

    /**
    * <p> * Description: 反射校验方法
    * </p>
    * <p>PackageName: org.baiyz</p> * <p>ClassName: RefelctClass</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:16:23
    */public class ReflectClass {
    /**
    * 检查方法
    * @param obj 任意Obj
    * @return boolean 是否检查成功
    */
    public boolean Check(Object obj) {
    try {
    // 空指针check
    if (obj == null) {
    return false;
    }
    Field[] declaredFields = obj.getClass().getDeclaredFields();
    for (Field declaredField : declaredFields) {
    // 查看是否存在注解
    if (declaredField.isAnnotationPresent(SelfAnno.class)) {
    SelfAnno annotation = declaredField.getAnnotation(SelfAnno.class);
    int checkLength = annotation.length();
    // 开始私有变量的访问
    declaredField.setAccessible(true);
    // 获取Name的值
    String NameVar = (String) declaredField.get(obj);
    // Name长度大于限制长度返回false
    return NameVar.length() < checkLength;
    }
    }
    } catch (IllegalAccessException e) {
    System.out.println("出现异常:" + e);
    throw new RuntimeException(e);
    }

    return true;
    }

    /**
    * 输出检查结果信息
    * @param checkAns 检查是否成功
    */
    public void CheckAnsOutStr(boolean checkAns) {
    if (checkAns) {
    System.out.println("超过注解长度限制");
    } else {
    System.out.println("检查成功");
    }
    }
    }
  4. 主函数逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package org.baiyz;  

    /**
    * <p> * Description: 主函数
    * </p>
    * <p>PackageName: org.baiyz</p> * <p>ClassName: Main</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since ${YEAR}-${MONTH}-${DAY} ${HOUR}:${MINUTE}:${SECOND}
    */
    public class Main {
    public static void main(String[] args) {
    // 初始化反射工具
    ReflectClass reflectClassUtils = new ReflectClass();
    BeVerified beVerifiedOverLimit = new BeVerified("Name 1xxxxsssssa");
    BeVerified beVerifiedInsideLimit = new BeVerified("Name 2");

    // 执行检查没有超过限制的
    reflectClassUtils.CheckAnsOutStr(reflectClassUtils.Check(beVerifiedInsideLimit));

    // 执行检查超过限制的
    reflectClassUtils.CheckAnsOutStr(reflectClassUtils.Check(beVerifiedOverLimit));
    }
    }
  5. 调用结果
    下面可以看出,声明的第一个类实例化对象中的Name长度超过限制,第二个未超过限制,自定义注解成功生效。

image.png

查看调用过程:
image.png

image.png

注解配合动态代理

InvocationHandler是Java动态代理机制中的一个接口。通过实现该接口,可以自定义代理类的代理行为。

在Java动态代理机制中,通过Proxy.newProxyInstance()方法创建一个代理类,并指定该代理类的行为由InvocationHandler实现。在代理类的方法被调用时,相应的调用请求会被转发到InvocationHandlerinvoke()方法中,并由该方法进行处理。invoke()方法有三个参数:

  • Object proxy:被代理的对象。
  • Method method:将要被执行的方法。
  • Object[] args:方法执行时所需要的参数。

invoke()方法中,可以通过条件判断、方法转发等方式自定义代理类的行为,例如增加日志记录、权限控制等。完成操作后,再调用相应的方法并返回执行结果。

需要注意的是,InvocationHandler接口中只有一个方法,因此在实现该接口时需要重写invoke()方法。

示例

代码仓库:learn-template-java/src/main/java/org/baiyz/proxyTest at master · baiyz0825/learn-template-java · GitHub

  1. 声明实现接口(JDK代理需要接口支持)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package org.baiyz.proxyTest;  

    /**
    * <p> * Description: 被代理对象实现接口
    * </p>
    * <p>PackageName: org.baiyz.proxyTest</p> * <p>ClassName: BeProxyClassInterface</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 15:09:12
    */
    public interface BeProxyClassInterface {
    public void RealDoBus();

    public void RealDoBusWithAnno();
    }
  2. 声明被代理对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package org.baiyz.proxyTest;  

    /**
    * <p> * Description: 实际业务类
    * </p>
    * <p>PackageName: org.baiyz.proxyTest</p> * <p>ClassName: BeProxyClass</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:55:20
    */
    public class BeProxyClass implements BeProxyClassInterface {
    @Override
    public void RealDoBus() {
    System.out.println("处理实际业务方法!");
    }

    @Override
    @ProxyAnno public void RealDoBusWithAnno() {
    System.out.println("处理实际业务方法!");
    }
    }
  3. 声明注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package org.baiyz.proxyTest;  

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    /**
    * <p> * Description: 标注注解
    * </p>
    * <p>PackageName: org.baiyz.proxyTest</p> * <p>ClassName: ProxyAnno</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:53:29
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value = ElementType.METHOD)
    public @interface ProxyAnno {
    }
  4. 实现代理对象实现InvocationHandler

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package org.baiyz.proxyTest;  

    import java.lang.annotation.Annotation;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;

    /**
    * <p> * Description: 自定义Proxy代理类
    * </p>
    * <p>PackageName: org.baiyz.proxyTest</p> * <p>ClassName: InvokeHandlerSelf</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:53:54
    */
    public class InvokeHandlerSelf implements InvocationHandler {
    private BeProxyClass realClass;

    public InvokeHandlerSelf(BeProxyClass realClass) {
    this.realClass = realClass;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    boolean needProxy = false;
    // 实际方法执行前 检查是否需要进行代理前置处理
    // 这里需要从实际对象中获取注解,否则注解需要在接口中标注才能生效
    Method targetMethod = realClass.getClass().getMethod(method.getName(), method.getParameterTypes());
    for (Annotation annotation : targetMethod.getAnnotations()) {
    if (annotation.annotationType() == ProxyAnno.class) {
    needProxy = true;
    break;
    }
    }
    if (needProxy) {
    System.out.println("[目标方法执行前]方法名称" + method.getName() + "被注解标注需要执行其他额外逻辑");
    Object invoke = method.invoke(realClass, args);
    System.out.println("[目标方法执行后]!");
    return invoke;
    }
    System.out.println("直接执行目标方法,不进行代理" + method.getName());
    // 反射调用目标方法
    return method.invoke(realClass, args);
    }
    }
  5. 主函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package org.baiyz.proxyTest;  

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;

    /**
    * <p> * Description: 主方法
    * </p>
    * <p>PackageName: org.baiyz.proxyTest</p> * <p>ClassName: Main</p> * * @author <a href="mail to: byz0825@outlook.com" rel="nofollow">BaiYZ</a>
    * @since 2023-06-10 14:53:16
    */
    public class Main {
    public static void main(String[] args) {
    // 创建代理对象 强制转化为实际被代理类
    BeProxyClassInterface proxy = (BeProxyClassInterface) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{BeProxyClassInterface.class}, new
    InvokeHandlerSelf(new BeProxyClass()));

    // 输出信息
    System.out.println("Proxy Name:" + proxy.getClass().getName());
    System.out.println("-----执行不带注解方法:RealDoBus-----");
    // 执行目标方法不带注解
    proxy.RealDoBus();
    System.out.println("-----执行带注解方法:RealDoBusWithAnno-----");
    proxy.RealDoBusWithAnno();

    }
    }
  6. 执行结果

image.png

示例调用过程

  1. 调用Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{BeProxyClassInterface.class}, new InvokeHandlerSelf(new BeProxyClass())); 创建Proxy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @CallerSensitive  
    public static Object newProxyInstance(ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h) {
    // 断言对象非空
    Objects.requireNonNull(h);

    @SuppressWarnings("removal")
    final Class<?> caller = System.getSecurityManager() == null
    ? null
    : Reflection.getCallerClass();

    /*
    * Look up or generate the designated proxy class and its constructor. */ Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
    // 调用创建实例
    return newProxyInstance(caller, cons, h);
    }
  2. newProxyInstance(caller, cons, h)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager  
    Constructor<?> cons,
    InvocationHandler h) {
    /*
    * Invoke its constructor with the designated invocation handler.
    *
    **/
    try {
    // 存在目标类调用类 System.getSecurityManager() == null ? null :Reflection.getCallerClass()
    if (caller != null) {
    checkNewProxyPermission(caller, cons.getDeclaringClass());
    }
    // 调用构造器,传入InvocationHandler构造对象
    return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException | InstantiationException e) {
    throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
    Throwable t = e.getCause();
    if (t instanceof RuntimeException) {
    throw (RuntimeException) t;
    } else {
    throw new InternalError(t.toString(), t);
    }
    }
    }
  3. 通过主类的类加载器,创建了对应的JDK动态代理类:

image.png

  1. 调用代理中的Proxy对象执行目标的方法

通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。

image.png

  1. 调用实际目标方法Object invoke = method.invoke(realClass, args);
    获取方法实际的方法访问器,进行ma.invoke(obj, args, caller) : ma.invoke(obj, args);

image.png

寻找对应接口实现类进行调用Invoke

image.png

  1. 执行目标方法

image.png