[SSM]GoF之代理模式

news2024/11/17 5:22:05

目录

十四、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.coloradmin.cn/o/804387.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

爬虫005_python类型转换_其他类型转换为整型_转换为Float类型_转换为字符串_转换为布尔值---python工作笔记023

首先来看,字符串转换成int 很简单 float转换成int 会把小数点后面的内容丢掉 boolean转换为int true是1 false 是0 然后字符串转换为int,要注意 不能有特殊字符比如1.23 中有点 就报错 上面字符串12ab,有ab也报错 看上面

向量与流迭代器(istream_iterator和ostream_iterator)

运行代码&#xff1a; //向量与流迭代器 #include"std_lib_facilities.h"struct Item {string name;int iid;double value;friend istream& operator>>(istream& is, Item& ii);friend ostream& operator<<(ostream& os, const Ite…

F5 LTM 知识点和实验 4-持久化

第四章:持久化 持久化: 大多数应用都是有状态的,比如,使用一个购物网站,最重要的是用户在放入一个商品之后,刷新网页要能继续看到购物车里的东西,这就需要请求报文发到同一个后端服务器上,持久化就能完成这个功能。 持久化支持一下几种场景: 源地址目标地址SSLSIPH…

【go-zero】浅析 01

“github.com/google/uuid” uuid.New().String() go-zero 文档 https://www.w3cschool.cn/gozero/ go-zero 官网 https://go-zero.dev/ 快速开始&#xff1a; $ mkdir go-zero-demo $ cd go-zero-demo $ go mod init go-zero-demo $ goctl api new greet $ go mod tidy Done…

揭开神秘的面纱--自己实现MyBatis 底层机制[下]

&#x1f600;前言 本篇博文是MyBatis 底层机制的核心实现&#xff0c;简单的手法让MyBatis 不再神秘&#xff0c;希望能够帮助到你&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的…

JAVA-字符串生成图片

直接上代码 public static void main(String[] args) throws IOException {createFontImage("红色", new Font("宋体", Font.BOLD, 50), 400, 400);}/*** 根据str,font的样式将文字变成图片,然后返回一个流** param str 字符串* param font 字体* pa…

奇富科技引领编制国内首个金融行业大模型标准

7月28日&#xff0c;由中国信息通信研究院&#xff08;以下简称中国信通院&#xff09;组织的“行业大模型高质量发展论坛暨可信AI大模型标准宣贯会”在江苏南京召开。会议发起了行业大模型标准联合推进计划&#xff0c;启动了多个行业的大模型标准编制工作&#xff0c;其中&am…

ES6 - promise.all和race方法的用法详解

文章目录 一、前言二、Promise.all()1&#xff0c;第一句&#xff1a;Promise.all()方法接受一个数组作为参数&#xff0c;且每一个都是 Promise 实例2&#xff0c;第二句&#xff1a;如果不是&#xff0c;就会先调Promise.resolve方法&#xff0c;将参数转为 Promise 实例再进…

Python web实战 | Docker+Nginx部署python Django Web项目详细步骤【干货】

概要 在这篇文章中&#xff0c;我将介绍如何使用 Docker 和 Nginx 部署 Django Web 项目。一步步讲解如何构建 Docker 镜像、如何编写 Docker Compose 文件和如何配置 Nginx。 1. Docker 构建 Django Web 项目 1.1 配置 Django 项目 在开始之前&#xff0c;我们需要有一个 D…

自定义代理

以下代码&#xff1a;可以在view 和model文章中进行添加 m_model new QStandardItemModel(2, FixedColumnCount, this);m_selection new QItemSelectionModel(m_model, this);ui->tableView->setModel(m_model); //设置模型ui->tableView->setSelectionModel(m_s…

第一堂棒球课:MLB棒球大联盟的主要战术·棒球1号位

MLB棒球大联盟的主要战术 攻击战术run-and-foul&#xff08;跑垒战术&#xff09;&#xff1a;以速度为优势&#xff0c;在适当的时机发动进攻&#xff0c;争取在一回合内完成得分。 grounder&#xff08;阻截战术&#xff09;&#xff1a;队员在垒包之间阻止对手的跑垒和传球。…

eclipse 最新版没有navigator视图如何解决

使用project exploere视图可以显示类似navigator视图 1.显示project exploere视图 window---->show view --->project exploere 2.project exploere视图转换为类似navigator视图 第一步&#xff1a;点击视图右上角三个点或者倒三角&#xff0c;点击fiters and custom…

【java入门学习】

文章目录 java学习章节目录学习前的准备main函数怎么写&#xff1f;Java程序运行的步骤如何让代码运行起来&#xff1f;java程序由三部分构成&#xff1a;注释注释规范 标识符练习 总结 java学习章节目录 SE语法 初阶段主要来熟悉java的语法&#xff1a; 1.初始java 2.数据类型…

真正实现无人化自主管理的清洁机器人来了!涂鸦智能新方案为你解密

如果你观察家庭清洁电器的发展历程&#xff0c;可以发现这就是一部人类“偷懒”史&#xff0c;而有技术含量的“懒”&#xff0c;真的可以改变世界&#xff01; 一、懒是产品创新的重要灵感来源 从洗衣机替代手洗&#xff0c;到扫地机器人帮助人们从繁琐的地面清洁中解脱&…

RL 实践(6)—— CartPole【REINFORCE with baseline A2C】

本文介绍 REINFORCE with baseline 和 A2C 这两个带 baseline 的策略梯度方法&#xff0c;并在 CartPole-V0 上验证它们和无 baseline 的原始方法 REINFORCE & Actor-Critic 的优势参考&#xff1a;《动手学强化学习》完整代码下载&#xff1a;7_[Gym] CartPole-V0 (REINFO…

复现YOLOv8改进最新MPDIoU:有效和准确的边界盒回归的损失,打败G/E/CIoU,效果明显!!!

MPDIoU: A Loss for Efficient and Accurate Bounding Box Regression 论文简介MPDIoU核心设计思路论文方法实验部分加入YOLOv5代码论文地址:https://arxiv.org/pdf/2307.07662.pdf 论文简介 边界盒回归(Bounding box regression, BBR)广泛应用于目标检测和实例分割,是目标…

详细介绍如何使用HuggingFace和PyTorch进行医学图像分割-附源码

医学图像分割是一种创新过程,使外科医生能够拥有虚拟的“X 射线视觉”。它是医疗保健领域非常有价值的工具,可提供非侵入性诊断和深入分析。考虑到这一点,在这篇文章中,我们将探索威斯康辛大学麦迪逊分校胃肠道图像分割Kaggle 挑战数据集。作为该项目的一部分,我们将使用 …

python核心-面向对象-三大特性-综合案例

# 定义三个类, 小狗, 小猫, 人 # 小狗: 姓名, 年龄(默认1岁); 吃饭, 玩, 睡觉, 看家(格式: 名字是xx, 年龄xx岁的小狗在xx) # 小猫: 姓名, 年龄(默认1岁); 吃饭, 玩, 睡觉, 捉老鼠(格式: 名字是xx, 年龄xx岁的小猫在xx) # 人: 姓名, 年龄(默认1岁), 宠物; 吃饭…

【替换】批量替换命令文件名称或者文件内容

你是否困扰过&#xff0c;每次文件F2进行修改&#xff0c;十几个没关系&#xff0c;百来个写代码麻烦&#xff0c;那就使用shell命令吧 执行语句 先养成习惯查询验证&#xff0c;因为是批量替换&#xff0c;为了防止全局替换嘎嘎叫 替换文件名 # 【习惯】先使用顾虑查询&…

暴力猴插件简明开发教程->百度首页默认设置为我的关注

文章目录 暴力猴插件开发简明教程->百度首页默认设置为我的关注缘起缘灭思路实现尾声 暴力猴插件开发简明教程->百度首页默认设置为我的关注 缘起 在我的百度首页有很多自己设置的导航链接(接近100个),里面放了我常用的网站, 如下图 但是最近一段时间, 我发现百度做了一…