动态代理更改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); } }
下面
ExampleClient
和ExampleClientImpl
仅用于模拟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; } }
至此,优化完成。🎉🎊