当前位置: 首页 > news >正文

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

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


1. 单元测试

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

  • 为什么重要:单元测试能及早发现错误,提高代码质量,并支持重构。测试覆盖率可以用数学公式表示。
  • 核心元素:JUnit使用注解(如@Test)标记测试方法,并提供断言方法(如assertEquals)来验证结果。
  • 示例代码:以下是一个简单的JUnit测试示例,测试一个计算器类的加法方法。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;public class CalculatorTest {@Testpublic 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(表示字段)等。
  • 示例代码:以下代码展示如何使用反射获取类的信息并调用方法。
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"))
// 示例:获取String类的Class对象
Class<?> stringClass = Class.forName("java.lang.String");

(2) 获取类中的成分

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

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

  • 方法 (Method)
    // 获取所有公共方法
    Method[] publicMethods = stringClass.getMethods();
    // 获取所有方法(包括私有)
    Method[] allMethods = stringClass.getDeclaredMethods();
    // 获取特定方法
    Method charAtMethod = stringClass.getMethod("charAt", int.class);
    

  • 构造器 (Constructor)
    // 获取所有公共构造器
    Constructor<?>[] publicConstructors = stringClass.getConstructors();
    // 获取所有构造器
    Constructor<?>[] allConstructors = stringClass.getDeclaredConstructors();
    // 获取特定构造器(如String的byte[]构造器)
    Constructor<?> byteConstructor = stringClass.getConstructor(byte[].class);
    

(3) 操作类成分

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

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

  • 调用方法
    // 调用charAt方法
    Method charAt = stringClass.getMethod("charAt", int.class);
    char result = (char) charAt.invoke("ABC", 1); // 返回 'B'
    

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

  • 注解处理
    // 获取类上的注解
    Annotation[] annotations = stringClass.getAnnotations();
    

具体示例
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;}
}
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对象@Testpublic void getClassInfo(){Class c1 = Dog.class;System.out.println(c1.getClasses());//类名的全类名System.out.println(c1.getSimpleName());//类名}//2.获取类的构造方法对象并对其进行操作@Testpublic 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. 获取类的成员变量对象并对其进行操作@Testpublic 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.获取类的成员方法对象并对其进行操作@Testpublic 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并注入依赖。

    // 伪代码:根据配置创建实例
    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语句,并将查询结果映射回对象。

    // 伪代码:将结果集映射到对象
    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动态代理示例
    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);
    

  • 配置文件驱动的对象创建 根据配置文件中的类名动态创建对象并调用方法。

    // 读取配置文件
    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);
    

  • 插件系统 允许程序在运行时加载并执行未知的插件模块。
// 加载插件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 或边界类型)。例如:

List<String> list = new ArrayList<>();

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


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

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

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() 方法获取运行时类信息:

    Class<?> listClass = stringList.getClass();
    

  2. 获取目标方法
    泛型擦除后,add 方法的参数类型实际为 Object

    Method addMethod = listClass.getMethod("add", Object.class);
    

  3. 通过反射调用方法
    直接插入非泛型指定类型的对象:

    addMethod.invoke(stringList, 123); // 插入整数
    


四、风险与问题
  1. 运行时异常
    后续按泛型类型操作集合时可能抛出 ClassCastException

    for (String s : stringList) {// 遍历到 Integer 时会抛出异常
    }
    

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


五、实际应用场景

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

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

总结

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

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

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


3. 注解

(一) 注解概述

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

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

@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 注解:

    Method method = ... // 获取方法的实例
    Override overrideAnnotation = method.getAnnotation(Override.class);
    if (overrideAnnotation != null) {System.out.println("该方法被 @Override 注解标记");
    }
    

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

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

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

    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:用于访问包上的注解。

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

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 {//解析注解@Testpublic 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()));}}@Testpublic 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()));}}
}

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

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 接口处理调用。
  • 示例代码:以下代码展示如何创建一个动态代理来记录方法调用。
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;@Overridepublic void sing(String name) {System.out.println(this.name+"正在唱歌"+name);}@Overridepublic 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() {@Overridepublic 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相关。

http://www.dtcms.com/a/604937.html

相关文章:

  • python机器学习工程化demo(包含训练模型,预测数据,模型列表,模型详情,删除模型)支持线性回归、逻辑回归、决策树、SVC、随机森林等模型
  • 逻辑回归在个性化推荐中的原理与应用
  • 织梦网站后台怎么登陆郑州知名做网站公司有哪些
  • 免费做网站的软件跨境电商自建站平台
  • 本机oracle连接延时41970 毫秒
  • 不到一块钱的带USB 2.4G收发 SOC芯片,集成2.4G射频 32位MCU
  • Ubuntu 24.04 安装 PostgreSQL
  • 数据科学每日总结--Day18--数据库
  • 【ZeroRange WebRTC】WebRTC 基于 STUN 的 srflx 直连原理与实现
  • neovim等模态编辑器最优雅的输入法解决方案
  • FaceBook叫板OpenAI!开源 Omnilingual ASR:支持1600多种语言的开源多语言语音识别
  • 分享一个MySQL万能备份脚本
  • 大模型数据洞察能力方法调研
  • 32位MCU芯片国产品牌(32系列单片机常用型号有哪些)
  • 网站底部留言代码赤峰建设淘宝网站
  • 方特网站是谁做的照片做视频的网站
  • Java 9 新特性详解
  • Spring boot 3.3.1 官方文档 中文
  • Sora 2——开启 AI 视频创作新时代
  • 异世界网络:BGP联邦的建立
  • PHP客户端调用由Go服务端GRPC接口
  • Java 开发 - 粘包处理器 - 基于消息头 + 消息体
  • dify零基础入门示例
  • 跨语言智能再升级!Multi-LMentry 打造多语理解新基准;Nemotron-Personas-USA重塑虚拟人画像生成
  • 门户网站建设项目书免费拒绝收费网站
  • 研发管理知识库(13)阿里云的DevOps工具介绍
  • WPF 使用UserControl / ContentControl显示子界面
  • Docker 的底层工作原理
  • 互联网门户网站是什么意思网站建设 源美设计
  • 重庆商业网站有哪些产品网站建设方案