代理模式最大的优势就是能够解耦,在spring中也是广泛使用。spring中一个重要的特性就是aop,aop是个啥东西呢?其实很简单,比如现在有个业务方法,那这个业务方法很重要,涉及到非常重要的业务数据,那对于广大企业应用来说,为了以后能够及时的定位问题,需要记录相关入参以及出参到日志表。
但是对于企业应用来说,需要记录日志的地方应该是蛮多的,如果每个方法中都手动的去写这些记录日志的东西,就会特别的冗余,那使用代理模式就可以解决。
一、静态代理
1、User接口
package com.ty.staticProxy; /** * @author Taoyong * @date 2018年7月1日 * 天下没有难敲的代码! */ public interface User { /* * 业务逻辑接口 */ public void work(String workName); }
2、UserImpl实现类
package com.ty.staticProxy; /** * @author Taoyong * @date 2018年7月1日 * 天下没有难敲的代码! */ public class UserImpl implements User { /* * 实际业务逻辑实现方法 */ @Override public void work(String workName) { System.out.println("我是做" + workName + "的"); } }
3、代理类
package com.ty.staticProxy; /** * @author Taoyong * @date 2018年7月1日 * 天下没有难敲的代码! * 此类为代理类,并且实现业务逻辑类接口,接收一个实际业务处理对象 */ public class ProxyUser implements User { private User user; public ProxyUser(User user) { this.user = user; } @Override public void work(String workName) { /* * 调用实际业务逻辑处理前可以定制化一些功能 */ System.out.println("工作前先放松放松============="); /* * 调用实际业务逻辑处理方法 */ user.work(workName); /* * 调用实际业务逻辑处理后也可以定制一些功能 */ System.out.println("工作后还是要放松放松============="); } }
4、StaticProxyDemo
package com.ty.staticProxy; /** * @author Taoyong * @date 2018年7月1日 * 天下没有难敲的代码! */ public class StaticProxyDemo { public static void main(String[] args) { User proxyUser = new ProxyUser(new UserImpl()); proxyUser.work("java开发"); } }
运行结果:
工作前先放松放松=============
我是做java开发的
工作后还是要放松放松=============
优点:
- 1、 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
- 2、 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的
缺点:
- 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 2、 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。使用静态代理模式需要程序员手写很多代码。
二、动态代理
在java中,实现动态代理主要有两种方式,一种是jdk动态代理,一种是cglib
1、jdk动态代理
User以及UserImpl跟上面一致
a、UserDynamicProxy
package com.ty.dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author Taoyong * @date 2018年7月2日 * 天下没有难敲的代码! */ public class UserDynamicProxy implements InvocationHandler { private User user; public UserDynamicProxy(User user) { this.user = user; } /* * jdk动态代理基于接口,其中proxy好像没啥卵用、method代表当前被代理对象的实际调用方法、args则代表方法参数 * 由invoke方法对被代理对象进行相关的增强 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("工作前放松放松========"); method.invoke(user, args); System.out.println("工作后也要放松放松==========="); return null; } }
b、DynamicProxyDemo
package com.ty.dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @author Taoyong * @date 2018年7月2日 * 天下没有难敲的代码! */ public class DynamicProxyDemo { public static void main(String[] args) { User user = new UserImpl(); InvocationHandler h = new UserDynamicProxy(user); /* * 使用Proxy的静态方法是生成代理类的核心。 * 一共有三个参数: * 1、第一个参数是被代理类的类加载器,通过此类加载器将代理类加载入jvm中; * 2、第二个参数则是被代理类所实现的所有接口,需要所有的接口的目的是创建新的代理类实现被代理类的所有接口,保证被代理类所有方法都能够 * 被代理。其实代理的核心就是新创建一个类并实例化对象,去集成被代理对象所有功能的同时,再加入某些特性化的功能; * 3、第三个参数则是真正的扩展,使用动态代理的主要目的就是能够对原方法进行扩展,尤其是对于大部分方法都具有的重复方法(例如记录日志), * 可以理解为面向切面编程中的增强. */ User proxy = (User) Proxy.newProxyInstance(User.class.getClassLoader(), user.getClass().getInterfaces(), h); /* * 在调用生成的代理类对象后,调用原方法后,该method对象以及参数等会被传入到InvocationHandler的invoke方法中,由InvocationHandler的 * invoke方法对被代理类对象进行增强。 */ proxy.work("敲代码"); proxy.eat("吃大餐"); } }
2、cglib代理
jdk动态代理的缺点就是必须基于接口,没有接口就无法实现代理,而cglib则是使用继承的方式去生成代理类,使用范围更广
a、添加cglib.jar、asm.jar(注意jar包版本)
使用cglib前必须进行导包,并且版本如果过低会导致报错
b、UserImpl(使不使用接口都可以)
package com.ty.dynamic.cglib; /** * @author Taoyong * @date 2018年7月1日 * 天下没有难敲的代码! */ public class UserImpl { /* * 实际业务逻辑实现方法 */ public void work(String workName) { System.out.println("我是做" + workName + "的"); } }
c、CglibDynamicProxy
package com.ty.dynamic.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * @author Taoyong * @date 2018年7月2日 * 天下没有难敲的代码! */ public class CglibDynamicProxy implements MethodInterceptor { /* * 实际的增强 */ @Override public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("这是前处理=============="); methodProxy.invokeSuper(obj, objects); System.out.println("这是前处理=============="); return null; } }
d、CglibProxyDemo
package com.ty.dynamic.cglib; import net.sf.cglib.proxy.Enhancer; /** * @author Taoyong * @date 2018年7月2日 * 天下没有难敲的代码! */ public class CglibProxyDemo { public static void main(String[] args) { /* * 创建字节码增强器 */ Enhancer enhancer = new Enhancer(); /* * 被代理类设置为字节码增强器父类,cglib使用的是继承方式去创建代理类 */ enhancer.setSuperclass(UserImpl.class); /* * 设置字节码增强器回调方法。对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦截
*/
enhancer.setCallback(new CglibDynamicProxy());
/*
* 创建代理实例
*/
UserImpl userImpl = (UserImpl) enhancer.create(); userImpl.work("敲代码"); } }
3、原理-----基于jdk动态代理
上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。上述代码里,唯一的“黑厘子”就是Proxy.newProxyInstance()方法,除此之外再没有任何特殊之处。
在JDK动态代理中涉及如下角色:
业务接口Interface、业务实现类target、业务处理类Handler、JVM在内存中生成的动态代理类$Proxy0
动态代理原理图:
说白了,动态代理的过程是这样的:
-
Proxy通过传递给它的参数(interfaces/invocationHandler)生成代理类$Proxy0;
-
Proxy通过传递给它的参数(ClassLoader)来加载生成的代理类$Proxy0的字节码文件;
动态代理的关键代码就是Proxy.newProxyInstance(classLoader, interfaces, handler),我们跟进源代码看看
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // handler不能为空 if (h == null) { throw new NullPointerException(); } final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ // 通过loader和接口,得到代理的Class对象 Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { // create proxy instance with doPrivilege as the proxy class may // implement non-public interfaces that requires a special permission return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { // 创建代理对象的实例 return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } }
我们看一下newInstance方法的源代码:
private static Object newInstance(Constructor<?> cons, InvocationHandler h) { try { return cons.newInstance(new Object[] {h} ); } catch (IllegalAccessException | InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString()); } } }
讲解完了代理类的生成源码,我们一定想要看看代理类的代码是什么样的,下面提供一个生成代理类的方法供大家使用:
/** * 代理类的生成工具 * @author ChenHao * @since 2019-4-2 */ public class ProxyGeneratorUtils { /** * 把代理类的字节码写到硬盘上 * @param path 保存路径 */ public static void writeProxyClassToHardDisk(String path) { // 第一种方法 // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true); // 第二种方法 // 获取代理类的字节码 byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces()); FileOutputStream out = null; try { out = new FileOutputStream(path); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { ProxyGeneratorUtils.writeProxyClassToHardDisk("C:/x/$Proxy11.class"); } }
此时就会在指定的C盘x文件夹下生成代理类的.class文件,我们看下反编译后的结果:
package org.fenixsoft.bytecode; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements DynamicProxyTest.IHello { private static Method m3; private static Method m1; private static Method m0; private static Method m2; /** *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白 *super(paramInvocationHandler),是调用父类Proxy的构造方法。 *父类持有:protected InvocationHandler h; *Proxy构造方法: * protected Proxy(InvocationHandler h) { * Objects.requireNonNull(h); * this.h = h; * } * */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } /** * *这里调用代理对象的sayHello方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。 *this.h.invoke(this, m3, null); this.h就是父类Proxy中保存的InvocationHandler实例变量 *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象, *再联系到InvacationHandler中的invoke方法。嗯,就是这样。 */ public final void sayHello() throws { try { this.h.invoke(this, m3, null); return; } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } // 此处由于版面原因,省略equals()、hashCode()、toString()三个方法的代码 // 这3个方法的内容与sayHello()非常相似。 static { try { m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
java.lang.reflect.Proxy
public class Proxy implements java.io.Serializable { protected InvocationHandler h; private Proxy() { } protected Proxy(InvocationHandler h) { doNewInstanceCheck(); this.h = h; } //略 }
这个代理类的实现代码也很简单,它为传入接口中的每一个方法,以及从 java.lang.Object中继承来的equals()、hashCode()、toString()方法都生成了对应的实现 ,并且统一调用了InvocationHandler对象的invoke()方法(代码中的“this.h”就是父类Proxy中保存的InvocationHandler实例变量)来实现这些方法的内容,各个方法的区别不过是传入的参数和Method对象有所不同而已,所以无论调用动态代理的哪一个方法,实际上都是在执行InvocationHandler.invoke()中的代理逻辑。