赞
踩
动态代理是基于什么原理?
反射机制是Java语言提供的一种基础功能, 赋予程序在运行时自省。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以在运行时修改类定义。
自省(introspection)指的是程序在运行时能够自我检查和自我修改的能力。在 Java 中,反射机制就是实现自省的一种手段,可以通过它在运行时动态地获取类的信息,包括类的属性、方法、构造函数等,以及在运行时创建对象、调用方法等操作。反射机制的核心是一个名为 Class 的类,它表示一个 Java 类型,通过它可以获取类的各种信息。反射机制在很多场景下都有用武之地,例如编写框架、编写插件、进行调试等
比如jdk自身提供的动态代理,就是利用了反射机制。用来包装RPC调用,面向切面编程。
java.lang.reflect包下的相关抽象。Class,Field,Method,Construct等,这些就是我们去操作类和对象的元数据对应。
反射提供的AccessibleObject.setAccessible(boolean flag)。他的子类大都重写了此方法,这里所谓的Accessible可以理解成修饰成员的public,protected,private,这意味着我们可以在运行时修改成员访问限制!
setAccessible的应用场景非常普遍,遍布日常开发,测试,依赖注入等各种框架中。比如ORM框架中,我们为一个Java实体对象,运行时自动生成setter,getter的逻辑,这是加载或者持久化数据非常必要的,框架通常可以利用反射做这个事情,而不需要我们手动写类似的重复代码。
另一个典型场景就是绕过 API 访问控制。我们日常开发时可能被迫要调用内部 API 去做些事情,比如,自定义的高性能 NIO 框架需要显式地释放 DirectBuffer,使用反射绕开限制是一种常见办法。
以下是常用的 Class 类的方法:
getConstructors():获取该类所有 public 构造方法的数组。
getDeclaredConstructors():获取该类所有构造方法的数组,包括 private 方法。
getMethods():获取该类所有 public 方法的数组,包括从基类继承的方法。
getDeclaredMethods():获取该类所有方法的数组,包括 private 方法,但不包括继承的方法。
getFields():获取该类所有 public 成员变量的数组。
getDeclaredFields():获取该类所有成员变量的数组,包括 private 成员变量,但不包括继承的成员变量。
newInstance():通过该类的无参构造方法创建一个实例对象。
getSimpleName():获取该类的简单类名。
getCanonicalName():获取该类的规范化名称。
isInterface():判断该类是否为接口。
isArray():判断该类是否为数组类型。
isPrimitive():判断该类是否为基本类型。
首先,他是一个代理机制。
可以看做是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。很多动态代理场景,可以看做装饰器模式应用。
装饰器(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 动态代理就没有这种限制。
只操作我们关心的类,而不必为其他相关类增加工作量。
高性能。
补充说明:
proxy到底是如何实现的?
Java中的动态代理技术就是利用Proxy类和InvocationHandler接口实现的。其实质是,在程序运行时动态生成一个代理类,在代理类中生成需要代理的方法的字节码,然后通过调用字节码实现方法的代理。在调用代理类的方法时,会通过代理类的对象调用InvocationHandler接口的invoke()方法来调用被代理类的方法。
具体实现步骤如下:
创建一个实现InvocationHandler接口的类,该类中需要持有一个被代理类的引用。
通过Proxy类的静态方法newProxyInstance()创建代理类的实例。该方法接收三个参数,分别是:
需要注意的是,如果被代理的类没有实现任何接口,就不能使用JDK动态代理。这时可以使用CGLIB库来实现动态代理,CGLIB是通过继承的方式实现代理的。
在代理对象调用方法时,会被转发到 InvocationHandler 的 invoke 方法,通过这个方法可以对方法调用进行拦截,并进行必要的处理。在 invoke 方法中,可以调用目标对象的方法,也可以不调用,也可以在方法调用前后添加其他的逻辑。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。