前言
在Java
中,特别是在构建Web
应用程序或微服务架构时,经常会遇到需要从一个服务向另一个服务发送HTTP
请求的场景。无论是为了调用远程API
、与其他服务通信还是进行数据同步,发送HTTP
请求都是常见的需求。通常性的做法是在Spring
框架中使用@RestController
或@Controller
注解来定义RESTful
服务,并借助RestTemplate
或更现代的WebClient
来执行HTTP
请求。
常见做法
使用 RestTemplate
RestTemplate
是Spring
框架提供的一种简单而强大的工具,用于执行HTTP
请求。它支持多种HTTP
方法(如GET、POST、PUT、DELETE
等),并且可以自动处理请求的序列化和响应的反序列化。这使得开发人员可以专注于业务逻辑,而不必关心底层的网络通信细节。
伪代码:
@RestController
@RequestMapping("/proxy")
public class ProxyController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/data")
public String forwardRequest() {
String targetUrl = "http://example.com/api/data";
// 发送GET请求并返回字符串响应
String response = restTemplate.getForObject(targetUrl, String.class);
return response;
}
}
使用 WebClient
@RestController
@RequestMapping("/proxy")
public class ProxyController {
private final WebClient webClient;
public ProxyController() {
this.webClient = WebClient.builder().baseUrl("http://example.com/api").build();
}
@GetMapping("/data")
public Mono<String> forwardRequest() {
return webClient.get()
.uri("/data")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class);
}
}
当需要处理大量的接口转发时,使用上述方法可能会导致代码冗余,并且每增加一个新的接口转发逻辑都需要修改现有代码。这不仅增加了代码的复杂度,也降低了代码的可维护性和扩展性。
函数式
可以将路由规则配置在外部文件(如yaml
或xml
文件)中,基于路由配置 RouterFunction
和 HandlerFunction
实现接口暴露。这样当需要新增或修改路由规则时,只需要更改配置文件即可,而不需要修改Java
代码。
RouterFunction
在SpringMvc
和WebFlux
都存在,本篇主要介绍SpringMvc
下使用。
定义配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<controllers>
<controller name="queryUser" desc="此处的beanName为注册进入Spring容器的名称,需保证唯一性">
<input desc="请求方服务能力开放平台配置">
<requesthead url="/queryUser" method="POST" token="" contenttype="" ></requesthead>
<parameters desc="请求参数配置">
<parameter name="operator" desc="参数1描述">1</parameter>
</parameters>
</input>
<auth desc="向第三方平台发起认证请求配置,若非必须发送发送认证请求,此处可不配置">
<requesthead url="http://localhost:8081/test/auth" method="POST" token="" contenttype="application/json" ></requesthead>
<parameters desc="请求参数配置">
<parameter name="username" desc="认证请求参数配置-用户名">yian</parameter>
<parameter name="password" desc="认证请求参数配置-密码">123456</parameter>
</parameters>
<responses desc="认证返回结果封装">
<parameter name="token" desc="认证token解析参数名"></parameter>
</responses>
</auth>
<output desc="按运营商统计近24小时内认证成功总数相关配置">
<requesthead url="http://localhost:8081/test/authResult" method="GET" token="abcd" contenttype="" ></requesthead>
<parameters desc="按运营商统计近24小时内认证成功总数请求参数配置">
<parameter name="operator" desc="请求参数配置">1</parameter>
</parameters>
<responses desc="最终返回结果封装" removeAttr="port">
<parameter name="code" desc="响应结果码"></parameter>
<parameter name="msg" desc="响应结果描述"></parameter>
<parameter name="data" desc="响应结果"></parameter>
</responses>
</output>
</controller>
</controllers>
对于XML
转POJO
对象,可以借助其注解实现,伪代码:
@Data
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name = "controllers")
@XmlAccessorType(XmlAccessType.FIELD)
public class ControllerBeanList {
@XmlElement(name = "controller")
private List<ControllerBean> controllerBeanList;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name = "controller")
@XmlAccessorType(XmlAccessType.FIELD)
public class ControllerBean {
@XmlAttribute(name = "name")
private String name;
@XmlElement(name = "input")
private InputBean inputBean;
@XmlElement(name = "auth")
private AuthBean authBean;
@XmlElement(name = "output")
private OutputBean outputBean;
}
将XML
转为指定的POJO
对象
/**
* 将XML转为指定的POJO对象
*
* @param clazz 需要转换的类
* @param xmlStr xml数据
* @return
*/
public static Object xmlStrToObject(Class<?> clazz, String xmlStr) throws Exception {
Object xmlObject = null;
Reader reader = null;
//利用JAXBContext将类转为一个实例
JAXBContext context = JAXBContext.newInstance(clazz);
//XMl 转为对象的接口
Unmarshaller unmarshaller = context.createUnmarshaller();
reader = new StringReader(xmlStr);
xmlObject = unmarshaller.unmarshal(reader);
if (reader != null) {
reader.close();
}
return xmlObject;
}
配置动态生成RouterFunction
官网:
https://docs.spring.io/spring-framework/reference/web/webmvc-functional.html
使用 RouterFunction
的主要步骤包括定义 HandlerFunction
、定义 RouterFunction
并将其组合在一起,这一步很关键。
private RouterFunction builderRouterFunction(ControllerBean controllerBean){
String reqUrl = controllerBean.getInputBean().getRequestHeadBean().getUrl();
String reqMethod = controllerBean.getInputBean().getRequestHeadBean().getMethod();
//构建RequestPredicate
RequestPredicate requestPredicate=null;
switch (reqMethod){
case METHOD_GET:
requestPredicate = RequestPredicates.GET(reqUrl).and(RequestPredicates.accept(MediaType.ALL));
break;
case METHOD_POST:
requestPredicate = RequestPredicates.POST(reqUrl).and(RequestPredicates.accept(MediaType.ALL));
break;
case METHOD_PUT:
requestPredicate = RequestPredicates.PUT(reqUrl).and(RequestPredicates.accept(MediaType.ALL));
break;
case METHOD_DELETE:
requestPredicate = RequestPredicates.DELETE(reqUrl).and(RequestPredicates.accept(MediaType.ALL));
break;
default:
requestPredicate = RequestPredicates.GET(reqUrl).and(RequestPredicates.accept(MediaType.ALL));
}
//封装处理Handler
HandlerFunction<ServerResponse> handlerFunction = new HandlerFunction<ServerResponse>() {
@Override
public ServerResponse handle(ServerRequest request) throws Exception {
//TODO 验证请求头
//TODO 验证接口权限
//TODO 构建第三方Auth请求
//TODO 正式发送转发请求
String outMethod = controllerBean.getOutputBean().getRequestHeadBean().getMethod();
switch (outMethod){
case METHOD_POST:
outResponseStr = HttpRequest.post(outPutUrl).header(outTokenKey,tokenValue).body(JSONObject.toJSONString(outBody)).execute().body();
break;
case METHOD_PUT:
outResponseStr = HttpRequest.put(outPutUrl).header(outTokenKey,tokenValue).body(JSONObject.toJSONString(outBody)).execute().body();
break;
case METHOD_DELETE:
outResponseStr = HttpRequest.delete(outPutUrl).header(outTokenKey,tokenValue).body(JSONObject.toJSONString(outBody)).execute().body();
break;
default:
outResponseStr = HttpRequest.get(outPutUrl).header(outTokenKey,tokenValue).form(BeanUtil.beanToMap(outBody)).execute().body();
}
//将转发请求的结果进行封装
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(result.toJSONString());
}
};
return RouterFunctions.route(requestPredicate,handlerFunction);
}
注册进入Spring容器中
/**
* 动态注册Web/WebFlux
* @param beanName
* @param routerFunction
*/
private void reginstControllerBean(String beanName,RouterFunction routerFunction){
//动态生成Web/WebFlux对象,并注入Spring容器
//将applicationContext转换为ConfigurableApplicationContext
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
//获取BeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getAutowireCapableBeanFactory();
//动态生成Controller+注册bean
defaultListableBeanFactory.registerSingleton(beanName,routerFunction);
}
到这里已基本完成,当需要新增或修改路由规则时,只需要更改配置文件即可。
来源:https://mp.weixin.qq.com/s/zIOrzJMwZRNJtSa0937lGA
没有回复内容