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

avatar
作者
筋斗云
阅读量:0

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

需求

某些场景,调用别人的方法,但是它外面包了一层,我们只需要里面实际的数据,例如后端开发中的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;     } } 

至此,优化完成。🎉🎊

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!