Java 设计模式系列:代理模式

news2025/2/28 5:06:51

文章目录

    • 介绍
    • 静态代理
      • 基本介绍
      • 应用实例
      • 静态代理优缺点
    • 动态代理
      • 基本介绍
      • JDK 中生成代理对象的 API
    • Cglib 代理
      • 基本介绍
      • 实现步骤

介绍

  • 1)代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象

  • 2)这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能

  • 3)被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象

  • 4)代理模式有不同的形式,主要有三种:

    • 静态代理
    • 动态代理:JDK 代理、接口代理
    • Cglib 代理:可以在内存动态的创建对象,而不需要实现接口,它是属于动态代理的范畴

案例准备

  • 1)定义一个接口:Calculator

  • 2)目标对象CalculatorImpl实现接口Calculator

Calculator.java

package com.mcode.api.proxy;

/**
 * ClassName: Calculator
 * Package: com.mcode.api.proxy
 * Description:
 *
 * @Author: robin
 * @Version: v1.0
 */
/**
 * 目标接口
 */
public interface Calculator {
    int add(int a, int b);
}

CalculatorImpl.java

package com.mcode.api.proxy;

import com.mcode.api.proxy.Calculator;

/**
 * ClassName: CalculatorImpl
 * Package: com.mcode.api.proxy
 * Description:
 *
 * @Author: robin
 * @Version: v1.0
 */
/**
 * 目标对象
 */
public class CalculatorImpl implements Calculator {
    /**
     * 相加方法
     * @param a
     * @param b
     * @return
     */
    @Override
    public int add(int a, int b) {

        int result = a + b;

        System.out.println("方法内部 result = " + result);

        return result;
    }
}

静态代理

基本介绍

静态代理在使里时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现租同的接口或者是继承和同父类—
应用实例

应用实例

  • 1)使用静态代理方式,就需要在代理对象CalculatorStaticProxy中也实现Calculator

  • 2)调用的时候通过调用代理对象的方法来调用目标对象

  • 3)特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法

核心代码

CalculatorStaticProxy.java

package com.mcode.api.proxy.staticproxy;

import com.mcode.api.proxy.Calculator;

/**
 * ClassName: CalculatorStaticProxy
 * Package: com.mcode.api.proxy.staticproxy
 * Description:
 *
 * @Author: robin
 * @Version: v1.0
 */
public class CalculatorStaticProxy implements Calculator {

    private Calculator target;


    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }

    @Override
    public int add(int a, int b) {
        // 附加功能由代理类中的代理方法来实现
        System.out.println("[日志] add 方法开始了,参数是:" + a + "," + b);

        // 通过目标对象来实现核心业务逻辑
        int addResult = target.add(a, b);

        System.out.println("[日志] add 方法结束了,结果是:" + addResult);

        return addResult;
    }
}

调用代理

CalculatorTest.java

package com.mcode.api.proxy.staticproxy;

import com.mcode.api.proxy.CalculatorImpl;

/**
 * ClassName: CalculatorTest
 * Package: com.mcode.api.proxy.staticproxy
 * Description:
 *
 * @Author: robin
 * @Version: v1.0
 */
public class CalculatorTest {
    public static void main(String[] args) {
        //创建被代理对象
        CalculatorImpl calculator = new CalculatorImpl();
        //创建代理对象,聚合被代理对象
        CalculatorStaticProxy proxy = new CalculatorStaticProxy(calculator);
        //通过代理对象,调用被代理对象的方法
        proxy.add(1, 2);
    }
}

执行结果

image

静态代理优缺点

  • 1)优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展

  • 2)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类

  • 3)缺点:一旦接口增加方法,目标对象与代理对象都要维护

动态代理

基本介绍

  • 1)代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理

  • 2)代理对象的生成,是利用 JDK 的 APl,动态的在内存中构建代理对象

  • 3)动态代理也叫做:JDK 代理、接口代理

JDK 中生成代理对象的 API

1)代理类所在包:java.lang.reflect.Proxy

● 2)JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:

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

核心代码

ProxyFactory.java

package com.mcode.api.proxy.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * ClassName: ProxyFactory
 * Package: com.mcode.api.proxy.dynamic
 * Description:
 *
 * @Author: robin
 * @Version: v1.0
 */

/**
 *  代理工厂
 */
public class ProxyFactory {
    /**
     * 目标对象
     */
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy() {

        /**
         * newProxyInstance():创建一个代理实例
         * 其中有三个参数:
         * 1、classLoader:加载动态生成的代理类的类加载器
         * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
         * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
         */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = (proxy, method, args) -> {
            /**
             * proxy:代理对象
             * method:代理对象需要实现的方法,即其中需要重写的方法
             * args:method所对应方法的参数
             */
            Object result = null;
            System.out.println("[动态代理][日志] " + method.getName() + ",参数:" + Arrays.toString(args));
            result = method.invoke(target, args);
            System.out.println("[动态代理][日志] " + method.getName() + ",结果:" + result);
            return result;
        };
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

其中几个参数

  • 1)ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法固定

  • 2)Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确认类型

  • 3)InvocationHandler h:事情处理,执行目标对象的方法时触发事情处理器方法,把当前执行的目标对象方法作为参数传入

调用代理

CglibProxyTest.java

package com.mcode.api.proxy.dynamic;

import com.mcode.api.proxy.Calculator;
import com.mcode.api.proxy.CalculatorImpl;

/**
 * ClassName: DynamicProxyTest
 * Package: com.mcode.api.proxy.dynamic
 * Description:
 *
 * @Author: robin
 * @Version: v1.0
 */
public class DynamicProxyTest {
    public static void main(String[] args) {
        //创建目标对象
        CalculatorImpl calculator = new CalculatorImpl();
        //通过代理工厂创建代理对象
        ProxyFactory proxyFactory = new ProxyFactory(calculator);
        //通过代理对象调用目标对象方法
        Calculator proxy = (Calculator)proxyFactory.getProxy();
        proxy.add(1,2);
    }
}

执行结果

image

Cglib 代理

基本介绍

  • 1)静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是 Cglib 代理

  • 2)Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将 Cglib 代理归属到动态代理。

  • 3)Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截

  • 4)在 AOP 编程中如何选择代理模式:

    • 目标对象需要实现接口,用 JDK 代理
    • 目标对象不需要实现接口,用 Cglib 代理
  • 5)Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类

实现步骤

  • 1)需要引入cglib的 jar 文件

  •         <!-- https://mvnrepository.com/artifact/cglib/cglib -->
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>3.3.0</version>
            </dependency>
    
  • 2)在内存中动态构建子类,注意代理的类不能为final,否则报错java.lang.IllegalArgumentException

  • 3)目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

核心代码

ProxyFactory.java

package com.mcode.api.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * ClassName: ProxyFactory
 * Package: com.mcode.api.proxy.cglib
 * Description:
 *
 * @Author: robin
 * @Version: v1.0
 */

/**
 * 代理工厂
 */
public class ProxyFactory implements MethodInterceptor {

    /**
     * 目标对象
     */
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy(){
        // 1、创建工具类
        Enhancer enhancer = new Enhancer();
        // 2、设置父类
        enhancer.setSuperclass(target.getClass());
        // 3、设置回调函数,需要实现MethodInterceptor接口
        enhancer.setCallback(this);
        // 4、创建子类对象,即代理对象
        return enhancer.create();
    }

    /**
     *
     * @param o 代理对象
     * @param method 目标方法
     * @param args 目标方法参数
     * @param methodProxy 代理对象生成的代理方法
     * @return
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;
        System.out.println("[cglib代理][日志] " + method.getName() + ",参数:" + Arrays.toString(args));
        result = method.invoke(target, args);
        System.out.println("[cglib代理][日志] " + method.getName() + ",结果:" + result);
        return result;
    }
}

调用代理

CglibProxyTest.java

package com.mcode.api.proxy.cglib;

import com.mcode.api.proxy.CalculatorImpl;

/**
 * ClassName: CglibProxyTest
 * Package: com.mcode.api.proxy.cglib
 * Description:
 *
 * @Author: robin
 * @Version: v1.0
 */
public class CglibProxyTest {
    public static void main(String[] args) {
        //创建目标对象
        CalculatorImpl calculator = new  CalculatorImpl();
        //通过代理工厂创建代理对象
        ProxyFactory proxyFactory = new ProxyFactory(calculator);
        //通过代理对象调用目标对象方法
        CalculatorImpl proxy = (CalculatorImpl)proxyFactory.getProxy();
        proxy.add(1,2);
    }
}

执行报错

Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class.

运行时增加 vm 环境变量

--add-opens java.base/java.lang=ALL-UNNAMED

执行结果

image

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

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

相关文章

深入理解原码、反码、补码(结合C语言)

一、引出问题 在学习C语言单目操作符中~按位取反的过程中&#xff0c;对这样一段代码的结果产生了疑惑&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h>int main() {int a 0;int b ~a;//按位取反printf("%d\n", b);return 0; }输出结果…

系列十七、理解SpringBoot中的starter 自定义一个starter

一、概述 作为后端Java程序员&#xff0c;基本上公司的日常开发都是基于SpringBoot进行的&#xff0c;我们使用SpringBoot也是沉醉于它的各种各样的starter带给我们的便利&#xff0c;这些starter为我们带来了众多的自动化配置&#xff0c;通过这些自动化配置&#xff0c;我们可…

为自己创建的游戏编程源码申请软件著作权详细流程(免费分享模板)

以为我这篇文章制作的游戏申请软件著作权为例 Ren‘py 视觉小说 交互式故事游戏制作过程学习笔记(Windows下实现)(多结局游戏)-CSDN博客 一、网站注册 申请软著时&#xff0c;所有的著作权人都需要在中国版权保护中心官网注册账号&#xff0c;并进行实名认证后&#xff0c;才…

使用WalletConnect Web3Modal v3 链接钱包基础教程

我使用的是vueethers 官方文档&#xff1a;WalletConnect 1.安装 yarn add web3modal/ethers ethers 或者 npm install web3modal/ethers ethers2.引用 新建一个js文件&#xff0c;在main.js中引入&#xff0c;初始化配置sdk import {createWeb3Modal,defaultConfig, } from…

【预计IEEE出版|EI征稿通知】第六届下一代数据驱动网络国际学术会议 (NGDN 2024)

第六届下一代数据驱动网络国际学术会议 (NGDN 2024) The Sixth International Conference on Next Generation Data-driven Networks 2024年4月26-28日 | 中国沈阳 基于前几届在英国埃克塞特 (ISPA 2020) 、中国沈阳 (TrustCom 2021) 和中国武汉 (IEEETrustCom-2022) 成功举…

半监督语义分割综述

paper link&#xff1a;https://arxiv.org/pdf/2302.09899.pdf 1. Introduction 图像分割是最古老、研究最广泛的计算机视觉 (CV) 问题之一。图像分割是指将图像划分为不同的非重叠区域&#xff0c;并将相应的标签分配给图像中的每个像素&#xff0c;最终获得ROI区域位置及其类…

【智能家居】二、添加火灾检测模块(烟雾报警功能点)

可燃气体传感器 MQ-2 和 蜂鸣器 代码段 controlDevice.h&#xff08;设备控制&#xff09;smokeAlarm.c&#xff08;烟雾报警器&#xff09;buzzer.c&#xff08;蜂鸣器&#xff09;mainPro.c&#xff08;主函数&#xff09;运行结果 可燃气体传感器 MQ-2 和 蜂鸣器 代码段 …

基于机器深度学习的交通标志目标识别

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 智能交通系统&#xff08;ITS&#xff09;&#xff0c;包括无人驾驶车辆&#xff0c;尽管在道路…

OrangePi 5:ROS2 Humble中使用激光雷达

OrangePi 5&#xff1a;ROS2 Humble中使用激光雷达 文章目录 OrangePi 5&#xff1a;ROS2 Humble中使用激光雷达1、硬件准备2、ROS2 Humble安装2.1 使用集成脚本安装2.2 按ROS2官方指导安装2.3 ROS2安装验证 3、YDLIDAR X2激光雷达驱动安装3.1 YDLIDAR X2激光雷达介绍3.2 YDLID…

go第三方包发布(短精细)

1、清除其他依赖项 $ go mod tidy # 清除不必要的依赖依赖清除完成后&#xff0c;查看go.mod文件配置是否规范 module github.com/fyupeng/rpc-go-netty go 1.19 require ( )2、本地版本创建 $ git tag v0.1.0 # 本地 创建标签3、版本提交 $ git push github v0.1.0 # 推送…

Programming Contest 2023(AtCoder Beginner Contest 331)D题 Tile Pattern --- 题解

目录 D - Tile Pattern 题目大意&#xff1a; 思路&#xff1a; 代码&#xff1a; D - Tile Pattern D - Tile Pattern (atcoder.jp) 题目大意&#xff1a; 给你一个n和q&#xff0c;n为局部棋盘大小(n*n) 并且给出局部棋盘中黑白子位置的放置情况&#xff0c;q为查询次数…

Docker部署Plik临时文件上传系统并且实现远程访问

文章目录 1. Docker部署Plik2. 本地访问Plik3. Linux安装Cpolar4. 配置Plik公网地址5. 远程访问Plik6. 固定Plik公网地址7. 固定地址访问Plik8. 结语 本文介绍如何使用Linux docker方式快速安装Plik并且结合Cpolar内网穿透工具实现远程访问&#xff0c;实现随时随地在任意设备上…

深入解析JVM内存结构:Metaspace、堆与垃圾收集器

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

Flask使用线程异步执行耗时任务

1 问题说明 1.1 任务简述 在开发Flask应用中一定会遇到执行耗时任务&#xff0c;但是Flask是轻量级的同步框架&#xff0c;即在单个请求时服务会阻被塞&#xff0c;直到任务完成&#xff08;注意&#xff1a;当前请求被阻塞不会影响到其他请求&#xff09;。 解决异步问题有…

C语言--每日选择题--Day32

如果大家对读研究生和就业不知道如何抉择&#xff0c;我的建议是看大家的经济基础&#xff0c;如果家里不是很需要你们工作&#xff0c;就读研提升自己的学历&#xff0c;反之就就业&#xff1b;毕竟经济基础决定上层建筑&#xff1b; 第一题 1. 下面代码的结果是&#xff1a;…

Paxos 算法

Paxos 算法 介绍 Paxos 算法是第一个被证明完备的分布式系统共识算法。共识算法的作用是让分布式系统中的多个节点之间对某个提案&#xff08;Proposal&#xff09;达成一致的看法。提案的含义在分布式系统中十分宽泛&#xff0c;像哪一个节点是 Leader 节点、多个事件发生的…

百马百担c语言编程

以下是一个百马百担问题的C语言编程实现&#xff1a; #include <stdio.h>int main() { int n, m, k; scanf("%d%d%d", &n, &m, &k); int a[n], b[m], c[k]; for (int i 0; i < n; i) { scanf("%d", &a[i]);…

LangChain 18 LangSmith监控评估Agent并创建对应的数据库

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…

ELK高级搜索,深度详解ElasticStack技术栈-下篇

前言&#xff1a;ELK高级搜索&#xff0c;深度详解ElasticStack技术栈-上篇 14. search搜索入门 14.1. 搜索语法入门 14.1.1 query string search 无条件搜索所有 GET /book/_search结果&#xff1a; {"took" : 969,"timed_out" : false,"_shar…

大数据湖项目建设方案:文档全文101页,附下载

关键词&#xff1a;大数据解决方案&#xff0c;数据湖解决方案&#xff0c;数据治理解决方案&#xff0c;数据中台解决方案 一、大数据湖建设思路 1、明确目标和定位&#xff1a;明确大数据湖的目标和定位是整个项目的基础&#xff0c;这可以帮助我们确定项目的内容、规模、所…