动态代理更改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;
}
}
至此,优化完成。🎉🎊