当前位置: 首页 > news >正文

动态代理更改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);}
}

下面ExampleClientExampleClientImpl 仅用于模拟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);
}

ClientProxyDhHandler feign调用其他微服务接口,统一解析代理处理器
可以看到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;}
}

至此,优化完成。🎉🎊

http://www.lryc.cn/news/409313.html

相关文章:

  • Redis缓存数据库进阶——Redis与分布式锁(6)
  • 网络芯片(又称为PHY网络芯片)
  • 01 Go Web基础_20240728 课程笔记
  • 嵌入式学习Day12---C语言提升
  • 6.6 使用dashboard商城搜索导入模板
  • 一文讲透useMemo和useCallback
  • 【环境变量】安装了一个软件,如何配置环境变量?
  • 重生之我当程序猿外包
  • 我想给 git 分支换一个名字,应该怎么做?
  • echarts多stack的legend点选
  • 搭建自己的金融数据源和量化分析平台(四):自动化更新上市公司所属一级、二级行业以及股票上市状态
  • 科创板重启IPO上会!募投审核新方向?思看科技等优化募投项目
  • 深入解析损失函数:从基础概念到YOLOv8的应用
  • 2.11.ResNet
  • GitLab添加TortoiseGIT生成SSH Key
  • 20240729 大模型评测
  • 基于微信小程序的校园警务系统/校园安全管理系统/校园出入管理系统
  • 达梦数据库归档介绍
  • OpenAI推出AI搜索引擎SearchGPT
  • elementplus菜单组件的那些事
  • 【VSCode实战】Golang无法跳转问题竟是如此简单
  • three.js中加载ply格式的文件,并使用tween.js插件按照json姿态文件运动
  • 性能对比:Memcached 与 Redis 的关键差异
  • app-routing.module.ts 简单介绍
  • 基于JSP的水果销售管理网站
  • web3d值得学习并长期发展,性价比高吗?
  • 【大数据面试题】38 说说 Hive 怎么行转列
  • C语言中的二维数组
  • Android12 添加屏幕方向旋转方案
  • Harmony-(1)-TypeScript-ArkTs