• 在Spring中定义RESTful服务更为灵活的做法
  • 发布于 2个月前
  • 242 热度
    0 评论
前言
在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;
     // 堆代码 duidaima.com
    @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);
}
到这里已基本完成,当需要新增或修改路由规则时,只需要更改配置文件即可。

启动验证


用户评论