- RestTemplate
- OpenFeign
- spring.cloud.nacos.discovery.username=nacos
- spring.cloud.nacos.discovery.password=nacos
- spring.cloud.nacos.discovery.namespace=public
- 选填
- com.woniu.outlet.client是Feign配置的服务接口的路径
- 下面的都是局部配置,即具体针对请求或者响应
- 启用请求与响应信息压缩,会增加cpu压力,但是节约网络资源
- 该配置可以配置符合类型时才进行压缩
- 下面是全局的压缩设置
- 默认响应码问题
- 超时与超时重试
- Feign请求拦截器
- Feign原理
- Feign与RestTemplate区别
RestTemplate
- 这是spring提供的用于访问Rest服务的工具。RestTemplate类似于门面模式中的门面,是对背后真正起作用的进行二次包装。
- 在默认的(未引入其它包的)情况下,在 RestTemplate 背后真正干活的是 JDK 中的网络相关类:
HttpURLConnection。如此之外,RestTemplate 还支持使用HttpClient和OkHTTP库,前提是,你要额外引入这两个包。
- 在默认的(未引入其它包的)情况下,在 RestTemplate 背后真正干活的是 JDK 中的网络相关类:
restTemplate算是rpc工具,也可以包装为http进行请求,毕竟http是rpc的一种实现
创建Bean
早期的RestTemplate,spring容器中已经默认创建了一个供使用,新版本的Springboot需要自己创建
在restTemplate的bean上面添加
@LoadBalanced即可实现自动负载均衡@Beanpublic RestTemplate restTemplate(RestTemplateBuilder builder) {return builder.build();}
API
Get/post/delete/put这些方法底层都是
exchange方法,直接使用exchange比较麻烦,所以提供了封装方法- exchange方法参数说明:
urlmethod请求类型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进行请求与响应
ResponseEntity<T> res=restTemplate.exchange(...,T.class,...)//T为请求返回的类型log.info("{}", responseEntity.getStatusCode()); // 响应码log.info("{}", responseEntity.getHeaders()); // 响应头log.info("{}", responseEntity.getBody()); // 响应体
String url = "http://localhost:8080/get4?username={1}&age={2}";ResponseEntity<List<User>> responseEntity1 = template.exchange(url, HttpMethod.GET, null,new ParameterizedTypeReference<List<User>>() {}, "tom", 10);
//Get请求方法参数写在url里面String url1 = "http://localhost:8080/get2?username={1}&password={2}";ResponseEntity<String> responseEntity1 = template.exchange(url1, HttpMethod.GET, null, String.class, "tom", 10);//或者-----------------------------------String url2 = "http://localhost:8080/get2?username={xxx}&password={yyy}";Map<String, Object> map = new HashMap<>();map.put("xxx", "tom");map.put("yyy", 20);ResponseEntity<String> responseEntity2 = template.exchange(url2, HttpMethod.GET, null, String.class, map);
//Post请求有请求体,所以调用exchange需要提供一个HttpEntityString url = "http://localhost:8080/post1";// 准备请求头部信息HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);// 无参数情况下,不需要设置请求 body 部分HttpEntity<?> entity = new HttpEntity<>(null, headers);ResponseEntity<String> responseEntity = template.exchange(url, HttpMethod.POST, entity, String.class);
// 准备请求头部信息HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);// 准备要提交的数据MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();parameters.add("username", "tom");parameters.add("age", 20);HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(parameters, headers);String url = "http://localhost:8080/post2";ResponseEntity<String> responseEntity = template.exchange(url, HttpMethod.POST, entity, String.class);
请求头与响应头
HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);headers.add("...", "...");
ResponseEntity<String> entity = template.postForEntity(url, params, String.class);// 查看响应的状态码System.out.println(entity.getStatusCodeValue());// 查看响应的响应体System.out.println(entity.getBody());// 查看响应头HttpHeaders headMap = entity.getHeaders();for (Map.Entry<String, List<String>> m : headMap.entrySet()) {System.out.println(m.getKey() + ": " + m.getValue());}
切换底层实现&Http框架
- 常用的实现有2种
HttpClientOkHttpRestTemplate默认的实现是JDK封装的URLConnection- HttpClient出现早,使用率更高 Okhttp性能最好 HttpClient和Okhttp都比默认实现效率高
- 这些框架能提升性能的原因是使用了
**http连接池**
- 引入了第三方实现,在创建restTemplate时返回第三方库实现类的对象即可
```xml
org.apache.httpcomponents httpclient
okHttp…
```java@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate(); // 默认实现// 默认实现本质就是这个:// RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());// 使用 HttpClient//RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());// 使用 OkHttp// RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());return restTemplate;}
OpenFeign
OpenFeign即Feign开源后的新名字。OpenFeign类似于RestTemplate,是对背后实现的(对HttpURLConnection和第三方库HttpClient,OKHttp进行了封装简化)一个门面封装使用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:8848spring.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只能走注册中心
切换底层实现
- RestTemplate在拼接url的时候,可以直接指定ip地址+端口号,不需要经过服务注册中心就可以直接请求接口; 也可以指定服务名,请求先到服务注册中心获取对应服务的ip地址+端口号,然后经过HTTP转发请求到对应的服务接口(注意:这时候的restTemplate需要添加
引入依赖后在配置文件中启用即可。不过feign有不一样的第三方依赖 ```java
io.github.openfeign feign-httpclient
```properties
feign.httpclient.enabled=true #切换为httpclient
feign.okhttp.enabled=true #切换为okhttp
