代理模式 (Proxy Pattern)
在软件开发中,由于一些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为"代理"的第三者来实现间接访问.该方案对应的设计模式被称为代理模式.
代理模式(Proxy Design Pattern ) 原始定义是:让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许将请求提交给对象前后进行一些处理。
静态代理
静态代理实现
//接口类: IUserDao <抽象目标类>
public interface IUserDao {void save();
}
//目标对象:UserDaoImpl <目标角色(被代理)>
public class UserDaoImpl implements IUserDao {@Overridepublic void save() {System.out.println("保存数据");}
}//静态代理对象:UserDaoProxy 需要实现IUserDao接口 <代理角色>
public class UserDaoProxy implements IUserDao {private IUserDao target;public UserDaoProxy(IUserDao target) {this.target = target;}@Overridepublic void save() {System.out.println("开启事务"); //扩展额外功能target.save();System.out.println("提交事务");}
}//测试类
public class TestProxy {@Testpublic void testStaticProxy(){//目标对象UserDaoImpl userDao = new UserDaoImpl();//代理对象UserDaoProxy proxy = new UserDaoProxy(userDao);proxy.save();}
}
其实就是编码实现, 通过组合或继承持有原始类 再扩展它的方法
动态代理
动态代理: 动态地在内存中构建代理对象, 从而实现对目标对象的代理功能.
静态代理与动态代理的区别:
- 静态代理在编译时就已经实现了,编译完成后代理类是一个实际的class文件
- 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件, 而是在运行时动态生成类字节码,并加载到JVM中.
Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的
java.lang.Class对象,作为方法区这个类的各种数据访问入口
动态代理就是想办法,根据接口或目标对象,动态生成代理类的字节码,然后再加载到JVM中使用
JDK动态代理实现
package org.yang.mca.pattern10;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class JDKProxyFactory {private Object target; //维护一个目标对象public JDKProxyFactory(Object target) {this.target = target;}//为目标对象生成代理对象public Object getProxyInstance(){//使用Proxy获取代理对象return Proxy.newProxyInstance(target.getClass().getClassLoader(), //目标类使用的类加载器target.getClass().getInterfaces(), //目标对象实现的接口类型, 必须有一个接口new InvocationHandler(){ //事件处理器 (调用拦截)/*** invoke方法参数说明* @param proxy 代理对象* @param method 对应于在代理对象上调用的接口方法Method实例* @param args 代理对象调用接口方法时传递的实际参数* @return: java.lang.Object 返回目标对象方法的返回值,没有返回值就返回null*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("JDK 代理拦截,调用前, "+method.getName());//执行目标对象方法Object ret = method.invoke(target, args);System.out.println("JDK 代理拦截,调用后, "+method.getName());return ret;}});}
}
测试
@Test
public void testJDKProxy() { IUserDao target = new UserDaoImpl(); System.out.println("目标对象信息 :"+ target.getClass());//目标对象信息 IUserDao proxy = (IUserDao) new JDKProxyFactory(target).getProxyInstance(); System.out.println("代理对象信息 :"+ proxy.getClass());//输出代理对象信息 proxy.save(); //执行代理方法 // while (true){ }
}
JDK 动态代理实现原理
使用阿里开源的 arthas 工具, jad 命令可以 直接反编译出class的源码
java -jar arthas-boot.jar
jad jdk.proxy2.$Proxy7
精简后大概代码
package jdk.proxy2;import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.yang.mca.pattern10.IUserDao;public final class $Proxy7
extends Proxy
implements IUserDao {//注意 JDK动态代理是基于接口, 没有接口是不行滴private static final Method m0;private static final Method m1;private static final Method m2;private static final Method m3;public $Proxy7(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m3 = Class.forName("org.yang.mca.pattern10.IUserDao").getMethod("save", new Class[0]);return;}}public final void save() {try {this.h.invoke(this, m3, null);return;}}
....
}
cglib 动态代理实现
cglib代理类,需要实现MethodInterceptor接口,并指定代理目标类target
CglibDynamicProxy.java
package org.yang.mca.pattern10;import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Calendar;/*** @author yangfh* @date 2023-11-23 17:45**/
public class CglibDynamicProxy implements MethodInterceptor {private Object target;public CglibDynamicProxy(Object target) {this.target = target;}public Object getProxy(){//增强器类,用来创建动态代理类Enhancer en = new Enhancer();//设置代理类的父类字节码对象en.setSuperclass(target.getClass());//设置回调: 对于代理类上所有的方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦截en.setCallback(this);//创建动态代理对象并返回return en.create();}/*** 实现回调方法* @param o 代理对象* @param method 目标对象中的方法的Method实例* @param args 实际参数* @param methodProxy 代理对象中的方法的method实例* @return: java.lang.Object*/@Overridepublic Object intercept(Object o, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {Calendar calendar = Calendar.getInstance();SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(formatter.format(calendar.getTime()) + " [" +method.getName() + "] CglibDynamicProxy 调用拦截]");Object result = methodProxy.invokeSuper(o, args);return result;}
}
测试:
//测试Cglib 实现 动态代理
public static void main(String[] args) { //目标对象 IUserDao target = new UserDaoImpl(); System.out.println("目标对象信息 :"+ target.getClass());//目标对象信息 //代理对象 CglibDynamicProxy dynamicProxy = new CglibDynamicProxy(target); IUserDao proxy = (IUserDao) dynamicProxy.getProxy(); System.out.println("代理对象信息 :"+ proxy.getClass());//输出代理对象信息 proxy.save(); //执行代理方法 }
jdk9 以上需要 open module 参数 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
cglib 动态代理实现原理
反编译后的源码
public class UserDaoImpl$$EnhancerByCGLIB$$2d13c5a
extends UserDaoImpl
{
....}
CGLIB 底层基于 ASM 操作字节码, 相对与JDK只能对接口进行代理, 它通过继承的方式, 可以对任意对象进行代理; (所以被 final 修饰的类, CGLIB是无能为力的)
- 最底层是字节码
- ASM是操作字节码的工具
- cglib基于ASM字节码工具操作字节码(即动态生成代理,对方法进行增强)
in short 通过继承的方式创建代理对象, 原理是: cglib -> ASM -> 字节码
代理模式总结
关键角色
代理(Proxy)模式分为三种角色:
- 目标角色
- 代理角色
- 应该面向抽象编程, 所以还有一个(抽象目标角色)
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代理。
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
代理模式优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
- 增加了系统的复杂度;
- 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,增加了代码维护的复杂度;