• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

奋斗的软件工程师

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

深入理解Java注解Annotation:从基础到实战

深入理解Java注解:从基础到实战

引言

Java注解(Annotation)是JDK 1.5引入的一个强大特性,它允许开发者在代码中添加元数据(metadata),这些元数据可以在编译时、类加载时或运行时被读取和处理。注解不仅简化了代码的配置和维护,还为框架和工具提供了丰富的扩展点。本文将详细介绍Java注解的基本概念、自定义注解的创建、注解的使用以及注解的解析,并通过一个综合案例来展示注解的实际应用。

1. 什么是注解?

注解(Annotation)是一种代码级别的说明,它可以在包、类、字段、方法、局部变量、方法参数等元素的前面声明,用来对这些元素进行说明。注解本质上是一个接口,所有注解都会继承java.lang.annotation.Annotation接口。

注解的作用主要包括:

  1. 编译检查:例如@Override注解用于检查方法是否重写了父类的方法。
  2. 代码分析:通过注解对代码进行分析,例如框架的配置。
  3. 生成帮助文档:例如@author和@version注解用于生成文档时标记作者和版本信息。

2. 自定义注解

2.1 注解的定义格式

自定义注解使用@interface关键字来定义,格式如下:

public @interface 注解名 {
    // 属性定义
    数据类型 属性名();
    数据类型 属性名() default 默认值;
}

例如,定义一个简单的注解Book:

public @interface Book {
    String name();
    double price() default 100.0;
    String[] author();
}
2.2 注解的属性

注解的属性可以是以下类型:

  • 八种基本数据类型(int, short, long, double, byte, char, boolean, float)
  • String, Class, 注解类型, 枚举类
  • 以上类型的一维数组形式

3. 注解的使用

3.1 注解的使用格式

注解的使用格式如下:

@注解名(属性=值, 属性=值)

例如,使用上面定义的Book注解:

@Book(name = "Java编程思想", author = {"Bruce Eckel"})
public class BookStore {
    @Book(name = "Effective Java", price = 50.0, author = {"Joshua Bloch"})
    public void buy() {
        System.out.println("购书.....");
    }
}
3.2 特殊属性value

如果注解中只有一个属性,并且属性名为value,则在使用时可以省略value属性名:

@interface A {
    String value();
}

@A("值") // 当自定义注解中仅有一个value属性时,可以省略value属性名
public void test() {
}
3.3 注解使用的注意事项
  1. 使用注解: @注解名(属性名=属性值,属性名=属性值...)
  2. 如果注解中只有一个属性,并且属性名是value,那么给value属性赋值的时候可以不书写value属性名:@MyAnno("abc")
  3. 如果注解中只有一个属性,并且属性名不是value,那么给该属性赋值,必须书写属性名:@MyAnno(name="abc")
  4. 如果注解中的属性没有默认值,那么使用的时候必须给属性赋值。
  5. 如果注解中的属性有默认值,那么使用的时候可以给属性赋值,覆盖默认值,也可以不赋值。
  6. 如果属性是数组,那么赋值的时候:属性名={属性值,属性值...}
  7. 如果属性是数组,并且赋值的时候只给一个值,则可以省略大括号:属性名=属性值
  8. 如果注解中含有多个属性,并且没有默认值,那么给value赋值的时候不能省略value:@MyAnno(value="abc", name="def")
  9. 如果注解中含有多个属性,并且有默认值,那么给value单独赋值可以省略value属性名:@MyAnno("张三")
  10. 同一个注解不能同时修饰一个方法或者类。

注解的使用规则可能会让人感到有些复杂,尤其是需要记住各种特殊情况和简写规则时。不过,理解这些规则可以帮助你更高效地使用注解,并且在阅读和维护代码时更容易理解其他开发者的意图。

如果你觉得这些规则过于复杂,或者你更倾向于保持代码的简洁性和可读性,那么完全可以按照最规范的方式来使用注解,即:

  1. 明确指定属性名:无论注解中有多少属性,都明确指定属性名。例如:

    @MyAnno(value = "abc", name = "def")
    
  2. 避免使用简写:即使注解中只有一个属性,也明确指定属性名。例如:

    @MyAnno(value = "abc")
    
  3. 始终提供所有属性的值:即使属性有默认值,也明确提供属性的值,以确保代码的可读性和一致性。

这样做的好处是:

  • 代码更易读:明确指定属性名和值,使得代码更易于理解和维护。
  • 减少错误:避免因为简写规则而导致的潜在错误或误解。
  • 一致性:保持代码风格的一致性,使得团队成员更容易理解和遵循。

当然,如果你在某些情况下确实需要使用简写规则来提高代码的简洁性,那么理解这些规则也是有帮助的。但在大多数情况下,保持代码的清晰和规范性往往比追求简洁更重要。

4. 元注解

元注解是用来约束自定义注解的使用范围和生命周期的注解。常用的元注解有@Target和@Retention。

通俗的来说 元注解就是修饰注解的注解 还有一个词叫元数据 就是修饰数据的数据 类似的还有 元宇宙

举个栗子

IDEA中Ctrl+N 找一下override

元数据

4.1 @Target

@Target用于指定注解的使用位置,可选的参数值在ElementType枚举类中定义:

  • TYPE:用在类、接口上
  • FIELD:用在成员变量上
  • METHOD:用在方法上
  • PARAMETER:用在参数上
  • CONSTRUCTOR:用在构造方法上
  • LOCAL_VARIABLE:用在局部变量上

例如:

@Target({ElementType.METHOD, ElementType.TYPE})
public @interface MyAnnotation {
}
4.2 @Retention

@Retention用于指定注解的生命周期,可选的参数值在RetentionPolicy枚举类中定义:

  • SOURCE:注解只存在于源代码中,编译后的字节码文件中不存在
  • CLASS:注解存在于源代码和字节码文件中,运行时内存中不存在(默认值)
  • RUNTIME:注解存在于源代码、字节码文件和运行时内存中,可以通过反射获取

例如:

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

5. 注解解析

注解解析是通过反射技术来获取注解中的数据。AnnotatedElement接口定义了解析注解的方法,Class、Method、Field等类都实现了该接口。

5.1 获取注解数据的原理
boolean isAnnotationPresent(Class<Annotation> annotationClass);
T getAnnotation(Class<T> annotationClass);

例如,解析Book注解:

public class TestBookStore {
    public static void main(String[] args) throws NoSuchMethodException {
        Class<BookStore> bookStoreClass = BookStore.class;
        Method method = bookStoreClass.getMethod("buy");

        if (method.isAnnotationPresent(Book.class)) {
            Book bookAnno = method.getAnnotation(Book.class);
            System.out.println("书名:" + bookAnno.name());
            System.out.println("价格:" + bookAnno.price());
            System.out.println("作者:" + Arrays.toString(bookAnno.author()));
        }
    }
}

6. 综合案例:模拟Junit测试

6.1 需求

模拟Junit测试的@Test注解,实现一个自定义注解@MyTest,并在目标类中使用该注解标记测试方法,最后通过反射调用所有带有@MyTest注解的方法。

6.2 实现
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}

public class TestAnnotationParse {
    @MyTest
    public void method1() {
        System.out.println("我是方法1");
    }

    @MyTest
    public void method3() {
        System.out.println("我是方法3");
    }

    public void method2() {
        System.out.println("我是方法2");
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<TestAnnotationParse> testClass = TestAnnotationParse.class;
        Method[] methods = testClass.getMethods();

        for (Method method : methods) {
            if (method.isAnnotationPresent(MyTest.class)) {
                method.invoke(testClass.newInstance());
            }
        }
    }
}

7. 总结

Java注解是一个强大的工具,它允许开发者在代码中添加元数据,并通过反射技术在运行时获取这些元数据。通过自定义注解,开发者可以实现许多高级功能,如编译检查、代码分析和生成文档。本文详细介绍了注解的基本概念、自定义注解的创建、注解的使用以及注解的解析,并通过一个综合案例展示了注解的实际应用。希望本文能帮助读者更好地理解和使用Java注解。

参考文献

  • Java Annotation Tutorial
  • Java Annotations
  • Java Reflection

posted on 2024-12-05 13:28  周政然  阅读(260)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3