讲解java代理模式

何谓代理模式

代理模式,即Proxy Pattern,23种java常用设计模式之一。代理模式提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

通俗来说,代理模式就相当于我们现实生活中的中介。举个例子,比如我们想要购买某件东西,比如衣服,电脑等,我们一般都不会选择直接去练习厂家购买,而是从厂家的代理商那里去买。比如我们在淘宝上买东西,淘宝肯定是不生产衣服的,他只是在我们和厂商之间搭桥,这里就可以认为淘宝起的就是代理的作用。

那么在我们软件开发中,如何应用代理模式呢?他又具体分为哪些呢?

代理模式是面向对象编程中比较常见的一种设计模式,这是常见代理模式的UML示意图:

在代理模式中:

  • 用户只关心具体的功能,而并不关心到底是谁提供的,即被代理的接口Subject
  • 上图得知,真正实现者是RealSubject,但是他并不会与用户直接接触,而是交给Proxy代理
  • 代理就是上图中的Proxy,它也实现了Subject 接口,所以它能够直接与用户接触
  • 用户调用Proxy的时候,其实内部调用了RealSubject,所以Proxy相当于中介,它可以增强RealSubject操作

代理模式的作用:

  1. 功能增强:原有功能添加额外功能
  2. 控制访问:代理类不让你访问目标

在java中,实现代理模式主要有两种方式:静态代理,动态代理,下面逐一进行讲解。


这里我先声明几个例类,方便下面演示:

  • 创建一个接口类UserDao

    package com.soberw.example.dao;
    
    /**
    * @author soberw
    * @Classname UserDao
    * @Description
    * @Date 2022-02-13 13:50
    */
    public interface UserDao {
    void show();
    }
  • 创建一个实现类UserDaoImpl,即被代理的目标对象

    package com.soberw.example.dao;
    
    /**
    * @author soberw
    * @Classname UserDaoImpl
    * @Description
    * @Date 2022-02-13 13:51
    */
    public class UserDaoImpl implements UserDao{
    //实现了某一功能
    @Override
    public void show() {
    System.out.println(" show something ...... ");
    }
    }

静态代理

之所以是静态代理,是因为他是在事先预定好的,即程序在运行之前,我们就确定了委托对象,通过代理类,我们可以实现在不改变原来功能的基础上,对原有接口功能的功能进行再拓展。

以上面例子说明:

  • 此时如果调用show()方法自然会正常执行方法流程

    创建测试方法执行:

    @Test
    public void testProxy1(){
    UserDao userDaoImpl = new UserDaoImpl();
    userDaoImpl.show();
    }

  • 但是现在想在方法中添加新的实现功能,我们这时候当然可以直接去修改实现类UserDaoImpl的源码,但是会导致:

      1. 源码可能改动比较多
      1. 重复代码比较多
      1. 代码维护性低
  • 因此我们可以使用代理完成对功能的拓展,那么如何通过静态代理实现呢?

  • 使用静态代理,需要我们代理对象和目标对象都实现同样的接口

  • 因此,这里我声明一个代理类UserProxyStatic

    package com.soberw.example.proxy;
    
    import com.soberw.example.dao.UserDao;
    
    /**
    * @author soberw
    * @Classname UserProxy
    * @Description
    * @Date 2022-02-13 13:54
    */
    public class UserProxyStatic implements UserDao {
    private UserDao userDao; public UserProxyStatic(UserDao userDao){
    this.userDao = userDao;
    } @Override
    public void show() {
    //加入额外的拓展功能
    System.out.println("something in show() before...");
    userDao.show();
    System.out.println("something in show() after...");
    }
    }
  • 测试执行:

    @Test
    public void testProxy2(){
    UserDao userDaoImpl = new UserDaoImpl();
    UserDao userProxy = new UserProxyStatic(userDaoImpl);
    userProxy.show();
    }

    这就实现了对原有功能的拓展,并且不改变源码。

总结一下:使用静态代理可以在不修改目标对象的前提下拓展目标对象的功能

但是其确定也非常明显:

  • 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
  • 不易维护。一旦原接口增加方法,目标对象与代理对象都要进行修改。

动态代理

于是为了解决静态代理的一些弊端,就有了动态代理,将程序的执行抉择放到了运行时,动态地创建代理对象,从而实现对目标对象的代理功能。

静态代理与动态代理的主要区别在于:

  • 静态代理在编译时已经实现,编译后会生成对应的实际的class文件
  • 动态代理是在运行时产生的,是在运行时动态的生成类字节码,并加载到了JVM中
  • 动态代理实际上用的就是反射的机制

而实现动态代理的方式又可分为两种:

  • JDK动态代理
  • CGLIB动态代理

JDK动态代理

JDK动态代理,实际上就是使用java官方给我们提供的一些API去实现的,涉及到的类都存在于java.lang.reflect包下

Proxy 核心类,通过调用此类的newInstance()静态方法生成代理类

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

参数说明:

  • ClassLoader loader:类加载器,通过反射获取对象并向内存中加载
  • Class<?>[] interfaces:接口集合,增强方法所在的类,即目标对象所实现的接口
  • InvocationHandler h:具体要完成哪些功能,即代理对象

返回值说明:

返回指定的接口的一个代理类实例对象

InvocationJandler 调用处理器,内部就一个方法invoke(),表示代理对象要执行的具体的方法,相当于中介类,调用目标方法,可在invoke()内部写入我们的增强功能:

public Object invoke(Object proxy, Method method, Object[] args)

参数说明:

  • Object proxy:代理的对象
  • Method method:代理对象调用的方法
  • Object[] args:方法参数

返回代理的对象

Method 协助代理类完成方法的调用操作,主要用的是内部的invoke()方法,注意,该invoke方法时具体的方法,而上面的invoke()是一个接口方法:

public Object invoke(Object obj, Object... args)

参数说明:

  • Object obj:调用的对象
  • Object... args:方法参数

返回方法的返回值

实际上:

如果我们的目标对象中有多个方法都需要增强业务,我们可以通过method.getName()方法获取方法名,然后根据不同的方法完成不同的操作逻辑。


具体实现如下:

  • 创建一个类作为代理类UserDaoProxy:

    package com.soberw.example.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy; /**
    * @author soberw
    * @Classname UserDaoProxy
    * @Description
    * @Date 2022-02-13 17:13
    */
    public class UserDaoProxy { private UserDaoProxy(){ }
    //传入目标对象,返回代理后的对象
    public static Object getProxyInstance(Object obj) {
    //调用代理类,传入参数,其中包括一个内部匿名类
    return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //在方法之前加入额外功能
    System.out.println("something in show() before..."); //调用原本的方法,并接收返回值
    Object result = method.invoke(obj, args); //在方法之后加入额外功能
    System.out.println("something in show() after..."); //返回新方法的返回值
    return result;
    }
    });
    }
    }
  • 测试起来也很简单:

    @Test
    public void testProxy3(){
    //创建目标对象实例并传入即得到代理对象
    UserDao userDao = new UserDaoImpl();
    UserDao dao = (UserDao) UserDaoProxy.getProxyInstance(userDao);
    dao.show();
    }

  • 但是实际上我们一般也不会去创建内部匿名类,下面提供另外一种写法:

  • 创建一个InvocationJandler的实现类UserProxyDynamic

    package com.soberw.example.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy; /**
    * @author soberw
    * @Classname UserProxyDynamic
    * @Description
    * @Date 2022-02-13 15:56
    */
    public class UserProxyDynamic implements InvocationHandler {
    //创建的是谁的代理对象,就把谁传递进来
    Object obj;
    //有参数构造传递
    public UserProxyDynamic(Object obj){
    this.obj = obj;
    } //增强的逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //在方法之前加入额外功能
    System.out.println("something in show() before..."); //调用原本的方法,并接收返回值
    Object result = method.invoke(obj, args); //在方法之后加入额外功能
    System.out.println("something in show() after..."); //返回新方法的返回值
    return result;
    }
    }
  • 进行测试:

    @Test
    public void testProxy4(){
    //创建目标对象实例并传入即得到代理对象
    UserDao userDao = new UserDaoImpl();
    UserDao dao = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), new UserProxyDynamic(userDao));
    dao.show();
    }

动态代理对象不需要实现接口,但是要求目标对象必须实现接口InvocationJandler,否则不能使用动态代理。

CGLIB动态代理

CGLIB(Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

前面说到限制动态代理的对象必须实现一个或多个接口,而使用CGLIB就不用,真正的达到了代理类无侵入

使用CGLIB需要引入CGLIB的jar包,或者如果你是maven项目,引入依赖:

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

还是以实现上面例子为目标:

  • 创建一个代理对象类UserDaoCGLIB,实现CGLIB提供的类MethodInterceptor

    package com.soberw.example.proxy;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /**
    * @author soberw
    * @Classname UserDaoCGLIB
    * @Description
    * @Date 2022-02-13 18:42
    */
    public class UserDaoCGLIB implements MethodInterceptor {
    private Object obj; public UserDaoCGLIB(Object obj) {
    this.obj = obj;
    } public Object getProxyInstance() {
    //工具类
    Enhancer en = new Enhancer();
    //设置父类
    en.setSuperclass(obj.getClass());
    //设置回调函数
    en.setCallback(this);
    //创建子类对象代理
    return en.create();
    } @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    //在方法之前加入额外功能
    System.out.println("something in show() before..."); //调用原本的方法,并接收返回值
    Object result = method.invoke(obj, objects); //在方法之后加入额外功能
    System.out.println("something in show() after..."); //返回新方法的返回值
    return result;
    }
    }
  • 测试:

    @Test
    public void testProxy5(){
    UserDaoImpl userDao = new UserDaoImpl();
    UserDao dao = (UserDao) new UserDaoCGLIB(userDao).getProxyInstance();
    dao.show();
    }

CGLIB代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但CGLIB会继承目标对象,需要重写方法,所以目标对象不能为final类。

但是毋庸置疑的是,CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。

它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。

浅谈java代理模式的更多相关文章

  1. 浅谈Java代理二:Cglib动态代理-MethodInterceptor

    浅谈Java代理二:Cglib动态代理-MethodInterceptor CGLib动态代理特点: 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生 ...

  2. 浅谈Java代理一:JDK动态代理-Proxy.newProxyInstance

    浅谈Java代理一:JDK动态代理-Proxy.newProxyInstance java.lang.reflect.Proxy:该类用于动态生成代理类,只需传入目标接口.目标接口的类加载器以及Inv ...

  3. 浅谈java性能分析

    浅谈java性能分析,效能分析 在老师强烈的要求下做了效能分析,对上次写过的词频统计的程序进行分析以及改进. 对于效能分析:我个人很浅显的认为就是程序的运行效率,代码的执行效率等等. java做性能测 ...

  4. !! 浅谈Java学习方法和后期面试技巧

    浅谈Java学习方法和后期面试技巧 昨天查看3303回复33 部落用户大酋长 下面简单列举一下大家学习java的一个系统知识点的一些介绍 一.java基础部分:java基础的时候,有些知识点是非常重要 ...

  5. 浅谈java类集框架和数据结构(2)

    继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...

  6. 浅谈Java线程安全

    浅谈Java线程安全 - - 2019-04-25    17:37:28 线程安全 Java中的线程安全 按照线程安全的安全程序由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类 ...

  7. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  8. 浅谈Java中的equals和==(转)

    浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str ...

  9. 浅谈Java中的对象和引用

    浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...

随机推荐

  1. 深入 Laravel 内核之外观模式(门面模式)

    门面模式核心内容: 客户端与子系统的通信通过外观对象进行: 外观对象封装一系列子系统的具体对应方法,对客户端只需暴露一个单一的入口方法: 客户端通过访问外观对象即可调用子系统的基础方法,无需关心子系统 ...

  2. centos6.5-rsync+inotify

    一.目的 通过监控192.168.3.10的目录,实现实时同步. 实验环境 centos1       192.168.3.10 centos2       192.168.3.11 二.配置 cen ...

  3. sqlsugar freesql hisql 三个ORM框架性能测试对比

    hisql与目前比较流行的ORM框架性能测试对比 总体测试结果 插入记录数 hisql(耗时) sqlsugar(耗时) freesql(耗时) 5条 0.0107秒 0.0312秒 0.02675秒 ...

  4. Appium安装部署

    一.安装JDK 下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html JD ...

  5. CentOS 7 如何清空文件内容

    https://www.cnblogs.com/zqifa/p/linux-vim-4.html 方法1.在非编辑状态下使用快捷键gg跳至首行头部,再使用dG即可清空,或 输入"%d&quo ...

  6. gitlab修改(重置)root用户密码

    gitlab修改(重置)root用户密码 1.使用root权限登录到服务器. 2.使用以下命令启动控制台:  gitlab-rails console production 该命令有时候启动比较慢,需 ...

  7. Go语言系列之网络编程

    现在我们几乎每天都在使用互联网,我们前面已经学习了如何编写Go语言程序,但是如何才能让我们的程序通过网络互相通信呢?本章我们就一起来学习下Go语言中的网络编程. 关于网络编程其实是一个很庞大的领域,本 ...

  8. [论文翻译] 分布式训练 Parameter sharding 之 ZeRO

    [论文翻译] 分布式训练 Parameter sharding 之 ZeRO 目录 [论文翻译] 分布式训练 Parameter sharding 之 ZeRO 0x00 摘要 0x01 综述 1.1 ...

  9. vue3知识点的自我总结

    1. 我们对ref的错误理解 ref 经常去监听基本数据类型. 同时也可以去监听[数组][对象]都是可以的. ref是深度的监听.并不是大家说的那样不能去监听复杂的数据类型. 只是根据我们推荐ref去 ...

  10. 极客大挑战2019 http

    极客大挑战 http referer 请求头 xff 1.查看源码,发现secret.php 2.提示要把来源改成Sycsecret.buuoj.cn,抓包,添加Referer Referer:htt ...