Spring boot框架下的Java 反射

1.Java反射基础

Java 反射机制是一种功能强大的工具,允许程序在运行时 动态地获取和操作类的信息,如类的名称、方法、构造函数和属性。这意味着程序可以在不提前知道类和方法的情况下,动态地执行代码。反射使代码具备"动态性 "和"灵活性",并且是许多框架(如 Spring、Hibernate)的基础。

1.1.反射机制的特点

在没有反射的情况下,Java 程序的所有操作(比如创建对象、调用方法等)都在编译时就已经确定了,编译器已经"写死"了操作对象的具体类和方法。这意味着编译后的字节码中,每个类、方法、属性的调用都是固定不变的。

反射 则打破了这种"编译时确定"的限制。反射代码不会在编译时"知道"自己要操作的具体对象或方法,而是将这些信息延迟到程序运行时 。运行时,Java 虚拟机(JVM)会根据提供的类名、方法名、属性名等**动态地解析和执行代码,**具体包括以下几步:

  1. 加载类 :根据传入的类名(通常是字符串形式),JVM 在运行时去加载这个类,生成对应的 Class 对象。

  2. 解析方法和属性 :通过 Class 对象获取类的结构,包括方法、构造器、属性等。此时 JVM 才会解析和确认需要调用的具体方法或属性。

  3. 执行操作 :通过 Method.invoke() 调用方法,或 Field.set() 修改属性。每个操作都是在运行时动态执行的。

1.2.Java 反射机制的功能

通过反射,Java 程序能够执行以下操作:

  • 获取类的结构:反射机制让程序可以在运行时获取类的完整结构信息,包括类名、方法、构造函数、属性等。

  • 创建对象实例:在运行时,通过反射可以动态创建类的对象实例,而不需要在代码中提前写明对象的具体类型。

  • 调用对象的方法或访问/修改属性值:反射不仅可以创建对象,还可以调用该对象的任何方法、访问或修改其属性,即使这些方法或属性在编写代码时并不知道或无法访问(如私有属性或方法)。


1.3.Java 反射的主要特性

  • 动态性:反射让 Java 程序在运行时决定使用哪个类、调用哪个方法、访问哪个属性,而不是在编译时就固定下来。例如,我们可以在程序运行时根据用户输入动态加载指定的类,并调用其方法。这使得程序可以根据不同情况动态调整行为。

    java 复制代码
    // 在运行时根据类名动态加载类
    Class<?> clazz = Class.forName("com.example.MyClass");
    // 根据类的结构调用方法或创建对象
    Method method = clazz.getMethod("myMethod");
    method.invoke(clazz.newInstance());
  • 灵活性:反射允许在运行时灵活地加载不同的类或方法,甚至可以动态加载外部模块。比如,我们可以在应用程序中使用插件,这些插件可以在不修改主程序代码的情况下被加载、更新或卸载。反射支持程序根据运行时的不同需求进行灵活的调整和扩展。

  • 通用性:反射支持在不修改源代码的情况下访问类的结构,这对于框架和工具的设计特别有帮助。Java 反射机制是许多框架(如 Spring 和 Hibernate)的基础。这些框架通过反射机制自动扫描和装配 Bean(组件)、创建对象、注入依赖等,而开发者不需要关心这些操作的具体实现。反射让代码具备了更高的复用性和可扩展性。

2.反射的核心类及其常用方法

在 Java 中,反射主要依靠一些核心类来实现。这些类主要位于 java.lang.reflect 包中,以下是最常用的核心类:

  • Class :代表一个类。通过 Class 对象可以获取类的所有信息(构造方法、字段、方法等)。
  • Method :代表类的方法。通过 Method 对象可以获取方法的名称、参数类型、返回类型以及访问权限等,并且可以在运行时调用方法。
  • Field :代表类的字段(属性)。通过 Field 对象可以获取属性名称、类型、修饰符等,并可以在运行时对属性进行读取和修改。
  • Constructor :代表类的构造方法。通过 Constructor 对象可以创建类的实例,并获取构造方法的参数类型和访问权限等。

2.1.Class

Class 类是 Java 反射机制的核心类之一,它代表 Java 中的每一个类。Java 中的每个类或接口在 JVM 中都对应着一个 Class 实例。通过 Class 类,我们可以获取到类的结构信息,比如类名、父类、实现的接口、方法、构造方法、属性等,从而进行动态操作。

Java 的 Class 类位于 java.lang 包中,因此可以直接使用,无需导包。

Class 类的作用

  • 获取类的基本信息(类名、包名、父类、接口等)。
  • 获取类的详细结构(构造方法、字段、方法等)。
  • 实例化对象,即动态创建该类的对象。
  • 判断类型,例如判断一个对象是否属于某个类。

2.1.1.获取 Class 对象的几种方式

在反射中,首先需要获得 Class 对象,它是操作类信息的起点。常见的获取 Class 对象的方式有以下几种:

1.通过类的全限定名(字符串) : 使用 Class.forName("类的全路径"),这种方式根据类的全限定名获取 Class 对象。例如:

java 复制代码
Class<?> clazz = Class.forName("com.example.MyClass");

这种方式适用于在类名是动态输入的场景下使用,比如从配置文件中读取类名。

2.通过类的实例 : 使用对象的 getClass() 方法获取其所属类的 Class 对象。例如:

java 复制代码
MyClass instance = new MyClass();
Class<?> clazz = instance.getClass();

这种方式适用于已经有对象的情况下获取类信息。

3.通过类的字面量 : 使用 类名.class 语法直接获取 Class 对象。例如:

java 复制代码
Class<MyClass> clazz = MyClass.class;

这种方式适用于编译时就明确知道类名的情况,简单快捷。


2.1.2.Class类的常用方法

1. 获取基本信息

这些方法让我们获取类的名字、父类和接口的信息。

  • getName() :返回类的全限定名(包含包名),例如 "java.util.ArrayList"
  • getSuperclass() :返回类的父类,例如 ArrayList 的父类是 AbstractList
  • getInterfaces() :返回类实现的所有接口,比如 ArrayList 实现了 List 接口。

示例

java 复制代码
Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println("全名: " + clazz.getName());               // 输出:java.util.ArrayList
System.out.println("父类: " + clazz.getSuperclass().getName());  // 输出:java.util.AbstractList
for (Class<?> iface : clazz.getInterfaces()) {
    System.out.println("接口: " + iface.getName());            // 输出:java.util.List
}

2. 获取结构信息

这些方法帮助我们了解类的具体结构,包括构造方法、方法和字段。

  • getConstructor() / getDeclaredConstructor():获取构造方法,用于创建对象。例如:
java 复制代码
Constructor<?> constructor = clazz.getConstructor();
Object instance = constructor.newInstance();
  • getMethod() / getDeclaredMethod():获取类中的方法,可以调用它来执行某个方法。
java 复制代码
Method addMethod = clazz.getMethod("add", Object.class);
addMethod.invoke(instance, "Hello");
  • getField() / getDeclaredField():获取类中的字段(属性),可以读取或修改其值。
java 复制代码
Field field = clazz.getDeclaredField("size");
field.setAccessible(true); // 使私有字段可访问
field.set(instance, 10);   // 设置值

3. 创建实例

反射中推荐使用构造方法的 newInstance() 来创建对象实例,替代过时的 Class.newInstance()

无参构造创建实例

java 复制代码
Class<?> clazz = Class.forName("java.util.ArrayList");
Object instance = clazz.getDeclaredConstructor().newInstance();  // 创建实例

2.2.Method

Method 类表示类中的一个方法。通过 Method 对象,我们可以在运行时获取方法的名称、参数类型、返回类型等信息,并可以调用该方法。 Method 类位于 java.lang.reflect 包中。


2.2.1.如何获取 Method 对象

在 Java 中,通过 Class 对象获取 Method 对象有两种常用方法:

1.getMethod(String name, Class<?>... parameterTypes) :用于获取类的公共方法public),包括继承的公共方法。

  • 参数 name :方法的名字。例如,获取 setName 方法,参数为 "setName"
  • 参数 parameterTypes :方法的参数类型(按顺序提供)。例如,如果方法的参数是一个 String,则填入 String.class

2.getDeclaredMethod(String name, Class<?>... parameterTypes) :用于获取类的声明方法 (包括 privateprotected 方法),但不包括父类继承的方法。

简单示例:获取和调用 Method

假设我们有一个 Person 类,其中包含一个 setName 和一个 getName 方法:

java 复制代码
public class Person {
    private String name;

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

    public String getName() {
        return this.name;
    }
}

获取 setName 方法的 Method 对象:

java 复制代码
Class<?> clazz = Person.class;

// 获取方法名称为 "setName",参数类型为 String 的方法
Method setNameMethod = clazz.getMethod("setName", String.class);

这里,getMethod 方法的第一个参数是方法的名称 "setName",第二个参数是参数类型 String.class,因为 setName 方法的参数是一个 String

2.2.2.Method 类的常用方法

1. 获取方法的基本信息

使用以下方法可以获取方法的名称、返回类型、参数类型等信息:

  • getName() :返回方法的名称。例如,setNameMethod.getName() 返回 "setName"
  • getReturnType() :返回方法的返回类型。例如,getNameMethod.getReturnType() 返回 String.class
  • getParameterTypes() :返回方法的参数类型数组。例如,setNameMethod.getParameterTypes() 返回 [String.class]

示例

java 复制代码
Class<?> clazz = Person.class;
Method setNameMethod = clazz.getMethod("setName", String.class);

System.out.println("方法名称: " + setNameMethod.getName());                // 输出:setName
System.out.println("返回类型: " + setNameMethod.getReturnType().getName()); // 输出:void
System.out.println("参数类型: " + setNameMethod.getParameterTypes()[0].getName());  // 输出:java.lang.String

2. 调用方法

可以使用 invoke() 方法在运行时调用方法。 invoke() 的格式如下:

java 复制代码
Object invoke(Object obj, Object... args)
  • 参数 obj:调用该方法的对象实例。
  • 参数 args:传递给方法的参数列表。

示例

java 复制代码
Class<?> clazz = Person.class;
Object personInstance = clazz.getDeclaredConstructor().newInstance();

// 获取 setName 方法
Method setNameMethod = clazz.getMethod("setName", String.class);

// 调用 setName 方法,将 personInstance 对象的 name 设置为 "Alice"
setNameMethod.invoke(personInstance, "Alice");

// 获取 getName 方法并调用它
Method getNameMethod = clazz.getMethod("getName");
String name = (String) getNameMethod.invoke(personInstance);
System.out.println("Person 的名字是:" + name);  // 输出:Alice

在这个例子中:

  • setNameMethod.invoke(personInstance, "Alice"):调用 setName 方法,参数为 "Alice"
  • getNameMethod.invoke(personInstance):调用 getName 方法,无参数,返回值是 "Alice"

3. 检查方法的修饰符

使用 getModifiers() 可以获得方法的修饰符,它返回一个整数,表示方法的访问控制修饰符和属性。可以使用 Modifier 类的方法解析这个修饰符值,比如判断是否为 publicprivatestatic 等。

  • Modifier.isPublic(int mod) :判断方法是否是 public
  • Modifier.isPrivate(int mod) :判断方法是否是 private
  • Modifier.isStatic(int mod) :判断方法是否是 static

示例

java 复制代码
Method setNameMethod = clazz.getMethod("setName", String.class);
int modifiers = setNameMethod.getModifiers();

System.out.println("是否为 public 方法: " + Modifier.isPublic(modifiers));  // true 或 false
System.out.println("是否为 static 方法: " + Modifier.isStatic(modifiers));  // true 或 false

2.3.Field

Field 类位于 java.lang.reflect 包中,主要用于以下操作:

  • 获取字段的基本信息:例如字段名称、类型和修饰符。
  • 读取和修改字段的值:在运行时对字段的值进行读取或赋值操作。
  • 判断字段是否包含指定的注解 :例如是否有 @Deprecated 或自定义注解。

2.3.1.如何获取 Field 对象

可以通过 Class 对象的以下方法来获取 Field 对象:

  1. getField(String name) :获取类或父类中指定名称的公共字段public)。
  2. getDeclaredField(String name):获取类中指定名称的字段,包括私有字段,但不包括继承的字段。
  3. getFields() :获取类及其父类的所有公共字段 ,返回一个 Field 数组。
  4. getDeclaredFields():获取类中声明的所有字段,包括私有字段。

获取 Field 对象的示例

假设有一个 Person 类:

java 复制代码
public class Person {
    private String name;
    public int age;
}

我们可以用以下代码来获取 nameage 字段的 Field 对象:

java 复制代码
Class<?> clazz = Person.class;

// 获取公共字段 age
Field ageField = clazz.getField("age");

// 获取私有字段 name
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);  // 允许访问私有字段

2.3.2.Field 类的常用方法

1. 获取字段的基本信息

可以使用以下方法获取字段的名称、类型和修饰符:

  • getName():返回字段的名称。
  • getType():返回字段的类型。
  • getModifiers() :返回字段的修饰符,可以使用 Modifier 类判断字段是否为 publicprivatestatic

示例

java 复制代码
Class<?> clazz = Person.class;
Field ageField = clazz.getField("age");

System.out.println("字段名称: " + ageField.getName());               // 输出:age
System.out.println("字段类型: " + ageField.getType().getName());    // 输出:int
System.out.println("字段修饰符: " + Modifier.toString(ageField.getModifiers()));  // 输出:public

2. 读取和修改字段的值

使用 Field 类的 get()set() 方法,可以在运行时读取或修改字段的值:

  • get(Object obj) :从指定对象 obj 中获取字段的值。
  • set(Object obj, Object value) :设置指定对象 obj 中字段的值为 value

示例

java 复制代码
Class<?> clazz = Person.class;
Object personInstance = clazz.getDeclaredConstructor().newInstance();

// 获取和设置 age 字段
Field ageField = clazz.getField("age");
ageField.set(personInstance, 25);  // 将 age 设置为 25
System.out.println("年龄: " + ageField.get(personInstance));  // 输出:年龄: 25

// 获取和设置私有字段 name
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);  // 允许访问私有字段
nameField.set(personInstance, "Alice");  // 将 name 设置为 Alice
System.out.println("名字: " + nameField.get(personInstance));  // 输出:名字: Alice

3. 检查字段的修饰符

可以通过 getModifiers() 方法获取字段的修饰符,并使用 Modifier 类判断字段是否是 publicprivatestatic

示例

java 复制代码
int modifiers = ageField.getModifiers();
System.out.println("是否为 public 字段: " + Modifier.isPublic(modifiers));  // 输出:true 或 false

4. 判断字段是否包含指定注解

可以使用 isAnnotationPresent(Class<? extends Annotation> annotationClass) 方法,判断字段上是否有指定的注解。

示例 : 假设 name 字段上有一个注解 @Deprecated,可以通过以下代码判断:

java 复制代码
Field nameField = clazz.getDeclaredField("name");
if (nameField.isAnnotationPresent(Deprecated.class)) {
    System.out.println("name 字段包含 @Deprecated 注解");
}

2.4.Constructor

Constructor 类位于 java.lang.reflect 包中,用于表示一个类的构造方法。通过 Constructor 对象,我们可以在运行时获取构造方法的信息,并使用该构造方法来创建对象实例。Constructor 类主要用于以下操作:

  • 获取构造方法的基本信息:例如构造方法的名称、参数类型、修饰符等。
  • 创建对象实例:使用构造方法来创建类的实例,支持无参构造和带参构造。

2.4.1.如何获取 Constructor 对象

可以通过 Class 对象来获取 Constructor 对象,有以下几种方法:

  1. getConstructor(Class<?>... parameterTypes) :获取类的公共构造方法(public),参数类型需要与指定的类型匹配。
  2. getDeclaredConstructor(Class<?>... parameterTypes):获取类的指定构造方法,包括私有构造方法。
  3. getConstructors() :获取类的所有公共构造方法,返回一个 Constructor 数组。
  4. getDeclaredConstructors():获取类声明的所有构造方法,包括私有构造方法。

获取 Constructor 对象的示例

假设我们有一个 Person 类:

java 复制代码
public class Person {
    private String name;
    private int age;

    public Person() {}  // 无参构造方法

    public Person(String name, int age) {  // 带参构造方法
        this.name = name;
        this.age = age;
    }
}

可以通过以下代码来获取 Person 类的无参和带参构造方法的 Constructor 对象:

java 复制代码
Class<?> clazz = Person.class;

// 获取无参构造方法
Constructor<?> noArgsConstructor = clazz.getConstructor();

// 获取带参数的构造方法
Constructor<?> paramConstructor = clazz.getConstructor(String.class, int.class);

2.4.2.Constructor 类的常用方法

1. 获取构造方法的基本信息

Constructor 提供了获取构造方法信息的方法:

  • getName():返回构造方法的名称(通常是类名)。
  • getParameterTypes():返回构造方法的参数类型列表,以数组形式返回。
  • getModifiers() :返回构造方法的修饰符,可以通过 Modifier 类来解析。

示例

java 复制代码
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);

System.out.println("构造方法名称: " + constructor.getName());  // 输出:Person
System.out.println("参数类型: " + Arrays.toString(constructor.getParameterTypes()));  // 输出:[class java.lang.String, int]
System.out.println("修饰符: " + Modifier.toString(constructor.getModifiers()));  // 输出:public

2. 创建对象实例

使用 ConstructornewInstance() 方法可以使用构造方法来创建类的实例:

无参构造方法创建实例

java 复制代码
Constructor<?> noArgsConstructor = clazz.getConstructor();
Object instance1 = noArgsConstructor.newInstance();  // 创建无参对象

带参构造方法创建实例

java 复制代码
Constructor<?> paramConstructor = clazz.getConstructor(String.class, int.class);
Object instance2 = paramConstructor.newInstance("Alice", 30);  // 创建带参对象

这里,newInstance() 方法接受的参数需要与构造方法的参数类型匹配。例如,paramConstructor.newInstance("Alice", 30) 中传入了 Stringint,符合构造方法的参数要求。


3. 检查构造方法的修饰符

可以通过 getModifiers() 方法获取构造方法的修饰符,并使用 Modifier 类判断是否是 publicprivateprotected

示例

java 复制代码
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
int modifiers = constructor.getModifiers();

System.out.println("是否为 public 构造方法: " + Modifier.isPublic(modifiers));  // true 或 false

2.Spring与反射的关系

Spring通过反射机制实现了Bean的动态创建、依赖注入和生命周期管理。反射不仅是Spring IoC的基础,还为AOP等其他功能提供支持。虽然反射带来了一定的性能损耗,但Spring通过优化和缓解技术有效提升了整体效率,使其在灵活性和性能之间取得平衡。

2.1. Spring与反射的关系

Java的反射机制允许程序在运行时动态地获取类的信息(如类的构造方法、方法、字段等),甚至能够创建对象、调用方法和修改字段。Spring利用反射机制实现了动态的依赖注入、AOP(面向切面编程)等功能,使开发者可以在不手动编写大量代码的情况下配置和管理应用程序。

2.2. 反射在Spring中的核心作用

在Spring中,反射的核心作用包括:

  • 动态Bean创建:通过反射,Spring容器可以在运行时创建Bean实例,而不依赖于类的具体实现。
  • 依赖注入:反射允许Spring动态获取并设置Bean的属性,支持构造方法注入、Setter注入等不同方式的依赖注入。
  • AOP代理:Spring AOP使用反射来动态地为目标对象创建代理类,通过代理类切入横切逻辑(如事务管理、日志记录等)。
  • 配置扫描与注解:反射用于扫描类上的注解,并根据注解配置Bean、执行依赖注入等。

2.3. Spring容器如何使用反射创建和管理Bean实例

在Spring的IoC容器中,反射的使用贯穿Bean的创建、初始化以及生命周期管理:

  • 扫描Bean定义 :Spring容器启动时,会扫描类路径下标注有特定注解(如@Component@Service等)的类,通过反射获取其构造方法和字段,以便后续注入依赖。

  • 实例化Bean:当需要实例化Bean时,Spring通过反射调用类的构造方法。对于带参数的构造方法,Spring会先获取该构造方法的参数类型,再从容器中找到对应的Bean实例进行注入。

  • 依赖注入 :Spring容器会分析Bean的属性和方法(如@Autowired注解的字段或方法),通过反射为这些属性赋值。如果是Setter注入,Spring会查找对应的Setter方法并调用;如果是字段注入,则直接设置字段的值。

  • Bean的初始化和后处理 :在实例化并注入依赖后,Spring通过反射执行Bean的初始化方法(如@PostConstruct标注的方法),并允许通过BeanPostProcessor进行进一步的处理。

2.4. 反射对Spring IoC和依赖注入的影响

反射机制对于Spring IoC容器和依赖注入的实现影响深远,具体体现在以下方面:

  • 灵活性:由于反射,Spring可以在运行时动态创建和配置Bean实例,而无需预先了解类的具体实现。这赋予了Spring框架极大的灵活性,可以适应不同场景下的依赖关系。

  • 解耦:反射使得依赖关系的注入可以通过配置或注解的方式完成,而不需要在代码中硬编码依赖关系。这实现了应用程序模块之间的低耦合度。

  • 效率和性能权衡:虽然反射提高了灵活性,但在大量Bean实例化和依赖注入时,反射会增加一定的性能开销。Spring通过Bean的生命周期管理、懒加载等机制来减少这种性能影响。

3.动态代理与反射

3.1. 动态代理的基本概念

动态代理是一种在运行时生成代理对象并在方法调用时动态添加额外逻辑的技术。相比于静态代理(编译时就定义好的代理类),动态代理允许程序在运行时创建代理对象,从而灵活地在不修改原始代码的情况下实现某些通用功能,例如:

  • 日志记录:记录方法的调用情况,如参数和返回值。
  • 事务管理:自动在方法前开启事务、方法结束后提交事务或在出现异常时回滚事务。
  • 权限控制:在方法执行前判断调用者是否有执行权限。

在Java中,动态代理有两种实现方式:

  1. JDK动态代理 :基于接口,代理类实现一个或多个接口,并通过java.lang.reflect.Proxy类生成代理对象。适用于接口定义良好的场景。
  2. CGLIB代理:基于继承,通过生成目标类的子类来创建代理对象。适用于没有接口的类。

在Spring中,AOP默认使用JDK动态代理;当目标类没有实现接口时,Spring会自动选择CGLIB代理。

3.2. 使用java.lang.reflect.Proxy创建动态代理类

java.lang.reflect.Proxy类是Java中JDK动态代理的核心工具。通过它,可以在运行时生成代理对象,并拦截方法调用。在使用Proxy创建动态代理类时,主要步骤如下:

步骤一:定义接口

动态代理依赖于接口,所以需要首先定义一个接口,比如用户服务接口UserService

java 复制代码
public interface UserService {
    void addUser(String name);
}

步骤二:实现接口

创建该接口的具体实现类UserServiceImpl

java 复制代码
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("User added: " + name);
    }
}

步骤三:创建InvocationHandler

InvocationHandler接口是代理逻辑的核心,用于定义代理对象在执行方法时的处理逻辑。可以在这个类中加入额外逻辑,比如日志记录或权限验证:

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class UserServiceInvocationHandler implements InvocationHandler {
    private Object target;

    public UserServiceInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Logging before method execution");
        Object result = method.invoke(target, args);
        System.out.println("Logging after method execution");
        return result;
    }
}

步骤四:创建代理对象

利用Proxy.newProxyInstance创建代理对象,这个方法需要提供类加载器、接口数组和InvocationHandler实现:

java 复制代码
import java.lang.reflect.Proxy;

public class ProxyDemo {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxyInstance = (UserService) Proxy.newProxyInstance(
            userService.getClass().getClassLoader(),
            userService.getClass().getInterfaces(),
            new UserServiceInvocationHandler(userService)
        );
        proxyInstance.addUser("Alice");
    }
}

执行效果:调用addUser方法时,UserServiceInvocationHandlerinvoke方法会拦截该调用,在目标方法前后输出日志。

3.3. 在 Spring AOP 中使用动态代理实现切面逻辑

在Spring AOP中,动态代理被用于无侵入地实现切面逻辑。AOP允许在目标方法的执行过程中(如方法执行前、后或发生异常时)添加额外操作,实现业务逻辑与横切关注点(如日志、事务)的分离。

3.3.1 动态代理的自动化实现

  • Spring AOP 默认使用 JDK动态代理 ,以实现基于接口的代理。通过@EnableAspectJAutoProxy注解,Spring会自动启用AOP功能,并根据需要创建动态代理。
  • CGLIB代理:当目标类没有实现接口时,Spring AOP 会使用 CGLIB 动态代理,生成目标类的子类,以拦截方法调用。CGLIB基于字节码增强技术生成代理类,而不需要目标类实现接口。

3.3.2 切面逻辑的实现

在 Spring AOP 中,切面(Aspect)包含切点(Pointcut)和通知(Advice)两部分。通知表示在方法执行前、后或抛出异常时要执行的具体操作,而切点则定义哪些方法上应用这些通知。

切面定义示例:

1.定义切面

使用 @Aspect 注解定义切面类,并在其中定义通知方法,通知类型可以是@Before@After@Around等。

java 复制代码
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.UserService.addUser(..))")
    public void logBefore() {
        System.out.println("Logging before method execution");
    }
}

2.启用AOP功能

在Spring配置类中,通过@EnableAspectJAutoProxy注解启用AOP功能。

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

3.代理对象的创建与切面应用

当调用目标方法(如UserService.addUser)时,Spring会自动创建代理对象,拦截调用并将其委托给切面逻辑。使用JDK动态代理的情况下,Spring通过Proxy类生成代理对象,从而应用切面逻辑。

3.3.3 AOP代理的执行流程

  1. 容器启动与切面注册 :Spring容器启动时,会扫描带有@Aspect注解的类,解析其中的切面方法并注册到容器中。
  2. 动态代理生成:当Bean被注入到其他组件中时,Spring会检查Bean是否需要代理(如是否有切面应用),并根据需要生成JDK或CGLIB代理对象。
  3. 方法拦截:当方法调用发生时,Spring的代理对象会拦截该调用,根据切点定义决定是否应用切面逻辑,并执行相应的通知方法。
  4. 完成方法调用:执行完通知方法后,代理对象继续执行目标方法。

4.通过反射访问Bean属性和方法

4.1. 通过反射访问Bean属性和方法

通过反射,我们可以在运行时获取Bean的属性和方法,这在Spring框架中非常有用。例如,Spring容器在进行依赖注入时,就使用了反射来动态访问和设置Bean的属性。

  • 获取类的所有字段和方法 :可以通过Class对象获取类的所有字段(包括私有字段)和方法。
  • 访问特定字段或方法:通过字段名或方法名,可以获取并访问特定字段或方法,并对其进行操作。

定义反射要调用的类:

java 复制代码
public class SampleBean {
    private String name;
    private int age;

    public void sayHello() {
        System.out.println("Hello, my name is " + name);
    }
}

通过反射访问 name 属性和 sayHello 方法:

java 复制代码
public static void main(String[] args) throws Exception {
    Class<?> clazz = SampleBean.class;
    SampleBean bean = new SampleBean();

    // 获取字段并设置值
    Field nameField = clazz.getDeclaredField("name");
    nameField.setAccessible(true);  // 使私有字段可访问
    nameField.set(bean, "Alice");

    // 调用方法
    Method sayHelloMethod = clazz.getMethod("sayHello");
    sayHelloMethod.invoke(bean);  // 输出: Hello, my name is Alice
}

4.2. 通过反射动态调用Bean的方法和访问属性

反射提供了一种灵活的方式,可以在运行时根据需要调用对象的方法或访问其属性。以下是反射动态调用方法和访问属性的具体步骤:

4.2.1 动态调用方法

动态调用方法通常通过以下步骤实现:

  • 获取方法对象 :使用Class.getMethod(用于公共方法)或Class.getDeclaredMethod(用于私有方法)获取方法对象。
  • 设置方法可访问性 :如果方法是私有的,使用setAccessible(true)允许访问。
  • 调用方法 :使用Method.invoke在指定的对象上调用该方法。

示例:

java 复制代码
public class DynamicInvoker {
    public void greet(String message) {
        System.out.println("Greeting: " + message);
    }
}

public static void main(String[] args) throws Exception {
    DynamicInvoker invoker = new DynamicInvoker();
    Class<?> clazz = invoker.getClass();

    // 获取并调用方法
    Method greetMethod = clazz.getMethod("greet", String.class);
    greetMethod.invoke(invoker, "Welcome!");  // 输出: Greeting: Welcome!
}

4.2.2 动态访问属性

类似于动态调用方法,我们可以使用反射动态访问对象的属性。可以设置属性的值或获取属性的当前值:

java 复制代码
public class Person {
    private String name;

    public String getName() {
        return name;
    }
}

public static void main(String[] args) throws Exception {
    Person person = new Person();
    Class<?> clazz = person.getClass();

    // 动态访问和修改私有属性
    Field nameField = clazz.getDeclaredField("name");
    nameField.setAccessible(true);  // 允许访问私有属性
    nameField.set(person, "Bob");

    System.out.println("Name: " + person.getName());  // 输出: Name: Bob
}

4.3. 使用反射更改私有属性的访问权限(setAccessible(true)

在Java中,私有属性和方法默认对外部类不可访问,但通过反射可以突破这个限制。setAccessible(true)方法允许我们在运行时更改字段、方法和构造方法的访问权限,从而访问和修改私有字段。

  • 使用场景setAccessible(true)常用于测试代码、框架和工具库中,在这些场景下可能需要访问对象的私有数据或调用私有方法。
  • 访问和修改私有字段 :通过反射获取私有字段后,使用setAccessible(true)可以使字段在当前代码块内可以访问。

示例:

java 复制代码
public class PrivateModifier {
    private int secretCode = 12345;

    private void revealSecret() {
        System.out.println("Secret Code: " + secretCode);
    }
}

public static void main(String[] args) throws Exception {
    PrivateModifier modifier = new PrivateModifier();
    Class<?> clazz = modifier.getClass();

    // 访问并修改私有字段
    Field secretCodeField = clazz.getDeclaredField("secretCode");
    secretCodeField.setAccessible(true);  // 设置访问权限
    secretCodeField.set(modifier, 67890);

    // 调用私有方法
    Method revealMethod = clazz.getDeclaredMethod("revealSecret");
    revealMethod.setAccessible(true);  // 设置访问权限
    revealMethod.invoke(modifier);  // 输出: Secret Code: 67890
}

在上面的代码中,通过setAccessible(true)我们可以访问并修改私有字段secretCode,并调用私有方法revealSecret,从而打印出更新后的秘密代码。

相关推荐
长栎12 分钟前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode16 分钟前
Redis 在生产项目的使用
前端·后端
用户5598224812220 分钟前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode21 分钟前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战22 分钟前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha41 分钟前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn41 分钟前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425911 小时前
ShardingJDBC
后端
行者全栈架构师1 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
令人头秃的代码0_01 小时前
mac(m5)平台编译openjdk
java