java代理模式
来源:互联网 发布:被墙域名查询 编辑:程序博客网 时间:2024/06/17 17:41
代理模式
代理模式(Proxy Pattern)的定义:为其他对象提供一种代理以控制对这个对象的访问。
代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。现实世界的代理人被授权执行当事人的一些事宜,无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上代理人是要有当事人的授权,并且在核心问题上还需要请示当事人。
生活中,比较常见的代理场景如:火车票代售点代卖火车票了。
在软件设计中,Spring的AOP就是使用代理模式的原理来实现的。
在代理模式中主要有四种角色:
- 客户端(client):使用代理对象和真实对象完成一些工作的调用方。
- 抽象对象(interface):代理对象和真实对象的公共对外方法(如订火车票)
- 代理对象(proxy):用来代理、封装代理对象,代理对象内包含真实对象的引用,从而能够操作真实对象。
- 真实对象(target):真正实现业务逻辑的对象;
业务场景
假设有这样一个业务场景:动物园中有许多小动物,小动物每天都要吃饭,现在需要记录动物园中所有动物的吃饭时间。
抽象对象(Animal),真实对象(Cat,SingleDog),代理对象(xxProxy),接口(吃饭:eat());
public interface Animal { /** * 吃饭方法 */ void eat();}public class Cat implements Animal { @Override public void eat() { System.out.println("吃猫粮"); }}public class SingleDog implements Animal{ @Override public void eat() { System.out.println("撒狗粮"); }}
java实现代理的方式有许多,接下来我们着重学习下
- 静态代理:继承方式 + 聚合方式
- 动态代理:jdk动态代理 + cglib动态代理
静态代理
静态代理是指,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。
1. 继承方式
public class CatTimeProxy extends Cat { @Override public void eat() { long startTime = System.currentTimeMillis(); System.out.println("start eat-----------------"); super.eat(); long endTime = System.currentTimeMillis(); System.out.println("end eat--------------------"+ (endTime - startTime) +"ms"); }}public class SingleDogTimeProxy extends SingleDog { @Override public void eat() { long startTime = System.currentTimeMillis(); System.out.println("start eat-----------------"); super.eat(); long endTime = System.currentTimeMillis(); System.out.println("end eat--------------------"+ (endTime - startTime) +"ms"); }}
测试类
//继承方式 public void testCatTimeProxy(){ //1.记录cat的吃饭时间 Animal animal = new CatTimeProxy(); animal.eat(); //2.记录SingleDog的吃饭时间 animal = new SingleDogTimeProxy(); animal.eat(); }
2. 聚合方式
public class AnimalTimeProxy implements Animal { private Animal target; public AnimalTimeProxy(Animal animal) { this.target = animal; } @Override public void eat() { long startTime = System.currentTimeMillis(); System.out.println("start eat-----------------"); target.eat(); long endTime = System.currentTimeMillis(); System.out.println("end eat--------------------" + (endTime - startTime) + "ms"); }}
测试类:
//聚合的方式(较灵活,因为实现了接口) public void tesAnimalTimeProxy(){ //1.记录cat的吃饭时间 Animal animal = new AnimalTimeProxy(new Cat()); animal.eat(); //2.记录SingleDog的吃饭时间 animal = new AnimalTimeProxy(new SingleDog()); animal.eat(); }
3. 继承与聚合的比较
聚合实现方式中代理类聚合了被代理类,且代理类及被代理类都实现了同一个接口,可实现灵活多变。继承式的实现方式则不够灵活。
举个极端的例子:动物园中有1000中动物, 现在要记录这1000种动物的吃饭时间,如果采用继承,则要为这1000种动物分别创建一个代理类…..
4. 静态代理的劣势
- 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。(ps:jdk8中,支持接口有default方法,可避免此问题)
动态代理
动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。
动态代理类使用字节码动态生成加载技术,在运行时生成加载类。生成动态代理类的方法很多,如,JDK 自带的动态处理、CGLIB、Javassist 或者 ASM 库。JDK 的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar 包,但相对功能比较弱。CGLIB 和 Javassist 都是高级的字节码生成库,总体性能比 JDK 自带的动态代理好,而且功能十分强大。ASM 是低级的字节码生成工具,使用 ASM 已经近乎于在使用 Java bytecode 编程,对开发人员要求最高,当然,也是性能最好的一种动态代理生成工具。但 ASM 的使用很繁琐,而且性能也没有数量级的提升,与 CGLIB 等高级字节码生成工具相比,ASM 程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 CGLIB 或者 Javassist。
这里着重介绍下jdk自带的动态代理,和CGLIB的动态代理
jdk动态代理
1.实现步骤
实现jdk动态代理,通常有两种常用的方法,但是最终的步骤都如下所示:
a. 创建一个实现InvocationHandler接口的类,它必须实现invoke()方法
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
2.代码示例
首先定义InvocationHandler实现类
public class JvmTimeHandler implements InvocationHandler { private Object target; public JvmTimeHandler(Object target) { super(); this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); System.out.println("start eat-----------------"); //!!注意proxy为当前调用类即JvmTimeHandler, method.invoke(xxx,zzz)第一个参数不能为proxy,会出现死循环 Object returnVal = method.invoke(target, args); long endTime = System.currentTimeMillis(); System.out.println("end eat--------------------" + (endTime - startTime) + "ms"); return returnVal; }}
方法(一)
public void testTimeHandler() { Animal target = new SingleDog(); Class clazz = target.getClass(); //通过 classloader,代理类的inteface[] ,以及调用处理器对象 直接生成代理对象 Animal proxy = (Animal) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new JvmTimeHandler(target)); proxy.eat(); }
方法(二)
public void testTimeHandler2() throws Exception { //a.建动态代理类 Class proxyClass = Proxy.getProxyClass(ProxyTest.class.getClassLoader(), Animal.class); //b.动态代理类的构造函数 Constructor constructor = proxyClass.getConstructor(InvocationHandler.class); //c.构造调用处理器对象 Animal target = new SingleDog(); InvocationHandler handler = new JvmTimeHandler(target); //d.通过构造函数创建动态代理类对象 Animal proxy = (Animal) constructor.newInstance(handler); proxy.eat(); }
3.源码解析
1)java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
2)java.lang.reflect.Proxy
这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
//构造函数 protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; }//查找或生成指定的代理类 private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { //......省略部分代码...... //如果代理类已经通过实现给定接口的类加载器创建了,则返回缓存中的该类的副本;否则将通过ProxyClassFactory创建代理类 return proxyClassCache.get(loader, interfaces); }//newProxyInstance()public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) { //......省略部分代码...... final Class<?>[] intfs = interfaces.clone(); //查找或生成指定的代理类 Class<?> cl = getProxyClass0(loader, intfs); //获得类的构造函数 final Constructor<?> cons = cl.getConstructor(constructorParams); //newInstance return cons.newInstance(new Object[]{h}); }
3)java.lang.reflect.Proxy.ProxyClassFactory(内部类)
通过ProxyClassFactory创建代理类.
// 根据给定的类加载器和接口数组生成代理类的工厂类 private static final class ProxyClassFactory implements BiFunction{ // 所有代理类名称的前缀 private static final String proxyClassNamePrefix = "$Proxy"; //用于生成唯一代理类名称的下一个序号 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { //验证interfaces合法性 //......省略部分代码...... //获取代理类序号 long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; //生成代理类字节码 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); //加载代理类字节码.并返回实例化对象 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); }}
4)生成的代理对象$Proxy0
目前我们只能使用代理对象,但是却获取不到代理对象的字节码,我们可以通过如下两种方式获取:
a.通过ProxyGenerator.generateProxyClass来获取,会在项目根目录下生成com.sun.proxy.$Proxy0.class文件
public static void craeteProxyClazzFile(String clazzName , Class<?> ... interfaces) { byte[] classFile = ProxyGenerator.generateProxyClass(clazzName, interfaces); try (FileOutputStream out = new FileOutputStream(System.getProperty("user.dir") + File.separator+clazzName+".class");){ out.write(classFile); } catch (Exception e) { e.printStackTrace(); } }
b.在Proxy.newInstance之前,增加如下配置,会在项目根目录下创建com/sun/proxy/$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
接下来我们来看下$Proxy0.class的代码构成:
public final class $Proxy0 extends Proxy implements Animal { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final void eat() throws { //调用父类的InvocationHandler.invoke()方法 super.h.invoke(this, m3, (Object[])null); } static { //......省略部分代码...... m3 = Class.forName("pattern.proxy.beans.Animal").getMethod("eat", new Class[0]); }}
4.jdk动态代理小结
jdk动态代理必须只针对接口方法的代理,如果业务类没有实现interface则无法使用jdk动态代理。如果业务类实现了接口,在接口新增了方法时,(jdk8中,支持接口default方法,否则会报错)如果代理类么有同步更新则是无法代理的,给之后的维护带来了麻烦。
cglib动态代理
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,我们可以使用cglib来实现动态代理。
cglib动态代理的原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。—这里可以得知final类是无法被代理的。
1.实现步骤
a.定义自己的回调函数类,通常我们使用实现了callback的子接口MethodInterceptor,InvocationHandler
b.enhance设置真实对象.class,设置callback(),生成代理对象;常用的拦截器有:
c.获取代理对象,执行业务方法
2.代码示例
方法(一)
//实现MethodInterceptor接口public class TimeInterceptorFactory implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) { enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object obj, Method m, Object[] objects, MethodProxy methodProxy) throws Throwable { long startTime = System.currentTimeMillis(); System.out.println("start eat-----------------"); //注意调用的方法!! methodProxy.invokeSuper() Object returnVal = methodProxy.invokeSuper(obj, objects); long endTime = System.currentTimeMillis(); System.out.println("end eat--------------------" + (endTime - startTime) + "ms"); return returnVal; }}
测试类:
public static void testTimeInterceptorFactory() { TimeInterceptorFactory interceptor = new TimeInterceptorFactory(); SingleDog proxy = (SingleDog) interceptor.getProxy(SingleDog.class); proxy.eat(); }
方法(二)
public static void testMethodInterceptor2() throws Exception { Enhancer enhancer = new Enhancer(); Animal target = new SingleDog(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(new net.sf.cglib.proxy.InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); System.out.println("start eat-----------------"); //调用的方法 Object returnVal = method.invoke(target, args); long endTime = System.currentTimeMillis(); System.out.println("end eat--------------------" + (endTime - startTime) + "ms"); return returnVal; } }); SingleDog proxy = (SingleDog) enhancer.create(); proxy.eat(); }
3.源码解析
如何查看cglib动态代理产生的类呢?我们可以在生成代理类之前增加一个系统变量,即可在工程根目录/net/sf/cglib/proxy 获得产生的代理类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "net/sf/cglib/proxy");
cglib动态代理会产生多个class文件:
MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB$$d45e49f7.classEnhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72.classSingleDog$$EnhancerByCGLIB$$ab95b44d$$FastClassByCGLIB$$5613c2ba.classSingleDog$$EnhancerByCGLIB$$ab95b44d.class //关注这个SingleDog$$FastClassByCGLIB$$47f8b6eb.class
1) SingleDog$$EnhancerByCGLIB$$ab95b44d
命名规则:package.真实对象class+$$EnhancerByCGLIB$$+key
//静态语句块static { //静态语句中调用 CGLIB$STATICHOOK1(); }static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; Class var0 = Class.forName("pattern.proxy.beans.SingleDog$$EnhancerByCGLIB$$ab95b44d"); Class var1; //实际调用类为:SingleDog //....省略部分..... CGLIB$eat$0$Proxy = MethodProxy.create(var1, var0, "()V", "eat", "CGLIB$eat$0"); }//MethodProxyfinal void CGLIB$eat$0() { super.eat();}//重写后的eat方法public final void eat() { /** * callback子接口的类型:常见的还有net.sf.cglib.proxy.InvocationHandler; */ MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { //设置this.CGLIB$CALLBACK_0 为 enhance.callbacks[0] CGLIB$BIND_CALLBACKS(this); //ca var10000 = this.CGLIB$CALLBACK_0; } if(var10000 != null) { /** * 执行callback_0.intercept * this == 即代理类SingleDog$$EnhancerByCGLIB$$ab95b44d * CGLIB$eat$0$Method即 被代理的业务方法 == SingleDog.eat(); * CGLIB$eat$0$Method = ReflectUtils.findMethods(new String[]{"eat", "()V"}, (var1 = Class.forName("pattern.proxy.beans.SingleDog")).getDeclaredMethods())[0]; * CGLIB$emptyArgs = 参数 * CGLIB$eat$0$Proxy = MethodProxy.create(var1, var0, "()V", "eat", "CGLIB$eat$0"); */ var10000.intercept(this, CGLIB$eat$0$Method, CGLIB$emptyArgs, CGLIB$eat$0$Proxy); } else { super.eat(); }}//由enhance.registerCallbacks(Class generatedClass, Callback[] callbacks) 调用此方法设置CGLIB$STATIC_CALLBACKS[] public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) { CGLIB$STATIC_CALLBACKS = var0; } //设置callback private static final void CGLIB$BIND_CALLBACKS(Object var0) { SingleDog$$EnhancerByCGLIB$$ab95b44d var1 = (SingleDog$$EnhancerByCGLIB$$ab95b44d)var0; //第一次进来时 var1.CGLIB$BOUND = false if(!var1.CGLIB$BOUND) { var1.CGLIB$BOUND = true; //CGLIB$THREAD_CALLBACKS = new ThreadLocal(); Object var10000 = CGLIB$THREAD_CALLBACKS.get(); //第一次进来时var10000 = null; if(var10000 == null) { /** * CGLIB$STATIC_CALLBACKS 由 CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) 设置 */ var10000 = CGLIB$STATIC_CALLBACKS; if(CGLIB$STATIC_CALLBACKS == null) { return; } } //设置this.CGLIB$CALLBACK_0 = enhance.setCallbacks()数组中的第一个 var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0]; } }
2) .net.sf.cglib.proxy.MethodProxy
methodProxy是在上述代理类中生成的:
/** * 在SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$STATICHOOK1()中调用 * c1 : SingleDog * c2 : pattern.proxy.beans.SingleDog$$EnhancerByCGLIB$$ab95b44d * desc: "()V", * name1: "eat" * name2: "CGLIB$eat$0" */ public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) { MethodProxy proxy = new MethodProxy(); //SingleDog.eat() proxy.sig1 = new Signature(name1, desc); //SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0() proxy.sig2 = new Signature(name2, desc); proxy.createInfo = new CreateInfo(c1, c2); return proxy; } private void init() { if (fastClassInfo == null) { synchronized (initLock) { if (fastClassInfo == null) { CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo(); //真实对象:SingleDog fci.f1 = helper(ci, ci.c1); //代理对象:SingleDog$$EnhancerByCGLIB$$ab95b44d fci.f2 = helper(ci, ci.c2); //业务方法SingleDog.eat() fci.i1 = fci.f1.getIndex(sig1); //SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0() fci.i2 = fci.f2.getIndex(sig2); fastClassInfo = fci; } } } }//解释:为什么在设置的callback接口实现类中,必须使用methodproxy.invokeSuper。public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; /** * fci.f2 : SingleDog$$EnhancerByCGLIB$$ab95b44d * fci.i2 : SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0() * 观察SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()的逻辑为:super.eat()即SingleDog.eat() * 得知等价于调用SingleDog.eat() */ return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } }
4.cglib动态代理小结
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理
参考文献
https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html
- Java代理之代理模式
- java代理模式---静态代理
- java代理模式--动态代理
- 代理模式&java动态代理
- JAVA代理模式--静态代理
- JAVA代理模式--动态代理
- JAVA动态代理 代理模式
- Java代理模式-静态代理
- java代理模式-动态代理
- Java代理模式 静态代理 动态代理
- JAVA代理模式与动态代理模式
- JAVA代理模式与动态代理模式
- JAVA代理模式与动态代理模式
- JAVA代理模式与动态代理模式
- JAVA代理模式与动态代理模式
- JAVA代理模式与动态代理模式
- Java代理模式和kotlin代理模式
- Java中的代理模式
- java配置定时任务
- make menuconfig的时候出现一大堆未定义的错误
- 物体检测之从RCNN到Faster RCNN
- 钻研不更新背后的seo原理
- js和jquery页面初始化加载函数的方法及先后顺序
- java代理模式
- RTSP握手及会话流程抓取(RTSP over TCP)
- iOS开发 ☞ 图片资源路径
- SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka)
- ELK6.1搭建--安装和配置
- Mysql系列课程--第六章 索引和视图
- java8 stream for循环 强for 效率
- PyQt5笔记(01) -- 创建空白窗体
- 使用npm react-tools编译jsx