动态代理更改Java方法的返回参数(可用于优化feign调用后R对象的统一处理)
动态代理更改Java方法的返回参数(可用于优化feign调用后R对象的统一处理)
- 需求
- 原始解决方案
- 优化后方案
- 1.首先创建AfterInterface.java
- 2.创建InvocationHandler处理代理方法
- 3. 调用
- 实际运行场景
- 拓展
需求
某些场景,调用别人的方法,但是它外面包了一层,我们只需要里面实际的数据,例如后端开发中的R对象,实际最终只需要data。也就是说可以看成,调用原始方法,代理后更改为别的类型。
下面是R对象,实际运用大同小异,方便大家理解。
public class R<T> implements Serializable {private static final long serialVersionUID = 1L;/*** 正确返回码*/public static final String SUCCESS_CODE = "0";/*** 返回码*/private String code;/*** 返回消息*/private String message;/*** 响应数据*/private T data;/*** 请求ID*/private String requestId;public boolean isSuccess() {return SUCCESS_CODE.equals(code);}
}
下面仅仅举例,重点看出怎么转换返回类型的,没有实际意义。具体使用场景可参考后面可参考具体在实际运用场景的
原始解决方案
PreInterface.java 模拟原始方法返回值。
package com.zdh.proxy.chagemethod;/*** @author zdh* @date 2024/07/24* @desc*/
public interface PreInterface {default double method1() {return 2.3;}default int method2() {return 123;}default boolean method3() {return true;}
}
新建一个PreManager 类,获得PreInterface对象,重新封装所有方法。
package com.zdh.proxy.chagemethod;/*** @author developer_ZhangXinHua* @date 2024/07/24* @desc (详细信息)*/
public class PreManager {//正常从容器中拿PreInterface afterInterface = new PreInterface(){};public String method1() {return "manager: "+ afterInterface.method1();}public String method2() {return "manager: "+ afterInterface.method2();}public String method3() {return "manager: "+ afterInterface.method3();}public static void main(String[] args) {PreManager preManager = new PreManager();System.out.println(preManager.method1());System.out.println(preManager.method2());System.out.println(preManager.method3());}
}
缺点显而易见,每个原始PreInterface就需要对应实现一个PreManager,而且需要重新实现每个方法。
优化后方案
PreInterface.java 与上面一样,不再给出。
1.首先创建AfterInterface.java
重点:因为后面代理的时候需要用到方法名和参数列表进行调用,所以方法名和参数列表一定要与PreInterface的对应的方法名相同。
package com.zdh.proxy.chagemethod;/*** @author zdh* @date 2024/07/24* @desc*/
public interface AfterInterface {public String method1();public String method2();public String method3();
}
2.创建InvocationHandler处理代理方法
package com.zdh.proxy.chagemethod;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author zdh* @date 2024/07/24* @desc*/
public class MethodProxyDhHandler implements InvocationHandler {private final Object target;public MethodProxyDhHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//根据反射,拿到和代理后的方法同名且方法参数相同的方法Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());Object ob= targetMethod.invoke(target, args);return "proxy after:"+ob;}public static <T> T create(Class<T> interfaceClass, Object target) {return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class[]{interfaceClass},new MethodProxyDhHandler(target));}}
3. 调用
public static void main(String[] args) {PreInterface preTarget = new PreInterface() {};AfterInterface afterInterface = create(AfterInterface.class, preTarget);String s1 = afterInterface.method1();System.out.println("s1 = " + s1);String s2 = afterInterface.method2();System.out.println("s2 = " + s2);String s3 = afterInterface.method3();System.out.println("s3 = " + s3);}
可以看到,已经全部转成了String类型。这里只是测试,如果使用Spring等框架,可以直接从容器中获取afterInterface ,然后afterInterface 创建代理到容器中。

实际运行场景
上述方式仅仅为了简化大家的理解,那么现在有个疑问,上述方式有啥用呢。目前我遇到场景可用于优化feign调用后R对象的统一处理 (仅适用公司内部,R对象都统一),获取到R对象,根据错误码等判断成功与否,若成功可以直接拆掉直接返回data。
R.java
后端controller返回参数,大同小异。
package cn.zdh;import lombok.Data;
import lombok.experimental.Accessors;import java.io.Serializable;@Data
@Accessors(chain = true)
public class R<T> implements Serializable {private static final long serialVersionUID = 1L;/*** 正确返回码*/public static final String SUCCESS_CODE = "0";/*** 返回码*/private String code;/*** 返回消息*/private String message;/*** 响应数据*/private T data;/*** 请求ID*/private String requestId;public boolean isSuccess() {return SUCCESS_CODE.equals(code);}/*** 构造带返回数据的成功响应*//*** 构造带返回数据的成功响应*/public static <T> R<T> success(T data) {return new R<T>().setCode(R.SUCCESS_CODE).setData(data);}
}
下面
ExampleClient和ExampleClientImpl仅用于模拟feign远程调用。正常项目里面只需要一个ExampleClient接口
package cn.zdh.client;import cn.zdh.R;import java.util.ArrayList;
import java.util.List;/*** @author zdh* @date 2024/07/24* @desc 模拟feign远程调用*/
public interface ExampleClient {default R<String> find1() {return R.success("f1");}default R<List<String>> find2() {List<String> list = new ArrayList<String>();list.add("a");list.add("b");return R.success(list);}default R<Double> find3(Double d) {return R.success(d);}}
package cn.zdh.client;import org.springframework.stereotype.Component;/*** @author developer_ZhangXinHua* @date 2024/07/24* @desc (详细信息)*/
@Component
public class ExampleClientImpl implements ExampleClient{
}
AfterExampleClient拆掉R之后的data作为方法的返回类型,注意方法名和参数要与ExampleClient 方法名和参数一 一对应。简单来讲,复制粘贴,把返回值删掉R。
package cn.zdh.afterclient;import java.util.List;/*** @author developer_ZhangXinHua* @date 2024/07/23* @desc 定义需要代理后的方法*/
public interface AfterExampleClient {String find1();List<String> find2();Double find3(Double d);
}
ClientProxyDhHandlerfeign调用其他微服务接口,统一解析代理处理器
可以看到invoke方法中对R对象进行了统一处理,并且后续根据需要,可以通过错误码进行日志输出和报错,通过全局异常处理器,返回前端。
package cn.zdh.proxy;//package com.zdh.proxyfeign;import cn.zdh.R;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author zdh* @date 2024/07/24* @desc feign调用其他系统接口,统一解析代理处理器*/
public class ClientProxyDhHandler implements InvocationHandler {private final Object target;public ClientProxyDhHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());R<?> r = (R<?>) targetMethod.invoke(target, args);if (r != null && r.isSuccess()) {return r.getData();}/*后续可以根据错误码进行日志输出和报错,通过全局异常处理器,返回前端。*/return null;}public static <T> T create(Class<T> interfaceClass, Object target) {return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class[]{interfaceClass},new ClientProxyDhHandler(target));}
}
ExampleService模拟service层的调用
package cn.zdh.service;import cn.zdh.afterclient.AfterExampleClient;
import cn.zdh.client.ExampleClient;
import cn.zdh.proxy.ClientProxyDhHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** @author developer_ZhangXinHua* @date 2024/07/23* @desc 模拟调用代理后的对象。*/
@Service
public class ExampleService{private final AfterExampleClient afterExampleClient;@Autowiredpublic ExampleService(ExampleClient exampleClient) {this.afterExampleClient = ClientProxyDhHandler.create(AfterExampleClient.class, exampleClient);}public void testAll(){//假设这里调用所有String f1 = afterExampleClient.find1();System.out.println("f1 = " + f1);List<String> f2 = afterExampleClient.find2();System.out.println("f2 = " + f2);Double f3 = afterExampleClient.find3(3.2);System.out.println("f3 = " + f3);}}
Spring Test 测试
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@ActiveProfiles(value = "test")
public class ZdhTest {@Autowiredprivate ExampleService exampleService;@Testpublic void test1(){exampleService.testAll();}}
测试结果

总结,每新增一个Client(feign的微服务调用接口)仅需要创建一个与其对应的AfterClient接口 。需要使用的service,只需要使用动态代理,传入ClientProxyDhHandler并注入到容器中,即可完成统一的远程调用处理。
拓展
如下还有一个小问题,此方式注入到Spring容器中,每次使用者都需要创建代理对象,很麻烦。
@Autowired
public ExampleService(ExampleClient exampleClient) {this.afterExampleClient = ClientProxyDhHandler.create(AfterExampleClient.class, exampleClient);
}
解决方案:通过配置类,一次配置,其他使用者直接进行注入。如下:
@Configuration
public class AfterClientConfiguration {@Beanpublic AfterExampleClient afterExampleClient(ExampleClient exampleClient) {AfterExampleClient afterExampleClient = ClientProxyDhHandler.create(AfterExampleClient.class, exampleClient);return afterExampleClient;}
}
至此,优化完成。🎉🎊
相关文章:
动态代理更改Java方法的返回参数(可用于优化feign调用后R对象的统一处理)
动态代理更改Java方法的返回参数(可用于优化feign调用后R对象的统一处理) 需求原始解决方案优化后方案1.首先创建AfterInterface.java2.创建InvocationHandler处理代理方法3. 调用 实际运行场景拓展 需求 某些场景,调用别人的方法࿰…...
Redis缓存数据库进阶——Redis与分布式锁(6)
分布式锁简介 1. 什么是分布式锁 分布式锁是一种在分布式系统环境下,通过多个节点对共享资源进行访问控制的一种同步机制。它的主要目的是防止多个节点同时操作同一份数据,从而避免数据的不一致性。 线程锁: 也被称为互斥锁(Mu…...
网络芯片(又称为PHY网络芯片)
Realtek RTL8152B是一种常见的主板集成网络芯片(又称为PHY网络芯片)。PHY芯片是指将网络控制芯片的运算部分交由处理器或南桥芯片处理,以简化线路设计,从而降低成本。 https://www.realtek.com/Download/List?cate_id585 Realt…...
01 Go Web基础_20240728 课程笔记
概述 如果您没有Golang的基础,应该学习如下前置课程。 基础不好的同学每节课的代码最好配合视频进行阅读和学习,如果基础比较扎实,则阅读本教程巩固一下相关知识点即可,遇到不会的知识点再看视频。 视频课程 最近发现越来越多…...
嵌入式学习Day12---C语言提升
目录 一、指针数组 1.1.什么是指针数组 2.2. 格式 2.3.存储 2.4.与字符型二维数组相比 2.5.什么时候使用指针数组 2.6.练习 二、数组指针 2.1.什么是数组指针 2.2.格式 2.3.一维数组 2.3.特点 2.4.什么时候使用 三、指针和数组的关系 3.1.一维数组和指针 …...
6.6 使用dashboard商城搜索导入模板
本节重点介绍 : 模板商城中搜索模板导入模板修改模板 大盘模板商城地址 免费的 地址 https://grafana.com/grafana/dashboards 搜索模板技巧 详情 导入dashboard 两种导入模式 url导入id导入json文件导入 导入 node_exporter模板 https://grafana.com/grafana/dashboa…...
一文讲透useMemo和useCallback
在React项目中是经常会使用到useMemo,useCallBack的,这是两个优化性能的方法,那么useMemo,useCallBack到底是什么呢?什么时候用呢? 下面将给打击分享相关知识,希望对大家有所帮助同时欢迎讨论指…...
【环境变量】安装了一个软件,如何配置环境变量?
配置环境变量为啥? 方便地在任何文件夹下调用某一指定目录下的文件。 配置步骤 以jdk17为例。 1.打开环境变量配置页面 2.新建一个变量,变量名为JAVA_HOME,内容为jdk的path路径 3.打开path变量,新建一个%JAVA_HOME%\bin&#x…...
重生之我当程序猿外包
第一章 个人介绍与收入历程 我出生于1999年,在大四下学期进入了一家互联网公司实习。当时的实习工资是3500元,公司还提供住宿。作为一名实习生,这个工资足够支付生活开销,每个月还能给父母转1000元,自己留2500元用来吃…...
我想给 git 分支换一个名字,应该怎么做?
Git中重命名分支的操作步骤如下: 确保你在要重命名的分支上。可以使用git branch或git status命令查看当前所在分支[1][2]. 使用以下命令重命名当前分支: git branch -m new-branch-name例如,将当前分支重命名为"feature-xyz": git branch -m feature-xyz-m参数是&q…...
echarts多stack的legend点选
echarts支持点击legend,实现显示和隐藏legend对应的数据,具体就是option里series里,name为legend值的数据。 如果配置了多个stack,那么可能你可能设置了多组legend,你点选的是多个legend组中的某组中的一个,那么如果不…...
搭建自己的金融数据源和量化分析平台(四):自动化更新上市公司所属一级、二级行业以及股票上市状态
前面做了更新沪深交易所的上市股票列表的读取和更新,但一旦股票退市则需要在数据库里将该股票状态更新为退市,同时附上退市日期,将股票名更改为XX退。 此外深交所下载的xls解析出来是没有上市公司所属的二级行业的,因此还需要建立…...
科创板重启IPO上会!募投审核新方向?思看科技等优化募投项目
撰稿 | 多客 来源 | 贝多财经 根据上交所项目审核动态最新公告,思看科技(杭州)股份有限公司(简称“思看科技”)将于8月2日上会,标志着时隔50天后科创板重新迎来首家上会企业,也标志着思看科技…...
深入解析损失函数:从基础概念到YOLOv8的应用
深入解析损失函数:从基础概念到YOLOv8的应用 在机器学习和深度学习中,损失函数是至关重要的组件,它们衡量模型的预测值与真实值之间的差距,从而指导模型的优化过程。本文将详细探讨损失函数的基本概念,及其在YOLOv8中…...
2.11.ResNet
ResNet 动机:我们总是想加更多层,但加更多层并不总是能改进精度 可以看出F1到F6模型越来越大,但F6距离最优解却总变远了,反而效果不好,通俗的来说就是学偏了,实际上我们希望是这样的: 更大…...
GitLab添加TortoiseGIT生成SSH Key
文章目录 前言一、PuTTYgen二、GitLab 前言 GitLab是一个用于托管代码仓库和项目管理的Web平台,公司搭建自己的gitlab来管理代码,我们在clone代码的时候可以选择http协议,也可以选择ssh协议来拉取代码。 SSH (Secure Shell)是一种通过网络进…...
20240729 大模型评测
参考: MMBench:基于ChatGPT的全方位多模能力评测体系_哔哩哔哩_bilibili https://en.wikipedia.org/wiki/Levenshtein_distance cider: https://zhuanlan.zhihu.com/p/698643372 GitHub - open-compass/opencompass: OpenCompass is an LLM evalua…...
基于微信小程序的校园警务系统/校园安全管理系统/校园出入管理系统
摘要 伴随着社会以及科学技术的发展,小程序已经渗透在人们的身边,小程序慢慢的变成了人们的生活必不可少的一部分,紧接着网络飞速的发展,小程序这一名词已不陌生,越来越多的学校机构等都会定制一款属于自己个性化的小程…...
达梦数据库归档介绍
一、什么是归档 数据库归档是一种数据管理策略,它涉及将旧的、不经常访问的数据移动到一个单独的存储设备,以便在需要时可以检索,同时保持数据库的性能和效率。 归档的主要目标是为了释放数据库中的空间,以便更有效地利用高性能…...
OpenAI推出AI搜索引擎SearchGPT
OpenAI推出AI搜索引擎SearchGPT 据英国《卫报》和美国消费者新闻与商业频道等媒体报道,7月25日,OpenAI宣布正在测试一款名为SearchGPT的全新人工智能(AI)搜索工具。该工具能够实时访问互联网信息,旨在为用户提供更具时…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
