[SSM]GoF之代理模式

目录

十四、GoF之代理模式

14.1对代理模式的理解

14.2静态代理

14.3动态代理

14.3.1JDK动态代理

14.3.2CGLIB动态代理


十四、GoF之代理模式

14.1对代理模式的理解

  • 场景:拍电影的时候,替身演员去代理演员完成表演。这就是一个代理模式。

  • 演员为什么要找替身呢?(为什么要使用代理模式?)

    • 怕自己受伤。(保护自己)

    • 自己完成不了高难度动作。(功能增强)

  • 在java程序中代理模式的作用:

    • 当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为。

    • 需要给某个对象的功能进行增强的时候,可以考虑找一个代理进行增强。

    • A对象无法和B对象直接交互,也可以使用代理模式来解决。

  • 代理模式中有三个角色:

    • 目标对象(演员)

    • 代理对象(替身演员)

    • 目标对象和代理对象的公共接口。(演员和替身演员应该具有相同的行为动作)

  • 为什么演员和替身演员要有相同的行为动作呢?

    • 不想让观众知道是替身演员,这里的观众其实就是“客户端程序”。

  • 使用代理模式,对于客户端程序来说,客户端是无法察觉到的,客户端在使用代理对象的时候就像在使用目标对象。

  • 代理模式是GoF23种设计模式之一,属于结构化设计模式。

 

  • 代理模式在代码实现上,包括两种形式:

    • 静态代理

    • 动态代理

14.2静态代理

OrderService接口

package com.hhb.proxy.service;
​
//订单业务接口
public interface OrderService {//代理对象和目标对象的公共接口
    //生产订单
    void generate();
​
    //修改订单信息
    void modify();
​
    //查看订单详情
    void detail();
}

OrderServiceImpl

package com.hhb.proxy.service;
​
public class OrderServiceImpl implements OrderService {
    /**
     * 问题:统计所有业务接口的每一个业务方法的耗时。
     * 解决方案一:硬编码,在每一个业务接口中的每一个业务方法中直接添加统计耗时的程序
     * 缺点:1.违背OCP开闭原则 2.代码没有得到复用
     * 解决方案二:编写业务类的子类,让子类继承业务类,对每个业务方法进行重写
     * 缺点:1.虽然解决了OCP开闭原则,但是代码耦合度很高,因为采用了继承关系。
     *      2.代码没有得到复用
     * 解决方案三:代理模式
     * 优点:1.解决了OCP问题 2.采用代理模式的has a,可以降低耦合度
     *
     * 目前使用的是静态代理,这个静态代理的缺点是:类爆炸
     * 解决方法:使用动态代理模式来解决这个问题。
     * 动态代理还是代理模式,只不过添加了字节码生成技术,可以在内存中为我们动态生成一个class字节码,这个字节码就是代理类
     * 在内存中动态生成字节码代理类的技术叫做:动态代理
     */
    @Override
    public void generate() {//目标方法
        //模拟生成订单的耗时
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }
​
    @Override
    public void modify() {//目标方法
        //模拟修改订单的耗时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
​
    @Override
    public void detail() {//目标方法
        //模拟查询订单的耗时
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("请看订单详情");
    }
}

OrderServiceProxy

package com.hhb.proxy.service;
​
//代理对象(代理对象和目标对象要具有相同的行为,就要实现同一个或同一些接口)
//客户端在使用代理对象的时候就像在使用目标对象一样。
public class OrderServiceProxy implements OrderService {
    //将目标对象作为代理对象的一个属性,这种关系叫做关联关系,比继承关系的耦合度低
    //代理对象中含有目标对象的引用。关联关系:has a
    //注意:这里要写一个公共接口类型,因为公共接口耦合度低
    private OrderService target;//这就是目标对象,目标对象一定实现了OrderService接口
    //创建代理对象的时候,传一个目标对象给代理对象。
    public OrderServiceProxy(OrderService target) {
        this.target = target;
    }
​
    @Override
    public void generate() {//代理方法
        //增强
        long begin = System.currentTimeMillis();
        //调用目标对象的目标方法
        target.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "");
    }
​
    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        target.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "");
    }
​
    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        target.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "");
    }
}

客户端

package com.hhb.proxy.client;
​
import com.hhb.proxy.service.OrderServiceImpl;
import com.hhb.proxy.service.OrderServiceProxy;
​
public class Test {
    public static void main(String[] args) {
        //创建目标对象
        OrderServiceImpl target = new OrderServiceImpl();
        //创建代理对象
        OrderServiceProxy proxy = new OrderServiceProxy(target);
        //调用代理对象的代理方法
        proxy.generate();
        proxy.modify();
        proxy.detail();
    }
}
  • 以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口。

  • OrderServiceImpl是目标类,OrderServiceProxy是代理类。

14.3动态代理

  • 在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用的问题。

  • 在内存当中动态生成类的技术常见的包括:

    • JDK动态代理技术:只能代理接口。

    • CGLIB动态代理技术:CGLIB是一个开源项目,是一个强大的、高性能、高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)

    • Javassist动态代理技术。

14.3.1JDK动态代理

OrderService接口

package com.hhb.proxy.service;
​
//订单业务接口
public interface OrderService {//代理对象和目标对象的公共接口
    //生产订单
    void generate();
​
    //修改订单信息
    void modify();
​
    //查看订单详情
    void detail();
}

OrderServiceImpl

package com.hhb.proxy.service;
​
public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {//目标方法
        //模拟生成订单的耗时
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }
​
    @Override
    public void modify() {//目标方法
        //模拟修改订单的耗时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
​
    @Override
    public void detail() {//目标方法
        //模拟查询订单的耗时
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("请看订单详情");
    }
}
  • 在动态代理中,OrderServiceProxy代理类是可以动态生成的,这个类不需要写,直接写客户端程序即可。

Client

package com.hhb.proxy.client;
​
import com.hhb.proxy.service.OrderService;
import com.hhb.proxy.service.OrderServiceImpl;
import com.hhb.proxy.service.TimerInvocationHandler;
import com.hhb.proxy.util.ProxyUtil;
​
import java.lang.reflect.Proxy;
​
public class Client {
    //客户端程序
    public static void main(String[] args) {
        //创建目标对象
        OrderService target = new OrderServiceImpl();
        //创建代理对象
       /* OrderService proxyObj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target));*/
        //使用工具类
        OrderService proxyObj = (OrderService) ProxyUtil.newProxyInstance(target);
        //调用代理对象的代理方法
        proxyObj.modify();
        proxyObj.detail();
        proxyObj.generate();
        String name = proxyObj.getName();
        System.out.println(name);
    }
}
  • 创建代理对象

    • newProxyInstance翻译为:新建代理对象,也就是说,通过调用这个方法可以创建代理对象。本质上,这个Proxy.newProxyInstance()方法的执行做了两件事:

      • 在内存中动态的生成了一个代理类的字节码class。

      • new对象了,通过内存中生成的代理类这个代码,实例化了代理对象。

    • 关于newProxyInstance()方法的三个重要的参数:

      • 第一个参数:ClassLoader loader

        • 它是类加载器。在内存中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器,所以这里需要指定类加载器。并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。

      • 第二个参数:Class<?>[] interfaces

        • 代理类和目标类要实现同一个接口或同一些接口。

        • 在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。

      • 第三个参数:InvocationHandler h

        • InvocationHandler 被翻译为:调用处理器,是一个接口。

        • 在调用处理器接口中编写的就是:增强代码。

        • 既然是接口,就要写接口的实现类。

TimerInvocationHandler

package com.hhb.proxy.service;
​
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
​
public class TimerInvocationHandler implements InvocationHandler {
    //目标对象
    private Object target;
​
    public TimerInvocationHandler(Object target) {
        this.target = target;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       //这个接口的作用是写增强代码
        long begin = System.currentTimeMillis();
        //调用目标对象上的目标方法
        //方法四要素:哪个对象,哪个方法,传什么参数,返回什么值
        Object retValue = method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "毫秒");
        //注意:这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回
        return retValue;
    }
}
  • 为什么要强行要求你必须实现InvocationHandler接口?

    • 因为一个类实现接口就必须实现接口中的方法。

    • 方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。

    • 注意:invoke方法不是程序员调用的,是JDK负责调用。

  • invoke方法什么时候被调用?

    • 当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。

  • invoke方法的三个参数

    • invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数,可以在invoke方法的大括号中直接使用。

    • 第一个参数:Object proxy 代理对象的引用,这个参数使用较少。

    • 第二个参数:Method method 目标对象上的目标方法。

    • 第三个参数:Object[] args 目标方法上的实参。

    • invoke方法执行过程中,使用method来调用目标对象的目标方法。

工具类:ProxyUtil

package com.hhb.proxy.util;
​
import com.hhb.proxy.service.TimerInvocationHandler;
​
import java.lang.reflect.Proxy;
​
public class ProxyUtil {
    /**
     * 封装一个工具方法,可以通过这个方法获取代理对象
     *
     * @param target
     * @return
     */
    public static Object newProxyInstance(Object target) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target));
    }
}

14.3.2CGLIB动态代理

  • CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现,所以被代理的目标类不能使用final修饰。

引入依赖

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>

UserService

package com.hhb.proxy.service;
​
//目标类
public class UserService {
    //目标方法
    public boolean login(String username, String password) {
        System.out.println("系统正在验证身份");
        if ("admin".equals(username) && "123".equals(password)) {
            return true;
        }
        return false;
    }
​
    //目标方法
    public void logout() {
        System.out.println("系统正在退出");
    }
}
  • 使用CGLIB在内存中为UserService类生成代理类,并创建对象:

Client

package com.hhb.proxy.client;
​
import com.hhb.proxy.service.TimerMethodInterceptor;
import com.hhb.proxy.service.UserService;
import net.sf.cglib.proxy.Enhancer;
​
public class Client {
    public static void main(String[] args) {
        //创建字节码增强器对象
        //这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类
        Enhancer enhancer = new Enhancer();
​
        //告诉CGLIB父类是谁,告诉CGLIB目标类是谁
        enhancer.setSuperclass(UserService.class);
​
        //设置回调(等同于JDK动态代理当中的调用处理器。InvocationHandler)
        //在CGLIB当中不是InvocatioHandler接口,是方法拦截器:MethodInterceptor
        enhancer.setCallback(new TimerMethodInterceptor());
​
        //创建代理对象
        //1.在内存中生成UserService类的子类,其实就是代理类的字节码。
        //2.创建代理对象
        UserService userServiceProxy = (UserService) enhancer.create();
​
        //调用代理对象的代理方法
        boolean succes = userServiceProxy.login("admin", "123");
        System.out.println(succes ? "登录成功" : "登录失败");
​
        userServiceProxy.logout();
    }
}

编写MethodInterceptor接口实现类

package com.hhb.proxy.service;
​
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
​
import java.lang.reflect.Method;
​
public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //前面增强
        long begin = System.currentTimeMillis();
​
        //调用目标对象的目标方法
        Object retValue = methodProxy.invokeSuper(target, objects);
​
        //后面增强
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "毫秒");
​
        return retValue;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/54221.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数控机床主轴品牌选择及选型,如何维护和保养?

数控机床主轴品牌选择及选型&#xff0c;如何维护和保养&#xff1f; 数控机床是一种高精度、高效率、高自动化的机床。其中&#xff0c;主轴是数控机床的核心部件&#xff0c;承担着转动工件、切削加工的任务&#xff0c;决定了加工的转速、切削力度和加工效率。因此&#xff…

解决多线程环境下单例模式同时访问生成多个实例

如何满足单例&#xff1a;1.构造方法是private、static方法、if语句判断 ①、单线程 Single类 //Single类&#xff0c;定义一个GetInstance操作&#xff0c;允许客户访问它的唯一实例。GetInstance是一个静态方法&#xff0c;主要负责创建自己的唯一实例 public class LazySi…

八大排序算法--快速排序(动图理解)

快速排序 概念 快速排序是对冒泡排序的一种改进。其基本原理是通过选取一个基准元素&#xff0c;将数组划分为两个子数组&#xff0c;分别对子数组进行排序&#xff0c;最终实现整个数组的有序排列。快速排序的时间复杂度最好为O(nlogn)&#xff0c;最坏为O(n^2)&#xff0c;…

Jupyter Notebook 7重磅发布,新增多个特性!

本文分享Jupyter Notebook大版本v7.0.0更新亮点&#xff0c;及简单测试&#xff01; 近日&#xff0c;Jupyter Notebook大版本v7.0.0更新&#xff0c;Jupyter Notebook 7基于JupyterLab&#xff0c;因此它包含了过去几年JupyterLab中添加的许多新功能和改进&#xff0c;部分亮…

学习笔记——压力测试案例,监控平台

测试案例 # 最简单的部署方式直接单机启动 nohup java -jar lesson-one-0.0.1-SNAPSHOT.jar > ./server.log 2>&1 &然后配置执行计划&#xff1a; 新建一个执行计划 配置请求路径 配置断言配置响应持续时间断言 然后配置一些查看结果的统计报表或者图形 然后我…

Linux之 centos、Ubuntu 安装常见程序 (-) Mysql 5.7 版本和8.0版本

CentOS 安装 MySql 注意 需要有root权限 安装5.7版本 – 由于MySql并不在CentOS的官方仓库中&#xff0c;所以需要通过rmp命令&#xff1a; 导入MySQL仓库密钥 1、配置MySQL的yum仓库 配置yum仓库 更新密钥 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 安装…

json-server详解

零、文章目录 json-server详解 1、简介 Json-server 是一个零代码快速搭建本地 RESTful API 的工具。它使用 JSON 文件作为数据源&#xff0c;并提供了一组简单的路由和端点&#xff0c;可以模拟后端服务器的行为。github地址&#xff1a;https://github.com/typicode/json-…

【SLAM】LoFTR知多少

1. LoFTR: Detector-Free Local Feature Matching with Transformers PAPER 论文 | LoFTR: Detector-Free Local Feature Matching with Transformers 代码 | CODE: 关键词 | detector-free, local feature matching LoFTR知多少 1. LoFTR: Detector-Free Local Feature M…

Docker创建tomcat容器实例后无法访问(HTTP状态 404 - 未找到)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

华为数通HCIA-ARP(地址解析协议)详细解析

地址解析协议 (ARP) ARP &#xff08;Address Resolution Protocol&#xff09;地址解析协议&#xff1a; 根据已知的IP地址解析获得其对应的MAC地址。 ARP&#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff09;是根据IP地址获取数据链路层地址的一个…

Pytorch深度学习-----神经网络之卷积层用法详解

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

【Github】自动监测 SSL 证书过期的轻量级监控方案 - Domain Admin

在现代的企业网络中&#xff0c;网站安全和可靠性是至关重要的。一个不注意的SSL证书过期可能导致网站出现问题&#xff0c;给公司业务带来严重的影响。针对这个问题&#xff0c;手动检测每个域名和机器的证书状态需要花费大量的时间和精力。为了解决这个问题&#xff0c;我想向…

ava版知识付费平台免费搭建 Spring Cloud+Spring Boot+Mybatis+uniapp+前后端分离实现知识付费平台

提供私有化部署&#xff0c;免费售后&#xff0c;专业技术指导&#xff0c;支持PC、APP、H5、小程序多终端同步&#xff0c;支持二次开发定制&#xff0c;源码交付。 Java版知识付费-轻松拥有知识付费平台 多种直播形式&#xff0c;全面满足直播场景需求 公开课、小班课、独…

运行vue项目显示找不到vue-cli

直接下载ruoyi源码到本地&#xff0c;启动ruoyi-ui的时候报错&#xff1a; 原来是电脑没配置nodejs。 所以先去官网下载nodejs&#xff0c;然后安装完之后&#xff0c;在命令行窗口输入&#xff1a; 显示安装成功。 但还没有结束&#xff0c;还要配置npm的全局模块的存放路径…

Debian/Ubuntu 安装 Chrome 和 Chrome Driver 并使用 selenium 自动化测试

截至目前&#xff0c;Chrome 仍是最好用的浏览器&#xff0c;没有之一。Chrome 不仅是日常使用的利器&#xff0c;通过 Chrome Driver 驱动和 selenium 等工具包&#xff0c;在执行自动任务中也是一绝。相信大家对 selenium 在 Windows 的配置使用已经有所了解了&#xff0c;下…

哔哩哔哩缓存转码|FFmpeg将m4s文件转为mp4|PHP自动批量转码B站视频

window下载安装FFmpeg 打开ffMpeg官网选择window>Windows builds from gyan.dev 打开https://www.gyan.dev/ffmpeg/builds/ 这里是上面提取的下载链接如果过期不能用自己去官网下 配置FFmpeg环境变量 上面下载的FFmpeg是绿色软件&#xff0c;下载解压到你的常用软件安装目…

《水经注地图服务》发布的影像数据如何在OsgEarth中调用

OsgEarth 是一个用于OpenSceneGraph (OSG)的可扩展地形渲染工具包&#xff0c;它是一个开源、高性能、3D 图形工具包。 只需创建一个简单的 XML 文件&#xff0c;将其指向您的图像、高程和矢量数据&#xff0c;将其加载到您最喜欢的 OSG 应用程序中&#xff0c;然后开始&#…

有赞商城无需代码连接美团开店宝的方法

1 使用场景 大部分的商家都会美团开店宝来作为线上店铺运营的营销服务平台&#xff0c;当有用户下单时&#xff0c;商家都希望第一时间知晓用户信息&#xff0c;但经常缺少数据依据&#xff0c;无法构建精确的客户画像&#xff0c;导致很多线索白白流失&#xff0c;难以做出有效…

在windows下安装ruby使用gem

在windows下安装ruby使用gem 1.下载安装ruby环境2.使用gem3.gem换源 1.下载安装ruby环境 ruby下载地址 选择合适的版本进行下载和安装&#xff1a; 在安装的时候&#xff0c;请勾选Add Ruby executables to your PATH这个选项&#xff0c;添加环境变量&#xff1a; 安装Ruby成…

在windows上安装minio

1、下载windows版的minio&#xff1a; https://dl.min.io/server/minio/release/windows-amd64/minio.exe 2、在指定位置创建一个名为minio文件夹&#xff0c;然后再把下载好的文件丢进去&#xff1a; 3、右键打开命令行窗口&#xff0c;然后执行如下命令&#xff1a;(在minio.…