Spring-Web (1) uso de RestTemplate y análisis de código fuente

Uso de Spring RestTemplate y análisis de código fuente

1. Descripción general de RestTemplate

​ RestTemplate es una herramienta que proporciona servicios de solicitud HTTP basados ​​en la especificación Rest encapsulada por el módulo Spring Web y se utiliza para acceder a interfaces Rest de terceros. Tradicionalmente, cuando el servidor accede HTTPal servicio, generalmente usa JDKo HttpURLConnection, pero estos métodos y herramientas son engorrosos, demasiado complicados y requieren una recuperación manual de recursos Apache. En este contexto, RestTemplate simplifica la comunicación con los servicios HTTP y cumple con el principio RestFul, solo debemos centrarnos en enviar solicitudes y obtener resultados. Es una clase de herramienta de bloqueo síncrono para ejecutar solicitudes . Solo encapsula operaciones subyacentes como la construcción de solicitudes, el reciclaje de recursos y el manejo de errores sobre la base de bibliotecas de clientes (por ejemplo , fuentes de servicios HTTP , etc.), lo que proporciona una forma más simple y sencilla. -to-use La API del método de plantilla ha mejorado enormemente nuestra eficiencia de desarrollo.HttpClientAPIRestTemplateHTTPHTTPJDK HttpURLConnectionApache HttpComponentsokHttp

Desde Spring 5.0, RestTemplate está en modo de mantenimiento. Se reemplaza por WebClient, que proporciona una API más moderna y admite soluciones síncronas, asíncronas y de transmisión, lo que admite escenarios de aplicaciones más complejos, ricos y flexibles. No hablaremos de ello aquí.

Documentos de referencia:

2. Uso de RestTemplate

1. Introducción a RestTemplate

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. Colocación de RestTemplate

2.1 Configurar fuente HTTP

(1) Introducción al cliente HTTP

​ RestTemplate hereda de HttpAccessor. Esta clase puede entenderse como una clase base abstracta para contactar/acceder a clientes subyacentes HTTP. Su código fuente es el siguiente. Se puede ver que el atributo de fábrica ClientHttpRequestFactory (o biblioteca de solicitud de cliente) en esta clase se usa especialmente para construir una solicitud de solicitud a través del cliente de conexión HTTP subyacente correspondiente y proporcionar servicios de acceso de solicitud HTTP hacia arriba, y su asignación predeterminada es SimpleClientHttpRequestFactory. Para cambiar entre diferentes fuentes HTTP, debemos asignar otros clientes a ClientHttpRequestFactory a través del método setRequestFactory.

public abstract class HttpAccessor {
    
    
    protected final Log logger = HttpLogging.forLogName(this.getClass());
    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
    private final List<ClientHttpRequestInitializer> clientHttpRequestInitializers = new ArrayList();

    public HttpAccessor() {
    
    
    }

    public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
    
    
        Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
        this.requestFactory = requestFactory;
    }
    
    ... ...
}

El cliente HTTP subyacente se usa para establecer una conexión HTTP, Factory se usa para construir una solicitud desde la conexión HTTP correspondiente para iniciar una solicitud, y RestTemplate se usa para encapsular el proceso anterior y proporcionar una API de plantilla más simple y conveniente. al mundo exterior. La biblioteca de conexión de cliente HTTP que admite RestTemplate se implementa desde la interfaz ClientHttpRequestFactory. Las bibliotecas de cliente comunes y sus correspondientes clientes HTTP se presentan de la siguiente manera:

  • SimpleClientHttpRequestFactory: la biblioteca de cliente predeterminada de RestTemplate, y su tipo de cliente de conexión HTTP correspondiente es HttpURLConnection que viene con java JDK como HTTPimplementación de cliente subyacente.
  • HttpComponentsAsyncClientHttpRequestFactory: el tipo de cliente de conexión HTTP correspondiente es HttpClient de Apache como HTTPimplementación de cliente subyacente.
  • OkHttp3ClientHttpRequestFactory: el tipo de cliente de conexión HTTP correspondiente es OkHttpClient como HTTPimplementación de cliente subyacente.

A juzgar por los comentarios de los desarrolladores y varias HTTPevaluaciones de rendimiento y facilidad de uso del cliente en Internet, OkHttpClientes superior Apache的HttpClienty Apache的HttpClientsuperior HttpURLConnection. Y debe tenerse en cuenta que HttpURLConnection que viene con Java JDK no es compatible con el método Patch del protocolo HTTP Podemos cambiar la biblioteca de clases de implementación del cliente HTTP subyacente de RestTemplate configurando el método setRequestFactory.

(2) Cambiar cliente HTTP

  • Configuración predeterminada de SimpleClientHttpRequestFactory
@Configuration
public class RestTemplateConfig {
    
    

   @ConditionalOnMissingBean(RestTemplate.class)
   @Bean
   public RestTemplate restTemplate() {
    
    
       return new RestTemplate(getClientHttpRequestFactory());
   }

   private ClientHttpRequestFactory getClientHttpRequestFactory() {
    
    
       SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
       factory.setReadTimeout(150000); //设置传输/读取数据的超时时间(以毫秒为单位)
       factory.setConnectTimeout(150000); //设置Connection的连接超时时间(以毫秒为单位)
       //factory.setBufferRequestBody(true); //设置是否应在factory内部缓冲/缓存请求正文body数据(传输大数据时应设置为true,默认为true)
       return factory;
   }
}
  • HttpComponentsAsyncClientHttpRequestConfiguración de fábrica

HttpComponentsAsyncClientHttpRequestFactory utiliza el cliente HttpClient de Apache HttpComponents para crear solicitudes de solicitud, lo que amplía funciones como la autenticación y la agrupación de conexiones HTTP. Los pasos de configuración incluyen principalmente lo siguiente:

  • Introduzca el paquete de dependencia HttpClient (de lo contrario, no puede configurar el grupo de conexiones, RequestConfig, etc., y solo puede usar la configuración predeterminada)
  • Configuración del grupo de conexiones HTTP (modo de conexión, número máximo de conexiones, etc.), configuración de solicitud de solicitud (tiempo de conexión, tiempo de lectura, etc.), configuración del cliente HttpClient
  • HttpComponentsAsyncClientHttpRequestConfiguración de fábrica
<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
	<version>4.5.6</version>
</dependency>
@Configuration
public class RestTemplateConfig {
    
    

    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
    
    
        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        return restTemplate;
    }

    private ClientHttpRequestFactory getClientHttpRequestFactory(){
    
    
        /**
        factory.setHttpClient(); //设置HttpClient客户端
        factory.setConnectionRequestTimeout(); //等价于设置其RequestConfig的ConnectionRequestTimeout
        factory.setConnectTimeout(); //等价于设置其RequestConfig的ConnectTimeout
        factory.setReadTimeout(); //等价于设置其RequestConfig的SocketTimeout
         **/
        return new HttpComponentsClientHttpRequestFactory(getHttpClient());
    }

    private HttpClient getHttpClient(){
    
    
        //1.注册HTTP和HTTPS请求服务(ConnectionManager初始化默认值已经注册完毕)
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();
        //2.声明连接池配置
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        connectionManager.setMaxTotal(1000);//设置连接池最大连接数
        connectionManager.setDefaultMaxPerRoute(500); // 单个连接路由(单个主机)的最大并发连接数
        connectionManager.setValidateAfterInactivity(3000); //最大连接空闲时间,重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过则释放socket重新建立
        //3.请求配置RequestConfig
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(2000) //从连接池中获取连接的最大超时时间,超时未拿到可用连接则会抛出异常
                .setConnectTimeout(1000) //建立连接(握手成功)的最大超时时间
                .setSocketTimeout(1000) //等待服务器返回响应(response)的最大超时时间
                .build();
        //4.设置默认请求头 headers
        List<Header> headers = new ArrayList<>();
        headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
        headers.add(new BasicHeader("Accept-Language", "zh-CN"));
        headers.add(new BasicHeader("Connection", "Keep-Alive"));
        headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));
        //5.配置HttpClient客户端
        return HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig) //引入RequestConfig请求配置
                .setConnectionManager(connectionManager) //引入PoolingHttpClientConnectionManager连接池配置
                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //保持长连接配置,需要在头添加 Keep-Alive(默认策略,返回此连接可以安全保持空闲状态的持续时间。如果连接处于空闲状态的时间超过此时间段,则不得重复使用。)
                .setDefaultHeaders(headers) //设置默认请求头
                .setRetryHandler(new DefaultHttpRequestRetryHandler(3,true)) //设置异常重试次数、是否开启重试(默认为3次,false不开启)
                .build();
    }

}
  • OkHttp3ClientHttpRequestConfiguración de fábrica

​ OkHttp es un cliente HTTP eficiente:

  • Permita que todas las solicitudes de la misma dirección de host compartan la misma conexión de socket;
  • El grupo de conexiones puede reducir el retraso de la solicitud;
  • La compresión GZIP transparente reduce el tamaño de los datos de respuesta;
  • El almacenamiento en caché de las respuestas puede evitar por completo algunas solicitudes de red duplicadas exactas

​ Cuando hay un problema con la red, OkHttp sigue cumpliendo con sus responsabilidades y restaurará automáticamente el problema general de conexión; si su servicio tiene varias direcciones IP, cuando falla la primera solicitud de IP, OkHttp probará alternativamente otras configuraciones que haya configurado IP. Usar OkHttp es fácil, su API de solicitud/respuesta tiene constructores fluidos e inmutabilidad. Admite llamadas de bloqueo síncrono y llamadas asíncronas con devolución de llamada.

<!--引入okhttp依赖-->
<dependency>
	<groupId>com.squareup.okhttp3</groupId>
	<artifactId>okhttp</artifactId>
	<version>4.9.0</version>
</dependency>
@Configuration
public class RestTemplateConfig {
    
    

    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
    
    
        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        return restTemplate;
    }
    
    /**
     * 使用OkHttpClient作为底层客户端
     * @return
     */
    private ClientHttpRequestFactory getClientHttpRequestFactory(){
    
    
        OkHttpClient okHttpClient = new OkHttpClient.newBuilder()
           	    .connectionPool(pool())
                .connectTimeout(5, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS)
                .build();
        return new OkHttp3ClientHttpRequestFactory(okHttpClient);
    }
    
        /**
     * 连接池配置 ConnectionPool
     * Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that share the same [Address] may share a [Connection].
     * @return
     */
    private ConnectionPool pool() {
    
    
        //maxIdleConnections 最大空闲连接数(默认5 min)
        //keepAliveDuration 空闲连接存活时间(默认5 min)
        return new ConnectionPool(200, 10, TimeUnit.SECONDS);
    }

}

2.2 Configurar Interceptor

A veces, necesitamos realizar algunas configuraciones generales de intercepción para las solicitudes, como imprimir registros de solicitudes, agregar verificación de token, información de encabezado predeterminada, etc. En este momento, podemos agregar interceptores para procesar las solicitudes de RestTemplate. RestTemplate puede configurar la lista de la cadena de interceptores internos a través del método setInterceptors para interceptar y procesar solicitudes. Cada interceptor personalizado debe implementar la interfaz ClientHttpRequestInterceptor.

inserte la descripción de la imagen aquí

/**
 * 记录RestTemplate访问信息日志
 */
public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    
    

    /**
     * intercept : 拦截 RestTemplate 请求处理,并返回响应Response
     * @param request the request, containing method, URI, and headers
     * @param body the body of the request
     * @param execution the request execution 请求上下文
     *                  - 若当前拦截器非链上最后一个拦截器:用于调用拦截器链chain中的下一个拦截器,将请求信息向链后传递
     *                  - 若当前拦截器为最后一个拦截器:用于执行request请求本身,并将响应向链前返回
     */
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    
    
        trackRequest(request,body);
        //调用execution传递request
        ClientHttpResponse httpResponse = execution.execute(request, body);
        trackResponse(httpResponse);
        return httpResponse;
    }

	//记录响应responset日志
    private void trackResponse(ClientHttpResponse httpResponse)throws IOException {
    
    
        System.out.println("============================response begin==========================================");
        System.out.println("Status code  : "+httpResponse.getStatusCode());
        System.out.println("Status text  : "+httpResponse.getStatusText());
        System.out.println("Headers      : "+httpResponse.getHeaders());
        System.out.println("=======================response end=================================================");
    }

	//记录请求request日志
    private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException {
    
    
        System.out.println("======= request begin ========");
        System.out.println("uri : "+request.getURI());
        System.out.println("method : "+request.getMethod());
        System.out.println("headers : "+request.getHeaders());
        System.out.println("request body : "+new String(body, "UTF-8"));
        System.out.println("======= request end ========");
    }
}
/**
 * 请求头部添加Token信息
 */
public class TokenClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    
    

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    
    

        //生成令牌 此处调用一个自己写的方法,有兴趣的朋友可以自行google如何使用ak/sk生成token,此方法跟本教程无关,就不贴出来了
        String token = TokenHelper.generateToken(checkTokenUrl, ttTime, methodName, requestBody);
        //将令牌放入请求header中
        request.getHeaders().add("X-Auth-Token",token);
        return execution.execute(request,body);
    }
}
@Configuration
public class RestTemplateConfig {
    
    

    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
    
    
        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        //配置自定义拦截器
        List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>();
        interceptors.add(new TokenClientHttpRequestInterceptor());
        interceptors.add(new TrackLogClientHttpRequestInterceptor());
        restTemplate.setInterceptors(interceptors);
        return restTemplate;
    }
}

2.3 Configuración del convertidor

El contenido de datos que pasa la interfaz de Reseful es una cadena en formato json, pero RestTemplateel método de encapsulación consiste en pasar directamente la clase java como objeto de datos, lo que HttpMessageConverterla convierte automáticamente en la capa inferior. Antes de que se envíe la solicitud POST/después de que se reciba la respuesta, RestTemplate recorrerá su lista de convertidores de mensajes internos, hará coincidir el tipo de respuesta del tipo de retorno establecido y el parámetro contentType de la solicitud, y seleccionará el primer convertidor de mensajes adecuado para que coincida y procese los datos del cuerpo de la solicitud.

(1) El convertidor corresponde a la relación de datos

De forma predeterminada, RestTemplatese registra automáticamente un conjunto de solicitudes independientes para que HttpMessageConvertergestionemos algunos tipos diferentes contentTypede solicitudes durante la inicialización. La relación correspondiente entre diferentes Convert y MediaType es la siguiente:

nombre de la clase Tipos de Java compatibles Tipos de medios admitidos
ByteArrayHttpMessageConverter byte[] admite todos los tipos de medios ( */*) y escribe con un Content-Typede application/octet-stream(convertidor de mensajes http de matriz de bytes)
StringHttpMessageConverter Cadena admite todos los tipos de medios de texto ( text/*) y escribe con un Content-Typede text/plain. (conversor de mensajes HTTP de cadena)
ResourceHttpMessageConverter Recurso / (El convertidor de mensajes http para leer y escribir recursos, como leer medios, archivos, etc.)
FuenteHttpMessageConverter Fuente application/xml, text/xml, application/*+xml (el convertidor de mensajes http de origen se utiliza para convertir objetos de tipo fuente)
TodoAbarcandoFormularioHttpMensajeConvertidor Mapa<K, Lista<?>> application/x-www-form-urlencoded, multipart/form-data (todos los convertidores de mensajes genéricos)
MappingJackson2HttpMessageConverter Objeto application/json, application/*+json (el convertidor de mensajes jackson puede convertir objetos json y Java)
Jaxb2RootElementHttpMessageConverter Objeto application/xml, text/xml, application/*+xml (JAXB puede convertir objetos java y xml)
JavaSerializationConverterJavaSerializationConverter Serializable x-java-serialization;charset=UTF-8 (convertidor de serialización)
FastJsonHttpMessageConverter Objeto / (El convertidor de mensajes FastJson puede convertir entre objetos json y Java)

inserte la descripción de la imagen aquí
(2) Registre el convertidor FastJsonHttpMessageConvert

<!-- 引入alibaba fastjson 依赖-->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.62</version>
</dependency>
@Configuration
public class RestTemplateConfig {
    
    

    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(){
    
    
        RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
        //1.获取restTemplate的MessageConverters List
        List<HttpMessageConverter<?>> messageConverters= restTemplate.getMessageConverters();
        //2.手动创建并配置FastJsonConverter
        FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
        //  添加fastJson的配置信息;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(
                SerializerFeature.WriteMapNullValue,        // 是否输出值为null的字段,默认为false,我们将它打开
                SerializerFeature.WriteNullListAsEmpty,     // 将Collection类型字段的字段空值输出为[]
                SerializerFeature.WriteNullStringAsEmpty,   // 将字符串类型字段的空值输出为空字符串""
                SerializerFeature.WriteNullNumberAsZero    // 将数值类型字段的空值输出为0
        );
        fastJsonConverter.setFastJsonConfig(fastJsonConfig);
        //  配置支持的数据类型,解决中文乱码:设置响应的content-type为application/json;charset=UTF-8
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fastJsonConverter.setSupportedMediaTypes(fastMediaTypes);
        //3.添加FastJsonConverter到restTemplate
        messageConverters.add(0,fastJsonConverter);
        restTemplate.setMessageConverters(messageConverters);
        return restTemplate;
    }
}

3. Solicitud de acceso a RestTemplate

Grupo de métodos Descripción
getForObject Recupera una representación a través de GET.
getForEntity Recupera un ResponseEntity(es decir, estado, encabezados y cuerpo) mediante GET.
headForHeaders Recupera todos los encabezados de un recurso mediante HEAD.
postForLocation Crea un nuevo recurso mediante POST y devuelve el Locationencabezado de la respuesta.
postForObject Crea un nuevo recurso mediante POST y devuelve la representación de la respuesta.
postForEntity Crea un nuevo recurso mediante POST y devuelve la representación de la respuesta.
put Crea o actualiza un recurso mediante PUT.
patchForObject Actualiza un recurso mediante PATCH y devuelve la representación de la respuesta. Tenga en cuenta que JDK HttpURLConnectionno es compatible PATCH, pero Apache HttpComponents y otros sí.
delete Elimina los recursos en el URI especificado mediante DELETE.
optionsForAllow Recupera los métodos HTTP permitidos para un recurso mediante ALLOW.
exchange Versión más generalizada (y menos obstinada) de los métodos anteriores que brinda flexibilidad adicional cuando es necesario. Acepta un RequestEntity(incluido el método HTTP, la URL, los encabezados y el cuerpo como entrada) y devuelve un ResponseEntity. Estos métodos permiten el uso de ParameterizedTypeReferenceen lugar de Classpara especificar un tipo de respuesta con genéricos.
execute La forma más generalizada de realizar una solicitud, con control total sobre la preparación de solicitudes y la extracción de respuestas a través de interfaces de devolución de llamada.

3.1 OBTENER solicitud

Hay dos tipos de métodos comúnmente utilizados mediante RestTemplateel envío HTTP GETde solicitudes de protocolo:

  • getForObject(): el valor devuelto corresponde al HTTPcuerpo de respuesta del protocolo, que se devuelve después de que HttpMessageConverter realiza automáticamente la conversión de tipos y encapsula el objeto.
  • getForEntity(): Lo que se devuelve es ResponseEntityun objeto, ResponseEntityque es HTTPla encapsulación de la respuesta. Además del cuerpo de la respuesta, también incluye HTTPel código de estado y contentType、contentLength、Headerotra información de la respuesta.
- getForEntity():
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String,?> uriVariables)
	- url: the URL
	- responseType: the type of the return value(ResponseEntity内的ResponseBody也自动由HttpMessageConverter进行了转换并封装进ResponseEntity,转换类型由responseType指定<T>)
	- uriVariables: the map containing variables for the URI template
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
	- url: the URL
	- responseType: the type of the return value
	- uriVariables: the variables to expand the template
<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType)
	- url: the URL
	- responseType: the type of the return value

- getForObject():
<T> T getForObject(String url, Class<T> responseType, Map<String,?> uriVariables)
	- url: the URL
	- responseType: the type of the return value
	- uriVariables: the map containing variables for the URI template
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables)
	- url: the URL
	- responseType: the type of the return value
	- uriVariables: the variables to expand the template
<T> T getForObject(URI url, Class<T> responseType)
	- url: the URL
	- responseType: the type of the return value
@SpringBootTest
class ServerLinkerApplicationTests {
    
    

    @Autowired
    RestTemplate restTemplate;

    @Test
    void contextLoads() {
    
    
        //1.无参GET请求(或直接在URL字符串中拼接参数):直接返回对象
        String url1 = "http://localhost:8080/testGet";
        ResponseBean responseBean1 = restTemplate.getForObject(url1, ResponseBean.class);
        
        //2.带参GET请求:使用占位符传参(当路径变量有多个时,可以按照顺序依次传递)
        String url2 = "http://localhost:8080/testGet/{1}/{2}";
        //String url2 = "http://localhost:8080/testGet?userId={1}&startTime={2}";
        ResponseBean responseBean2 = restTemplate.getForObject(url2,ResponseBean.class,"001","2022-09-02");
        
        //3.带参GET请求:使用Map传参
        String url3 = "http://localhost:8080/testGet?userId={user_Id}&startTime={start_Time}";
        Map<String, String> params = new HashMap<>();
        params.put("user_Id", "001");
        params.put("start_Time", "2022-09-02");
        ResponseBean responseBean3 = restTemplate.getForObject(url3,ResponseBean.class,params);
        
        //4.getForEntity使用
        String url4 = "http://localhost:8080/testGet";
        ResponseEntity<ResponseBean> response = restTemplate.getForEntity(url4, ResponseBean.class);
        //  (1)获取响应体转换对象
        ResponseBean responseBean = response.getBody();
        //  (2)获取response额外信息
        HttpStatus statusCode = response.getStatusCode();
        int statusCodeValue = response.getStatusCodeValue();
        HttpHeaders headers = response.getHeaders();
        System.out.println("HTTP 响应状态:" + statusCode);
        System.out.println("HTTP 响应状态码:" + statusCodeValue);
        System.out.println("HTTP Headers信息:" + headers);
    }
}

3.2 Solicitud POST

Al RestTemplateenviar HTTP PSOTsolicitudes de protocolo, también hay dos tipos de métodos de uso común (solo se usa un parámetro de solicitud de objeto más para transferir datos de la publicación):

  • postForObject(): el valor devuelto corresponde al HTTPcuerpo de respuesta del protocolo, que se devuelve después de que HttpMessageConverter realiza automáticamente la conversión de tipos y encapsula el objeto.
  • postForEntity(): Lo que se devuelve es ResponseEntityun objeto, ResponseEntityque es HTTPla encapsulación de la respuesta. Además del cuerpo de la respuesta, también incluye HTTPel código de estado y contentType、contentLength、Headerotra información de la respuesta.
- postForEntity():
<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String,?> uriVariables)
	- url: the URL
	- request: the Object to be POSTed (may be null)(request传输对象会自动通过转换器Converter转换为JSON格式字符串传输)
	- responseType: the type of the return value
	- uriVariables: the variables to expand the URI template using the given map
<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
	- url: the URL
	- request: the Object to be POSTed (may be null)
	- responseType: the type of the return value
	- uriVariables: the variables to expand the template
<T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType)
	- url: the URL
	- request: the Object to be POSTed (may be null)
	- responseType: the type of the return value
	
- postForObject():
<T> T postForObject(String url, Object request, Class<T> responseType, Map<String,?> uriVariables)
	- url - the URL
	- request - the Object to be POSTed (may be null)(The request parameter can be a HttpEntity in order to add additional HTTP headers to the request.)
	- responseType - the type of the return value
	- uriVariables - the variables to expand the template(URI Template variables are expanded using the given map.)
<T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
	- url - the URL
	- request - the Object to be POSTed (may be null)
	- responseType - the type of the return value
	- uriVariables - the variables to expand the template
<T> T postForObject(URI url, Object request, Class<T> responseType)
	- url - the URL
	- request - the Object to be POSTed (may be null)
	- responseType - the type of the return value
@SpringBootTest
class WeChatPusherApplicationTests {
    
    

    @Autowired
    RestTemplate restTemplate;

    @Test
    void PostTest(){
    
    
        //1.简单POST请求(或直接在URL字符串中拼接参数):传递DataItem数据(body),直接返回对象
        String url1 = "http://localhost:8080/testGet";
        DataItem dataItem = new DataItem("Number","Red");
        ResponseBean responseBean1 = restTemplate.postForObject(url1, dataItem,ResponseBean.class);

        //2.带路径参数POST请求:使用占位符传参(当路径变量有多个时,可以按照顺序依次传递)
        String url2 = "http://localhost:8080/testGet/{1}/{2}";
        ResponseBean responseBean2 = restTemplate.postForObject(url2,dataItem,ResponseBean.class,"001","2022-09-02");

        //3.带路径参数POST请求:使用Map传参
        String url3 = "http://localhost:8080/testGet?userId={user_Id}&startTime={start_Time}";
        Map<String, String> params = new HashMap<>();
        params.put("user_Id", "001");
        params.put("start_Time", "2022-09-02");
        ResponseBean responseBean3 = restTemplate.postForObject(url3,dataItem,ResponseBean.class,params);

        //4.postForEntity使用
        String url4 = "http://localhost:8080/testGet";
        ResponseEntity<ResponseBean> response = restTemplate.postForEntity(url4, dataItem,ResponseBean.class);
        //  (1)获取响应体转换对象
        ResponseBean responseBean = response.getBody();
        //  (2)获取response额外信息
        HttpStatus statusCode = response.getStatusCode();
        int statusCodeValue = response.getStatusCodeValue();
        HttpHeaders headers = response.getHeaders();
        System.out.println("HTTP 响应状态:" + statusCode);
        System.out.println("HTTP 响应状态码:" + statusCodeValue);
        System.out.println("HTTP Headers信息:" + headers);
    }

}

3. Análisis del código fuente de RestTemplate (toma POST como ejemplo)

El proceso de ejecución de la solicitud POST es el siguiente:

1. Llame al método postForObject() y llame a execute() en el método

2. El método execute() eventualmente llamará al método doExecute()

  • Obtener conexión createRequest (): obtenga el objeto de conexión de solicitud del cliente HTTP subyacente de acuerdo con la URL y el método de solicitud
  • Agregar/convertir datos de transferencia doWithRequest(): adjuntar datos de conversión al cuerpo de la solicitud (objeto -> Lista de convertidores -> Cadena JSON)
  • Solicitud de ejecución ejecutar (): procesamiento del interceptor, obtener respuesta
  • Manejo de excepciones de respuesta handleResponse(): llame a ErrorHandle
  • Conversión de datos de respuesta extractData (): convierta los datos del cuerpo de respuesta en datos correspondientes al tipo de respuesta y devuelva (Cadena JSON -> Lista de convertidores ->)
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
    
    

    //...

    //1.调用postForObject()方法:
    //	- 将传递body数据对象object request封装为一个RequestCallback对象
    //	- 调用execute方法执行post request的处理逻辑
    public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)
            throws RestClientException {
    
    
        RequestCallback requestCallback = httpEntityCallback(request, responseType);
        HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<>(responseType, getMessageConverters());
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
    }

    //2.execute()方法最终会调用doExecute()方法:这里才是真正的整个post处理流程
    //	- 获取连接:根据url和请求方式从底层HTTP客户端中获取request连接对象
    //	- 附加/转换传递数据:执行doWithRequest()方法给请求body附加数据(object -> Converters List -> JSON String)
    //	- 执行请求:拦截器处理、获取响应
    //	- 响应异常处理:调用 ErrorHandle
    //	- 响应数据转换:将response Body数据转换为对应responseType的数据并返回(JSON String -> Converters List -> <T>)
    @Nullable
    protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
                              @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
    
    

        Assert.notNull(url, "URI is required");
        Assert.notNull(method, "HttpMethod is required");
        ClientHttpResponse response = null;
        try {
    
    
            //(1)根据url和请求方式,从HTTP客户端(连接池)中获取连接对象request
            ClientHttpRequest request = createRequest(url, method);
            //(2)判断:如果post传递的body数据(一般为JSON)不为空,则执行doWithRequest()方法给请求附加参数(遍历HttpMessageConverters List,将传递的object request数据转换为JSON String字符串)
            if (requestCallback != null) {
    
    
                requestCallback.doWithRequest(request);
            }
            //(3)执行request请求(经过拦截器),并获取响应response
            response = request.execute();
            //(4)进行响应的后续处理:状态码判断、异常/错误处理(errorHandle)
            handleResponse(url, method, response);
            //(5)响应的数据处理与转换:调用responseExtractor.extractData()方法,遍历HttpMessageConverters List,将Body数据转换为对应responseType的数据并返回
            return (responseExtractor != null ? responseExtractor.extractData(response) : null);
        }
        catch (IOException ex) {
    
    
            String resource = url.toString();
            String query = url.getRawQuery();
            resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
            throw new ResourceAccessException("I/O error on " + method.name() +
                    " request for \"" + resource + "\": " + ex.getMessage(), ex);
        }
        finally {
    
    
            if (response != null) {
    
    
                response.close();
            }
        }
    }

    //3.doWithRequest()方法:转换/附加POST请求的Body数据
    //	- 获取object body数据对象
    //	- 遍历 messageConverter List,转换为JSON String数据
    @Override
    @SuppressWarnings("unchecked")
    public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
    
    
        super.doWithRequest(httpRequest);
        //(1)获取传递的object body数据对象
        Object requestBody = this.requestEntity.getBody();
        //(2)判断requestbody是否为空,若为空则处理头header信息
        if (requestBody == null) {
    
    
            HttpHeaders httpHeaders = httpRequest.getHeaders();
            HttpHeaders requestHeaders = this.requestEntity.getHeaders();
            if (!requestHeaders.isEmpty()) {
    
    
                requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
            }
            if (httpHeaders.getContentLength() < 0) {
    
    
                httpHeaders.setContentLength(0L);
            }
        }
        //(3)若requestbody不为空,则遍历 messageConverter List,将Object数据对象转换为JSON String数据(GenericHttpMessageConverter的子类)
        else {
    
    
            Class<?> requestBodyClass = requestBody.getClass();
            Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
                    ((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
            HttpHeaders httpHeaders = httpRequest.getHeaders();
            HttpHeaders requestHeaders = this.requestEntity.getHeaders();
            MediaType requestContentType = requestHeaders.getContentType();
            for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
    
    
                if (messageConverter instanceof GenericHttpMessageConverter) {
    
    
                    GenericHttpMessageConverter<Object> genericConverter =
                            (GenericHttpMessageConverter<Object>) messageConverter;
                    if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
    
    
                        if (!requestHeaders.isEmpty()) {
    
    
                            requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
                        }
                        logBody(requestBody, requestContentType, genericConverter);
                        genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);
                        return;
                    }
                }
                else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
    
    
                    if (!requestHeaders.isEmpty()) {
    
    
                        requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
                    }
                    logBody(requestBody, requestContentType, messageConverter);
                    ((HttpMessageConverter<Object>) messageConverter).write(
                            requestBody, requestContentType, httpRequest);
                    return;
                }
            }
            String message = "No HttpMessageConverter for " + requestBodyClass.getName();
            if (requestContentType != null) {
    
    
                message += " and content type \"" + requestContentType + "\"";
            }
            throw new RestClientException(message);
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_40772692/article/details/126675996
Recomendado
Clasificación