• 解锁新技能RestTemplate设置全局、单个请求超时时间及支持https请求


    springboot请求第三方接口时会用到RestTemplate,其底层实现逻辑默认是通过SimpleClientHttpRequestFactory来实现,具体由socket连接来实现;可以替换其默认实现为HttpComponentsClientHttpRequestFactory。

    一、自定义RestTemplate实例对象
        @Primary
        @Bean
        public RestTemplate restTemplate(ObjectProvider<HttpClientCustomizer> httpClientCustomizers, ClientHttpRequestFactory clientHttpRequestFactory, HttpClientProperties httpClientProperties) {
            RestTemplate restTemplate = new RestTemplate();
            //设置BufferingClientHttpRequestFactory将输入流和输出流保存到内存中,允许多次读取
            restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(clientHttpRequestFactory));
            //设置自定义异常处理
            restTemplate.setErrorHandler(new CustomResponseErrorHandler());
            if (httpClientProperties.isInterceptor()) {
                //添加拦截器
              restTemplate.setInterceptors(Collections.singletonList(httpClientCustomizers.orderedStream().findFirst().get()));
            }
    
            return restTemplate;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    二、RestTemplate自定义全局超时时间
        @Bean
        public ClientHttpRequestFactory clientHttpRequestFactory(HttpClientProperties properties) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
            //SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
            //读取超时5秒,默认无限限制,单位:毫秒
            factory.setReadTimeout(properties.getReadTimeOut());
            //连接超时10秒,默认无限制,单位:毫秒
            factory.setConnectTimeout(properties.getConnectTimeOut());
            return factory;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    三、RestTemplate设置单个请求的超时时间

    首先看下HttpComponentsClientHttpRequestFactory类的createRequest方法源码:

    	@Override
    	public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
    		HttpClient client = getHttpClient();
    
    		HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
    		postProcessHttpRequest(httpRequest);
    		HttpContext context = createHttpContext(httpMethod, uri);
    		if (context == null) {
    			context = HttpClientContext.create();
    		}
    
    		// Request configuration not set in the context
    		if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) {
    			// Use request configuration given by the user, when available
    			RequestConfig config = null;
    			if (httpRequest instanceof Configurable) {
    				config = ((Configurable) httpRequest).getConfig();
    			}
    			if (config == null) {
    				config = createRequestConfig(client);
    			}
    			if (config != null) {
    				context.setAttribute(HttpClientContext.REQUEST_CONFIG, config);
    			}
    		}
    
    		if (this.bufferRequestBody) {
    			return new HttpComponentsClientHttpRequest(client, httpRequest, context);
    		}
    		else {
    			return new HttpComponentsStreamingClientHttpRequest(client, httpRequest, context);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    其中createHttpContext方法默认返回的是null,因此HttpContext中的RequestConfig配置值为null,所以需要按照接下来的代码生成并设置;RequestConfig配置类中的socketTimeout是设置读取超时时间,connectTimeout是设置连接超时时间的两个属性,明白了这些就应该知道怎样设置单个请求超时时间了;

    定义一个HttpContextFactory类,即HttpContext类的一个实现:

    public class HttpContextFactory implements BiFunction<HttpMethod, URI, HttpContext> {
        @Override
        public HttpContext apply(HttpMethod httpMethod, URI uri) {
            RequestConfig requestConfig = HttpContextHolder.peek();
            if (Objects.nonNull(requestConfig)) {
                HttpContext context = HttpClientContext.create();
                context.setAttribute(HttpClientContext.REQUEST_CONFIG, requestConfig);
                return context;
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    定义一个持有RequestConfig线程上下文对象类:

    public class HttpContextHolder {
    
        private static final ThreadLocal<RequestConfig> threadLocal = new NamedThreadLocal<>("HTTP进程执行状态上下文");
    
        public static void bind(RequestConfig requestConfig) {
            threadLocal.set(requestConfig);
        }
    
        public static RequestConfig peek() {
            return threadLocal.get();
        }
    
        public static void unbind() {
            threadLocal.remove();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    设置将HttpContextFactory类实例对象:

        @Bean
        public ClientHttpRequestFactory clientHttpRequestFactory(HttpClientProperties properties) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
            //SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
            //读取超时5秒,默认无限限制,单位:毫秒
            factory.setReadTimeout(properties.getReadTimeOut());
            //连接超时10秒,默认无限制,单位:毫秒
            factory.setConnectTimeout(properties.getConnectTimeOut());
            //设置HTTP进程执行状态工厂类
            factory.setHttpContextFactory(new HttpContextFactory());
            return factory;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    调用示例:

    @RequestMapping("api/http")
    @RestController
    public class HttpClientController {
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("get1")
        public BaseResponse get1(HttpServletRequest request) {
            String timeout = request.getParameter("timeout");
            BaseResponse<String> result;
            try {
                HttpContextHolder.bind(RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(-1).build());
                result = restTemplate.getForObject("https://127.0.0.1:8080/api/http/testResponse?timeout=" + timeout, BaseResponse.class);
            } finally {
                HttpContextHolder.unbind();
            }
            return result;
        }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这样设置有点麻烦,可以定义一个注解,以AOP切面的方式来使用:

    定义注解@TargetHttpTimeout:

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface TargetHttpTimeout {
        /**
         * 读取超时时间,默认:-1
         */
        int readTimeout() default -1;
    
        /**
         * 连接超时时间,默认:-1
         */
        int connectTimeout() default -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    定义拦截器HttpTimeoutMethodInterceptor:

    public class HttpTimeoutMethodInterceptor implements MethodInterceptor {
        @Nullable
        @Override
        public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
            try {
                Method method = invocation.getMethod();
                if (method.isAnnotationPresent(TargetHttpTimeout.class)) {
                    TargetHttpTimeout targetHttpTimeout = method.getAnnotation(TargetHttpTimeout.class);
                    RequestConfig requestConfig = RequestConfig.custom()
                            .setSocketTimeout(targetHttpTimeout.readTimeout())
                            .setConnectTimeout(targetHttpTimeout.connectTimeout())
                            .build();
                    HttpContextHolder.bind(requestConfig);
                }
                return invocation.proceed();
            } finally {
                HttpContextHolder.unbind();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    将AOP切面及切点关联起来并注入容器:

        @Bean
        @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
        public Advisor httpTimeoutPointCutAdvice() {
            //限定方法级别的切点
            Pointcut mpc = new AnnotationMatchingPointcut(null, TargetHttpTimeout.class, false);
            //组合切面(并集),即只要有一个切点的条件符合,则就拦截
            Pointcut pointcut = new ComposablePointcut(mpc);
            //切面增强类
            AnnotationPointcutAdvisor advisor = new AnnotationPointcutAdvisor(new HttpTimeoutMethodInterceptor(), pointcut);
            //切面优先级顺序
            advisor.setOrder(AopOrderInfo.HTTP_CLIENT_INTERCEPTOR);
            return advisor;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通过上述几步的优化就可以优雅的按照注解的方式设置单个请求的超时时间:

        @GetMapping("get2")
        @TargetHttpTimeout(readTimeout = 2000, connectTimeout = -1)
        public BaseResponse get2(HttpServletRequest request) {
            String timeout = request.getParameter("timeout");
            BaseResponse<String> result = restTemplate.getForObject("https://127.0.0.1:8080/api/http/testResponse?timeout=1000", BaseResponse.class);
    
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    四、RestTemplate支持https请求
        @Bean
        public ClientHttpRequestFactory clientHttpRequestFactory(HttpClientProperties properties) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
            //SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
            //读取超时5秒,默认无限限制,单位:毫秒
            factory.setReadTimeout(properties.getReadTimeOut());
            //连接超时10秒,默认无限制,单位:毫秒
            factory.setConnectTimeout(properties.getConnectTimeOut());
            //设置HTTP进程执行状态工厂类
            factory.setHttpContextFactory(new HttpContextFactory());
            //开启HTTPS请求支持
            if (properties.isSsl()) {
                TrustStrategy acceptingTrustStrategy = (x509Certificates, authType) -> true;
                SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
                SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
    
                CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(connectionSocketFactory).build();
                factory.setHttpClient(httpClient);
            }
            return factory;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    通过上述的步骤完整的实现了http全局超时时间的设置,单个请求超时时间设置,https请求支持;

    GitHub地址:https://github.com/mingyang66/spring-parent

  • 相关阅读:
    Java容器之Map
    PCF8574/ PCF8574A/ PCF8574T I2C to parellal 8-bits I/O
    mybatisplus分页插件
    java毕业生设计大学生社团管理系统计算机源码+系统+mysql+调试部署+lw
    dell的笔记本电脑如果开机总是黑屏 需要开几次才能点亮屏幕
    阿里云 Flink+Hologres:构建企业级一站式实时数仓
    C#之性能优化
    VScode连接Xshell 并解决【过程试图写入的管道不存在】报错
    javaEE - 2(11000字详解多线程)
    算法leetcode|1656. 设计有序流(rust和go重拳出击)
  • 原文地址:https://blog.csdn.net/yaomingyang/article/details/126254098