在讲服务网关之前,先来讲讲Feign远程调用服务;
1,* Feign远程调用服务的使用:
1.1,在之前使用RestTemplate出现的问题:
- 因为要使用url来进行访问远程服务,因此就必须要传递**url;**
- 这就导致了代码可读性差;
- 编程体验不统一;
- 维护麻烦;
1.2,Feign远程调用服务的介绍:
- Feign是一个声明式的http客户端,官方地址:[https://github.com/OpenFeign/feign](https://github.com/OpenFeign/feign);其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
1.3,* Feign远程调用的使用:(代替Feign)
在Feign中集成了Ribbon负载均衡,因此,完全不需要我们操心要访问哪一个接口;
- **注意事项:**
- **在Feign接口中的所有参数都要添加注解!!!因为SpringBoot是根据注解来进行扫描和装配;**
- **在Feign接口中默认的对象类型是一个HashMap类型,如果不自己声明好的话;**
- **在Feign接口中的方法访问路径必须是完整的;**
1.3.1,导入Feign的依赖:
- 在order-service中导入Feign依赖:
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.3.2,在启动类上添加@EnableFeignClients注解开启Feign功能:
- **@EnableFeignClients**;该注解会开启Spring自动装配Feign;
- 在order-service的启动类OrderApplication中开启:
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
//记得注释掉原来的RestTemplate
// @Bean
// @LoadBalanced
// public RestTemplate restTemplate(){
// return new RestTemplate();
// }
//
}
1.3.3,编写Feign客户端:client
1. 创建接口:![image.png](https://cdn.nlark.com/yuque/0/2022/png/25975946/1651918918575-fcfa1bcd-c658-4f1c-90a0-7dbe9827398d.png#clientId=ud8a07446-acc0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=79&id=ucbafbdcd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=119&originWidth=276&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4955&status=done&style=none&taskId=u24ad5e27-5839-4ca4-bb1c-827d4a3cf11&title=&width=184) **FeignClient主要设置在消费者模块中;**
1. 编写接口:**在接口上添加@FeignClient注解,设置访问的服务名称;**
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author Jztice5
* @date 2022年05月07日 18:15
*/
@FeignClient("userservice")
public interface UsetClient {
//使用Restful服务接口风格即可;
@GetMapping("/user/{id}")
User findUserById(@PathVariable("id") Long id);
}
因为客户端是一个接口,在业务层里面直接调用对应的方法即可;
1.3.4,调用Feign替代RestTemplate:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
//注入Feign客户端
@Autowired
private UserClient userClient;
@Override
public Order queryOrderById (Long orderId) {
//查询订单
Order order = orderMapper.findById(orderId);
//使用Feign进行远程调用
Long userId = order.getUserId();
User user = userClient.findUserById(userId);
//set
order.setUser(user);
return order;
}
}
1.4,* Feign的注解:
注解 | 作用 |
---|---|
@EnableFeignClients | 在启动类上添加,开启Feign远程调用的自动装配; |
@FeignClient() | 在(value)内设置该Feign接口要访问的服务名称; |
2,Feign的自定义配置:
2.1,Feign支持的自定义配置:
- **Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:**
- **一般地,我们需要配置的是:日志级别;**
失败重试机制:Feign自身没有重试机制,但是底层的负载均衡是用 Ribbon来实现的,因此,会使用Ribbon的重试机制; 什么是失败重试机制:就比如,在Ribbon中第一次访问8081接口失败,那么就会重试,尝试地去访问8082接口,直到访问成功;
2.2,Feign的配置方式:
2.2.1,方式一:配置文件的方式:
- **在order-service中的application.yml文件中配置;**
- **在config中填写服务名称(全局)或者default(局部);**
A,全局生效:default
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
B,局部生效:服务名称
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: BASIC # 日志级别
2.2.2,方式二:Java代码的方式:
- 先新建一个专门放配置类的包:![image.png](https://cdn.nlark.com/yuque/0/2022/png/25975946/1651934632842-7f028b5a-5bd1-405f-8d2f-ef48157b0980.png#clientId=u16b6aa6c-db15-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=105&id=ufc1dc274&margin=%5Bobject%20Object%5D&name=image.png&originHeight=157&originWidth=369&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7335&status=done&style=none&taskId=u56ad306c-f89c-4d72-a691-f140549130e&title=&width=246)
- **在Feign配置类中的代码:**
- 使用**Logger.Level**来声明这个Bean
import feign.Logger;
import org.springframework.context.annotation.Bean;
/**
* @author Jztice5
* @date 2022年05月07日 22:41
*/
public class DefaultFeignConfiguration {
@Bean
public Logger.Level logLevel(){
//设置日志级别:BASIC
return Logger.Level.BASIC;
}
}
A,全局生效:在模块中的启动类上的@EnableFeignClients注解添加Configuration
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
B,局部生效:在@FeignClient注解添加Configuration
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)
2.2.3,日志的级别:x4
- NONE:不记录任何日志信息,这是默认值。
- **BASIC**:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、响应体。
一般使用 BASIC 日志级别;
3,Feign的性能优化:
3.1,Feign的底层客户端实现:
- 默认实现:URLconnection,不支持连接池;
- Apache HttpClient:支持连接池;
- OKHttp:支持连接池;
3.2,因此优化Feign的性能主要包括:
1. 使用连接池替代默认的URLconnection;
1. 日志级别:使用BASIC或none;
3.3,* Feign的性能优化-连接池配置:
3.3.1,导入依赖:feign-httpclient
- 在order-service的pom文件中引入依赖:
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
3.3.2,配置连接池:
- 在order-service的application文件中进行配置:
feign:
client:
config:
default:
loggerLevel: BASIC # 日志级别
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
说明:每个不同的微服务连接路径是不同的,如:访问user-service这个微服务就是一条路径,最多有50个连接。通常最大连接数除以一共有多少个微服务,则每个微服务平均占用多少个连接。后期可再根据访问性能的变化进行调整。 只要将依赖导入进来并配置好参数以后就能实现连接池;
4,* Feign的最佳使用:
4.1,在之前使用Feign出现的问题:
- **在之前,Feign客户端和服务提供者的Controller代码十分相似;**
- **甚至可以说是一模一样,否则无法构建成功的访问关系;**
- **如果业务开始变多的话,那么就要在每一个业务中的两边服务里面写很多份相似的代码,导致代码过度重复,极大地影响了开发效率,因此,需要一种方法来简化这样的代码;**
4.2,方式一:为FeignClient和controller定义统一父接口:
- 优点:服务紧耦合;
- 缺点:在父接口参数列表的映射不会被继承;
4.3,* 方式二:将Feign的客户端抽取为独立的模块:(推荐)
- 将FeignClient抽取为独立的模块,并把与接口有关的pojo,默认的Feign的配置都放到这个模块中,提供给所有消费者使用;![image.png](https://cdn.nlark.com/yuque/0/2022/png/25975946/1651982723705-08ed106e-c93e-42e8-9e17-c617cdf91f9b.png#clientId=u5ed531ec-e39f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=225&id=u78c025c9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=240&originWidth=745&originalType=binary&ratio=1&rotation=0&showTitle=false&size=104401&status=done&style=none&taskId=u38d80f5f-82ec-4d83-9f5b-a9eaf6c18c0&title=&width=699)
1. **创建feign-api模块:**![image.png](https://cdn.nlark.com/yuque/0/2022/png/25975946/1651992232496-fdcf02ea-b897-4ae2-b8fe-228258c0dfe7.png#clientId=u5ed531ec-e39f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=152&id=u1d652a76&margin=%5Bobject%20Object%5D&name=image.png&originHeight=217&originWidth=177&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5639&status=done&style=none&taskId=ufd80b43c-a222-47ef-9c64-a08a7fc0da7&title=&width=124)
1. 注意:该模块一定要与父模块构建继承关系;
1. 如果在创建的时候没有选择继承关系,可以在父模块中的pom文件添加:
<modules>
<module>feignClient-api</module>
</modules>
3. **在feign-api中的pom文件导入openfeign依赖:**
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
2. **编写client客户端:**
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import cn.itcast.feign.pojo.User;
/**
* @author Jztice5
* @date 2022年05月07日 18:15
*/
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findUserById(@PathVariable("id") Long id);
}
3. **编写feign配置类:**
/**
* @author Jztice5
* @date 2022年05月07日 22:41
*/
public class DefaultFeignConfiguration {
@Bean
public Logger.Level logLevel(){
return Logger.Level.BASIC;
}
}
4. **复制pojo类:略;**
4. **修改在order-service的Java代码:**
//过程略
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.Order;
import cn.itcast.feign.pojo.User;
//主要是将原来在order-service模块中导入的pojo和clients更改为在feign模块下的包;
6. **重新启动Order-service服务:**
1. **此时会发现,找不到需要的feign客户端对象;**
1. *** 因为在SpringBoot项目中order-service模块范围内无法扫描到我们创建的feign模块,因此无法实现对feign的自动装配;也就无法创建bean对象注入;**![image.png](https://cdn.nlark.com/yuque/0/2022/png/25975946/1651994139957-6ea13699-8c66-4160-a512-c8e90e60c657.png#clientId=u2c81f8b3-adde-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=53&id=ub232c956&margin=%5Bobject%20Object%5D&name=image.png&originHeight=53&originWidth=900&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10476&status=done&style=none&taskId=u8d4661cb-57f9-4cfa-9acb-c7de8a6e09c&title=&width=900)
解决方式见下一步骤:
* 无法扫描feign独立模块客户端包的解决方案:
在order-service的启动类上添加,并配置Feign;引入配置类字节码
A,方案一:指定FeignClient所在的包:(全包扫描)
@EnableFeignClients(basePackages = "cn.itcast.feign.clients",defaultConfiguration = DefaultFeignConfiguration.class)
B,* 方案二:指定FeignClient类的字节码:(精准定位,推荐)
@EnableFeignClients(clients = {UserClient.class},defaultConfiguration = DefaultFeignConfiguration.class)
SpringBoot中默认扫描范围是单个模块内所有的包,以及导入的依赖包;(是无法扫描到其他模块的包的),只有在启动类上添加对应的包路径或字节码文件才能实现扫描并装配;