您现在的位置是:首页 >技术杂谈 >【设计模式】代理模式网站首页技术杂谈
【设计模式】代理模式
Java 的代理模式是一种设计模式,它可以让一个对象(代理对象)代替另一个对象(目标对象)去执行一些操作,并且可以在执行前后添加一些额外的功能。代理模式可以实现对目标对象的功能扩展和保护。
Java 的代理模式有两种实现方式:静态代理和动态代理。静态代理是在编译时就生成了代理类的字节码文件,而动态代理是在运行时动态生成代理类并加载到 JVM 中。
静态代理
静态代理的实现步骤如下:
- 定义一个接口及其实现类;
- 创建一个代理类同样实现这个接口;
- 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法;
- 通过代理类来访问目标对象的方法,并且可以在方法执行前后添加自定义的操作。
静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活。需要对每个目标类都单独写一个代理类,而且接口一旦新增加方法,目标对象和代理对象都要进行修改。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。从 JVM 层面来说,静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
动态代理
动态代理的实现方式有多种,比如 JDK 动态代理、CGLIB 动态代理等。JDK 动态代理是利用 Java 内部的反射机制生成一个实现了目标接口的匿名类,然后调用目标方法;CGLIB 动态代理是利用 ASM 开源包,对目标类的字节码文件进行修改,生成子类来处理目标方法。CGLIB 动态代理这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
JDK 动态代理
JDK动态代理是面向接口的,必须要实现了接口的业务类才生成代理对象。
在运行期动态创建一个interface
实例的方法如下:
-
定义一个
InvocationHandler
实例,它负责实现接口的方法调用; -
通过
Proxy.newProxyInstance()
创建实例,它需要3个参数:- 使用的
ClassLoader
,通常就是接口实现类的ClassLoader
; - 需要实现的接口数组,至少需要传入一个接口进去;
- 用来处理接口方法调用的
InvocationHandler
实例。
- 使用的
-
将返回的
Object
强制转型为接口。
示例实现代码:
public interface RequestInterface {
void request();
}
public class RequestInstance implements RequestInterface {
@Override
public void request() {
System.out.println("print request.");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JdkProxyHandler implements InvocationHandler {
private Object instance;
public JdkProxyHandler(Object instance) {
this.instance = instance;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before instance execution");
System.out.println("Method:" + method);
method.invoke(instance, args);
System.out.println("after instance execution");
return null;
}
}
public class Main {
public static void main(String[] args) {
RequestInstance instance = new RequestInstance();
JdkProxyHandler proxyHandler = new JdkProxyHandler(instance);
ClassLoader classLoader = instance.getClass().getClassLoader();
RequestInterface requestInst = (RequestInterface) Proxy.newProxyInstance(
classLoader, new Class[]{RequestInterface.class}, proxyHandler);
requestInst.request();
}
}
运行上述代码之后,控制台打印出:
before instance execution
Method:public abstract void jdk.instance.RequestInterface.request()
print request.
after instance execution
可以看出,通过代理的方式,已经成功增强了 RequestInstance
的 request()
方法。
如果想通过 JDK 动态代理实现一个通用的代理,那么可以在代理处理器中实现一个创建代理的静态方法,通过传入不同的实现类来实现对同一接口、不同实现的通用代理。假设实现一个统计接口耗时的代理:
public class JdkProxyRuntimeHandler implements InvocationHandler {
private Object target;
public JdkProxyRuntimeHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long starTime = System.nanoTime();
Object result = method.invoke(this.target, args);//@1
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + " 方法耗时(纳秒): " + (endTime - starTime));
return result;
}
/**
* 用来创建targetInterface接口的代理对象
*
* @param target 被代理的对象
* @param targetInterface 被代理的接口
* @param <T> 实现类类名
* @return 代理对象
*/
@SuppressWarnings("unchecked")
public static <T> T createProxy(Object target, Class<T> targetInterface) {
if (!targetInterface.isInterface()) {
throw new IllegalStateException("targetInterface 不属于接口类型!");
} else if (!targetInterface.isAssignableFrom(target.getClass())) {
throw new IllegalStateException("target 不是 targetInterface 接口的实现类!");
}
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JdkProxyRuntimeHandler(target));
}
}
public class Main {
public static void main(String[] args) {
RequestInterface proxyInstance = JdkProxyRuntimeHandler.createProxy(new RequestInstance(), RequestInterface.class);
proxyInstance.request();
}
}
上面当我们创建一个新的接口的时候,不需要再去新建一个代理类了,只需要使用 JdkProxyRuntimeHandler.createProxy
创建一个新的代理对象就可以了,方便了很多。
使用 JDK 动态代理需要注意几点:
- JDK 中的 Proxy 只能为接口生成代理类,如果想给某个类创建代理类,那么需要用到 CGLIB 了
- 当调用通过 Proxy 创建代理对象的任意方法时候,会被
InvocationHandler
接口中的invoke
方法进行处理
CGLIB 动态代理
JDK 的动态代理只能为接口创建代理,在应用上具有局限性。在实际的场景中,我们的类不一定有接口,如果我们想为普通的类也实现代理功能,我们就需要用到CGLIB 来实现了。
CGLIB 是一个强大、高性能的字节码生成库,底层使用了 ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。它用于在运行时扩展 Java 类和实现接口,本质上它是通过动态生成一个子类去覆盖所要代理的类。Enhancer
可能是 CGLIB 中最常用的一个类,和 JDK 中的 Proxy 不同的是,Enhancer
既能够代理一般的类,也能够代理接口。Enhancer
创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object
中继承的 toString
和 hashCode
方法)。Enhancer
不能够拦截final方法,例如 Object.getClass()
方法,这是由 Java final 方法语义决定的。基于同样的道理,Enhancer
也不能对 final 类进行代理操作。
由于 Spring 已将第三方 CGLIB Jar 包中所有的类集成到 Spring 自己的 Jar 包中,因此可直接使用接口 MethodInterceptor
来实现代理。
CGLIB动态代理的基本实现代码:
public class RawRequestInstance {
public String request() {
System.out.println("print request.");
return "print raw request.";
}
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
public class CglibProxy {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
// 声明生成的代理类是某一个父类的子类
enhancer.setSuperclass(this.target.getClass());
// 指定回调方法,即代理中需要加强的内容
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("before instance execution");
System.out.println("Method:" + method);
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("after instance execution");
return result;
});
return enhancer.create();
}
}
public class Main {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
RawRequestInstance proxyInst = (RawRequestInstance) proxy.getInstance(new RawRequestInstance());
System.out.println(proxyInst.request());
}
}
运行上述代码之后,控制台打印出:
before instance execution
Method:public void instance.RawRequestInstance.request()
print request.
after instance execution
print raw request.
实际上,CGLIB 代理类在方法被调用时,里面的所有的方法都是会被拦截处理的,下面我们修改一下实现类 RequestInstance
的成员方法,然后再执行一次以上的代码:
public class RawRequestInstance {
public String request() {
System.out.println("print request.");
return "print raw request." + request2();
}
public String request2() {
System.out.println("print request2.");
return "print raw request 2.";
}
}
运行上述代码之后,控制台打印出:
before instance execution
Method:public java.lang.String instance.RawRequestInstance.request()
print request.
before instance execution
Method:public java.lang.String instance.RawRequestInstance.request2()
print request2.
after instance execution
after instance execution
print raw request.print raw request 2.
可见,即便 request2()
是在 request1()
中被调用,但它仍然被代理拦截进行处理。实际上这就是 Spring 中的 @Configuration
注解的实现方式,使用 CGLIB 代理拦截 @Bean
注解的方法,从而确保注入的被依赖 Bean 都是同一个。
同样地,我们实现一个通用的、用于统计接口耗时的代理:
public class CglibProxyRuntime implements MethodInterceptor {
private Object target;
public CglibProxyRuntime(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long starTime = System.nanoTime();
Object result = method.invoke(target, objects);
long endTime = System.nanoTime();
System.out.println(method + " 方法耗时(纳秒): " + (endTime - starTime));
return result;
}
/**
* 创建任意类的代理对象
*
* @param target 被代理的对象
* @param <T> 实现类类名
* @return 代理对象
*/
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
CglibProxyRuntime costTimeProxy = new CglibProxyRuntime(target);
Enhancer enhancer = new Enhancer();
enhancer.setCallback(costTimeProxy);
enhancer.setSuperclass(target.getClass());
return (T) enhancer.create();
}
}
public class Main {
public static void main(String[] args) {
RawRequestInstance proxy = CglibProxyRuntime.createProxy(new RawRequestInstance());
System.out.println(proxy.request());
}
}
输出结果为:
print request.
print request2.
public java.lang.String instance.RawRequestInstance.request() 方法耗时(纳秒): 403800
print raw request.print raw request 2.
从上面的示例代码可知,CGLIB 代理拦截方法后的处理逻辑,主要是通过自定义一个 MethodInterceptor
接口并重写其方法,将该接口赋值给 enhancer
作为 callback
回调方法。这里的 MethodInterceptor
其实也是继承了 Callback
接口,因此我们还可以定义其他继承了 Callback
的接口,从而增强代理类的功能。
FixedValue 返回固定值
enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "**** Fixed Value ****";
}
});
在上面的代码中直接修改 setCallback()
方法的赋值,最终输出结果为:
**** Fixed Value ****
NoOp.INSTANCE 拦截无操作
直接上代码:
enhancer.setCallback(NoOp.INSTANCE);
最终输出结果为:
print request.
print request2.
print raw request.print raw request 2.
CallbackFilter 不同方法实现不同拦截策略
定义一个新的实现类 MultiMethodInstance
:
public class MultiMethodInstance {
public String runtime() {
System.out.println("print runtime.");
return "print runtime.";
}
public String fixedValue() {
System.out.println("fixed value");
return "fixed value";
}
public String doNothing() {
System.out.println("----------- do nothing -----------");
return "----------- do nothing -----------";
}
}
然后定义一个新的代理拦截类:
import org.springframework.cglib.proxy.*;
import java.lang.reflect.Method;
public class CglibProxyFilter {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
//创建多个Callback
Callback[] callbacks = {
// 拦截所有方法名含有 run 的方法
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long starTime = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
long endTime = System.nanoTime();
System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
return result;
}
},
// 拦截所有方法名含有 fixed 的方法,返回固定值的
new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "**** Fixed Value ****";
}
},
// 拦截后无操作
NoOp.INSTANCE
};
enhancer.setCallbacks(callbacks);
// 设置过滤器 CallbackFilter,判断调用方法时使用哪个 Callback 来处理,返回的是 callbacks 数组的下标
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
String methodName = method.getName();
return methodName.contains("run") ? 0
: (methodName.contains("fixed") ? 1 : 2);
}
});
return enhancer.create();
}
}
通过定义多个 Callback
接口的实现,以及提供判断每个方法调用时所选用的回调策略(拦截处理),来完成对同一个实现类中不同方法进行不同的代理拦截策略。具体执行哪个 Callback
,会通过 CallbackFilter
中的 accept
方法来判断,这个方法返回 callbacks
数组的索引。上述代码的输出结果如下:
print runtime.
public java.lang.String instance.MultiMethodInstance.runtime(),耗时(纳秒):29824600
print runtime.
**** Fixed Value ****
----------- do nothing -----------
----------- do nothing -----------
多策略拦截代理还能进一步优化,使用 CallbackHelper
替代 CallbackFilter
能使代码结构更加清晰,扩展性更强。具体实现代码如下:
import org.springframework.cglib.proxy.*;
import java.lang.reflect.Method;
public class CglibProxyCallbackHelper {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
// 定义所有可用的 Callback 接口方法
Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
long starTime = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
long endTime = System.nanoTime();
System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
return result;
};
Callback fixedValueCallback = (FixedValue) () -> "**** Fixed Value ****";
Callback noOpCallback = NoOp.INSTANCE;
// 定义拦截策略的判断逻辑
CallbackHelper callbackHelper = new CallbackHelper(target.getClass(), null) {
@Override
protected Object getCallback(Method method) {
String methodName = method.getName();
return methodName.contains("run") ? costTimeCallback
: (methodName.contains("fixed") ? fixedValueCallback : noOpCallback);
}
};
enhancer.setCallbacks(callbackHelper.getCallbacks());
enhancer.setCallbackFilter(callbackHelper);
return enhancer.create();
}
}
优化后的输出结果不变,这里就不再展示了。
小结
Java 标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例。动态代理是通过 Proxy
创建代理对象,然后将接口方法“代理”给 InvocationHandler
完成的。动态代理实际上是 JVM 在运行期动态创建 class 字节码并加载的过程
JDK 动态代理和 CGLIB 动态代理的区别在于:
- JDK 动态代理是面向接口的,必须要实现了接口的业务类才生成代理对象;而 CGLIB 动态代理则是通过生成业务类的子类作为代理类,无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。
- Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
- Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB 使用 ASM 框架直接对字节码进行操作,在类的执行过程中比较高效。
在 SpringBoot 中,有很多地方应用了代理模式,比如 AOP、事务管理、缓存等。例如,当我们使用 @Transactional 注解来开启事务时,SpringBoot 会根据目标类是否实现了接口来选择使用 JDK 动态代理还是 CGLIB 动态代理来创建一个带有事务功能的代理对象。