Java 反射机制,动态代理是基于什么原理?

Java反射机制允许程序在运行时获取类的信息并操作,如Class、Field、Method等。动态代理基于反射,用于在运行时创建代理对象,常用于RPC调用和AOP。JDK动态代理通过Proxy和InvocationHandler实现,当目标类未实现接口时,可使用CGLIB作为替代。动态代理提供了一种在调用真实对象方法前后添加额外逻辑的方式。
摘要由CSDN通过智能技术生成

动态代理是基于什么原理?

Java 反射机制,动态代理是基于什么原理?

反射机制是Java语言提供的一种基础功能, 赋予程序在运行时自省。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以在运行时修改类定义。

自省(introspection)指的是程序在运行时能够自我检查和自我修改的能力。在 Java 中,反射机制就是实现自省的一种手段,可以通过它在运行时动态地获取类的信息,包括类的属性、方法、构造函数等,以及在运行时创建对象、调用方法等操作。反射机制的核心是一个名为 Class 的类,它表示一个 Java 类型,通过它可以获取类的各种信息。反射机制在很多场景下都有用武之地,例如编写框架、编写插件、进行调试等

比如jdk自身提供的动态代理,就是利用了反射机制。用来包装RPC调用,面向切面编程。

1.反射机制

java.lang.reflect包下的相关抽象。Class,Field,Method,Construct等,这些就是我们去操作类和对象的元数据对应。

反射提供的AccessibleObject.setAccessible(boolean flag)。他的子类大都重写了此方法,这里所谓的Accessible可以理解成修饰成员的public,protected,private,这意味着我们可以在运行时修改成员访问限制!

setAccessible的应用场景非常普遍,遍布日常开发,测试,依赖注入等各种框架中。比如ORM框架中,我们为一个Java实体对象,运行时自动生成setter,getter的逻辑,这是加载或者持久化数据非常必要的,框架通常可以利用反射做这个事情,而不需要我们手动写类似的重复代码。

  • 比如:@Data注解是Lombok提供的一个注解,它通过在编译时使用Java注解处理器生成Java类的方法,自动生成Java类的构造函数、getter、setter、equals、hashCode和toString等方法,从而简化了Java类的开发。其中,生成的getter和setter方法就是通过反射机制实现的,可以直接访问类的私有成员变量。

另一个典型场景就是绕过 API 访问控制。我们日常开发时可能被迫要调用内部 API 去做些事情,比如,自定义的高性能 NIO 框架需要显式地释放 DirectBuffer,使用反射绕开限制是一种常见办法。

以下是常用的 Class 类的方法:

getConstructors():获取该类所有 public 构造方法的数组。
getDeclaredConstructors():获取该类所有构造方法的数组,包括 private 方法。
getMethods():获取该类所有 public 方法的数组,包括从基类继承的方法。
getDeclaredMethods():获取该类所有方法的数组,包括 private 方法,但不包括继承的方法。
getFields():获取该类所有 public 成员变量的数组。
getDeclaredFields():获取该类所有成员变量的数组,包括 private 成员变量,但不包括继承的成员变量。
newInstance():通过该类的无参构造方法创建一个实例对象。
getSimpleName():获取该类的简单类名。
getCanonicalName():获取该类的规范化名称。
isInterface():判断该类是否为接口。
isArray():判断该类是否为数组类型。
isPrimitive():判断该类是否为基本类型。

2.动态代理

首先,他是一个代理机制。

可以看做是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。很多动态代理场景,可以看做装饰器模式应用。

装饰器(Decorator)模式是一种结构型设计模式,它允许在不改变对象自身的基础上,在程序运行期间动态地为对象添加功能。该模式实现了一种“即插即用”的设计思想,能够在运行时动态地增加或删除对象的职责。

  • 在装饰器模式中,装饰器类和被装饰类具有相同的接口,这样装饰器对象就可以取代被装饰对象。装饰器类中包含了被装饰对象的实例,并且可以在运行时动态地为被装饰对象添加功能。当需要增加或删除对象的职责时,只需要在运行时动态地向对象添加或删除装饰器即可。

下面是一个简单的装饰器模式的示例:

// Component 接口
interface Component {
    void operation();
}

// ConcreteComponent 类
class ConcreteComponent implements Component {
    public void operation() {
        System.out.println("ConcreteComponent.operation() called.");
    }
}

// Decorator 类
abstract class Decorator implements Component {
    protected Component component;
    public Decorator(Component component) {
        this.component = component;
    }
    public void operation() {
        component.operation();
    }
}

// ConcreteDecorator 类
class ConcreteDecorator extends Decorator {
    public ConcreteDecorator(Component component) {
        super(component);
    }
    public void operation() {
        super.operation();
        System.out.println("ConcreteDecorator.operation() called.");
    }
}

// 测试代码
Component component = new ConcreteComponent(); // 创建被装饰对象
component.operation(); // 调用被装饰对象的方法
component = new ConcreteDecorator(component); // 动态地为对象添加装饰器
component.operation(); // 调用被装饰对象的方法,同时加上了装饰器的功能

在上面的示例中,Component 是抽象的组件接口,定义了组件的基本操作。ConcreteComponent 是具体的组件类,实现了组件接口中的方法。Decorator 是装饰器类,实现了组件接口并包含了一个组件对象,它的作用是对组件对象进行包装,可以在运行时动态地为组件添加新的功能。ConcreteDecorator 是具体的装饰器类,实现了装饰器接口,并重写了 operation 方法,增加了新的功能。

在测试代码中,首先创建了一个具体的组件对象 ConcreteComponent,并调用其 operation 方法,输出了一条信息。然后创建了一个具体的装饰器对象 ConcreteDecorator,并将被装饰对象传递给它。最后再次调用被装饰对象的 operation 方法,此时输出的信息中包含了装饰器的功能。

通过代理可以让调用者与实现者之间解耦。比如进行 RPC 调用,框架内部的寻址、序列化、反序列化等,对于调用者往往是没有太大意义的,通过代理,可以提供更加友善的界面。


public class MyDynamicProxy {
    public static void main (String[] args) {
        HelloImpl hello = new HelloImpl();
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        // 构造代码实例
        Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), new Class<?>[]{Hello.class}, handler);
        // 调用代理方法
                proxyHello.sayHello();
    }
}
interface Hello {
    void sayHello();
}

    class HelloImpl implements Hello {
        @Override
        public void sayHello() {
            System.out.println("Hello World");
        }
    }

    class MyInvocationHandler implements InvocationHandler {
        private Object target;

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

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

Invoking sayHello
Hello World

从api设计来说, 这样设计仍然有很大的局限性, 因为它是以接口为中心的, 相当于增加了一种对于被调用者没有太大意义的限制. 我们实例化的事proxy对象, 而不是真正的被调用的类型, 这在实践中可能会带来各种不便和能力退化.

但是,如果调用者没有实现接口, 我们又该怎么解决呢?

可以选择cglib,cglib 动态代理采取的是创建目标类的子类的方式,因为是子类化,我们可以达到近似使用
被调用者本身的效果。在 Spring 编程中,框架通常会处理这种情况,当然我们也可以显式
指定。

  • JDK Proxy 的优势:
    最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
    平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
    代码实现简单。

  • cglib 框架的优势:
    有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似 cglib 动态代理就没有这种限制。
    只操作我们关心的类,而不必为其他相关类增加工作量。
    高性能。

补充说明:

  1. proxy到底是如何实现的?

    Java中的动态代理技术就是利用Proxy类和InvocationHandler接口实现的。其实质是,在程序运行时动态生成一个代理类,在代理类中生成需要代理的方法的字节码,然后通过调用字节码实现方法的代理。在调用代理类的方法时,会通过代理类的对象调用InvocationHandler接口的invoke()方法来调用被代理类的方法。

    具体实现步骤如下:

    1. 创建一个实现InvocationHandler接口的类,该类中需要持有一个被代理类的引用。

    2. 通过Proxy类的静态方法newProxyInstance()创建代理类的实例。该方法接收三个参数,分别是:

    • 类加载器(ClassLoader)
    • 代理类要实现的接口列表(Class<?>[])
    • 一个实现InvocationHandler接口的对象(InvocationHandler)
    1. 在调用代理类的方法时,实际上是调用了InvocationHandler接口的invoke()方法。在invoke()方法中,可以通过反射来调用被代理类的方法,并实现代理逻辑。

    需要注意的是,如果被代理的类没有实现任何接口,就不能使用JDK动态代理。这时可以使用CGLIB库来实现动态代理,CGLIB是通过继承的方式实现代理的。

    在代理对象调用方法时,会被转发到 InvocationHandler 的 invoke 方法,通过这个方法可以对方法调用进行拦截,并进行必要的处理。在 invoke 方法中,可以调用目标对象的方法,也可以不调用,也可以在方法调用前后添加其他的逻辑。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爪哇小白2021

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值