RestTemplate

  • 这是spring提供的用于访问Rest服务的工具。RestTemplate类似于门面模式中的门面,是对背后真正起作用的进行二次包装。
    • 在默认的(未引入其它包的)情况下,在 RestTemplate 背后真正干活的是 JDK 中的网络相关类:HttpURLConnection 。如此之外,RestTemplate 还支持使用HttpClientOkHTTP 库,前提是,你要额外引入这两个包。
  • restTemplate算是rpc工具,也可以包装为http进行请求,毕竟http是rpc的一种实现

    创建Bean

  • 早期的RestTemplate,spring容器中已经默认创建了一个供使用,新版本的Springboot需要自己创建

  • 在restTemplate的bean上面添加@LoadBalanced即可实现自动负载均衡

    1. @Bean
    2. public RestTemplate restTemplate(RestTemplateBuilder builder) {
    3. return builder.build();
    4. }

    API

  • Get/post/delete/put这些方法底层都是exchange方法,直接使用exchange比较麻烦,所以提供了封装方法

  • exchange方法参数说明:
    • url
    • method 请求类型
    • requestEntity 封装有请求头与请求体的对象,**HttpEntity**是RequestEntyty和ResponseEntity的父类
    • responseType 指定返回响应的body的数据类型,即exchange里的Class类型参数
    • 有返回值的返回responseEntity:该对象中有响应码、contentType、contentLength、响应消息体等
  • Get请求因为没有请求体,调用exchange无需**HttpEntity**。注意请求体里包含请求头和请求内容 | 请求类型 | API | 说明 | | —- | —- | —- | | GET 请求 | getForEntity 方法 | 返回的 ResponseEntity 包含了响应体所映射成的对象 | | GET 请求 | getForObject 方法 | 返回的请求体将映射为一个对象 | | POST 请求 | postForEntity 方法 | 返回包含一个对象的 ResponseEntity ,这个对象是从响应体中映射得到的 | | POST 请求 | postForObject 方法 | 返回根据响应体匹配形成的对象 | | PUT 请求 | put 方法 | PUT 资源到特定的 URL | | DELETE 请求 | delete 方法 | 对资源执行 HTTP DELETE 操作 | | 任何请求 | exchange 方法 | 返回包含对象的 ResponseEntity ,这个对象是从响应体中映射得到的 | | 任何请求 | execute 方法 | 返回一个从响应体映射得到的对象 |

exchange进行请求与响应

  1. ResponseEntity<T> res=restTemplate.exchange(...,T.class,...)
  2. //T为请求返回的类型
  3. log.info("{}", responseEntity.getStatusCode()); // 响应码
  4. log.info("{}", responseEntity.getHeaders()); // 响应头
  5. log.info("{}", responseEntity.getBody()); // 响应体
  1. String url = "http://localhost:8080/get4?username={1}&age={2}";
  2. ResponseEntity<List<User>> responseEntity1 = template.exchange(url, HttpMethod.GET, null,new ParameterizedTypeReference<List<User>>() {}, "tom", 10);
  1. //Get请求方法参数写在url里面
  2. String url1 = "http://localhost:8080/get2?username={1}&password={2}";
  3. ResponseEntity<String> responseEntity1 = template.exchange(url1, HttpMethod.GET, null, String.class, "tom", 10);
  4. //或者-----------------------------------
  5. String url2 = "http://localhost:8080/get2?username={xxx}&password={yyy}";
  6. Map<String, Object> map = new HashMap<>();
  7. map.put("xxx", "tom");
  8. map.put("yyy", 20);
  9. ResponseEntity<String> responseEntity2 = template.exchange(url2, HttpMethod.GET, null, String.class, map);
  1. //Post请求有请求体,所以调用exchange需要提供一个HttpEntity
  2. String url = "http://localhost:8080/post1";
  3. // 准备请求头部信息
  4. HttpHeaders headers = new HttpHeaders();
  5. headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  6. // 无参数情况下,不需要设置请求 body 部分
  7. HttpEntity<?> entity = new HttpEntity<>(null, headers);
  8. ResponseEntity<String> responseEntity = template.exchange(url, HttpMethod.POST, entity, String.class);
  1. // 准备请求头部信息
  2. HttpHeaders headers = new HttpHeaders();
  3. headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  4. // 准备要提交的数据
  5. MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();
  6. parameters.add("username", "tom");
  7. parameters.add("age", 20);
  8. HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(parameters, headers);
  9. String url = "http://localhost:8080/post2";
  10. ResponseEntity<String> responseEntity = template.exchange(url, HttpMethod.POST, entity, String.class);

请求头与响应头

  1. HttpHeaders headers = new HttpHeaders();
  2. headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  3. headers.add("...", "...");
  1. ResponseEntity<String> entity = template.postForEntity(url, params, String.class);
  2. // 查看响应的状态码
  3. System.out.println(entity.getStatusCodeValue());
  4. // 查看响应的响应体
  5. System.out.println(entity.getBody());
  6. // 查看响应头
  7. HttpHeaders headMap = entity.getHeaders();
  8. for (Map.Entry<String, List<String>> m : headMap.entrySet()) {
  9. System.out.println(m.getKey() + ": " + m.getValue());
  10. }

切换底层实现&Http框架

  • 常用的实现有2种HttpClient OkHttp RestTemplate默认的实现是JDK封装的URLConnection
    • HttpClient出现早,使用率更高 Okhttp性能最好 HttpClient和Okhttp都比默认实现效率高
    • 这些框架能提升性能的原因是使用了**http连接池**
  • 引入了第三方实现,在创建restTemplate时返回第三方库实现类的对象即可 ```xml org.apache.httpcomponents httpclient

okHttp…

  1. ```java
  2. @Bean
  3. public RestTemplate restTemplate() {
  4. RestTemplate restTemplate = new RestTemplate(); // 默认实现
  5. // 默认实现本质就是这个:
  6. // RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
  7. // 使用 HttpClient
  8. //RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
  9. // 使用 OkHttp
  10. // RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
  11. return restTemplate;
  12. }

OpenFeign

  • OpenFeignFeign开源后的新名字。OpenFeign类似于RestTemplate,是对背后实现的(对HttpURLConnection和第三方库HttpClientOKHttp进行了封装简化)一个门面封装

    使用OpneFeign

    依赖

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    

    配置

    ```properties server.port=8080 spring.application.name=a-service spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

    spring.cloud.nacos.discovery.username=nacos

    spring.cloud.nacos.discovery.password=nacos

    spring.cloud.nacos.discovery.namespace=public

选填

feign.httpclient.enabled=true #切换为httpclient feign.okhttp.enabled=true #切换为okhttp

com.woniu.outlet.client是Feign配置的服务接口的路径

logging.level.com.woniu.outlet.client=DEBUG #启用feign的debug日志

下面的都是局部配置,即具体针对请求或者响应

启用请求与响应信息压缩,会增加cpu压力,但是节约网络资源

feign.compression.request.enabled=true feign.compression.response.enabled=true

该配置可以配置符合类型时才进行压缩

feign.compression.response..mime- types:text/html,text/xml,text/plain,application/xml,application/json

下面是全局的压缩设置

server: compression: enabled: …

<a name="q4xcr"></a>
### 配置日志

- SpringCloudFeign 为每一个 FeignClient 都提供了一个 feign.Logger 实例。
   - 可以根据 **logging.level.<FeignClient>** 参数配置格式来开启 Feign 客户端的 DEBUG 日志,其中 **<FeignClient>** 部分为 Feign 客户端定义接口的完整路径
   - 还要配置个`Looger.Level`的bean
```properties
logging.level.com.woniu.outlet.client=DEBUG
@Bean
public Logger.Level feignLoggerLevel() {
    return  Logger.Level.级别;
}
/** 日志级别有如下:
NONE    不输出任何日志
BASIC    只输出 Http 方法名称、请求 URL、返回状态码和执行时间
HEADERS    输出 Http 方法名称、请求 URL、返回状态码和执行时间 和 Header 信息
FULL    记录 Request 和 Response 的 Header,Body 和一些请求元数据  */

启用Feign

@EnableFeignClients(basePackages = "...")

创建调用方法的配置接口

  • 创建一个接口,接口对应一个其他应用,接口里配置的方法即那个应用的控制器方法的方法简写

    • 一个服务只能被一个配置接口绑定 ```java @FeignClient(value/name = “b-service”) // 这里要和 b-service 在 nacos-server 上登记的名字相呼应,即这里绑定的是被调方 public interface BServiceClient {

    @GetMapping(“/“) public String index();

    @PostMapping(“/login1”) public String login1(@RequestParam(“username”) String username,

                     @RequestParam("uesrname") String password);
    

    @PostMapping(“/login2”) public String login2(@SpringQueryMap LoginToken token);

    @PostMapping(“/login3”) public String login3(@RequestBody LoginToken token);

}

<a name="oMIcP"></a>
### 调用远程方法
```java
bService.index();  //调用直接像调普通方法一样调即可
bService.login1("rzd","123456");
//上面地代码可能有遗漏,bService应该是一个实现类。可以直接通过匿名内部类实现

默认响应码问题

  • feign默认的正确响应码为200,正确返回时feign会自动解析响应体提取响应数据返回。非200时feign会抛出一个运行时异常。

    超时与超时重试

  • Feign自身也具备重试功能。不过因为OpenFeign可以整合了Ribbon,二者都开启重试功能就会导致混乱(重试次数变成ribbon和feign的重试的乘积) 因此不是太早的版本都把**feign**的重试功能默认关闭了

    • 一般都是推荐使用ribbon的重试即可
  • 如果同时配置了Ribbon、Feign,那么 Feign 的配置将生效
  • Ribbon 的配置要想生效必须满足微服务相互调用的时候通过注册中心,如果你是在本地通过 @FeignClient 注解的 url 参数进行服务相互调用的测试,此时 ribbon 设置的超时时间将会失效,但是通过 Feign 设置的超时时间不会受到影响(仍然会生效)

    feign:
    client:
      config:
        default: #defalut表示对所有服务生效,下面配置同ribbon一样的用法
          connectTimeout: 100000   #毫秒   
          readTimeout: 10000     #毫秒
    

    Feign请求拦截器

  • 我们可以通过Feign附带的拦截器功能,实现对请求添加请求头等信息

    • 如下代码的作用是将当前请求的头都添加到将要发出的请求中

      //配置下feign提供的拦截器即可
      @Bean
      public RequestInterceptor requestInterceptor() {
         return requestTemplate -> {
             //获取请求信息并转为HttpServletRequest
             ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
             HttpServletRequest request = attributes.getRequest();
             Enumeration<String> headerNames = request.getHeaderNames();
             if (headerNames == null)
                 return;
      
             while (headerNames.hasMoreElements()) {
                 String name = headerNames.nextElement();
                 String values = request.getHeader(name);
                 requestTemplate.header(name, values);
             }
      
         };
      }
      
      @Component
      public class FeignRequestInterceptor implements RequestInterceptor
      {
      @Override
      public void apply(RequestTemplate requestTemplate)
      {
         HttpServletRequest httpServletRequest = ServletUtils.getRequest();
         if (StringUtils.isNotNull(httpServletRequest))
         {
             Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
             // 传递用户信息请求头,防止丢失
             String userId = headers.get(CacheConstants.DETAILS_USER_ID);
             if (StringUtils.isNotEmpty(userId))
             {
                 requestTemplate.header(CacheConstants.DETAILS_USER_ID, userId);
             }
             String userName = headers.get(CacheConstants.DETAILS_USERNAME);
             if (StringUtils.isNotEmpty(userName))
             {
                 requestTemplate.header(CacheConstants.DETAILS_USERNAME, userName);
             }
             String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER);
             if (StringUtils.isNotEmpty(authentication))
             {
                 requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication);
             }
         }
      }
      }
      

      Feign原理

  • 链接

    Feign与RestTemplate区别

  • 使用的区别

    • RestTemplate需要每个请求都拼接url+参数+类文件,灵活性高但是消息封装更加
    • feign可以伪装成类似SpringMVC的controller一样,将rest的请求进行隐藏,不用再自己拼接url和参数,可以便捷优雅地调用HTTP API。
  • 底层实现方式的区别

    • RestTemplate在拼接url的时候,可以直接指定ip地址+端口号,不需要经过服务注册中心就可以直接请求接口也可以指定服务名,请求先到服务注册中心获取对应服务的ip地址+端口号,然后经过HTTP转发请求到对应的服务接口(注意:这时候的restTemplate需要添加@LoadBalanced注解,进行负载均衡)。
    • Feign的底层实现是动态代理,如果对某个接口进行了@FeignClient注解的声明,Feign就会针对这个接口创建一个动态代理的对象,在调用这个接口的时候,其实就是调用这个接口的代理对象,代理对象根据@FeignClient注解中name的值在服务注册中心找到对应的服务,然后再根据@RequestMapping等其他注解的映射路径构造出请求的地址,针对这个地址,再从本地实现HTTP的远程调用。
    • 总之就是resttemplate既可以走服务注册中心,也可以不走注册中心,而Feign只能走注册中心

      切换底层实现

  • 引入依赖后在配置文件中启用即可。不过feign有不一样的第三方依赖 ```java

    io.github.openfeign feign-httpclient

io.github.openfeign feign-okhttp

```properties
feign.httpclient.enabled=true   #切换为httpclient
feign.okhttp.enabled=true   #切换为okhttp