深入理解java的Proxy-静态代理和动态代理(附代码演示)

1.什么是代理模式

所谓代理模式,就是在不改变原始类(被代理类)的情况,使用代理类给原始类附加功能。附加的功能基本是与原始类的业务不想关的功能,即一些非功能性的需求,比如监控、统计、事务、限流等。其中代理类和被代理要实现同一个接口或者共同继承某个类。
在这里插入图片描述
从上图我们看出,代理类和原始类都实现了同一个接口,即都是同一种类型,同时代理类中引用了原始类作为属性,这样就在调用方法的时候做了增强。

2.代理的分类

代理分为静态代理和动态代理,下面我们分别看下。

2.1 静态代理示例及定义

示例说明:我要有两种方式(字节流和缓冲字节流来复制文本),顺便计算两种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 这个类。
代理类扩展了目标对象的功能,在客户端和目标对象之前起到了一个中介的作用,一定程度上降低了系统的耦合度。但是代理类要和目标对象实现同样的接口,自然就会产生很多代理类,同时,接口一旦增加新的方法,那么代理类也要做实现,这样就需要维护多个实现类。

2.2 动态代理示例及定义

动态代理是相对静态代理来说的,与静态代理类不同,动态代理类的字节码是在程序运行时由java反射机制动态生成的,不需要程序员手动编写代理类源码。所以动态代理不仅简化了编码工作,还提高了程序的可扩展性。

2.2.1 JDK的动态代理类

我们可以用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 动态代理是怎么实现的。

2.2.2 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 的动态代理。

2.2.3 cglib动态代理

由于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);
        }
    }
    }

2.2.4 cglib生成动态代理类的特点

首先,用 CGlib 生成代理类是目标类的子类;
第二,用 CGlib 生成 代理类不需要接口;
第三,用 CGLib 生成的代理类会重写了父类的各个方法;
第四,拦截器中的 intercept 方法内容正好就是代理类中的方法体。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值