代理模式
代理模式说的是在某些情况下,访问对象不适合直接访问目标对象,此时访问对象可以通过代理对象间接访问目标对象,也就是说代理对象在访问对象和目标对象之间充当中介的角色。代理模式分为静态代理和动态代理。
静态代理
一般情况下访问对象直接访问目标对象
代理模式在原来的基础上增加了代理对象
例子:用户购买商品时,需要在支付操作前后加入前置通知和后置通知,在不修改目标对象的原则上,通过在代理对象增加了前置通知和后置通知的功能增强。
//ToCPayment.java
public interface ToCPayment {
void pay();
}
//oCPaymentImpl.java
public class ToCPaymentImpl implements ToCPayment {
@Override
public void pay() {
System.out.println("普通用户进行付款");
}
}
//AlipayToC.java
public class AlipayToC implements ToCPayment {
private ToCPayment toCPayment;
public AlipayToC(ToCPayment toCPayment) {
this.toCPayment = toCPayment;
}
@Override
public void pay() {
//前置通知
Before();
toCPayment.pay();
//后置通知
After();
}
private void After() {
System.out.println("后置增强");
}
private void Before() {
System.out.println("前置增强");
}
}
//ProxyDemo.java
public class ProxyDemo {
public static void main(String[] args) {
AlipayToC alipayToC = new AlipayToC(new ToCPaymentImpl());
alipayToC.pay();
}
}
//输出结果
前置增强
普通用户进行付款
后置增强
优点:可以在不修改目标对象的基础上,在代理对象中实现对目标对象的功能拓展
缺点:代理类和目标类都实现了接口,当接口中增加新的方法时,代理对象和目标对象都需要实现新方法,对于不同的目标类都需要实现对应的代理类,所以需要创建很多的代理类,维护成本提高。
动态代理
动态代理通过使用JDK API动态地在内存中构建出代理对象,从而实现对目标对象的代理功能,动态代理又被称为JDK代理或接口代理。SpringAOP底层的实现原理就是动态代理。
静态代理与动态代理的区别:
静态代理在编译时就已经实现了,编译完成后代理类是一个实际的 class 文件
动态代理是在运行时动态生成的,即编译完成后没有实际的 class 文件,而是在运行时动态生成类的字节码并加载到 JVM 中
JDK动态代理
//AlipayInvocationHandler.java
public class AlipayInvocationHandler implements InvocationHandler {
private Object target;
public AlipayInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Before();
Object result = method.invoke(target, args);
After();
return result;
}
private void After() {
System.out.println("后置增强");
}
private void Before() {
System.out.println("前置增强");
}
}
//JdkDynamicProxyUtil.java
public class JdkDynamicProxyUtil {
public static <T>T newProxyInstance(Object target, InvocationHandler h){
ClassLoader classLoader = target.getClass().getClassLoader();
Class<T>[] interfaces = (Class<T>[]) target.getClass().getInterfaces();
return (T)Proxy.newProxyInstance(classLoader,interfaces,h);
}
}
//ProxyDemo.java
//只需要写一个实现接口的被代理类,省略了之前AlipayToC类的具体实现,提高代码复用
public class ProxyDemo {
public static void main(String[] args) {
/*ToCPayment toCProxy = new AlipayToC(new ToCPaymentImpl());
toCProxy.pay();*/
ToCPaymentImpl toCPayment = new ToCPaymentImpl();
System.out.println(toCPayment.getClass());
AlipayInvocationHandler h = new AlipayInvocationHandler(toCPayment);
ToCPayment toCProxy = JdkDynamicProxyUtil.newProxyInstance(toCPayment, h);
toCProxy.pay();
System.out.println(toCProxy.getClass());
while(true){}
}
}
输出结果
class demo.pattern.proxy.impl.ToCPaymentImpl
前置增强
普通用户进行付款
后置增强
class com.sun.proxy.$Proxy0
JDK动态代理源码分析
在查看底层源码实现之前,需要下载java的诊断工具Archas,下载官网地址:https://arthas.aliyun.com/doc/
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率
首先下载Arthas监控工具,下载安装完成后,进入arthas页面,执行java -jar arthas-boot.jar启动程arthas程序,然后,就可以看到正在运行的目标类ProxyDemo,
输入2,进入这个类的监控页面
然后输入jad com.sun.proxy.$Proxy0,就可以看到目标类ProxyDemo的源码,这里只展示核心代码。
package com.sun.proxy;
import demo.pattern.proxy.ToCPayment;
public final class $Proxy0 extends Proxyimplements ToCPayment {
private static Method m3;
//构造方法,传入的invocationHandler是之前接口InvocationHandler的实现类
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
//通过反射获取被代理对象的ToCPayment方法
m3 = Class.forName("demo.pattern.proxy.ToCPayment").getMethod("pay", new Class[0]);
}
public final void pay() {
//h是之前实现InvocationHandler的实现类,所以这里实际上调用的是自定义InvocationHandler的实现类中invoke方法
this.h.invoke(this, m3, null);
}
}
代理类实现了ToCPayment接口,重写了pay方法,当调用代理类的pay方法时,实际上调用的是AlipayInvocationHandler的invoke方法
public final void pay() {
//h是之前实现InvocationHandler的实现类,所以这里实际上调用的是自定义InvocationHandler的实现类中invoke方法
this.h.invoke(this, m3, null);
}
public class AlipayInvocationHandler implements InvocationHandler {
private Object target;
public AlipayInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Before();
Object result = method.invoke(target, args);
After();
return result;
}
private void After() {
System.out.println("后置增强");
}
private void Before() {
System.out.println("前置增强");
}
}
再来看看Proxy的newProxyInstance的源码,主要做以下三件事
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//获取代理类的Class对象
Class<?> cl = getProxyClass0(loader, intfs);
//通过反射获取Class对象的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
//传入InvocationHandler的实现类实例化代理对象
return cons.newInstance(new Object[]{h});
}
从这里可以知道实例化代理类的参数h是自定义接口的实现类,所以代理类调用pay方法时最终调用的是AlipayInvocationHandler的invoke方法。
jdk动态代理源码的运行流程图
Cglib动态代理
cglib动态代理是基于ASM机制实现的,不需要被代理类实现接口,而是生成目标类的子类作为代理类,弥补了jdk动态代理的缺陷。
//CommonPayment.java
public class CommonPayment {
public void pay() {
System.out.println("以普通用户身份进行支付");
}
}
//AliPayMethodInterceptor.java
public class AliPayMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//前置通知
Before();
Object result = methodProxy.invokeSuper(o, args);
//后置通知
After();
return result;
}
private void After() {
System.out.println("后置增强");
}
private void Before() {
System.out.println("前置增强");
}
}
//CglibDanamicProxyUtil.java
public class CglibDanamicProxyUtil {
public static <T>T createProxy(T target, MethodInterceptor methodInterceptor){
Enhancer enhancer = new Enhancer();
//设置目标类的Class
enhancer.setSuperclass(target.getClass());
//设置回调
enhancer.setCallback(methodInterceptor);
return (T)enhancer.create();
}
}
//ProxyDemo.java
public class ProxyDemo {
public static void main(String[] args) {
CommonPayment commonPayment = new CommonPayment();
AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
CommonPayment commonProxy = CglibDanamicProxyUtil.createProxy(commonPayment, interceptor);
commonProxy.pay();
while(true){}
}
}
输出结果
前置增强
以普通用户身份进行支付
后置增强
class demo.pattern.proxy.impl.CommonPayment$$EnhancerByCGLIB$$55e31cb6
Cglib动态代理源码分析
对cglib代码使用同样的操作方式,可以得到demo.pattern.proxy.impl.CommonPayment E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIB55e31cb6代理对象的源码
public class CommonPayment$$EnhancerByCGLIB$$55e31cb6 extends CommonPayment implements Factory {
public final void pay() {
//判断是否设置了回调对象,CGLIB$CALLBACK_0是enhancer中setCallback()方法传进来的回调对象
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
CommonPayment$$EnhancerByCGLIB$$55e31cb6.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
//如果设置了回调对象,直接调用methodInterceptor接口的实现类中的intercept方法
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$pay$0$Method, CGLIB$emptyArgs, CGLIB$pay$0$Proxy);
return;
}
//如果没有设置回调对象,则直接调用父类的pay()方法
super.pay();
}
}
Cglib动态代理会根据被代理类的信息以及设置的回调对象动态的生成代理类,可以看出代理类继承了目标类,当调用代理类的目标方法时,会根据是否设置了回调对象执行不同的操作,若设置了回调,则会调用methodInterceptor接口的实现类中的intercept方法,然后在该方法里面通过invokeSuper()方法调用目标类的pay()方法,若没有设置回调,则直接调用父类,也就是目标类的pay()方法。
Cglib动态代理的运行流程图
代理模式的优缺点
**优点:**代理模式在访问对象与目标对象之间充当中介的角色,从而保护目标对象,代理对象还可以扩展目标对象的功能;同时代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
**缺点:**代理模式在访问对象和目标对象增加了一个代理,这会增加系统的复杂度。
代理模式的区别
静态代理与动态代理区别
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
JDK动态代理与Cglib动态代理的区别
使用 CGLib 实现动态代理,CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,在JDK1.6 之前比使用 Java 反射效率要高。唯一需要注意的是,CGLib 不能对声明为 final 的类或者方法进行代理,因为 CGLib 原理是动态生成被代理类的子类。
在 JDK1.6、JDK1.7、JDK1.8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLib 代理效率,只有当进行大量调用的时候,JDK1.6 和 JDK1.7 比 CGLib 代理效率低一点,但是到 JDK1.8 的时候,JDK 代理效率高于 CGLib 代理。所以如果有接口使用 JDK 动态代理,如果没有接口使用 CGLIB 代理。
代理模式的应用场景
功能增强:当需要对一个对象的访问提供一些额外操作时,可以使用代理模式
远程(Remote)代理:实际上,RPC 框架也可以看作一种代理模式,GoF 的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。
防火墙(Firewall)代理:当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。