所谓代理模式,就是在不改变原始类(被代理类)的情况,使用代理类给原始类附加功能。附加的功能基本是与原始类的业务不想关的功能,即一些非功能性的需求,比如监控、统计、事务、限流等。其中代理类和被代理要实现同一个接口或者共同继承某个类。

从上图我们看出,代理类和原始类都实现了同一个接口,即都是同一种类型,同时代理类中引用了原始类作为属性,这样就在调用方法的时候做了增强。
代理分为静态代理和动态代理,下面我们分别看下。
示例说明:我要有两种方式(字节流和缓冲字节流来复制文本),顺便计算两种copy所需要的时长。
首先,定义一个文件copy的接口:
public interface IFileCopyService {void copy(String srcPath, String destPath) throws IOException;
}
第二,定义一个字节流copy的实现类:
public class InputStreamCopyServiceImpl implements IFileCopyService{public void copy(String srcPath, String destPath) throws IOException {File src = new File(srcPath);File dest = new File(destPath);if (!src.isFile()) {throw new RuntimeException("拷贝的源文件不是文件类型,请检查源文件");}InputStream is = new FileInputStream(src);OutputStream os = new FileOutputStream(dest);//设置一个缓存区,每次读取一个字节,从输入流中一个字节一个字节的,将读取的字节放入这下面这个字节数组中,如果字节数组满了,然后将此字节数组写入到输出流中byte[] bufferBytes = new byte[1024];int length = 0;//如果是-1,说明已经到文件结尾了,就暂停了;//如果不是-1,当bufferBytes字节数组满了以后,就将此字节数组写入到输出流中,完成后,继续往bufferBytes字节数组中写入,如此循环while ((length = is.read(bufferBytes)) != -1) {os.write(bufferBytes, 0, length);}os.flush();os.close();is.close();}
}
第三,定义一个缓冲字节流copy的实现类:
public class BufferedInputStreamCopyServiceImpl implements IFileCopyService {public void copy(String srcPath, String destPath) throws IOException {File src = new File(srcPath);File dest = new File(destPath);if (!src.isFile()) {throw new RuntimeException("拷贝的源文件不是文件类型,请检查源文件");}InputStream is = new BufferedInputStream(new FileInputStream(src));OutputStream os = new BufferedOutputStream(new FileOutputStream(dest));//设置一个缓存区,每次读取一个字节,从输入流中一个字节一个字节的,将读取的字节放入这下面这个字节数组中,如果字节数组满了,然后将此字节数组写入到输出流中byte[] bufferBytes = new byte[1024];int length = 0;//如果是-1,说明已经到文件结尾了,就暂停了;//如果不是-1,当bufferBytes字节数组满了以后,就将此字节数组写入到输出流中,完成后,继续往bufferBytes字节数组中写入,如此循环while ((length = is.read(bufferBytes)) != -1) {os.write(bufferBytes, 0, length);}os.flush();os.close();is.close();}
}
第四,定义一个代理类:
public class FileCopyServiceProxy implements IFileCopyService {private IFileCopyService fileCopyService;public FileCopyServiceProxy(IFileCopyService fileCopyService) {this.fileCopyService = fileCopyService;}public void copy(String srcPath, String destPath) throws IOException {long startTime = System.currentTimeMillis();fileCopyService.copy(srcPath, destPath);long endTime = System.currentTimeMillis();System.out.println("执行时长:" + (endTime - startTime));}
}
第五,定义测试类:
public class StaticProxyTest {public static void main(String[] args) {String srcPath = "d:/data.txt";String destPath = "d:/data_copy.txt";IFileCopyService fileCopyService = new InputStreamCopyServiceImpl();FileCopyServiceProxy fileCopyServiceProxy = new FileCopyServiceProxy(fileCopyService);try {fileCopyServiceProxy.copy(srcPath, destPath);} catch (IOException e) {throw new RuntimeException(e);}BufferedInputStreamCopyServiceImpl bufferedInputStreamCopyService = new BufferedInputStreamCopyServiceImpl();FileCopyServiceProxy bufferedInputStreamCopyServiceProxy = new FileCopyServiceProxy(bufferedInputStreamCopyService);try {bufferedInputStreamCopyServiceProxy.copy(srcPath, destPath);} catch (IOException e) {throw new RuntimeException(e);}}
}
执行结果:
执行时长:1
执行时长:0
通过上面的示例,我们基本知道了静态代理了吧。所谓静态代理,就是在程序编译之前,由程序员创建或特定工具自动生成,即代理类是事先定义好的,比如上面的FileCopyServiceProxy 这个类。
代理类扩展了目标对象的功能,在客户端和目标对象之前起到了一个中介的作用,一定程度上降低了系统的耦合度。但是代理类要和目标对象实现同样的接口,自然就会产生很多代理类,同时,接口一旦增加新的方法,那么代理类也要做实现,这样就需要维护多个实现类。
动态代理是相对静态代理来说的,与静态代理类不同,动态代理类的字节码是在程序运行时由java反射机制动态生成的,不需要程序员手动编写代理类源码。所以动态代理不仅简化了编码工作,还提高了程序的可扩展性。
我们可以用java.lang.reflect包中的Proxy类和InvocationHandler接口来实现生成动态代理类。
还是上面静态代理的示例,我们用动态代理看怎么实现。
首页,和静态代理一样,也是创建IFileCopyService、InputStreamCopyServiceImpl和BufferedInputStreamCopyServiceImpl;
第二,编写FileCopyInvocationHandler类,实现InvocationHandler
public class FileCopyInvocationHandler implements InvocationHandler {private Object object;public FileCopyInvocationHandler(Object object) {this.object = object;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long startTime = System.currentTimeMillis();//按照反射的定义,对应任意的对象,我们都能调用它的方法Object invoke = method.invoke(object, args);long endTime = System.currentTimeMillis();System.out.println(object.getClass().getSimpleName()+"执行时长:" + (endTime - startTime));return invoke;}
}
第三,编写测试类
public class DynamicProxyTest {public static void main(String[] args) {System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");String srcPath = "d:/data.txt";String destPath = "d:/data_copy.txt";IFileCopyService inputStreamCopyService = new InputStreamCopyServiceImpl();FileCopyInvocationHandler fileCopyInvocationHandler = new FileCopyInvocationHandler(inputStreamCopyService);//通过Proxy的静态方法newProxyInstance,创建出动态代理类IFileCopyService dynamicProxy = (IFileCopyService) Proxy.newProxyInstance(inputStreamCopyService.getClass().getClassLoader(), inputStreamCopyService.getClass().getInterfaces(), fileCopyInvocationHandler);try {dynamicProxy.copy(srcPath, destPath);} catch (IOException e) {throw new RuntimeException(e);}IFileCopyService bufStreamCopyService = new BufferedInputStreamCopyServiceImpl();FileCopyInvocationHandler fileCopyInvocationHandler1 = new FileCopyInvocationHandler(bufStreamCopyService);IFileCopyService dynamicProxy1 = (IFileCopyService) Proxy.newProxyInstance(bufStreamCopyService.getClass().getClassLoader(), bufStreamCopyService.getClass().getInterfaces(), fileCopyInvocationHandler1);try {dynamicProxy1.copy(srcPath, destPath);} catch (IOException e) {throw new RuntimeException(e);}}
}
执行结果如下:
InputStreamCopyServiceImpl执行时长:0
BufferedInputStreamCopyServiceImpl执行时长:1
我们看到,这里没有写任何代理类,而是在程序运行中动态生成了。是不是很奇怪啊,下面我们就探究一下JDK 动态代理是怎么实现的。
这里生成了一个$Proxy0代理类,如下:
//$Proxy0 继承了Proxy类,由于java是单继承,所以只能实现接口,这也正好说明了
//为什么jdk动态代理必须基于接口
public final class $Proxy0 extends Proxy implements IFileCopyService {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler var1) throws {super(var1);}public final boolean equals(Object var1) throws {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void copy(String var1, String var2) throws IOException {try {super.h.invoke(this, m3, new Object[]{var1, var2});} catch (RuntimeException | IOException | Error var4) {throw var4;} catch (Throwable var5) {throw new UndeclaredThrowableException(var5);}}public final int hashCode() throws {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("com.example.demo.IFileCopyService").getMethod("copy", Class.forName("java.lang.String"), Class.forName("java.lang.String"));m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}
从代码中,我们看到,$Proxy0源码中的copy方法:
public final void copy(String var1, String var2) throws IOException {try {//下面这个方法调用的就是super.h.invoke(this, m3, new Object[]{var1, var2});} catch (RuntimeException | IOException | Error var4) {throw var4;} catch (Throwable var5) {throw new UndeclaredThrowableException(var5);}}
这个supper.h.invoke Proxy中的 h 的 invoke 方法,即InvocationHandler.invoke也就是上面 FileCopyInvocationHandler .invoke 方法,这就是 jdk 的动态代理。
由于JDK的动态代理必须是基于接口的,如果是类的话,就没办法了.而cglib 动态代理就可解决关于类的动态代理。
还是基于上面的例子,我们用cglib来实现类的动态代理。
首先,加入cglib包,maven下可以引入:
cglib cglib 3.3.0
第二,我们编写一个基于字符流进行文本copy的类:
public class CharCopyServiceImpl {public void copy(String srcPath, String destPath) throws IOException {File src = new File(srcPath);File dest = new File(destPath);if (!src.isFile()) {throw new RuntimeException("拷贝的源文件不是文件类型,请检查源文件");}String encoding = "utf-8";Reader reader = new InputStreamReader(new FileInputStream(src), encoding);BufferedReader bufferedReader = new BufferedReader(reader);Writer writer = new OutputStreamWriter(new FileOutputStream(dest), encoding);BufferedWriter bufferedWriter = new BufferedWriter(writer);String lineTxt = null;while ((lineTxt = bufferedReader.readLine()) != null) {bufferedWriter.write(lineTxt);bufferedWriter.newLine();}bufferedReader.close();reader.close();bufferedWriter.close();writer.close();}
}
第三,编写一个拦截器
public class CglibProxyInterceptor implements MethodInterceptor {public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {long startTime = System.currentTimeMillis();Object object = methodProxy.invokeSuper(o, objects);long endTime = System.currentTimeMillis();System.out.println(method.getName() + "执行时长:" + (endTime - startTime));return object;}
}
第四,编写一个测试类:
public class DynamicProxyCglibTest {public static void main(String[] args) throws IOException {String userDir = System.getProperty("user.dir");System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, userDir);String srcPath = "d:/data.txt";String destPath = "d:/data_copy.txt";Enhancer enhancer = new Enhancer();enhancer.setSuperclass(CharCopyServiceImpl.class);enhancer.setCallback(new CglibProxyInterceptor());CharCopyServiceImpl charCopyService = (CharCopyServiceImpl) enhancer.create();charCopyService.copy(srcPath, destPath);}
}
执行结果如下:
CGLIB debugging enabled, writing to 'D:\workspace\workspace\demo'
copy执行时长:18
这里我们看到,它生成了三个类:
CharCopyServiceImpl$$EnhancerByCGLIB$$5fa596f4
CharCopyServiceImpl$$EnhancerByCGLIB$$5fa596f4$$FastClassByCGLIB$$b70f6486
CharCopyServiceImpl$$FastClassByCGLIB$$ba69bbb0
我们主要看下
CharCopyServiceImpl$$EnhancerByCGLIB$$5fa596f4
这个类。
代码有省略:
//继承了CharCopyServiceImpl类
public class CharCopyServiceImpl$$EnhancerByCGLIB$$5fa596f4 extends CharCopyServiceImpl implements Factory {final void CGLIB$copy$0(String var1, String var2) throws IOException {super.copy(var1, var2);}//重写了copy方法,拦截器调用intercept()方法,intercept()方法由我自定义CglibProxyInterceptor 实现,调用intercept()方法,从而完成了由代理对象访问到目标对象的动态代理实现。public final void copy(String var1, String var2) throws IOException {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {var10000.intercept(this, CGLIB$copy$0$Method, new Object[]{var1, var2}, CGLIB$copy$0$Proxy);} else {super.copy(var1, var2);}}}
首先,用 CGlib 生成代理类是目标类的子类;
第二,用 CGlib 生成 代理类不需要接口;
第三,用 CGLib 生成的代理类会重写了父类的各个方法;
第四,拦截器中的 intercept 方法内容正好就是代理类中的方法体。