在上一个案列中Springcloud-注册中心
般在springboot使用的交互是利用RestTemplate发起远程调用的代码,Feign是Springcloud整合的声明式组件,
Feign
Feign和RestTemplate都是用于在Java中实现RESTful API调用的工具,但它们之间有一些区别和优缺点。
区别
- Feign是一个声明式HTTP客户端,它使HTTP API请求变得更简单和更优雅。它基于注解,在创建动态代理时解析接口方法,并将其映射到HTTP请求。也就是说,您只需要编写Java接口并添加注解,就可以轻松地使用Feign处理HTTP请求。另一方面,RestTemplate是一个基于HTTP协议的同步请求客户端,需要显式地编写代码来发送HTTP请求和处理响应。
- Feign使用Netflix公司的Ribbon客户端负载平衡器来分发HTTP请求,而RestTemplate没有默认的负载平衡器。
- Feign还支持异步请求,通过使用Hystrix来实现异步执行HTTP请求,并提供了服务降级、断路器等重要的功能,而RestTemplate则不支持异步请求,但可以通过Spring Cloud Circuit Breaker来实现类似的功能。
- Feign还提供了可插拔的编码器和解码器,以将Java对象转换为HTTP请求、响应和错误,而RestTemplate使用消息转换器来完成类似的编解码工作。
优点
Feign具有更加优雅简洁的方式来定义和声明请求,也更加易于使用和理解。
Feign的异步请求和服务降级、断路器等功能可以使应用程序更加健壮和可靠,同时可以提高可用性和可伸缩性。
RestTemplate作为JDK中标准库的一部分,在Spring Boot中也有深入的集成,如果你不想增加额外的依赖,RestTemplate是一个很好的选择,同时它也可以胜任大多数的HTTP请求场景。
不同的项目在不同的情况下可能会选择使用不同的工具来完成对RESTful API的调用,开发人员需要根据具体情况进行选择。
步骤
- 引入依赖
<!--feign客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.启动类添加注解开关@EnableFeignClients()
3. 为需要做调用的远程服务建立一个接口,其中建立方法,方法的返回值和mapping对应被远程调用服务中的返回值和mapping
//value表示服务名
@FeignClient(value = "userservice",configuration = Defaultconfig.class)//服务名
public interface UserClient {
@GetMapping("/user/{id}")//服务的地址
User findByif(@PathVariable("id") Long id);
}
路由对应被调用的服务接口路由
4. 此时controller调用变成
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.用Feign远程调用
User user = userClient.findByif(order.getUserId());
order.setUser(user);
// 4.返回
return order;
}
此时就可以发起交互了,只需要服务名,feign就解析服务名得到ip地址发起交互
在默认情况下,Feign 会使用 Ribbon 进行负载均衡,而在 Ribbon 的配置文件中,会指定服务的负载均衡规则。如果没有指定负载均衡规则,则使用默认的轮询规则。因此,即使没有配置目标服务的 IP 地址,Feign 也可以通过 Ribbon 进行负载均衡,并向目标服务发起 HTTP 请求。
对于为什么接口可以依赖注入@Autowired userClient userapi,userapi 是一个接口,通常情况下,Spring无法直接实例化接口。但是,这里的 userapi 很可能是一个Feign客户端接口,用于通过Feign框架调用远程的REST服务。Feign会在运行时动态生成接口的实现,因此不需要将其标记为 @Component 或添加 @Bean 注解。Spring会自动识别 @FeignClient 注解,然后为这个接口创建一个代理对象,以便远程调用。
较于,原生的Resttemplate更符合Spring的开发规范
/**
* @target 不在采用硬编码形式 通过服务名
* 服务名小写,会被改为大写
*/
//String url="http://localhost:8082/user/"+order.getUserId().toString()
String url="http://userservice/user/"+order.getUserId().toString();
User user=restTemplate.getForObject(url, User.class);
总之,Feign 的功能依赖于 Spring Cloud 的服务注册与发现模块以及 Ribbon 负载均衡模块(需要服务消费者和提供者在同一注册中心)。在默认情况下,Feign 可以通过这些模块获取目标服务的 名字解析IP 地址列表,并使用 Ribbon 进行负载均衡,而不需要显式指定 IP 地址。
和Resttemplate的优缺点比较
在进行远程调用时,Feign 和 RestTemplate 都可以实现基于 HTTP 协议的服务间通信。它们之间的主要区别在于 Feign 是一种声明式的 HTTP 客户端,自动化生成 HTTP 请求代码,而 RestTemplate 则是一种传统的 HTTP 客户端,需要手动编写 HTTP 请求代码。
在实现远程调用时,Feign 和 RestTemplate 都有进行优化,以提高性能和稳定性。
Feign 的优化:
1.负载均衡:Feign 内置了负载均衡机制,可以通过 Ribbon 来进行负载均衡,自动将多个服务实例均匀地分配到不同的调用请求上。
2.客户端支持:Feign 支持多种客户端,包括 Apache HttpClient 和 OkHttp,可以通过修改配置文件或代码来指定客户端。
3.易于使用:Feign 的声明式风格使得使用起来更为简单,不需要手动编写 HTTP 请求代码,只需要定义接口并添加注解即可。
4.异步请求:Feign 支持异步请求,可以通过使用 Java 8 之后的 CompletableFuture 对象来实现异步请求。
RestTemplate 的优化:
支持多种请求方式:
1.RestTemplate 支持多种 HTTP 请求方法,包括 GET、POST、PUT、DELETE 等,可以根据业务需要选择合适的请求方式。
2.支持请求和响应数据的序列化和反序列化:RestTemplate 支持将请求和响应数据转换为 Java 对象,便于在代码中处理和操作数据。
易于使用:RestTemplate 使用简单,只需要调用其方法即可发起 HTTP 请求。
3.客户端支持:RestTemplate 同样支持多种客户端,可以通过使用不同的 HttpMessageConverter 来实现不同的序列化和反序列化方式。
支持自定义拦截器:RestTemplate 支持自定义拦截器,可以在请求和响应过程中进行一些操作,比如添加请求头、记录请求日志等。
总体来说,Feign 和 RestTemplate 都有自己的优点和适用场景,可以根据具体的业务需求来选择使用哪种方式来进行远程调用。而Feign自带负载均衡和声明式风格,和SpringMvc的结构更契合的特性,我们一般选择Feign作用远程调用的工具
2.2.自定义配置
Feign可以支持很多的自定义配置,如下表所示:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。
下面以日志为例来演示如何自定义配置。
2.2.1.配置文件方式
基于配置文件修改feign的日志级别可以针对单个服务:
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
也可以针对所有服务(default):
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
basic的日志信息:向哪个服务发起请求,响应如何
而日志的级别分为四种:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
2.2.2.Java代码方式
也可以基于Java代码来修改日志级别,先声明一个类,然后声明一个Logger.Level的对象:
第一步设置在任意类作为配置类,以bean方式注入配置
//feign 配置类的第二种方式
public class Defaultconfig {
@Bean
public Logger.Level getLogLevel(){
return Logger.Level.BASIC;
}
}
2.拥有配置以后
对服务进行配置
在feign客户端注解添加
@FeignClient(value = "userservice",configuration = Defaultconfig.class)//服务名
全局添加配置
@EnableFeignClients(defaultConfiguration = Defaultconfig.class)
2.3.Feign使用优化
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
•URLConnection:默认实现,不支持连接池
•Apache HttpClient :支持连接池
•OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。
这里我们用Apache的HttpClient来演示。
1)引入依赖
在order-service(服务消费者)的pom文件中引入Apache的HttpClient依赖:
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2)配置连接池
在order-service的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:
总结,Feign的优化:
1.日志级别尽量用basic
2.使用HttpClient或OKHttp代替URLConnection
① 引入feign-httpClient依赖
② 配置文件开启httpClient功能,设置连接池参数
最佳使用
抽象接口
我们发现服务提供者user实列和调用者order在controller的代码是一样的,代码可以通过继承来共享:
接口的提供实列和调用实列的方法格式是一样的,那么我们就可以进行一个抽象
1)定义一个API接口,利用定义方法,并基于SpringMVC注解做声明。
2)Feign客户端和Controller都集成改接口
但是这样做会有一些明显的缺点
缺点:
-
服务提供方、服务消费方紧耦合
-
参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解
在实际开发中,我们可以通过抽取Feign相关的代码模块(调用者和接受者的接口格式都差不多),将其封装成单独的模块,以便在其他项目中进行复用。这样可以提高代码的可复用性和维护性,并且方便团队协作。
dubbo也是这样做的,本质是java设计模式的,相对于继承偶尔都没有那么高
以下是在实际项目中抽取Feign模块的一些步骤和示例:
新建一个模块module
目录结构如下,user服务提供者,order服务消费者
在Maven模块中,我们可以将所有Feign相关的代码和配置文件都放在一个独立的模块中。我们可以在项目中添加以下三个主要的依赖:
- spring-cloud-starter-openfeign:包含了Spring Cloud对Feign的支持,以及必要的依赖项。
- spring-boot-starter-web:用于支持Web项目。
- spring-boot-starter-test:用于支持单元测试。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
把接口业务相关的实体类包,feign自定义配置类包,和建立一个通过接口包放入该模块
接口包中定义Feign接口,便于其他服务使用
在项目中定义Feign接口,以及对应的请求URL和请求参数。示例如下:
假如我的naocs中有一个userservice 作为服务提供者
其中的controller 提供接口
@RestController
@RequestMapping("user")
public class UserController{
@Resource
Usersercvice service;
@Override
@PostMapping("/{id}")
public User SaveArticle(@PathVariable int id) {
return servvice.queryByid(id);
}
}
在feign模块新建一个对应服务的接口,服务名,路径,和返回值和暴露服务的接口对应
@FeignClient("userservice")//该feign的暴露接口服务的服务名 方便ioc注入spring 如果注册中心没有注册需要填写url参数
public interface UserApi {
@GetMapping("/user/{id}")//服务的地址
User findByif(@PathVariable("id") Long id);
}
在模块定义好接口 在交给服务提供方实现
- 这种是先定义好对外抛出的服务在决定feign远程调用的接口
- 如果是你的开发先定好了远程调用的feign格式
那么你的对外抛出服务建议这样使用
@RestController
//对应client中提供的地址
@RequestMapping("user")
public class UserController implements UserApi{
@Resource
Usersercvice service;
@Override
@PostMapping("/{id}")
public User SaveArticle(@PathVariable int id) {
return servvice.queryByid(id);
}
}
定义Feign配置类
在项目中定义Feign的配置类,其中可以设置连接超时时间、读取超时时间、重试次数等参数。示例如下:
@Configuration
public class ApiConfig {
@Bean
public Request.Options options() {
return new Request.Options(5000, 10000);
}
@Bean
public Retryer retryer() {
return new Retryer.Default();
}
// @Bean
// public Retryer retryer() {
// return new Retryer.Default(100, SECONDS.toMillis(1), 5); // 初始间隔100毫秒,递增间隔1秒,最多重试5次
//}
}
启动类不需要任何操作 (可有可无 该模块的作用只是提供统一接口)
public class Main {
//这个模块的作用只是用于调用模块的接口代码,不需要启动springboot服务
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
在其他模块中中使用Feign
我们可以在其他应用程序中使用我们创建的Feign模块。在其他应用程序中添加以下依赖项:
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>#相对模块的路径
<version>1.0</version>
</dependency>
此时我们的order服务实体类中原本包含的实体类user,改为api模块的user 统一消费方和服务方
然后,我们可以像下面这样使用Feign:
在feign服务消费者的配置中使用该模块的配置类
在业务中使用该模块的接口
@Autowired
private UserApi userApi;
@GetMapping("/{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
Order order = orderService.queryOrderById(orderId);
User user = userApi.findByif(order.getUserId());
order.setUser(user);
return order;
}
但是这样会报错,特别是空指针异常因为模块和启动类也就是组件扫面不在一个包,定义的api没有注入ioc,无法在其他模块使用自动装配注入,所以我们可以在服务调用的启动类中指定扫描
方式一:
启动类中
指定Feign应该扫描的包:
@EnableFeignClients(basePackages = "com.feign.clients")
方式二:
指定需要加载的Client接口:
@EnableFeignClients(clients = {UserClient.class})
这里演示方式一,因为实际开发一般会把用到的接口放在一个包
通过以上步骤,我们可以将Feign相关代码模块独立出来,方便在其他项目中进行复用。
ps:feign的熔断降级
因为后续微服务开发中有专门的框架,但是feign本身也可以熔断降级
优先查看feign标识的接口注解参数
有一个fallback失败回调,意味着我们可以定义失败回调,当出现失败调用的时候执行该部分
定义回调类实现feign对外调用的接口
@Component//注入ioc
//失败回调类
public class IArticleClientFalback implements IArticleClient {
@Override
public ResponseResult SaveArticle(ArticleDto dto) {
return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"获取数据失败");
}
}
在调用使用feign的启动类,或者配置类中扫描回调类所在的包
并且在配置文件中写清楚,失败回调触发场景
feign:
# 开启feign对hystrix熔断降级的支持
hystrix:
enabled: true
# 修改调用超时时间
client:
config:
default:
connectTimeout: 2000
readTimeout: 2000
如果超过2秒则执行失败回调
实体类转换
在使用Feign进行远程服务调用时,通常情况下会定义一个接口来描述远程服务,并使用@FeignClient注解来声明该接口是一个Feign客户端。该接口中定义的方法描述了远程服务的请求方式、请求参数和返回值类型等信息。
在封装Feign代码模块时,可以针对不同的远程服务定义不同的Feign客户端接口,每个接口中定义的返回值类型可能不同。在将Feign客户端集成到其他项目中时,我们可能需要将远程服务返回的实体类转换为本地项目中的实体类,以方便数据的处理。这时,可以使用Java中的对象映射库来进行实体类之间的转换。
常用的对象映射库包括Dozer、MapStruct、ModelMapper等。这些库可以将一个实体类映射为另一个实体类,可以支持属性之间的复杂转换。以MapStruct为例,可以定义一个名为“Mapper”的接口,用于定义实体类之间的映射关系,如下所示:
@Mapper
public interface UserMapper {
UserDTO toUserDTO(User user);
}
在这个示例中,UserMapper接口使用@Mapper注解标识,并定义了一个方法toUserDTO,用于将User实体类映射为UserDTO实体类。在其他项目中使用Feign客户端调用远程服务时,可以先将返回的实体类通过UserMapper进行转换,然后再进行处理。
@Autowired
private UserMapper userMapper;
public void getUser(Long userId) {
User user = userFeignClient.getUser(userId);
UserDTO userDTO = userMapper.toUserDTO(user);
// 处理UserDTO对象
}
通过使用对象映射库,我们可以方便地将远程服务返回的实体类转换为本地项目中的实体类,减少系统之间的耦合,增加代码可读性和可维护性。