您的当前位置:首页正文

@ 注解那点事儿——Java

2024-11-25 来源:个人技术集锦

什么是注解?

简介

Java注解(Annotation)是ava语言中非常重要的一个特性。它是用于为程序元素(类、方法、变量等)添加元数据的一种机制,本质上是一种接口,定义了一组可选属性和默认值,可以表示程序中的元数据信息。通过使用注解,开发者可以为代码添加各种信息和功能,例如标记废弃的方法或类、进行安全检查、提供配置文件读取、记录日志信息等。
注解是非常灵活和强大的特性,它可以帮助程序员更好地组织和管理代码,提高开发效率和代码质量,实现了更高效、更智能的编程方式

应用

原理

Java注解是一种通过在程序中添加元数据的方式来为代码添加额外信息和标记的机制。Java注解本质上是一种接口(只不过它的接口名前面加了一个@符号),定义了注解的元素和属性,可以包含基本数据类型、枚举类型、Class类型、注解类型以及数组等元素,并可以有默认值。
Java注解的实现原理主要基于Java**反射机制**,在编译期间将注解信息编译到class文件中,然后在运行时通过反射机制获取注解信息,根据信息与注解规定进行逻辑处理。

注解的类型

标准注解

  • @Override:指示编译器检查该方法是否正确地覆盖了超类中的方法。
  • @Deprecated:指示已过时的方法或类。
  • @SuppressWarnings:指示编译器忽略特定的编译警告。
  • @SafeVarargs:指示方法不会发出未经检查的泛型数组或可变参数的警告。
  • @FunctionalInterface:指示接口是一个函数式接口,即只有一个抽象方法。

元注解

元注解:注解的注解,就是给你自己定义的注解添加注解

  1. @Repeatable:用于标识注解可以重复使用。
  2. @Retention:指定注释的生命周期。
    取值范围:
    • RetentionPolicy.SOURCE(只在源代码中保留)
    • RetentionPolicy.CLASS(在编译时被保留,在运行时被丢弃)
    • RetentionPolicy.RUNTIME(在编译时被保留,在运行时也会被保留,可以通过反射获取到该注解)。
  3. @Target:指定注释可以放置的元素类型,如类、方法、字段等。
    取值(ElementType)作用
    ElementType.TYPE允许被修饰的注解作用在类、接口和枚举上
    ElementType.FIELD允许作用在属性字段上
    ElementType.METHOD允许作用在方法上
    ElementType.PARAMETER允许作用在方法参数上
    ElementType.CONSTRUCTOR允许作用在构造器上
    ElementType.LOCAL_VARIABLE允许作用在本地局部变量上
    ElementType.ANNOTATION_TYPE允许作用在注解上
    ElementType.PACKAGE允许作用在包上
  4. @Documented:指示注释是否包含在Javadoc中。
  5. @Inherited 表示自动继承注解类型。 如果注解声明中存在 @Inherited 元注解,则注解所修饰类的所有子类都将会继承此注解。

自定义注解

Java自定义注解使用@关键字定义新的Annotation类型,其中自定义注解默认继承了java.lang.annotation.Annotation接口。
在定义Annotation的成员变量时,需要以无参数方法的形式来声明,该方法名和返回值定义了该成员的名字和类型,称为配置参数,类型只能是基本数据类型、String类型、Class类型、枚举类型、注解类型及以上所有类型的数组,并可指定初始值,可以使用default关键字来指定成员变量的初始值。

小案例:在运行时通过反射获取类、字段和方法上的注解信息。

定义注解(该注解定义了两个属性:value和names,其中,value属性有默认值为空字符串,names属性有默认值为空数组)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "";
    String[] names() default {};
}

使用该注解时,可以像下面这样指定属性值:

@MyAnnotation(value = "hello", names = {"Jack", "Lucy"})
public void myMethod() {
    // do something
}

测试下效果,在运行时,通过反射来获取注解的属性值

  public static void main(String[] args) throws NoSuchMethodException {
        Class<?> clazz = MyClassTest.class;
        MyAnnotation methodAnnotation = clazz.getDeclaredMethod("myMethod").getAnnotation(MyAnnotation.class);
        String methodValue = methodAnnotation.value();
        String[] methodNames = methodAnnotation.names();
        System.out.println("methodValue: " + methodValue);
        for (String methodName : methodNames) {
            System.out.println(methodName);
        }
    }

结果如下:

自定义注解案例

案例:扫描包内所有带注解的方法并执行。

定义注解

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

使用注解

public class MyClass {
    @Run
    public void myMethod1() {
        // do something
        System.out.println("我是被@Run注解的方法,我被执行了");
    }

    public void myMethod2() {
        // do something
        System.out.println("我是没有@Run注解的方法,我被执行了");
    }
}

解析注解

编写一个工具类,它可以扫描指定包中所有带有@Run注解的方法。

public class Runner {
    public static void run(String packageName) throws Exception {
        List<Class<?>> classes = getClasses(packageName);
        for (Class<?> clazz : classes) {
            for (Method method : clazz.getDeclaredMethods()) {
                if (method.isAnnotationPresent(Run.class)) {
                    method.invoke(clazz.newInstance());
                }
            }
        }
    }

    private static List<Class<?>> getClasses(String packageName) throws Exception {
       List<Class<?>> classes = new ArrayList<>();
        String path = packageName.replace(".", "/");
        URL url = Thread.currentThread().getContextClassLoader().getResource(path);
        if (url != null) {
            File dir = new File(url.toURI());
            for (File file : dir.listFiles()) {
                String fileName = file.getName();
                if (file.isDirectory()) {
                    classes.addAll(getClasses(packageName + "." + fileName));
                } else if (fileName.endsWith(".class")) {
                    String className = packageName + "." + fileName.substring(0, fileName.length() - 6);
                    Class<?> clazz = Class.forName(className);
                    classes.add(clazz);
                }
            }
        }
        return classes;
    }
}

使用测试

测试代码是否顺利将com.example包内所有带@Run注解的方法执行:

public class Test {
    public static void main(String[] args) throws Exception {
        Runner.run("com.example");
    }
}

结果如下

显示全文