如何在 Spring Boot 服务应用程序中的 REST 服务调用之间按原样传递请求参数?

Posted

技术标签:

【中文标题】如何在 Spring Boot 服务应用程序中的 REST 服务调用之间按原样传递请求参数?【英文标题】:How to pass request parameters as-is between REST service calls in a Spring Boot services application? 【发布时间】:2019-08-12 06:04:08 【问题描述】:

我们正在进行架构重构,将单一的 J2EE EJB 应用程序转换为 Spring 服务。为了做到这一点,我通过打破应用程序与其域的连接来创建服务。目前,我有三个,每个都通过 Rest 调用另一个服务。

在这个项目中,我们的最终目的是将应用程序转换为微服务,但由于云基础设施尚不清楚并且可能无法实现,因此我们决定这样做,并认为既然服务使用了 Rest,它将是将来很容易进行转换。

我们的方法有意义吗?我的问题源于此。


我向 UserService 发送了一个请求,带有一个标头参数,来自 Postman 的 userName。

GET http://localhost:8087/users/userId?userName=12345

UserService 调用另一个服务,该服务调用另一个服务。服务之间的休息调用顺序是这样的:

UserService ---REST--> CustomerService ---REST--> AlarmService

由于我现在正在做这样携带通用请求参数的工作,因此我需要在每个发出 Rest 请求的方法中设置通用标头参数,方法是将它们从传入请求带到传出请求:

@RequestMapping(value="/users/userId", method = RequestMethod.GET)
public ResponseEntity<Long> getUserId(@RequestHeader("userName") String userName) 
    ...
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList
(MediaType.APPLICATION_JSON));

        headers.set("userName", userName);

        HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
        HttpEntity<Long> response =
                restTemplate.exchange(CUSTOMER_REST_SERVICE_URI,
                HttpMethod.GET, entity, Long.class);
     ...
 

用户服务:

package com.xxx.userservice.impl;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;
import java.util.Map;

@RestController
public class UserController  extends AbstractService

    Logger logger = Logger.getLogger(UserController.class.getName());

    @Autowired
    private RestTemplate restTemplate;

    private final String CUSTOMER_REST_SERVICE_HOST = "http://localhost:8085";
    private final String CUSTOMER_REST_SERVICE_URI = CUSTOMER_REST_SERVICE_HOST + "/customers/userId";

    @RequestMapping(value="/users/userId", method = RequestMethod.GET)
    public ResponseEntity<Long> getUserId(@RequestHeader("userName") String userName) 
        logger.info(""user service is calling customer service..."");
        try 

            //do the internal customer service logic

            //call other service.
            HttpHeaders headers = new HttpHeaders();
            headers.setAccept(Collections.singletonList
(MediaType.APPLICATION_JSON));
            headers.set("userName", userName);
            HttpEntity<String> entity = new HttpEntity<>("parameters", headers);

            HttpEntity<Long> response =
                    restTemplate.exchange(CUSTOMER_REST_SERVICE_URI,
                    HttpMethod.GET, entity, Long.class);

            return ResponseEntity.ok(response.getBody());
         catch (Exception e) 
            logger.error("user service could not call customer service: ", e);
            throw new RuntimeException(e);
        
        finally 
            logger.info("customer service called...");
        
    


客户服务:

package com.xxxx.customerservice.impl;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.xxx.interf.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CustomerController  extends AbstractService

    private final String ALARM_REST_SERVICE_HOST = "http://localhost:8086";
    private final String ALARM_REST_SERVICE_URI = ALARM_REST_SERVICE_HOST + "/alarms/maxAlarmCount";

    @Autowired
    private CustomerService customerService;

    @Autowired
    private RestTemplate restTemplate;

    ...

    @GetMapping(path="/customers/userId", produces = "application/json")
    public long getUserId(@RequestHeader(value="Accept") String acceptType) throws RemoteException 

        //customer service internal logic.
        customerService.getUserId();

        //customer service calling alarm service.
        return restTemplate.getForObject(ALARM_REST_SERVICE_URI, Long.class);

    


报警服务:

package com.xxx.alarmservice.impl;

import com.xxx.interf.AlarmService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PriceAlarmController extends AbstractService

    @Autowired
    private AlarmService priceAlarmService;

    @RequestMapping("/alarms/maxAlarmCount")
    public long getMaxAlarmsPerUser() 

        // alarm service internal logic.
        return priceAlarmService.getMaxAlarmsPerUser();
    


我已经尝试过这些配置和拦截器文件,但我可以将它们仅用于日志记录,并且不能通过使用它们来传输标头参数。可能是因为每个服务都有它们。而且,这个拦截器只适用于首先使用 RestTemplate 发送请求的 UserService。来自 Postman 的调用服务和第一个请求无法使用它,因为它们不会像 UserService 那样打印任何日志消息。

通用模块:

package com.xxx.common.config;

import com.xxx.common.util.HeaderRequestInterceptor;
import org.apache.cxf.common.util.CollectionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class RestTemplateConfig 

    @Bean
    public RestTemplate restTemplate() 
        RestTemplate restTemplate = new RestTemplate();

        List<ClientHttpRequestInterceptor> interceptors
                = restTemplate.getInterceptors();
        if (CollectionUtils.isEmpty(interceptors)) 
            interceptors = new ArrayList<>();
        
        interceptors.add(new HeaderRequestInterceptor());
        restTemplate.setInterceptors(interceptors);
        return restTemplate;
    

ClientHttpRequestInterceptor:


package com.xxx.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.Charset;

public class HeaderRequestInterceptor implements ClientHttpRequestInterceptor 

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public ClientHttpResponse intercept(
            HttpRequest request,
            byte[] body,
            ClientHttpRequestExecution execution) throws IOException
    
        log.info("HeaderRequestInterceptor....");
        logRequest(request, body);
        request.getHeaders().set("Accept", MediaType.APPLICATION_JSON_VALUE);

        ClientHttpResponse response = execution.execute(request, body);
        logResponse(response);

        return response;
    

    private void logRequest(HttpRequest request, byte[] body) throws IOException
    
        log.info("==========request begin=======================");
    

    private void logResponse(ClientHttpResponse response) throws IOException
    
        log.info("==========response begin=============");
    



如何通过在单个位置使用某种拦截器或其他机制来管理通用标头信息(如 userName)的传递?

【问题讨论】:

【参考方案1】:

在您的 HeaderRequestInterceptor 的拦截方法中,您可以通过以下方式访问当前的 http 请求及其标头(在您的情况下为 userId):

@Override
public ClientHttpResponse intercept(HttpRequest request..
...
   HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
   String userId = httpServletRequest.getHeader("userId");
   request.getHeaders().set("userId", userId);

【讨论】:

以上是关于如何在 Spring Boot 服务应用程序中的 REST 服务调用之间按原样传递请求参数?的主要内容,如果未能解决你的问题,请参考以下文章

Windows 服务器中的 Spring Boot 应用程序

如何在 spring-boot 中的微服务中创建 Super/Base/Parent 类 - 在子类中扩展该类

如何将 Spring Boot Rest 服务和 Angular App 捆绑在一个 war 文件中

如何理解spring boot中的微服务架构的体现

如何在 Spring Boot 中从资源服务器中的令牌中提取声明

如何在嵌入式 tomcat 服务器上部署 Spring Boot Web 应用程序,来自 Spring Boot 本身