Java高级特性:单元测试、反射、注解、动态代理

Java作为一种强大的面向对象编程语言,提供了多种高级特性来提升开发效率和灵活性。这些特性包括单元测试、反射、注解和动态代理,它们在现代Java开发中扮演着重要角色。


1. 单元测试

单元测试是软件开发中的关键实践,用于**验证代码的单个单元(如方法或类)**是否按预期工作。在Java中,JUnit是最流行的单元测试框架,它简化了测试编写和执行过程。

  • 为什么重要:单元测试能及早发现错误,提高代码质量,并支持重构。测试覆盖率可以用数学公式表示。
  • 核心元素 :JUnit使用注解(如@Test)标记测试方法,并提供断言方法(如assertEquals)来验证结果。
  • 示例代码:以下是一个简单的JUnit测试示例,测试一个计算器类的加法方法。
java 复制代码
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class CalculatorTest {

    @Test
    public void testAdd() {
        Calculator calculator = new Calculator();
        int result = calculator.add(2, 3);
        assertEquals(5, result); // 验证结果是否等于5
    }
}

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

在这个示例中:

  • @Test 注解标记了测试方法。
  • assertEquals 用于比较预期值和实际值。
  • 运行测试时,如果add方法返回5,测试通过;否则失败。

示例

• 某个系统,有多个业务方法,请使用Junit单元测试框架,编写测试代码,完成对这些方法的正确性测试。

具体步骤

① 将Junit框架的jar包导入到项目中 (注意:IDEA集成了Junit框架,不需要我们自己手工导入了)

② 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法**(必须:公共、无参、无返回值)**

③ 测试方法上必须声明@Test注解 ,然后在测试方法中,编写代码调用被测试的业务方法进行测试;

④ 开始测试:选中测试方法,右键选择"JUnit运行",如果测试通过则是绿色;如果测试失败,则是红色


2. 反射

反射(Reflection)是Java的一种机制,允许程序在运行时检查和修改类、对象、方法和字段的结构。它通过java.lang.reflect包实现,常用于框架开发(如Spring)和动态加载类。

(一) 认识反射(Reflection)

反射是指在程序运行时,能够动态地获取、检查和操作类、对象、方法、属性等程序自身结构的能力。它允许程序在运行时"观察"和修改自身的状态和行为,而无需在编译时知道具体的类型信息。简单来说,反射就是程序在运行时"认识自己"的能力。

核心思想是:通过类的元数据(Metadata),在运行时动态地获取类的信息,并对其成员进行操作。

理解:反射就是加载类并允许以编程的方式解剖类中的各种成分(成员变量,方法,构造器等)

  • 为什么重要:反射提供了动态性,例如在未知类名时创建对象或调用方法。但需注意性能开销,因为反射操作通常比直接调用慢(时间复杂度约为 O(n),其中 n 是反射调用的复杂度)。
  • 核心类 :主要类包括Class(表示类)、Method(表示方法)、Field(表示字段)等。
  • 示例代码:以下代码展示如何使用反射获取类的信息并调用方法。
java 复制代码
import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        // 获取String类的Class对象
        Class<?> stringClass = String.class;
        
        // 获取toUpperCase方法并调用
        Method toUpperCaseMethod = stringClass.getMethod("toUpperCase");
        String str = "hello";
        String result = (String) toUpperCaseMethod.invoke(str);
        System.out.println(result); // 输出 "HELLO"
    }
}

在这个示例中:

  • Class.forName()类名.class 获取Class对象。
  • getMethod 获取方法,invoke 调用方法。
  • 反射常用于动态代理和注解处理。

(二) 获取类及其成分并操作

(1) 获取类 (Class)

通常可以通过以下方式获取一个类的 Clas 对象:

  • 使用 类名.class (如:String.class)
  • 通过对象调用 getClass() 方法 (如:"hello".getClass())
  • 使用 Class.forName("全限定类名") (如:Class.forName("java.lang.String"))
java 复制代码
// 示例:获取String类的Class对象
Class<?> stringClass = Class.forName("java.lang.String");
(2) 获取类中的成分

一旦获取了 Class 对象,就可以进一步获取其内部的成员:

  • 字段 (Field)

    java 复制代码
    // 获取所有公共字段
    Field[] publicFields = stringClass.getFields();
    // 获取所有字段(包括私有)
    Field[] allFields = stringClass.getDeclaredFields();
    // 获取特定字段
    Field specificField = stringClass.getDeclaredField("value");
  • 方法 (Method)

    java 复制代码
    // 获取所有公共方法
    Method[] publicMethods = stringClass.getMethods();
    // 获取所有方法(包括私有)
    Method[] allMethods = stringClass.getDeclaredMethods();
    // 获取特定方法
    Method charAtMethod = stringClass.getMethod("charAt", int.class);
  • 构造器 (Constructor)

    java 复制代码
    // 获取所有公共构造器
    Constructor<?>[] publicConstructors = stringClass.getConstructors();
    // 获取所有构造器
    Constructor<?>[] allConstructors = stringClass.getDeclaredConstructors();
    // 获取特定构造器(如String的byte[]构造器)
    Constructor<?> byteConstructor = stringClass.getConstructor(byte[].class);
(3) 操作类成分

获取到这些成员后,可以对其进行操作:

  • 创建对象实例

    java 复制代码
    // 使用无参构造器创建String实例
    Object strInstance = stringClass.newInstance();
    // 使用带参构造器
    Constructor<?> constructor = stringClass.getConstructor(byte[].class);
    String strFromBytes = (String) constructor.newInstance(new byte[]{65, 66, 67});
  • 调用方法

    java 复制代码
    // 调用charAt方法
    Method charAt = stringClass.getMethod("charAt", int.class);
    char result = (char) charAt.invoke("ABC", 1); // 返回 'B'
  • 访问/修改字段值

    java 复制代码
    // 获取并修改私有字段(需要设置可访问性)
    Field valueField = stringClass.getDeclaredField("value");
    valueField.setAccessible(true); // 突破封装访问限制
    char[] oldValue = (char[]) valueField.get("Hello");
    valueField.set("Hello", new char[]{'W', 'o', 'r', 'l', 'd'});
  • 注解处理

    java 复制代码
    // 获取类上的注解
    Annotation[] annotations = stringClass.getAnnotations();

具体示例
java 复制代码
package demo1reflect;

import lombok.Data;

@Data
public class Dog {
    private String name;
    private int age;
    private String hobby;

    private Dog() {
    }

    public Dog(String name) {
        this.name = name;
    }
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private void eat() {
        System.out.println("吃吃吃吃吃");
    }

    public String eat(String food){
        System.out.println("吃吃吃吃吃:" + food);
        return "yammy";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getHobby() {
        return hobby;
    }

    public void setHobby(String hobby) {
        this.hobby = hobby;
    }
}
java 复制代码
package demo1reflect;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Objects;

public class ReflectDome2 {
    //1.获取Class对象
    @Test
    public void getClassInfo(){
        Class c1 = Dog.class;
        System.out.println(c1.getClasses());//类名的全类名
        System.out.println(c1.getSimpleName());//类名
    }
    //2.获取类的构造方法对象并对其进行操作
    @Test
    public void getConstructorInfo() throws Exception {
        //获取类的构造方法对象
        //1、获取Class对象
        Class c1 = Dog.class;
        //2、获取构造方法对象
        Constructor[] constructor = c1.getDeclaredConstructors();
        for (Constructor c : constructor) {
            System.out.println(c.getName() + "(" + c.getParameterCount() + ")");
        }
        //3. 获取单个构造方法对象
        Constructor c2 = c1.getDeclaredConstructor();//获取无参构造方法对象
        System.out.println(c2.getName() + "(" + c2.getParameterCount() + ")");
        Constructor c3 = c1.getDeclaredConstructor(String.class, int.class);//获取有参构造方法对象
        System.out.println(c3.getName() + "(" + c3.getParameterCount() + ")");
        //4. 获取的作用:创建对象
        //创建无参对象:私有的-暴力反射!
        c2.setAccessible(true);//暴力反射,绕过访问权限修饰符
        Dog d1 = (Dog) c2.newInstance();
        System.out.println(d1);
        //创建有参对象
        Dog d2 = (Dog) c3.newInstance("小花", 5);
        System.out.println(d2);
    }

    //3. 获取类的成员变量对象并对其进行操作
    @Test
    public void test03() throws Exception {
        //1. 获取类对象
        Class c1 = Dog.class;
        //2. 获取成员变量对象
        Field[] fields= c1.getDeclaredFields();
        for (Field field : fields){
            System.out.println(field.getName() + "(" + field.getType().getName() + ")");
        }
        //3.获取单个成员变量对象
        Field field = c1.getDeclaredField("hobby");
        System.out.println(field.getName() + "(" + field.getType().getName() + ")");
        Field field1 = c1.getDeclaredField("age");
        System.out.println(field1.getName() + "(" + field1.getType().getName() + ")");
        //4. 获取类的成员方法对象的目的依然是取值和设置值
        Dog d = new Dog("旺财", 5);
        field.setAccessible( true);//暴力反射,hobby属性是私有的,所以需要设置
        field.set(d, "看家");
        System.out.println(d);

        String hobby = (String) field.get(d);//强转的原因: get方法返回的是Object类型
        System.out.println(hobby);
    }

    //4.获取类的成员方法对象并对其进行操作
    @Test
    public void test4() throws Exception {
        //1.获取类本身
        Class c1 = Dog.class;
        //2.获取方法对象
        Method[] methods = c1.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName() + "(" + method.getParameterTypes() + ")");
        }
        //3.获取单个方法对象
        Method m = c1.getDeclaredMethod("eat");//获取无参数eat方法对象
        System.out.println(m.getName() + "(" + m.getParameterTypes() + ")");
        Method m2 = c1.getDeclaredMethod("eat", String.class);//获取有参数eat方法对象
        System.out.println(m2.getName() + "(" + m2.getParameterTypes() + ")");
        //4.调用方法对象的作用依然是执行
        Dog d1 = new Dog("金毛",3);
        m.setAccessible( true);
        m.invoke(d1);//调用无参数方法对象,相当于d1.eat();
        Object obj = m2.invoke(d1,"骨头");//调用有参数方法对象,相当于d1.eat("骨头");
        System.out.println(obj);
    }
}

(三) 反射的作用和应用场景

基本作用:可以得到一个类的全部成分然后操作;可以破坏封装性;可以绕过泛型的约束!

最重要的用途:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能。

1、作用
  • 动态性:程序可以在运行时动态加载类、创建对象、调用方法,提高灵活性。
  • 框架开发:是各种框架(如Spring、Hibernate)实现依赖注入、AOP等高级特性的基础。
  • 通用代码:编写可处理任意类型的通用工具或库(如序列化/反序列化库)。
  • 测试工具:单元测试框架(如JUnit)利用反射查找并执行测试方法。
2、应用场景
  • IoC (控制反转) / DI (依赖注入) 容器 框架通过反射扫描类路径,读取注解(如 @Component, @Autowired),动态创建Bean并注入依赖。

    java 复制代码
    // 伪代码:根据配置创建实例
    Class<?> beanClass = Class.forName(beanClassName);
    Object beanInstance = beanClass.newInstance();
    // 查找并注入依赖字段
    for (Field field : beanClass.getDeclaredFields()) {
        if (field.isAnnotationPresent(Autowired.class)) {
            field.setAccessible(true);
            Object dependency = ...; // 获取依赖对象
            field.set(beanInstance, dependency);
        }
    }
  • ORM (对象关系映射) 框架 如Hibernate,通过反射获取实体类的字段信息,动态生成SQL语句,并将查询结果映射回对象。

    java 复制代码
    // 伪代码:将结果集映射到对象
    ResultSet rs = ...;
    User user = new User();
    for (Field field : User.class.getDeclaredFields()) {
        field.setAccessible(true);
        field.set(user, rs.getObject(field.getName()));
    }
  • 动态代理 (Proxy) 在运行时创建实现特定接口的代理类,用于实现AOP(面向切面编程),如日志记录、事务管理。

    java 复制代码
    // Java动态代理示例
    InvocationHandler handler = (proxy, method, args) -> {
        System.out.println("调用方法: " + method.getName());
        return method.invoke(target, args);
    };
    MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
            MyInterface.class.getClassLoader(),
            new Class[]{MyInterface.class},
            handler);
  • 配置文件驱动的对象创建 根据配置文件中的类名动态创建对象并调用方法。

    java 复制代码
    // 读取配置文件
    String className = config.getProperty("processor.class");
    String methodName = config.getProperty("processor.method");
    // 反射创建并调用
    Class<?> processorClass = Class.forName(className);
    Object processor = processorClass.newInstance();
    Method method = processorClass.getMethod(methodName);
    method.invoke(processor);
插件系统 允许程序在运行时加载并执行未知的插件模块。
java 复制代码
// 加载插件JAR,获取入口类并执行
URLClassLoader loader = new URLClassLoader(new URL[]{new File("plugin.jar").toURI().toURL()});
Class<?> pluginClass = loader.loadClass("com.example.Plugin");
PluginInterface plugin = (PluginInterface) pluginClass.newInstance();
plugin.execute();
绕过泛型类型约束

在 Java 中,反射(Reflection) 确实可以绕过泛型(Generics)的类型约束,这主要是由于 Java 泛型的实现机制------类型擦除(Type Erasure)


一、类型擦除机制

Java 泛型在编译期进行类型检查,但在运行时会将泛型类型信息擦除,替换为原始类型(通常是 Object 或边界类型)。例如:

java 复制代码
List<String> list = new ArrayList<>();

编译后,List<String> 会被擦除为 List<Object>(实际是原始类型 List)。因此运行时无法直接获取泛型的具体类型参数(如 String)。


二、反射绕过泛型约束的原理

由于类型擦除,运行时集合中存储的实际是 Object 类型。通过反射操作集合时,可以绕过编译期的泛型检查,向集合插入非泛型指定类型的对象。例如:

java 复制代码
List<String> stringList = new ArrayList<>();
stringList.add("Hello");

// 反射获取 add 方法
Method addMethod = ArrayList.class.getMethod("add", Object.class);
addMethod.invoke(stringList, 123); // 插入 Integer 类型

此时,stringList 中既包含 String 类型,也包含 Integer 类型,绕过了泛型约束。


三、具体操作步骤
  1. 获取集合的 Class 对象

    通过 getClass() 方法获取运行时类信息:

    java 复制代码
    Class<?> listClass = stringList.getClass();
  2. 获取目标方法

    泛型擦除后,add 方法的参数类型实际为 Object

    java 复制代码
    Method addMethod = listClass.getMethod("add", Object.class);
  3. 通过反射调用方法

    直接插入非泛型指定类型的对象:

    java 复制代码
    addMethod.invoke(stringList, 123); // 插入整数

四、风险与问题
  1. 运行时异常

    后续按泛型类型操作集合时可能抛出 ClassCastException

    java 复制代码
    for (String s : stringList) {
        // 遍历到 Integer 时会抛出异常
    }
  2. 破坏类型安全

    泛型的核心目的是保证类型安全,反射绕过约束会破坏这一机制。


五、实际应用场景

虽然不推荐,但在某些特殊场景下可能有用,例如:

  • 框架开发:如 JSON 序列化库需动态处理不同类型。
  • 兼容旧代码:适配遗留的非泛型代码。

总结

反射绕过泛型是 Java 类型擦除机制的副作用。尽管技术上可行,但会破坏类型安全,需谨慎使用。正确做法是:

  • 优先使用泛型保证类型安全;
  • 如需动态类型,改用 List<Object> 或设计多态结构。
3、注意事项
  • 性能开销:反射操作通常比直接代码调用慢,因为涉及动态解析和类型检查。
  • 安全限制:可能突破封装性(访问私有成员),需谨慎使用并注意安全管理器设置。
  • 代码可读性:过度使用反射可能导致代码难以理解和维护。

总之,反射是一个强大的工具,它为程序提供了高度的灵活性和动态能力,尤其在框架开发和需要处理未知类型的场景中不可或缺。但需权衡其带来的性能损失和复杂性,在适当场景下使用。


3. 注解

(一) 注解概述

注解(Annotation)是 Java 提供的一种元数据 机制,用于为程序元素(如类、方法、变量、参数等)添加额外的说明信息。这些信息本身不会直接影响程序的逻辑,但可以被编译器、开发工具或其他程序读取并利用。

  • 核心作用: 提供关于程序代码的元数据描述。
  • 编译时处理: 编译器可以利用注解信息进行编译检查(如@Override)或生成辅助代码/文件(如APT)。
  • 运行时处理: 通过反射机制读取注解信息,实现运行时动态行为(如Spring框架的依赖注入)。
  • 文档化: @Deprecated等注解有助于生成文档。
  • 为什么重要 :注解简化了配置和元数据管理,例如在JUnit中标记测试方法,或在Spring中依赖注入。常见的内置注解包括@Override@Deprecated@SuppressWarnings
  • 自定义注解 :开发者可以定义自己的注解,通过@interface关键字。
  • 示例代码:以下展示如何定义和使用一个自定义注解。
java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 定义一个自定义注解,用于标记重要方法
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时保留
@Target(ElementType.METHOD)         // 注解只能用于方法
public @interface ImportantMethod {
}

// 使用注解的类
public class AnnotationExample {
    @ImportantMethod
    public void criticalOperation() {
        System.out.println("执行重要操作");
    }

    public static void main(String[] args) {
        AnnotationExample example = new AnnotationExample();
        example.criticalOperation(); // 输出 "执行重要操作"
    }
}

在这个示例中:

  • @ImportantMethod 是一个自定义注解。
  • @Retention 指定注解保留到运行时,以便反射处理。
  • 注解可以被反射API读取,用于动态行为。

(二) 元注解

元注解(Meta-Annotation)是用于注解其他注解 的注解。它们定义了被注解的注解的行为和适用范围。Java 提供了几个核心的元注解(位于java.lang.annotation包):

  1. @Retention : 指定被注解的注解的保留策略 (即生命周期)。
    • RetentionPolicy.SOURCE: 仅存在于源代码中,编译时被丢弃(如@Override, @SuppressWarnings)。
    • RetentionPolicy.CLASS: 保留在 class 文件中,但不会被加载到 JVM(默认策略,较少用)。
    • RetentionPolicy.RUNTIME: 保留在 class 文件中,并被加载到 JVM,可在运行时通过反射读取(框架常用)。[即一直保留到运行阶段!]
  2. @Target : 指定被注解的注解可以应用于哪些程序元素
    • 常用元素类型:ElementType.TYPE(类/接口/枚举), ElementType.FIELD(字段), ElementType.METHOD(方法), ElementType.PARAMETER(参数), ElementType.CONSTRUCTOR(构造器), ElementType.LOCAL_VARIABLE(局部变量), ElementType.ANNOTATION_TYPE(注解类型), ElementType.PACKAGE(包), ElementType.TYPE_PARAMETER(类型参数-JDK8), ElementType.TYPE_USE(类型使用-JDK8)。
  3. @Documented: 指明被注解的注解应该被包含在 Javadoc 文档中。
  4. @Inherited : 指明被注解的注解具有继承性。如果父类使用了该注解,子类默认也会继承这个注解(仅对类注解有效)。
  5. @Repeatable (JDK8+): 指明被注解的注解可以在同一个元素上重复使用

(三) 自定义注解

开发者可以根据需要定义自己的注解类型。定义格式类似于接口,但使用 @interface 关键字:

java 复制代码
@Retention(RetentionPolicy.RUNTIME) // 元注解:运行时保留
@Target(ElementType.METHOD)         // 元注解:只能注解方法
public @interface MyCustomAnnotation {
    // 定义注解的属性(成员变量)
    String value() default "defaultValue"; // 带默认值的属性
    int priority() default 0;              // 带默认值的属性
    String[] tags() default {};            // 数组类型属性
}
  • 注解的属性声明类似于接口的方法,但没有参数,可以有默认值 (default ...)。
  • 属性的类型有限制:基本类型、StringClass、枚举类型、注解类型,以及这些类型的数组。
  • 使用注解时,通过 @AnnotationName(propertyName=value) 格式指定属性值。如果注解只有一个名为 value 的属性且需要赋值时,可以省略 value= 直接写值。如果所有属性都有默认值,可以不指定任何属性值 (@AnnotationName)。

(四) 注解的解析

注解本身只是元数据,其功能需要通过解析器来实现。解析方式主要有两种:

  1. 编译时解析 (APT - Annotation Processing Tool):
    • 在编译阶段,由特定的注解处理器 (javax.annotation.processing.Processor) 处理源代码或 class 文件中的注解。
    • 处理器可以读取注解信息,生成新的源代码、资源文件或编译错误/警告
    • 处理过程在 Javac 编译时自动触发。处理器生成的代码会参与后续的编译。
    • 特点: 不修改原有代码,生成新代码。常用于生成样板代码(如 Builder 模式)、验证约束(如检查是否实现了接口)、生成配置文件等。例如 Lombok 库的核心原理。
  2. 运行时解析 (Reflection):
    • 在程序运行时,通过 Java 的反射 API (java.lang.reflect) 获取注解信息。
    • 核心类:AnnotatedElement 接口(Class, Method, Field, Constructor 等都实现了它),提供 getAnnotation(Class), isAnnotationPresent(Class), getAnnotations() 等方法。
    • 特点: 需要注解保留策略为 RUNTIME。常用于框架的动态行为,如 Spring 的依赖注入 (@Autowired)、事务管理 (@Transactional)、Web 路由 (@RequestMapping) 等。

AnnotatedElement 接口解析

AnnotatedElement 是 Java 反射 API 中的核心接口,用于处理注解(Annotation)。它定义了一套标准方法来访问和操作类、方法、字段等元素上的注解。许多核心类如 ClassMethodFieldConstructor 都实现了这个接口,使得开发者可以统一地获取和检查注解信息。

1. AnnotatedElement 接口的作用

AnnotatedElement 接口的主要目的是提供一种通用的方式来访问元素的注解。在 Java 中,注解用于添加元数据到代码中,例如标记方法为测试用例或指定字段的序列化规则。通过实现这个接口,任何支持注解的元素(如类、方法等)都能通过反射机制被查询和处理。这简化了注解的解析过程,无需为每种元素类型编写重复代码。

2. 核心方法解析

AnnotatedElement 接口定义了多个方法,用于获取和检查注解。以下是主要方法的详细说明:

  • getAnnotation(Class<T> annotationClass)

    这个方法用于获取指定类型的注解实例。参数 annotationClass 是注解的 Class 对象。如果元素上存在该注解,则返回注解实例;否则返回 null。例如,检查一个方法是否使用了 @Override 注解:

    java 复制代码
    Method method = ... // 获取方法的实例
    Override overrideAnnotation = method.getAnnotation(Override.class);
    if (overrideAnnotation != null) {
        System.out.println("该方法被 @Override 注解标记");
    }
  • isAnnotationPresent(Class<? extends Annotation> annotationClass)

    这个方法检查元素上是否存在指定类型的注解。它返回一个布尔值:true 表示注解存在,false 表示不存在。这个方法比 getAnnotation 更轻量级,适合快速检查:

    java 复制代码
    Field field = ... // 获取字段的实例
    if (field.isAnnotationPresent(Deprecated.class)) {
        System.out.println("该字段已废弃,请勿使用");
    }
  • getAnnotations()

    这个方法返回元素上的所有注解(包括继承的注解,如果有)。返回值是一个 Annotation[] 数组。如果元素没有注解,则返回空数组。这适用于需要遍历所有注解的场景:

    java 复制代码
    Class<?> clazz = ... // 获取类的实例
    Annotation[] annotations = clazz.getAnnotations();
    for (Annotation ann : annotations) {
        System.out.println("注解类型: " + ann.annotationType());
    }
  • 其他相关方法

    • getDeclaredAnnotations():返回直接声明在元素上的注解(不包括继承的注解)。
    • getAnnotationsByType(Class<T> annotationClass):用于获取重复注解(Java 8 引入),返回指定类型的所有注解实例。
    • getDeclaredAnnotationsByType(Class<T> annotationClass):类似,但只包括直接声明的注解。
3. 实现类和实际应用

AnnotatedElement 接口由多个 Java 核心类实现,包括:

  • Class:用于访问类或接口上的注解。
  • Method:用于访问方法上的注解。
  • Field:用于访问字段上的注解。
  • Constructor:用于访问构造器上的注解。
  • Package:用于访问包上的注解。

这些实现使得注解解析具有一致性。例如,在自定义注解处理器中,你可以通过反射遍历一个类的所有元素:

java 复制代码
import java.lang.annotation.*;
import java.lang.reflect.*;

// 定义一个自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface MyTest {
    String value();
    double height() default 180.2;
    String[] address();
}


// 使用注解的示例类
@MyTest(value = "wjk", address = "beijing")
public class Demo {
    @MyTest(value = "wy",  address = {"beijing","boston"})
    public void go(){
    }
}


// 解析注解的主类
public class AnnotationDome2 {
    //解析注解
    @Test
    public void praseClass() throws Exception {
        //获取类对象
        Class c = Demo.class;
        //判断类对象上是否有MyTest注解
        if (c.isAnnotationPresent(MyTest.class)) {
            //获取类对象上的MyTest注解
            MyTest myTest = (MyTest) c.getDeclaredAnnotation(MyTest.class);
            //获取注解的属性值
            System.out.println(myTest.value());
            System.out.println(myTest.height());
            System.out.println(Arrays.toString(myTest.address()));
        }
    }
    @Test
    public void praseMethod() throws  Exception{
        Class c = Demo.class;//获取Demo类对象
        //获取Demo类对象中的方法对象
        Method method = c.getMethod("go");
        //判断方法上是否有MyTest注解
        if (method.isAnnotationPresent(MyTest.class)){
            //获取方法上的MyTest注解
            MyTest myTest = method.getDeclaredAnnotation(MyTest.class);
            //获取注解的属性值
            System.out.println(myTest.value());
            System.out.println(myTest.height());
            System.out.println(Arrays.toString(myTest.address()));
        }
    }
}

运行上述代码,输出可能为:

java 复制代码
wjk
180.2
[beijing]
wy
180.2
[beijing, boston]
4. 注意事项和最佳实践
  • 性能考虑:反射操作(如获取注解)可能较慢,应在必要时使用,例如在框架初始化时缓存结果。
  • 注解保留策略 :确保注解的 @Retention 设置为 RetentionPolicy.RUNTIME,否则反射无法访问。
  • 继承性 :默认情况下,注解不会被继承;如果需要,使用 @Inherited 元注解。
  • 错误处理 :调用这些方法时,如果元素不可访问(如私有字段),可能抛出 SecurityException,需适当处理。

(五) 注解的作用与应用场景

  • 编译检查: @Override, @FunctionalInterface 帮助编译器检查代码的正确性。
  • 代码生成: APT 用于自动生成代码,减少重复劳动(如 Dagger, Lombok, MapStruct)。
  • 框架配置: 简化配置,代替 XML 配置文件(如 Spring Boot 的 @SpringBootApplication, @RestController, @Bean)。
  • 运行时行为控制: 框架根据运行时解析的注解实现 AOP、事务、权限控制等(如 Spring, Hibernate)。
  • 文档生成: @Deprecated, @see, @param, @return 等用于 Javadoc。
  • 代码分析: 静态代码分析工具(如 FindBugs, SonarQube)可以利用注解信息。
  • 测试: JUnit 的 @Test, @Before, @After 等标记测试方法。
  • 序列化/反序列化: Jackson 的 @JsonProperty, @JsonIgnore 控制 JSON 处理。
  • 依赖注入: Guice, Spring 的 @Inject, @Autowired
  • 标记接口替代: 比标记接口更灵活(如 @Entity 代替 Entity 接口)。
  • 约束校验: Bean Validation (JSR 303/349/380) 的 @NotNull, @Size, @Pattern 等。

总结: Java 注解是一种强大的元数据机制,通过元注解定义其行为,允许开发者创建自定义注解。注解的生命周期(@Retention)和适用范围(@Target)由元注解控制。注解的功能依赖于编译时(APT)或运行时(反射)的解析器。


4. 动态代理

动态代理(Dynamic Proxy)是Java反射机制的一部分,允许在运行时创建代理对象,用于拦截方法调用。它常用于AOP(面向切面编程)和日志记录等场景。

  • 为什么重要:动态代理实现了间接调用,增强了代码的灵活性和可维护性。例如,在代理模式中,时间复杂度可能为 O(1) 如果代理直接委托,但添加逻辑会增加开销。
  • 核心类java.lang.reflect.Proxy 类用于创建代理对象,InvocationHandler 接口处理调用。
  • 示例代码:以下代码展示如何创建一个动态代理来记录方法调用。
java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


// 定义接口
public interface StarServer {
    void sing(String name);
    String dance();
}

// 实现接口的类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Star implements StarServer{
    private String name;
    @Override
    public void sing(String name) {
        System.out.println(this.name+"正在唱歌"+name);
    }
    @Override
    public String dance() {
        System.out.println(this.name+"正在跳舞...");
        return "Thanks";
    }
}


// InvocationHandler实现-代理工厂:创建代理并实现方法
//代理工具类:负责创建代理
public class ProxyUtil {
    //创建一个Proxy对象,代理
    public static StarServer createProxy(Star s){
        /**
         * 参数一:用于执行用哪个类加载器去加载生成的代理类
         * 参数二:用于指定代理类需要实现的接口:明星类实现了接口,所以代理类必须实现这个接口
         * 参数三:用于指定代理类需要如何去代理(代理要做的事情)
         */
        StarServer proxy = (StarServer) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                //new Class[]{StarServer.class},
                s.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //声明代理对象要干的事情
                        //参数一:代理对象
                        //参数二:正在被代理的方法
                        //参数三:被代理的方法的参数
                        String name = method.getName();
                        if (name.equals("sing")){
                            System.out.println("准备话筒...");
                        }else if (name.equals("dance")){
                            System.out.println("准备场地...");
                        }
                        //真正干活,找Star对象来执行被代理的行为:method.invoke(star,args);
                        Object result = method.invoke(s, args);
                        return result;
                    }
                });
        return proxy;
    }
}

//测试
public class Test {
    static void main(String[] args) {
        //创建一个Star对象
        Star star = new Star("yyqx");
        //为star创建一个代理对象
        StarServer proxy =ProxyUtil.createProxy(star);
        proxy.sing("Relax");
        System.out.println(proxy.dance());
    }
}

综合应用

这些技术常结合使用。例如:

  • 在单元测试中,使用注解标记测试方法。
  • 反射用于读取注解信息或动态创建对象。
  • 动态代理在测试中模拟对象行为(如Mockito框架)。 整体上,它们提升了Java的灵活性和可扩展性。

总结:单元测试确保代码质量,反射提供运行时自省能力,注解简化元数据管理,动态代理实现方法拦截。掌握这些高级特性是Java开发者的关键技能,能显著提高开发效率和代码健壮性。实践中,建议优先使用框架(如JUnit、Spring)来简化实现。

Javase完结,后面会接着写Javaweb相关。

相关推荐
雾林小妖2 小时前
springboot实现跨服务调用/springboot调用另一台机器上的服务
java·spring boot·后端
百***58142 小时前
Windows操作系统部署Tomcat详细讲解
java·windows·tomcat
Boop_wu2 小时前
[Java EE] 多线程 -- 初阶(3)
java·开发语言
稻香味秋天2 小时前
单元测试指南
数据库·sqlserver·单元测试
Felix_XXXXL2 小时前
MySQL----case的用法
java·后端
咕白m6253 小时前
基于Java 实现 PPT 到 PDF 的高效转换
java
七夜zippoe3 小时前
Java并发编程基石:深入理解JMM(Java内存模型)与Happens-Before规则
java·开发语言·spring·jmm·happens-before
YDS8293 小时前
苍穹外卖 —— Spring Task和WebSocket的运用以及订单统一处理、订单的提醒和催单功能的实现
java·spring boot·后端·websocket·spring
速易达网络3 小时前
C语言常见推理题
java·c语言·算法