Java - Reflection and AOP

本文为目前自己对于理解Java反射这一机制的阶段性总结。如有错误并且身为读者的您有教学的兴致可以私信给予指正,我会非常感谢!——XnLemon
Java 的反射机制是一个强大的功能,使得程序能够在运行时动态地与类和对象交互。这包括但不限于创建对象、访问字段、调用方法以及获取类结构信息(如父类、接口、注解等)。这使得 Java 程序可以在运行时动态地创建对象、调用方法、改变字段等,即便这些类、方法或字段在编写原始代码时不可知。这对于开发灵活的应用程序和构建复杂的框架(如 Spring)至关重要。虽然反射作为相对底层的 API 有一定学习曲线,但它在软件开发中扮演着不可或缺的角色。
在我看来,它与 AOP 这一思想有着极为密切的联系,特别是通过动态代理等技术实现方法拦截和增强逻辑的运行时织入。反射对于项目的解耦方面有着巨大的作用,例如实现框架的依赖注入、配置驱动的行为以及插件化架构。
E.g
//举例:获取类的Class对象的三种声明方式与输出一个类的所有字段和方法
//User.java
public class User {
private String name;
private String email;
private String password;
static {
System.out.println("User class is loaded");
}
public User() {
this.name = "Nene7ko";
this.email = "Nene7ko@gmail.com";
this.password = "Nene7ko";
}
public User(String name, String email, String password) {
this.name = name;
this.email = email;
this.password = password;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
}
//Main.java
public class Main {
public static void main(String[] args) throws Exception {
// 1. 类字面量 - 不触发初始化
Class<User> clazz = User.class;
System.out.println("通过.class获取: " + clazz.getName());
// 2. Class.forName - 触发初始化
Class<?> clazz1 = Class.forName("org.example.entity.User");
System.out.println("通过Class.forName获取: " + clazz1.getName());
// 3. getClass() - 不触发初始化(因为类已初始化)
User user = new User("XnLemon", "xianingawa@gmail.com", "password");
Class<?> clazz2 = user.getClass();
System.out.println("通过getClass()获取: " + clazz2.getName());
Field[] field = clazz.getDeclaredFields();
for (Field f : field) {
System.out.println("1:" + f.getName());
}
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
System.out.println("2:" + m.getName());
//调用一个方法
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
System.out.println("3:"+clazz.getDeclaredMethod("getName").invoke(obj));
}
}
}
输出结果示例如下:
通过.class获取: org.example.entity.User
User class is loaded
通过Class.forName获取: org.example.entity.User
通过getClass()获取: org.example.entity.User
1:name
1:email
1:password
2:getPassword
2:getEmail
2:getName
3:Nene7ko
Class 类中用于获取构造方法的方法
方法名 | 描述 |
---|---|
Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor getConstructor(Class<?>… parameterTypes) | 返回单个公共构造方法对象 |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 返回单个构造方法对象 |
Constructor 类中用于创建对象的方法
方法名 | 描述 |
---|---|
T newInstance(Object… initargs) | 根据指定的构造方法创建对象 |
void setAccessible(boolean flag) | 设置为 true,表示取消访问检查 |
反射获取成员变量
方法名 | 描述 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
void set(Object instance, Object value) | 给指定对象的成员变量赋值 |
Object get(Object instance) | 返回成员变量的值 |
在了解到以上这些之后,我开始探究Reflection这一功能在AOP中的应用
首先我们需要理解一个问题:我们为何需要AOP (Aspect Orient Programming)?
在我看来,AOP是OOP思想的延伸体现。
OOP虽然能很好地进行垂直分解(创建职责单一的对象),但在处理横切关注点(cross-cutting concerns)时存在天然缺陷:
日志记录、事务管理、安全检查等需求会横向跨越多个业务模块
导致相同代码重复散落在各业务类中
违反DRY原则(Don't Repeat Yourself)
面向切面编程在我看来是一种将我们需要进行横向拓展的业务给切片剥离出来并使用Reflection中的动态代理功能(基础实现),实例化作为一个新的代理target对象,从而可以对一些常用的方法(如记录日志,更新等等)减少复用。
在程序运行期间,以不修改源码的前提下对方法进行增强,从而降低代码耦合度,是一种便于维护,提高开发效率的一种思考方式。
现代框架(如Spring)已将AOP与IOC完美融合,形成如下结构: