动态代理更改Java方法的返回参数(可用于优化feign调用后R对象的统一处理)

news2024/11/16 0:31:10

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

    @Override
    public 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;
    }

    @Override
    public 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;

    @Autowired
    public 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 {

    @Autowired
    private ExampleService exampleService;


    @Test
    public 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 {


    @Bean
    public AfterExampleClient afterExampleClient(ExampleClient exampleClient) {
        AfterExampleClient afterExampleClient = ClientProxyDhHandler.create(AfterExampleClient.class, exampleClient);
        return afterExampleClient;
    }
}

至此,优化完成。🎉🎊

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

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

相关文章

手机空号过滤批量查询的意义及方法

手机空号过滤批量查询是现代营销和通信管理中常用的技术手段&#xff0c;旨在通过批量处理手机号码&#xff0c;筛选出活跃号码和空号等无效号码&#xff0c;以提高营销效率和减少不必要的通信成本。以下是关于手机空号过滤批量查询的详细解答&#xff1a; 一、手机空号过滤批…

3dsMax 设置近平面削减,靠近模型之后看不到模型,看很小的模型放大看不到

3dsMax 设置近平面削减&#xff0c;靠近模型之后看不到模型&#xff0c;看很小的模型放大看不 问题展示 解决办法_1 把这两个东西最上面的拖拽到最上面&#xff0c;最下面的拖拽到最下面。 解决办法_2 勾选视口裁剪 把这两个东西最上面的拖拽到最上面&#xff0c;最下面的…

华为ensp中ISIS原理与配置(超详细)

isis原理与配置 8-20字节&#xff1b; 地址组成&#xff1a;area id&#xff0c;system id&#xff0c;set三部分组成&#xff1b; system id占6个字节&#xff1b;sel占一个&#xff0c;剩下的为area id区域号&#xff1b; system id 唯一&#xff0c; 一般将router id 配…

opengl 写一个3D立方体——计算机图形学编程 第4章 管理3D图形数据 笔记

计算机图形学编程&#xff08;使用OpenGL和C&#xff09; 第4章 管理3D图形数据 笔记 数据处理 想要绘制一个对象&#xff0c;它的顶点数据需要发送给顶点着色器。通常会把顶点数据在C端放入 一个缓冲区&#xff0c;并把这个缓冲区和着色器中声明的顶点属性相关联。 初始化立…

Python中高效处理大数据的几种方法

随着数据量的爆炸性增长&#xff0c;如何在Python中高效地处理大数据成为了许多开发者和数据科学家的关注焦点。Python以其简洁的语法和丰富的库支持&#xff0c;在数据处理领域占据了重要地位。本文将介绍几种在Python中高效处理大数据的常用方法。 目录 1. 使用Pandas进行数…

基于STM32的逻辑分析仪

文章目录 一、逻辑分析仪体验1、使用示例1.1 逻辑分析仪1.2 开源软件PulseView 2、核心技术2.1 技术方案2.2 信号采集与存储2.3 数据上传 3、使用逻辑分析仪4、 SourceInsight 使用技巧4.1新建工程4.2 设置工程名及工程数据目录4.3 指定源码目录4.4 添加源码4.5 同步文件4.6 操…

为RTEMS Raspberrypi4 BSP添加SPI支持

为RTEMS Raspberrypi4 BSP添加SPI支持 主要参考了dev/bsps/shared/dev/spi/cadence-spi.c RTEMS 使用了基于linux的SPI框架&#xff0c;SPI总线驱动已经在内核中实现。在这个项目中我需要实习的是 RPI4的SPI主机控制器驱动 SPI在RTEMS中的实现如图&#xff1a; 首先需要将S…

25.x86游戏实战-理解发包流程

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

江科大/江协科技 STM32学习笔记P9-11

文章目录 OLED1、OLED硬件main.c EXTI外部中断1、中断系统2、中断执行流程图3、STM32中断4、中断地址的作用5、EXTI6、EXTI基本结构7、AFIO复用IO口8、EXTI框图或门和与门 9、旋转编码器介绍10、硬件电路 OLED 1、OLED硬件 SCL和SDA是I2C的通信引脚&#xff0c;需要接在单片机…

java包装类型缓存简单探究-Integer为例

文章目录 包装类型缓存自动装箱与valueOf感悟结语 包装类型缓存 包装类型缓存是什么 本文以常用的Integer包装类为例做一个探索&#xff0c;感兴趣可以用类似方法查看其他包装类。 我们都知道它会缓存 -128到127之间的整数Integer对象。 结论大伙都知道。那么我们今天就来探究…

【Android】安卓四大组件之广播知识总结

文章目录 动态注册使用BroadcastReceiver监听Intent广播注册Broadcast Receiver 静态注册自定义广播标准广播发送广播定义广播接收器注册广播接收器 有序广播修改发送方法定义第二个广播接收器注册广播接收器广播截断 使用本地广播实践-强制下线使用ActivityCollector管理所有活…

ubuntu那些ppa源在哪

Ubuntu中的 PPA 终极指南 - UBUNTU粉丝之家 什么是PPA PPA 代表个人包存档。 PPA 允许应用程序开发人员和 Linux 用户创建自己的存储库来分发软件。 使用 PPA&#xff0c;您可以轻松获取较新的软件版本或官方 Ubuntu 存储库无法提供的软件。 为什么使用PPA&#xff1f; 正如…

【JavaEE】Spring Boot 自动装配原理(源码分析)

一. 前言 我们在写Spring Boot的程序代码的时候, 可以注入很多我们没有定义过的Bean.例如: Autowired private ApplicationContext applicationContext; Autowired public DataSourceTransactionManager transactionManager; Autowired public AutowireCapableBeanFactory …

软件开发者消除edge浏览器下载时“此应用不安全”的拦截方法

当Microsoft Edge浏览器显示“此应用不安全”或者“已阻止此不安全的下载”这类警告时&#xff0c;通常是因为Windows Defender SmartScreen或者其他安全功能认为下载的文件可能存在安全风险。对于软件开发者来说&#xff0c;大概率是由于软件没有进行数字签名&#xff0c;导致…

Visual Studio 2022新建 cmake 工程测试 tensorRT 自带样例 sampleOnnxMNIST

1. 新建 cmake 工程 vs2022_cmake_sampleOnnxMNIST_test( 如何新建 cmake 工程&#xff0c;请参考博客&#xff1a;Visual Studio 2022新建 cmake 工程测试 opencv helloworld ) 2. 删除默认生成的 vs2022_cmake_sampleOnnxMNIST_test.h 头文件 3. 修改默认生成的 vs2022_cma…

【屏显MCU】多媒体接口总结

本文主要介绍【屏显MCU】的基本概念&#xff0c;用于开发过程中的理解 以下是图层叠加示例 【屏显MCU】多媒体接口总结 0. 个人简介 && 授权须知1. 三大引擎1.1 【显示引擎】Display Engine1.1.1 【UI】 图层的概念1.1.2 【Video】 图层的概念1.1.3 图层的 Blending 的…

一键解锁:科研服务器性能匹配秘籍,选择性能精准匹配科研任务和计算需求的服务器

一键解锁&#xff1a;科研服务器性能匹配秘籍 HPC科研工作站服务器集群细分领域迷途小书童 专注于HPC科研服务器细分领域kyfwq001 &#x1f3af;在当今科技飞速发展的时代&#xff0c;科研工作对计算资源的需求日益增长&#x1f61c;。选择性能精准匹配科研任务和计算需求的服…

古籍双层PDF制作教程:保姆级古籍数字化教程

在智慧古籍数字化项目中&#xff0c;很多图书馆要求将古籍导出为双层PDF&#xff0c;并且确保输出双层PDF底层文本与上层图片偏移量控制在1毫米以内。那么本教程带你使用古籍数字化平台&#xff0c;3分钟把一个古籍书籍转化为双侧PDF。 第1步&#xff1a;上传古籍 点批量上传…

前序+中序、中序+后序构造二叉树

https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/ 前序中序 前序遍历&#xff0c;节点按照 [根左右] 排序。 中序遍历&#xff0c;节点…

JavaEE - Spring Boot 简介

1.Maven 1.1 什么是Maven 翻译过来就是: Maven是⼀个项⽬管理⼯具。基于POM(Project Object Model,项⽬对象模型)的概念&#xff0c;Maven可以通 过⼀⼩段描述信息来管理项⽬的构建&#xff0c;报告和⽂档的项⽬管理⼯具软件。 可以理解为&#xff1a;Maven是一个项目管理工具…