OpenFeign是一个声明式的http客户端,是SpringCloud在Netflix开源的Feign基础上改造而来。其作用就是帮助我们简单的实现http请求的发送。
OpenFegin可以被SpringBoot自动装配,使用起来非常简单,具体使用步骤如下:
1.引入依赖
在微服务的pom.xml文件中引入如下所示的依赖:
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-openfeignartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-loadbalancerartifactId>
- dependency>
2.添加注解
在微服务的启动类上面添加注解,来开启OpenFeign的功能:

3.编写Feign的客户端
在order-service中新建一个接口,内容如下:
- @FeignClient("服务名字")
- public interface UserClient {
- @GetMapping("/user/{id}")
- User findById(@PathVariable("id") Long id);
- }
这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
服务名称:userservice
请求方式:GET
请求路径:/user/{id}
请求参数:Long id
返回值类型:User
这样,OpenFeign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。
4.使用OpenFeign,
下面是使用OpenFeign实现远程调用的代码。另外,OpenFeign的底层是基于LoadBlancer组件的,所以会自动帮助我们进行负载均衡。
- //从容器中获取OpenFeign客户端
- @Autowired
- private UserClient userClient;
-
- public User queryOrderById(Long orderId){
- //通过OpenFeign发起Http请求,进行远程调用
- User user = userClient.findById(order.getUserId());
- //返回
- return user;
- }
通过以上OpenFeign的使用,就可以简化之前的代码
- @Autowired
- private DiscoveryClient discoveryClient;
- @Autowired
- private RestTemplate restTemplate;
-
- public User queryOrderById(Long orderId){
- //根据服务名称获取服务实例
- List
instances = discoveryClient.getInstances("服务名称"); - //负载均衡,挑选一个实例
- ServiceInstance instance = instance.get(RandomUtil.randomInt.size());
- //获取实例的IP和端口
- URI uri = instance.getUri();
- //根据IP和端口号使用RestTemplate进行远程访问
- ResponseEntity<实体类> response = restTemplate.exchange(
- String url,//请求路径
- HttpMethod method,//请求方式
- HttpEntity> requestEntity,//请求实体,可以为null
- Class
responseType,//返回值类型 - Map
uriVariables//请求参数 - )
- //解析响应
- if(response.getStatusCode().is2xxSuccessful()){
- //查询失败,直接结束
- return;
- }
- 返回值类型 data = response.getBody();
- if(Object.isEmpty(data)){
- return;
- }
- }
Feign 底层发起 Http 请求,依赖于其它的框架。其底层客户端实现包括:
URLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
所以说,可以通过使用连接池代替默认的 URLConnection 来提高 OpenFeign 的性能。这里我们用Apache 的 HttpClient 来演示。
第一步:引入依赖
在微服务的pom文件中引入Apache的HttpClient依赖
- <dependency>
- <groupId>io.github.openfeigngroupId>
- <artifactId>feign-httpclientartifactId>
- dependency>
第二步:配置连接池
在微服务的配置文件application.yml中添加配置
- feign:
- client:
- config:
- default: # default全局的配置
- loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
- httpclient:
- enabled: true # 开启feign对HttpClient的支持
- max-connections: 200 # 最大的连接数
- max-connections-per-route: 50 # 每个路径的最大连接数
接下来,在FeignClientFactoryBean中的loadBalance方法中打断点:

Debug方式启动order-service服务,可以看到这里的client,底层就是Apache HttpClient:

所谓最近实践,就是使用过程中总结的经验,最好的一种使用方式。仔细观察OpenFeign,可以发现客户端与服务提供者的Controller层的代码非常相似。
- @FeginClient("userservice")
- public interface UserClient{
- @GetMapping("/user/{id}")
- User queryById(@PathVariable("id") Long id);
- }
UserController:
- @GetMapping("/user/{id}")
- public User queryById(@PathVariable("id") Long id){
- return userService.queryBuId(id);
- }
可以通过抽取的方式避免重复编码。企业当中一般有如下两种抽取思路。在抽取完成之后,就可以在微服务中的pom文件里引入依赖坐标,避免重复编码。
思路1:抽取到微服务之外的公共module,再让微服务。抽取更加简单,工程结构也比较清晰,但缺点是整个项目耦合度偏高。
思路2:每个微服务自己抽取一个module。抽取相对麻烦,工程结构相对更复杂,但服务之间耦合度降低。

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志,所以要想书输出OpenFeign的日志,要保证的第一个条件就是在application.yml配置文件中将FeignClient所在包的日志级别设置为DEBUG。OpenFeign本身的日志级别有四级,默认的日志级别是NONE,所以默认我们看不到请求日志。
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
要想改变 OpenFeign 的默认日志级别,让日志生效,我们需要声明一个类型为 Logger.Level 的 Bean,在其中定义日志级别。
- public class DefaultFeignConfig {
- @Bean
- public Logger.Level feignLogLevel(){
- return Logger.Level.FULL;
- }
- }
但是,上面这个类并非配置类,要想让这个bean生效,有两种方式:
局部生效:在某个FeignClient中配置,只对当前FeignClient生效
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
全局生效:在启动类的@EnableFeignClients注解中配置,针对微服务中的所有FeignClient生效。
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
在实际应用中,我们可以在一个类似于工具类的微服务中,定义上述的DefaultFeignConfig。然后哪个微服务需要OpenFeign的日志,就在pom.xml文件里引入依赖坐标,并在自己的FeignClient注解或启动类上用上述二种方式使其生效即可。
一般只在调试的时候开启OpenFeign的日志,其它时候一般不建议开启,毕竟对性能还是会产生一定的影响。
Gateway是一个在分布式系统中作为入口的服务器,用于接收所有外部请求,并将这些请求路由到相应的服务器节点上。
在 SpringCloud 中网关的实现包括两种,分别是 Gateway、Zuul。Zuul 由 Netflix 出品,是基于 Servlet 的实现,属于阻塞式编程。而 Gateway 由 Spring 官方出品,基于 WebFlux 实现,属于响应式编程的实现,具备更好的性能。Gateway 的核心功能如下:
权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

第一步:创建一个SpringBoot的项目,并引入Gateway的依赖
- <!--网关-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-gateway</artifactId>
- </dependency>
- <!--nacos服务发现依赖-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
- </dependency>
第二步:在配置文件中编写基础配置和路由规则
- server:
- port: 10010 # 网关端口
- spring:
- application:
- name: gateway # 服务名称
- cloud:
- nacos:
- server-addr: localhost:8848 # nacos地址
- gateway:
- routes: #网关路由配置
- - id: user-service #路由id,自定义,只要唯一即可
- # uri: http://127.0.0.1:8081 #路由的目标地址 http就是固定地址
- uri: lb://userservice #路由的目标地址 lb就是负载均衡,后面跟服务名称
- predicates: #路由断言,也就是判断请求是否符合路由规则的条件
- - Path=/user/** #这个是按照路径匹配,只要以/user/开头就符合要求
第三步:启动项目,符合 Path 规则的一切请求,都会代理到 uri 参数指定的地址。

在 Gateway 中,如果客户端发送的请求满足了断言的条件,则映射到指定的路由器,就能转发到指定的服务上进行处理。断言配置的示例如下,配置了两个路由规则,有一个 predicates 断言配置,当请求 url 中包含 api/thirdparty,就匹配到了第一个路由 route_thirdparty。
- server:
- port: 10010 # 网关端口
- spring:
- application:
- name: gateway # 服务名称
- cloud:
- nacos:
- server-addr: localhost:8848 # nacos地址
- gateway:
- routes: #网关路由配置
- - id: user-service #路由id,自定义,只要唯一即可
- # uri: http://127.0.0.1:8081 #路由的目标地址 http就是固定地址
- uri: lb://userservice #路由的目标地址 lb就是负载均衡,后面跟服务名称
- predicates: #路由断言,也就是判断请求是否符合路由规则的条件
- - Path=/user/** #这个是按照路径匹配,只要以/user/开头就符合要求
-
- - id: user-service #路由id,自定义,只要唯一即可
- # uri: http://127.0.0.1:8081 #路由的目标地址 http就是固定地址
- uri: lb://userservice #负载均衡,将请求转发到注册中心注册的passiava-thirdarty服务
- predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- - Path=/api/question/** #路径匹配,如果请求路径包含api/question,则符合
常见的断言规则如下:

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。Spring提供了31种不同的路由过滤器工厂。但是,每一种过滤器的作用都是固定的,如果我们希望拦截请求,做自己的业务逻辑可以自定义全局过滤器。
下面Spring提供的几种常见过滤器:
| 名称 | 说明 |
| AddRequestHeader | 给当前请求添加一个请求头 |
| RemoveRequestHeader | 移除请求中的一个请求头 |
| AddResponseHeader | 给响应结果中添加一个响应头 |
| RemoveResponseHeader | 从响应结果中移除一个响应头 |
| RequestRateLimiter | 限制请求的流量 |
下面是使用过滤器的一个例子:
- spring:
- cloud:
- gateway:
- routes:
- - id: user-service
- uri: lb://userservice
- predicates:
- - Path=/user/**
- filters: #过滤器
- #给所有进入userservice的请求添加一个请求头
- - AddRequestHeader=Truth, Itcast is freaking awesome!
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:
- spring:
- cloud:
- gateway:
- routes:
- - id: user-service
- uri: lb://userservice
- predicates:
- - Path=/user/**
- default-filters: # 默认过滤项
- #对进入Gateway的所有请求添加请求头
- - AddRequestHeader=Truth, Itcast is freaking awesome!
不同过滤器的执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链中,排序后依次执行每个过滤器。排序的规则如下:

1.什么是跨域问题
浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题,主要包括:
域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
域名相同,端口不同:localhost:8080 和 localhost8081
解决方案:可以通过CORS进行解决,这个以前应该学习过,这里不再赘述了。不知道的话可以查看:跨域资源共享 CORS 详解 - 阮一峰的网络日志。
注意:这里请求的发起者一般是前端项目,浏览器本身发出的请求不会发生跨域问题,就像idea中运行一个web项目,从浏览器中去访问此项目,并不会出现跨域问题。
2.模拟跨域问题
找到课前资料的页面文件:

放入tomcat或者nginx这样的web服务器中,启动并访问。
可以在浏览器控制台看到下面的错误:

从localhost:8090访问localhost:10010,端口不同,显然是跨域的请求。
3.解决跨域问题
在gateway服务的application.yml文件中,添加下面的配置:
- spring:
- cloud:
- gateway:
- # 。。。
- globalcors: # 全局的跨域处理
- add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
- corsConfigurations:
- '[/**]':
- allowedOrigins: # 允许哪些网站的跨域请求
- - "http://localhost:8090"
- allowedMethods: # 允许的跨域ajax的请求方式
- - "GET"
- - "POST"
- - "DELETE"
- - "PUT"
- - "OPTIONS"
- allowedHeaders: "*" # 允许在请求中携带的头信息
- allowCredentials: true # 是否允许携带cookie
- maxAge: 360000 # 这次跨域检测的有效期