zoukankan      html  css  js  c++  java
  • javaweb--Rest访问(RestTemplate)

    Rest访问(RestTemplate)
    在实际的项目中,往往需要发送一个Get/Post请求到其他的系统(Rest API),比如向人员管理部门请求,然后解析返回信息获取该用户的基本信息等。JDK传统的HttpURLConnection、Apache HttpClient、Netty 4和OkHttp等可以实现访问请求。不过spring的RestTemplate封装了这些操作库,使之更容易使用。

    一. Rest的具体使用
    1.1 get方式
    RestTemplate get方式中有两个方法: getForObject和getForEntity
    getForObject可以指定返回类型,getForEntity同一返回ResponseEntity

    1.1.1 getForObject
    有三种重载方式
    (1) T getForObject(URI url, Class responseType)
    (2) T getForObject(String url, Class responseType, MapString< String, ?> urlVariables)
    (3) T getForObject(String url, Class responseType, Object… urlVariables)
    其中url为请求url,可用通配符表示请求参数,responseType为请求返回的对象类,自动封装成对象该对象形式(如String.class 或User.class), Map< String, ?> urlVariables表示请求参数,与通配符对应即可, Object… urlVariables为请求的参数数组形式,按顺序一一匹配url中内.

    下面举一个例子:
    定义一个返回类型:User

    public class User
    { 
        private String name; 
        private Integer age;
    }
    
    省略get和set方法

    发送方

    private RestTemplate restTemplate = new RestTemplate();
    private String name = "xiaoming"; 
    private Integer age = 18;
    
    #重载方式3
    String url = "http://localhost:8080/getUser?name={name}&age={age}";
    Object[] arr = new Object[]{name, age};
    User u = restTemplate.getForObject(url, User.class, arr);
    
    #重载方式2
    String url = "http://localhost:8080/getUser?name={name}&age={age}"; 
    Map<String, Object> map = new HashMap<>(); 
    map.put("name", name); 
    map.put("age", age);
    User u = restTemplate.getForObject(url, User.class, map);
    
    #重载方式1
    #没有参数的get方式直接调用重载方式1。如
    String url = "http://localhost:8080/findAllUser";
    User u = restTemplate.getForObject(url, User.class);
    
    #实际生产中常用的是
    String url = "http://localhost:8080/getUser?name=${name}&age=${age}"; 
    url.replace("${name}", name)
       .replace("${age}", age);
    User u = restTemplate.getForObject(url, User.class);

    接受方controller

    public  User getUser(@RequestParam String name, @RequestParam Integer age)

    1.1.2 getForEntity
    getForEntity与getForObject请求参数基本一样,只是返回内容不一样 .getForEntity返回ResponseEntity,里面包含返回消息内容和http headers,http 状态码。
    在实际的生产环境中get请求几乎全部getForEntity,因为需要判断状态码判断接口是否调用成功(status == 10000)。返回类型往往是Json格式的字符串,然后通过转换为Json获取对应的信息。

    发送方

    String url = "http://localhost:8080/getUser?name={name}&age={age}"; 
    Map<String, Object> map = new HashMap<>(); 
    map.put("name", name);
    map.put("age", age);
    ResponseEntity<User> res = restTemplate.getForEntity(url, User.class, map);
    User u = res.getBody();
    HttpHeaders headers = res.getHeaders();
    HttpStatus status = res.getStatusCode();

    1.2 Post方式
    post方法主要有3种方法:postForObject , postForEntity和postForLocation

    1.2.1 postForObject
    (1) T postForObject(URI url, Object request, Class responseType )
    (2) T postForObject(String url, Object request, Class responseType, MapString< String, ?> urlVariables)
    (3) T postForObject(String url, Object request, Class responseType, Object… urlVariables)
    形式和getForObject类似。

    发送方

    String url = "http://localhost:8080/getUser";
    LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); 
    map.add("name", name);
    map.add("age", age);
    User u = restTemplate.postForObject(url, map, User.class);

    更常见的为:

    String url = "http://localhost:8080/getUser";
    LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); 
    map.add("name", name); 
    map.add("age", age);
    HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(map);
    
    ##还可以设置http头
    HttpHeaders headers = new HttpHeaders();
    headers.add("msg", "head msg test");
    HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(map, headers);
    
    User u = restTemplate.postForObject(url, httpEntity, User.class);

    接受方

    @RequestMapping(value = "/getUser", method = RequestMethod.POST)
        public User get1(@RequestParam String name, @RequestParam Integer age, @RequestHeader(required = false) String msg)

    1.2.2 postForEntity
    postForEntity返回ResponseEntity,里面包含返回消息内容和http headers,http 状态码。在实际中,也是往往使用PostForEntity.

    1.2.3 postForLocation
    postForLocation返回URI,返回的是response header中的location信息,一般用于资源定位。

    1.3 其他方式
    http请求共有8种方式,Post和Get是最常见的两类方式。

    方法 描述
    GET 请求指定的页面信息,并返回实体主体。
    HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
    POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
    PUT 从客户端向服务器传送的数据取代指定的文档的内容。
    DELETE 请求服务器删除指定的页面。
    CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
    OPTIONS 允许客户端查看服务器的性能。
    TRACE 回显服务器收到的请求,主要用于测试或诊断。

    RestTemplate支持以下6种主要的方式。

    另外,exchange和execute方法提供了以上方法的通用版本,用来支持额外的、不常用的组合(如:HTTP PATCH,带有消息体的HTTP PUT,等等)。注意,无论怎样使用底层HTTP库,都必须支持必要的组合。

    1.4 配置
    1.4.1 设置超时时间
    spring boot 中

    @Configuration
    public class AppConfig
    {
        @Bean
        @ConfigurationProperties(prefix = "custom.rest.connection")
        public HttpComponentsClientHttpRequestFactory customHttpRequestFactory() 
        {
            return new HttpComponentsClientHttpRequestFactory();
        }
    
        @Bean
        public RestTemplate customRestTemplate()
        {
            return new RestTemplate(customHttpRequestFactory());
        }
    }

    然后在配置文件指定
    custom.rest.connection.connection-request-timeout=3000
    custom.rest.connection.connect-timeout=3000
    custom.rest.connection.read-timeout=3000

    1.4.2 上传文件

    public void testUpload() throws Exception {  
        String url = "http://127.0.0.1:8080/test/upload.do";  
        String filePath = "C:\Users\MikanMu\Desktop\test.txt";  
    
        RestTemplate rest = new RestTemplate();  
        FileSystemResource resource = new FileSystemResource(new File(filePath));  
        MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();  
        param.add("jarFile", resource);  
        param.add("fileName", "test.txt");  
        //设置头为上传
        HttpHeaders header = new HttpHeaders();
        header.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(param, headers);
    
        String string = rest.postForObject(url, httpEntity, String.class);  
    }

    1.4.3 下载文件

    // 小文件  
    RequestEntity requestEntity = RequestEntity.get(uri).build();  
    ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class);  
    byte[] downloadContent = responseEntity.getBody();  
    
    // 大文件  
    ResponseExtractor<ResponseEntity<File>> responseExtractor = new ResponseExtractor<ResponseEntity<File>>() {  
        @Override  
        public ResponseEntity<File> extractData(ClientHttpResponse response) throws IOException {  
            File rcvFile = File.createTempFile("rcvFile", "zip");  
            FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile));  
            return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile);  
        }  
    };  
    File getFile = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor); 

    二. RestTemplate的原理(转)
    RestTemplate包含以下几个部分:

    • HttpMessageConverter 对象转换器
    • ClientHttpRequestFactory 默认是JDK的HttpURLConnection
    • ResponseErrorHandler 异常处理
    • ClientHttpRequestInterceptor 请求拦截器

    2.1 RestTemplate类图

    2.2 postForEntity 处理过程
    以postForEntity方法作为切入点,来梳理一下请求是如何执行的,以下概要流程图。(灰色方框内为doExcute方法的内部处理。)
    postForEntity方法中,创建了两个内部类对象requestCallback和responseExtractor并传递给execute方法,分别用于请求和响应的关键处理。
    总结了一下,不管是请求还是响应,这里的关键处理就是明确资源的媒体类型(也就是要明确请求端和响应端交换的信息的格式),
    根据媒体类型选择适合的解析器,将消息写入输出流或者从输入流读入。

    2.3 requestCallback.doWithRequest 处理过程
    ——内部类AcceptHeaderRequestCallback.doWithRequest的处理。
    发送请求时,Http头部需要设置Accept字段,该字段表明了发送请求的这方接受的媒体类型(消息格式),也是响应端要返回的信息的媒体类型(消息格式)。
    根据postForEntity方法的第三个参数responseType,程序将选择适合的解析器XXXConverter,并依据该解析器找出所有支持的媒体类型。

    ——内部类HttpEntityRequestCallback.doWithRequest的处理。
    如果是POST请求并且消息体存在时,除了设置Accept字段,还可能需要设置Content-Type字段,该字段表明了所发送请求的媒体类型(消息格式),也是响应端接受的媒体类型(消息格式)。
    根据postForEntity方法的第二个参数request,程序将选择适合的解析器XXXConverter,将请求消息写入输出流。

    2.4 responseExtractor.extractData 处理过程
    与请求消息体的处理过程相似。
    虽然,postForEntity方法中responseExtractor对象的类型为ResponseEntityResponseExtractor,但是实际执行处理过程是HttpMessageConverterExtractor的对象实例。
    在postForObject方法中,则是直接使用了HttpMessageConverterExtractor创建对象。 下图画出的也是HttpMessageConverterExtractor类中的extractData方法的处理过程。

    2.5 关于GenericHttpMessageConverter
    在以上几个方法的梳理过程中,我注意到每次消息解析转换都要作GenericHttpMessageConverter分支判断,为什么呢?

    GenericHttpMessageConverter接口继承自HttpMessageConverter接口,二者都是在org.springframework.http.converter路径下。
    此包中还有其他几种Converter实现类,看名字就可以猜到主要功能。唯独GenericHttpMessageConverter没猜出来。
    于是,我在eclipse中使用Ctrl+Shift+G快捷键搜索了一下它的实现类AbstractGenericHttpMessageConverter。
    看到AbstractJackson2HttpMessageConverter类的时候,我好像明白了。
    GenericHttpMessageConverter是其他转换器派生类的接口,用于解析特殊格式的资源,比如json,xml等。

    2.6 关于RestTemplate 中的转换器列表
    转换器列表messageConverters是final类型的,由RestTemplate的构造函数赋值。一旦创建了RestTemplate对象,该对象也就同时拥有了一个当前系统支持的转换器列表。

    那么,对于需要引用jar包的转换器,RestTemplate是怎么添加转换器实例的呢?
    在声明messageConverters列表之前,定义了几个布尔型静态常量,该常量是对某一个特殊类是否可以被加载的判断结果。
    在RestTemplate的构造函数中,根据该常量值来判断是否将某个转换器的实例加入到列表中。

    private static boolean romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", RestTemplate.class.getClassLoader());
    
        private static final boolean jaxb2Present =
                ClassUtils.isPresent("javax.xml.bind.Binder", RestTemplate.class.getClassLoader());
    
        private static final boolean jackson2Present =
                ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) &&
                        ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader());
    
        private static final boolean jackson2XmlPresent =
                ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", RestTemplate.class.getClassLoader());
    
        private static final boolean gsonPresent =
                ClassUtils.isPresent("com.google.gson.Gson", RestTemplate.class.getClassLoader());
    
    
        private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();

    由此可知,RestTemplate的初始化顺序:
    创建(new)一个RestTemplate实例时,首先装载RestTemplate类,然后按照出现的顺序转载静态变量或代码。
    装载完成之后,进行实例化。首先实例化成员变量,然后执行构造函数。

    那么,外部引用的类是否可以被加载具体是怎么判断的?
    通过ClassUtils.isPresent(String className, ClassLoader classLoader)方法。——感觉ClassUtils可以单独写一篇orz
    ClassUtils 类在 spring-core 工程的 org.springframework.util 路径下。
    简单来说,isPresent实际上是返回了ClassUtils.forName方法的处理结果,当forName方法正常执行,则鉴定的类被加载,返回true;若抛出异常(注意,此处异常是Throwable)则返回false。
    forName方法的处理是:
    首先,根据类名的长度(<=8)来确定是否是原始类型,若是原始类型则返回类对象Class

  • 相关阅读:
    【JQuery Easy UI】后台管理系统的简单布局分享
    Effective JavaScript Item 10 避免使用with
    娓娓道来c指针 (4)解析c的声明语句
    打造敏捷外包团队的高度自主与自我学习的生态系统
    LeetCode --- Count And Say
    RAD Studio XE8 技术研讨会讲义与范例程序下载
    SpringMVC工作原理
    SpringMVC 学习笔记(十一) SpirngMVC执行流程
    转 jeecg3.5中多数据源的配置
    浅谈JEECG多数据源的使用
  • 原文地址:https://www.cnblogs.com/wpcnblog/p/11551245.html
Copyright © 2011-2022 走看看