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完美融合,形成如下结构:

 
以上为我对于项目结构以及AOP思想的一些笨拙的思考。接下来我们来看反射里的动态代理及代理对象,本文暂时只更新一下基于JDK的动态代理TwT
 
我们先来比较Java的class和interface的区别:
  • 可以实例化class(非abstract);
  • 不能实例化interface。

所有interface类型的变量总是通过某个实例向上转型并赋值给接口类型变量的,有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?

这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

定义接口:

public interface Hello {
    void morning(String name);
}

编写实现类:

public class HelloWorld implements Hello {
    public void morning(String name) {
        System.out.println("Good morning, " + name);
    }
}

创建实例,转型为接口并调用:

Hello hello = new HelloWorld();
hello.morning("Nene7ko");

以上为静态代码的实现,接下来我们来看动态代码的实现

重点是直接通过JDK提供的一个Proxy.newProxyInstance()方法创建了一个接口对象 实现如下

//Target.java
public class Target implements TargetInterface {
    public void save() {
        System.out.println("save running.....");
    }
}
//TargetInterface.java
public interface TargetInterface {

    public void save();

}

//ProxyTest.java
public class ProxyTest {

    public static void main(String[] args) {

        //目标对象
        final Target target = new Target();

        //增强对象
        final Advice advice = new Advice();

        //返回值 就是动态生成的代理对象
        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(), //目标对象类加载器
                target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
                new InvocationHandler() {
                    //调用代理对象的任何方法  实质执行的都是invoke方法
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        advice.before(); //前置增强
                        Object invoke = method.invoke(target, args);//执行目标方法
                        advice.afterReturning(); //后置增强
                        return invoke;
                    }
                }
        );

        //调用代理对象的方法
        proxy.save();

    }

}

以下为源码里newProxyInstance的具体实现

//Proxy.java
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);

        @SuppressWarnings("removal")
        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    }

在运行期动态创建一个interface实例的方法如下:

1.定义一个InvocationHandler实例 (源码见InvocationHandler.java),它负责实现接口的方法调用;
2.通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    1>使用的ClassLoader,通常就是接口类的ClassLoader;
    2>需要实现的接口数组,至少需要传入一个接口进去;
    3>用来处理接口方法调用的InvocationHandler实例。
3.将返回的Object强制转型为接口。

动态代理不是"魔术",而是运行时生成的真实类。并且注意本处所有的接口方法都会被代理,包括自定义的业务方法以及Object的toString()方法等。

所以在上述例子中 我们的代理对象可以把目标对象target的核心目标方法提取出来进行操作 从而可以在前后随意加上我们想要的同类型拓展业务 从而达到AOP的思想。

当然以上的实现可以继续优化,例如:利用缓存使得性能提升,使用异常处理让代码更健壮,当然由于笔者能力暂时有限TwT 所以在此不再进行拓展。

如果有对引导一位新人感兴趣的老师 请帮帮我!><