问题引入
JAVA项目中如何实现接口调用?
HttpClient:
HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的的支持Http协议的客户端编程工具包,并且它支持 HTTP协议最新版本和建议。HttpClient相比传统JDK自带的 URLConnection,提升了易用性和灵活性,使客户端发送 HTTP 请求变得容易,提高了开发的效率。
Okhttp:
一个处理网络请求的开源项目,是安卓端最火的轻量级框架,由 Square公司贡献,用于替代HttpUrlConnection和Apache HttpClient。OkHttp拥有简洁的API、高效的性能,并支持多种协议(HTTP/2和SPDY)。
HttpURLConnection:
HttpURLConnection是Java的标准类,它继承自URLConnection,可用于向指定网站发送:GET 请求、POST 请求。HttpURLConnection 使用比较复杂,不像HttpClient那样容易使用。
RestTemplate WebClient:
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程HTTP 服务的方法,能够大大提高客户端的编写效率。
背景介绍
OpenFeign是Spring Cloud的一个声明性HTTP客户端(出自于Netflix的Feign,Feign是Netflix开发的声明式、模板化的HTTP客户端),它简化了我们与其他服务交互的方式。Spring Cloud对OpenFeign进行了增强,使得Spring Cloud OpenFeign支持Spring MVC注解。同时,Spring Cloud整合了Ribbon和 Eureka注册中心(Nacos也可以),这让 Spring Cloud OpenFeign的使用更加方便。
Spring Cloud OpenFeign是一个声明式的 HTTP客户端,它简化了HTTP客户端的开发,使编写Web服务的客户端变得更容易。
Spring Cloud OpenFeign能够帮助我们定义和实现依赖服务接口。在Spring Cloud OpenFeign的帮助下,只需要创建一个接口并用注解方式配置它,就可以完成服务提供方的接口绑定,减少在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。
Eureka注册中心示例
首先搭建注册中心,本文以单机版Eureka作为注册中心
依赖文件pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lwy.it</groupId>
<artifactId>eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka</name>
<description>Demo project for Spring Boot Service</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
application.yml配置如下:
spring:
application:
name: eureka-server
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
# 是否将自己作为一个服务注册(这里是集群部署互相注册,所以需要作为一个服务注册), 默认为true
register-with-eureka: false
# 是否从其他eureka服务拉取已注册的服务信息, 默认为true
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka/
启动服务后访问:http://localhost:8761/
服务提供者
引入依赖pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lwy.it</groupId>
<artifactId>demo-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-service</name>
<description>Demo project for Spring Boot Service</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
服务端提供一个BookVO,作为服务出入参:
package com.lwy.it.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class BookVO implements Serializable {
private int bookId;
private String bookName;
private double bookPrice;
private String bookDescription;
}
同时提供一个BookController,包含表单,RequestBody,PathVariable等参数方法:
package com.lwy.it.controller;
import com.lwy.it.vo.BookVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@RestController
@RequestMapping("/book")
@Slf4j
public class BookController {
/**
* 模拟数据库返回结果
*/
private List<BookVO> database() {
BookVO bookVO = new BookVO();
bookVO.setBookId(1);
bookVO.setBookName("MySQL实战教程");
bookVO.setBookPrice(99.8);
bookVO.setBookDescription("数据库,是一个程序员的必备技能。而MySQL作¬为时下最流行的关系型数据库管理系统,甚至在可以预见的未来MySQL都将是最流行的关系型数据库管理系统。");
BookVO bookVO1 = new BookVO();
bookVO1.setBookId(2);
bookVO1.setBookName("愿你的青春不负梦想");
bookVO1.setBookPrice(56.9);
bookVO1.setBookDescription("梦想导师俞敏洪暌违两年,写给千万年轻人的诚意励志新作!");
BookVO bookVO2 = new BookVO();
bookVO2.setBookId(3);
bookVO2.setBookName("大话设计模式");
bookVO2.setBookPrice(66.6);
bookVO2.setBookDescription("设计模式的趣味解读,面向对象的深入剖析。在诙谐与温馨中做一次面向对象编程思维的体操。");
return Arrays.asList(bookVO, bookVO1, bookVO2);
}
/**
* 无参数GET请求
*/
@GetMapping("/list")
public List<BookVO> getAllBooks() {
return database();
}
/**
* Path路径参数GET请求
*/
@GetMapping("/{bookId}")
public BookVO getBookById(@PathVariable Integer bookId) {
List<BookVO> bookVOS = database();
return bookVOS.parallelStream().filter((BookVO bookVO) -> {
if (bookVO.getBookId() == bookId.intValue()) {
return true;
}
return false;
}).findAny().get();
}
/**
* 表单参数提交
*/
@GetMapping("/login")
public String login(String username, Integer password) {
log.info("参数为,userName:{},password:{}", username, password);
if (Objects.equals(username, "admin") && Objects.equals(password, 123456)) {
return "SUCCESS";
}
return "FAILURE";
}
/**
* JSON格式请求体参数
*/
@PutMapping("/saveBook")
public BookVO saveBook(@RequestBody BookVO bookVO) {
log.info("存储Book为:{}", bookVO);
return bookVO;
}
/**
* RequestParam格式请求参数
*/
@GetMapping("/param")
public BookVO getBook(BookVO bookVO) {
log.info("获取到的Book为:{}", bookVO);
return bookVO;
}
}
注册到Eureka注册中心配置,application.properties:
spring.application.name=demo-server
server.port=8088
server.servlet.context-path=/server
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
服务调用者
引入依赖pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lwy.it</groupId>
<artifactId>demo-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-client</name>
<description>Demo project for Spring Boot Service</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 引入hystrix依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
客户端配置,注意此处开启了GZIP。GZIP是一种数据格式,采用deflate算法压缩数据,是一种流行的文件压缩算法,应用十分广泛,当压缩—个纯文本文件时,效果是非常明显的,大约可以减少70%以上的文件大小。
spring.application.name=demo-client
server.port=8086
server.servlet.context-path=/client
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
# Spring Boot 默认日志级别是info,feign的debug日志级别就不会输入
logging.level.com.lwy.it.feign=debug
# 开启请求GZIP
feign.compression.request.enabled=true
# 开启响应GZIP
feign.compression.response.enabled=true
# 设置支持GZIP压缩的MIME类型,即请求/响应类型
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 配置启动压缩数据量的最小阈值,单位字节。默认为2014
feign.compression.request.min-request-size=1024
使用GZIP的优点在于网络数据经过压缩后实际上降低了网络传输的子节数,可以加快网页加载的速度。网页加载可以节省流量,改善用户的浏览体验。
OpenFeign日志级别配置,OpenFeign可以开启请求响应详细日志打印,方便我们调试程序,在构建客户端、方法执行器的时候,都可以看到设置了日志类及日志级别。注意配置文件中配置日志级别为debug才可以看到。
package com.lwy.it.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
// @Configuration
// 注意:此处使用@Configuration注解就会全局生效,如果想指定对应微服务生效,就不能配置
public class FeignConfiguration {
/**
* 日志级别:
* NONE:默认值,性能最佳,适用于生产环境,不记录任何日志
* BASIC: 适用于生产环境问题追踪,仅记录请求方法、URL、响应状态代码以及执行时间
* HEADERS:在BASIC基础上增加请求和响应header
* FULL:比较适合开发及测试环境定位,记录请求和响应的header、body和元数据
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
或者通过配置文件修改日志级别:
feign:
client:
config:
# 对应微服务
demo-server:
loggerLevel: FULL
使用Spring Cloud OpenFeign,只需要创建一个接口并注解,就能很容易地调用各服务提供的HTTP接口。
通过OpenFeign Client访问服务端代码BookFeignService类,注意与服务提供方Controller对比异同点:
package com.lwy.it.feign;
import com.lwy.it.config.FeignConfiguration;
import com.lwy.it.vo.BookVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* name 指定调用rest接口所对应的服务名
* path 指定调用rest接口所在Controller指定的@RequestMapping
* configuration 局部配置,让调用的微服务生效,在@FeignClient注解中指定使用的配置类,或者使用上面配置文件进行配置
*/
@FeignClient(name = "demo-server", path = "/server/book", configuration = FeignConfiguration.class)
public interface BookFeignService {
// 声明需要调用rest接口对应的方法
@GetMapping("/list")
List<BookVO> getAllBooks();
@GetMapping("/{bookId}")
BookVO getBookById(@PathVariable("bookId") Integer id);
/**
* 在OpenFeign中方法参数前如果没有注解,默认添加@RequestBody注解,最多只能有一个不带注解的参数
* 普通表单参数必须添加@RequestParam注解,如果变量名和参数名称对应可以不写name
*/
@GetMapping("/login")
String login(@RequestParam("username") String username, @RequestParam("password") Integer password);
@PutMapping("/saveBook")
BookVO saveBook(@RequestBody BookVO bookVO);
@GetMapping("/param")
BookVO getBook(@RequestParam int bookId, @RequestParam String bookName, @RequestParam String bookDescription, @RequestParam double bookPrice);
}
这里的BookVO与服务提供方代码一致。
启动类,启动类开启Feign Clients,否则报BookFeignService引入错误。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class DemoClientApplication {
public static void main(String[] args) {
SpringApplication.run(DemoClientApplication.class, args);
}
}
写一个调用验证的Controller(Spring Cloud OpenFeign能够帮助我们定义和实现依赖服务接口。在Spring Cloud OpenFeign的帮助下,只需要创建一个接口并用注解方式配置它,就可以完成服务提供方的接口绑定,减少在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。):
package com.lwy.it.controller;
import com.lwy.it.feign.BookFeignService;
import com.lwy.it.vo.BookVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class ClientController {
// 直接引入BookFeignService,分别调用方法
@Autowired
private BookFeignService bookFeignService;
@GetMapping("/books")
public List<BookVO> getFirstBook() {
return bookFeignService.getAllBooks();
}
@GetMapping("/book-id")
public BookVO getBookById() {
return bookFeignService.getBookById(2);
}
@GetMapping("/login")
public String login() {
return bookFeignService.login("admin", 123456);
}
@GetMapping("/book-save")
public BookVO saveBook() {
BookVO bookVO = new BookVO();
bookVO.setBookId(100);
bookVO.setBookPrice(199.99);
bookVO.setBookName("OpenFeign教程");
bookVO.setBookDescription("Spring Cloud出品,OpenFeign默认将Ribbon作为负载均衡器,直接内置了Ribbon。在导入OpenFeign依赖后无需专门导入Ribbon依赖。");
return bookFeignService.saveBook(bookVO);
}
@GetMapping("/book-param")
public BookVO getBook() {
return bookFeignService.getBook(100, "OpenFeign教程", "Spring Cloud出品,OpenFeign默认将Ribbon作为负载均衡器,直接内置了Ribbon。在导入OpenFeign依赖后无需专门导入Ribbon依赖。", 199.99);
}
}
访问http://localhost:8086/client/books等链接进行验证。
Spring Cloud OpenFeign基于OpenFeign实现,它除了提供声明式的 HTTP客户端外,还整合了Spring Cloud Hystrix,能够轻松实现熔断器模型。
推荐学习资料:https://blog.youkuaiyun.com/qq_43437874/category_11612066.html
Nacos注册中心示例
注册中心搭建参考:https://blog.youkuaiyun.com/liwenyang1992/article/details/126475927
示例通过父工程来控制依赖版本,父工程pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lwy.it</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
<modules>
<module>order-service</module>
<module>stock-service</module>
</modules>
<!-- springboot的版本管理器,以后公司中开发就可以使用parent去继承公司自定义的父maven-->
<name>springcloudalibaba</name>
<description>springcloudalibaba</description>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<!-- 在 dependencyManagement 中添加如下配置,用于版本管理使用,格式如下 -->
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Alibaba 版本管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Boot 版本管理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud 版本管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
服务提供者
库存服务stock-service,引入依赖如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloudalibaba</artifactId>
<groupId>com.lwy.it</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>stock-service</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
application.yml配置如下:
server:
port: 8090
# 应用名称 Nacos会将该名称当作服务名称
spring:
application:
name: stock-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
namespace: springcloudalibaba
提供库存服务示例:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@RestController
public class StockController {
private final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
@GetMapping("/stock")
public String getStock() {
return "Stock Service Time : " + dateTimeFormat.format(LocalDateTime.now());
}
}
访问:http://localhost:8848/nacos注册中心,可以看到服务已经注册。
访问:http://localhost:8090/stock服务,可以看到返回结果。
服务调用者
引入OpenFeign依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloudalibaba</artifactId>
<groupId>com.lwy.it</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 引入OpenFeign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
application.yml配置
server:
port: 8082
# 应用名称 Nacos会将该名称当作服务名称
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
namespace: public
ribbon:
eager-load:
# 开启饥饿加载模式
enabled: true
# 配置stock-service使用饥饿加载,多个使用逗号分隔
clients: stock-service
OpenFeign客户端:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
/**
* name 指定调用rest接口所对应的服务名
* path 指定调用rest接口所在context-path路径 + @RequestMapping路径
*/
@Component
@FeignClient(name = "stock-service")
public interface StockOpenFeignService {
// 声明需要调用的rest接口对应的方法
@GetMapping("/stock")
String getStock();
}
示例Controller:
import com.lwy.it.feign.StockOpenFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private StockOpenFeignService stockOpenFeignService;
@GetMapping("/test")
public String getOrders() {
return "Order Service ---> " + stockOpenFeignService.getStock();
}
}
启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
契约配置
Spring Cloud在Feign的基础上做了扩展,使用SpringMVC的注解来完成Feign的功能。原生的Feign是不支持 Spring MVC注解的,如果你想在Spring Cloud中使用原生的注解(@RequestLine等注解)方式来定义客户端也是可以的,通过配置契约来改变这个配置,Spring Cloud中默认的是SpringMvcContract。
Spring Cloud 1的早期版本就是用的原生Fegin。随着netflix的停更才替换成了Openfeign。
使用配置类
/**
* 修改契约配置,支持Feign原生的注解 @RequestLine 等
*/
@Bean
public Contract feignContract() {
return new Contract.Default();
}
使用配置文件:
feign:
client:
config:
# 对应微服务
stock-service:
# 还原成原生注解
contract: feign.Contract.Default
@Component
@FeignClient(name = "stock-service")
public interface StockOpenFeignService {
// 使用Feign原生注解
@RequestLine("GET /stock")
String getStock();
}
注意,配置使用原生注解后,SpringMVC注解不再生效
超时时间配置
全局配置:
@Bean
public Request.Options options() {
return new Request.Options(30, TimeUnit.SECONDS, 30, TimeUnit.SECONDS, true);
}
yml中配置:
feign:
client:
config:
# 对应微服务
stock-service:
# 连接超时时间,默认2秒
connectTimeout: 5000
# 请求超时时间,默认5s
readTimeout: 10000
备注:Feign底层使用的是Ribbon,但超时时间以Feign配置为准
自定义拦截器实现认证逻辑
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
log.info("Feign拦截器");
}
}
配置文件生效:
feign:
client:
config:
# 对应微服务
stock-service:
# 配置拦截器
requestInterceptors[0]:
com.lwy.it.interceptor.CustomFeignInterceptor
全局生效:
@Bean
public RequestInterceptor requestInterceptor() {
return new CustomFeignInterceptor();
}