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

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

需求

某些场景,调用别人的方法,但是它外面包了一层,我们只需要里面实际的数据,例如后端开发中的R对象,实际最终只需要data。也就是说可以看成,调用原始方法,代理后更改为别的类型。

下面是R对象,实际运用大同小异,方便大家理解。

java 复制代码
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 模拟原始方法返回值。

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对象,重新封装所有方法。

java 复制代码
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的对应的方法名相同。

java 复制代码
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处理代理方法

java 复制代码
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. 调用

java 复制代码
    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返回参数,大同小异。

java 复制代码
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 接口

java 复制代码
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);
    }

}
java 复制代码
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。

java 复制代码
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对象进行了统一处理,并且后续根据需要,可以通过错误码进行日志输出和报错,通过全局异常处理器,返回前端。

java 复制代码
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层的调用

java 复制代码
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 测试

java 复制代码
@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容器中,每次使用者都需要创建代理对象,很麻烦。

java 复制代码
@Autowired
public ExampleService(ExampleClient exampleClient) {
       this.afterExampleClient = ClientProxyDhHandler.create(AfterExampleClient.class, exampleClient);
}

解决方案:通过配置类,一次配置,其他使用者直接进行注入。如下:

java 复制代码
@Configuration
public class AfterClientConfiguration {


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

至此,优化完成。🎉🎊

相关推荐
重生之我要进大厂2 分钟前
LeetCode 876
java·开发语言·数据结构·算法·leetcode
_祝你今天愉快5 分钟前
技术成神之路:设计模式(十四)享元模式
java·设计模式
小筱在线43 分钟前
SpringCloud微服务实现服务熔断的实践指南
java·spring cloud·微服务
luoluoal1 小时前
java项目之基于Spring Boot智能无人仓库管理源码(springboot+vue)
java·vue.js·spring boot
ChinaRainbowSea1 小时前
十三,Spring Boot 中注入 Servlet,Filter,Listener
java·spring boot·spring·servlet·web
小游鱼KF1 小时前
Spring学习前置知识
java·学习·spring
扎克begod1 小时前
JAVA并发编程系列(9)CyclicBarrier循环屏障原理分析
java·开发语言·python
青灯文案11 小时前
SpringBoot 项目统一 API 响应结果封装示例
java·spring boot·后端
我就是程序猿1 小时前
tomcat的配置
java·tomcat
阳光阿盖尔1 小时前
EasyExcel的基本使用——Java导入Excel数据
java·开发语言·excel