揭开 Java 注解的神秘面纱

1.注解的概念以及自定义注解

注解就是Java中的特殊标记,比如 Junit 中的 @Test 注解以及标记方法重写的 @Override等。注解的作用是让其他程序根据注解信息来决定怎么执行该程序

想揭开注解的面纱我们得先从它的语法入手,下面是定义注解的语法。

java 复制代码
public @interface 名称{
    public 属性类型 属性名() default 默认值;
}

自定义一个注解:

java 复制代码
public @interface MyAnnotation {
    //属性1
    String s();

    //属性2 默认值 true
    boolean b() default true;

    //属性3 类型为字符串数组
    String[] arr();
}

使用规则:

java 复制代码
//可以在类上面使用
@MyAnnotation(b="hello",arr={"value1","value2","value3"})
public class Test {

    //可以在属性上使用
    @MyAnnotation(s="hello",b = false,arr={"value1","value2","value3"})
    public String str;
    
    //可以在方法上面使用
    @MyAnnotation(b="hello",arr={"value1","value2","value3"})
    public void test1(){

    }
}
  • 注解里面有多少个非 default 属性,使用的时候就得初始化几个属性。

  • 如果注解中只有一个value属性(default不算),使用注解时,value名称可以不写。比如:

    java 复制代码
    public @interface MyAnnotation {
        //属性1
        String value();
        
        //属性2 默认值 true
        boolean b() default true;
    }
    java 复制代码
    @MyAnnotation("sdfd") //前提是属性名称必须为 value
    public class Test {
        
    }

2.元注解

元注解就是修饰注解的注解。

  1. @Target(ElementType.TYPE):声明被修饰的注解只能在哪些位置使用。
    1. TYPE:类,接口
    2. FIELD:成员变量
    3. METHOD:成员方法
    4. PARAMETER:方法参数
    5. CONSTRUCTOR:构造器
    6. LOCAL_VARIABLE:局部变量
  2. @Retention(RetentionPolicy.RUNTIME)
    1. SOURCE:只作用在源码阶段,字节码文件中不存在
    2. CLASS:保留到字节码文件阶段,运行阶段不存在
    3. RUNTIME:一直保留到运行阶段
java 复制代码
@Target({ElementType.TYPE,ElementType.METHOD})//当前 MyAnnotation 注解只能用在类和方法上
@Retention(RetentionPolicy.RUNTIME)//一直保留到运行阶段
public @interface MyAnnotation {
    String a();
    boolean b() default true;
    String[] arr();
}

@Target注解中,属性是 ElementType[]类型,所以可以有多个值。

3.注解的解析与作用【模拟 Junit 框架中 @Test 注解】

注解到底有什么用呢?上面我们自定义了一个注解,貌似啥用都没有。其实不然,注解一般要与反射结合使用,在才能发挥它的最大作用。接下来就来简单地模拟 Junit中的 @Test 注解,看看注解与反射如何结合使用。下面先给出反射中与注解相关的 API。

在反射中,Class、Method、Field、Constructor,都实现了 AnnotatedElement 接口。这个接口提供一些方法:

方法名 作用 返回值
isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断指定类型的注解是否出现在当前元素上 boolean
getAnnotation(Class<T> annotationClass) 获取当前元素上指定类型的注解,如果没有则返回 null T
getAnnotations() 获取当前元素上的所有注解,如果没有则返回长度为 0 的数组 Annotation[]
getAnnotationsByType(Class<T> annotationClass) 获取当前元素上与指定类型相关联的注解,包括直接存在、间接存在和继承的注解,如果没有则返回长度为 0 的数组 T[]
getDeclaredAnnotation(Class<T> annotationClass) 获取当前元素上直接存在的指定类型的注解,如果没有则返回 null T
getDeclaredAnnotations() 获取当前元素上直接存在的所有注解,如果没有则返回长度为 0 的数组 Annotation[]
getDeclaredAnnotationsByType(Class<T> annotationClass) 获取当前元素上直接或间接存在的指定类型的注解,如果没有则返回长度为 0 的数组 T[]

Junit 中的 @Test 注解有什么用呢?@Test 用来标识一个方法是一个测试方法的标记。要运行这个方法,Junit首先会创建一个类的新实例,然后调用这个注解的方法。如果这个方法抛出了异常,Junit会将其报告为失败。如果没有抛出异常,测试就被认为成功了。

那么是怎么做到直接加上 @Test 就能直接运行该方法呢?

这里我们先自定义一个注解:

java 复制代码
@Target(ElementType.METHOD) //只能注解方法
@Retention(RetentionPolicy.RUNTIME) //一直到运行阶段
public @interface MyTest {
    
}

加上我们自己的注解:

java 复制代码
public class Test {
    @MyTest
    public void test1(){
        System.out.println("启动 test1 成功!");
    }

    public void test2(){
        System.out.println("启动 test2 成功!");
    }

    @MyTest
    public void test3(){
        System.out.println("启动 test3 成功!");
    }
}

重点来了,我们这里需要实现一个 Main 方法

java 复制代码
public class Main {
    public static void main(String[] args) throws Exception {
        Test test = new Test();
        //1.获得 Test 类的 class对象
        Class<?> c = Test.class;
        //2.获取该对象的所有方法
        Method[] methods = c.getDeclaredMethods();
        //3.遍历数组中的每个方法,提取有 @MyTest 注解的方法并运行该方法
        for(Method m : methods){
            //判断当前方法是否被 @MyTest 注解修饰
            if(m.isAnnotationPresent(MyTest.class)){
                //运行该方法
                m.invoke(test);
            }
        }
    }
}

点击运行,就能让所有的加了 @MyTest 注解的方法运行:

再来总结一下:注解是一种为类、方法、字段、参数等元素添加元数据的机制,它可以用来描述、限制、扩展或修改这些元素的行为。注解本身并不会影响程序的逻辑,它只是提供了一些额外的信息,需要由其他程序(比如编译器、框架、工具等)来读取和处理。

注解和反射的关系是:注解可以被反射读取和处理,反射可以利用注解来实现一些高级的功能。注解和反射的组合使用是一种常见的编程模式,它可以简化代码、增强灵活性、提高效率。例如,Java中的内置注解@Deprecated@Override@SuppressWarnings等都是通过反射来实现编译时或运行时的检查和提示的。又例如,Spring框架中的@Autowired@Controller@RequestMapping等注解都是通过反射来实现依赖注入、控制器映射、请求处理等功能的。

4.注解的本质

注解到底是怎么实现的呢?这里通过把MyAnnotation.class文件的反编译成.java看看是什么样的(利用Xjad反编译工具)。

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。注解格式"@注解名(...)",本质上就是该注解的一个实现类对象,实现了该注解以及Annotation接口。具体的底层原理可以看这篇文章👉Java注解(Annotation)原理详解_annotation原理-CSDN博客,写得真的不错(俺也是参考了这篇文章)。

相关推荐
shigen012 分钟前
结合HashMap与Java 8的Function和Optional消除ifelse判断
java·开发语言
yuhaiqiang20 分钟前
超乎你的想象!SpringBoot处理1 次 Http请求竟需要申请这么一大块内存!
java·spring
砖业洋__24 分钟前
Spring高手之路24——事务类型及传播行为实战指南
java·spring·事务·nested·事务传播行为
黄俊懿25 分钟前
【深入理解SpringCloud微服务】了解微服务的熔断、限流、降级,手写实现一个微服务熔断限流器
java·分布式·后端·spring cloud·微服务·架构·手写源码
追风筝的Coder29 分钟前
泛微开发修炼之旅--44用友U9与ecology对接方案及源码
java
鱟鲥鳚39 分钟前
对象关系映射ORM
java
aristo_boyunv1 小时前
【线程池】ThreadPoolExecutor应用
java·线程池·并发
Xua30551 小时前
浅谈Spring Cloud:OpenFeign
后端·spring·spring cloud
工程师老罗1 小时前
Java笔试面试题AI答之设计模式(4)
java·开发语言·设计模式
KuaiKKyo1 小时前
c++9月20日
java·c++·算法