动态代理更改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)搜索工具。该工具能够实时访问互联网信息,旨在为用户提供更具时…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
