所谓代理模式,就是在不改变原始类(被代理类)的情况,使用代理类给原始类附加功能。附加的功能基本是与原始类的业务不想关的功能,即一些非功能性的需求,比如监控、统计、事务、限流等。其中代理类和被代理要实现同一个接口或者共同继承某个类。
从上图我们看出,代理类和原始类都实现了同一个接口,即都是同一种类型,同时代理类中引用了原始类作为属性,这样就在调用方法的时候做了增强。
代理分为静态代理和动态代理,下面我们分别看下。
示例说明:我要有两种方式(字节流和缓冲字节流来复制文本),顺便计算两种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下可以引入:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
第二,我们编写一个基于字符流进行文本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 方法内容正好就是代理类中的方法体。