1、问题介绍

由于调用的某个第三方接口存在超时的情况,需要临时缩短该接口的请求超时时间。为了避免全局覆盖,在调用该接口时重新创建RestTemplate实例。由于不知当前RestTemplate实例的配置,新RestTemplate实例调用接口发生406 Not Acceptable错误。

Http状态码中,406 Not Acceptable表示:请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。表示客户端错误,服务器端无法提供与 Accept-Charset 以及 Accept-Language 消息头指定的值相匹配的响应。本次出现这种错误的情况是:RestTemplate调用的url需要权限,导致HttpUrlConnection连接失败。

2、RestTemplate介绍

RestTemplate是Spring提供的调用远程REST服务的类。SpringBoot没有自动配置RestTemplate实例,而是自动配置了RestTemplateBuilder,可用于创建RestTemplate实例,能够确保RestTemplate实例采用了合适的HttpMessageConverters。基本用法如下:

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class MyService {
    private final RestTemplate restTemplate;
    public MyService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }
}

3、添加拦截器(解决方法)

  • 定义拦截器
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;

public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
    private static final String REQUEST_PATH_WITH_AUTHORIZATION = "admin";
    private static final String HEADER_NAME_TOKEN = "auth_token";

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        String path = request.getURI().getPath();
        if (path.contains(REQUEST_PATH_WITH_AUTHORIZATION)) {
            // 某种token生成策略
            String token = "123456";
            request.getHeaders().add(HEADER_NAME_TOKEN, token);
        }
        return execution.execute(request, body);
    }
}
  • 设置拦截器
import com.example.interceptor.RestTemplateInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.time.Duration;

@Configuration
public class RestTemplateConfig {
    @Value("${rest.connectTimeout:5}")
    private Integer connectTimeout;

    @Value("${rest.readTimeout:5}")
    private Integer readTimeout;

    @Resource
    private RestTemplateBuilder restTemplateBuilder;
    @Resource
    private RestTemplateInterceptor restTemplateInterceptor;

    @Bean
    public RestTemplate restTemplate() {
        // 设置超时时间,detectRequestFactory默认true,设置为false则超时时间设置无效
        return restTemplateBuilder.additionalInterceptors(restTemplateInterceptor)
                .setConnectTimeout(Duration.ofSeconds(connectTimeout))
                .setReadTimeout(Duration.ofSeconds(readTimeout))
                .detectRequestFactory(true)
                .build();
    }
}

4、小结

在进行某个Bean的自定义时,最好了解现有Bean的创建过程,不然可能漏掉重要配置。