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

至此,优化完成。🎉🎊

相关推荐
qq_4419960517 分钟前
Mybatis官方生成器使用示例
java·mybatis
巨大八爪鱼24 分钟前
XP系统下用mod_jk 1.2.40整合apache2.2.16和tomcat 6.0.29,让apache可以同时访问php和jsp页面
java·tomcat·apache·mod_jk
码上一元2 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
计算机-秋大田2 小时前
基于微信小程序的养老院管理系统的设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
魔道不误砍柴功4 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v4 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge4 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@4 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄4 小时前
SpringBoot
java·spring