为了后续顺利学习 Spring Cloud OpenFeign,下面将介绍如何准备学习环境,该环境主要包含两个服务,一个注册中心 Eureka,另一个示例服务 service-demo,该服务将提供简单的 GET、POST、PUT 等简单服务,用于 Spring Cloud OpenFeign 调用。项目结构如下图:

本教程采用 JDK17,如下图:

Eureka 是 Netflix 开源的一个服务发现框架,主要用于在分布式系统中实现服务注册和发现的功能(点击快速学习)。项目结构如下图:

上述项目中,仅有一个 application.yml 配置,和一个启动类 NetflixEurekaServerApplication。
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>3.2.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hxstrive</groupId>
<artifactId>openfeign-eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>netflix-eureka-server</name>
<description>netflix-eureka-server</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<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>
<finalName>netflix-eureka-server</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>application.yml 配置内容如下:
# 默认端口 server: port: 8761 # 应用名称 spring: application: name: eureka-server # Eureka 配置 eureka: # 指定 Eureka 服务实例自身的主机名 instance: hostname: localhost client: register-with-eureka: false fetch-registry: false server: enable-self-preservation: true renewal-percent-threshold: 0.5
下面是一个简单的 Spring Boot 启动类,该类上额外添加了 @EnableEurekaServer 注解,用于标记该服务是一个 Eureka 服务器,代码如下:
package com.hxstrive.netflixeurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* 启动 Eureka Server 注册中心
* @author hxstrive
*/
@SpringBootApplication
@EnableEurekaServer
public class OpenfeignEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(OpenfeignEurekaServerApplication.class, args);
}
}service-demo 服务提供简单的 GET、POST、PUT 等简单服务,用于 Spring Cloud OpenFeign 调用。
项目结构如下图:

包说明:
entity 存放实体,仅有一个 User 实体
dto 数据传输对象,其中 CommonReturn 定义了接口通用传输格式
handler 定义了全局异常处理器
exception 定义业务自定义异常类
controller 定义三个业务 Controller,分别用于简单 POST、GET 服务,用户信息管理 Controller
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>3.2.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hxstrive</groupId>
<artifactId>openfeign-service-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-demo</name>
<description>service-demo</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.3</spring-cloud.version>
<base.path>${project.basedir}</base.path>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- spring cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</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>
<finalName>service-demo</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>application.yml 配置内容如下:
server: port: 8090 spring: application: name: service-demo ## 服务地址 eureka: client: enabled: true service-url: # 注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔 defaultZone: http://localhost:8761/eureka instance: hostname: localhost # 心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端, # 不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活 lease-renewal-interval-in-seconds: 5 # 心跳超时时间10s,默认90s。从client端最后一次发出心跳后, # 达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除 lease-expiration-duration-in-seconds: 10
package com.hxstrive.service_demo.dto;
import lombok.Data;
/**
* 通用返回对象
* @author hxstrive.com
*/
@Data
public class CommonReturn<T> {
private int code;
private String message;
private T data;
private String appName;
private String port;
public static <T> CommonReturn<T> success(T data) {
CommonReturn<T> commonReturn = new CommonReturn<>();
commonReturn.setCode(1);
commonReturn.setData(data);
return commonReturn;
}
public static <T> CommonReturn<T> success() {
return success(null);
}
public static <T> CommonReturn<T> fail(String message) {
CommonReturn<T> commonReturn = new CommonReturn<>();
commonReturn.setCode(0);
commonReturn.setMessage(message);
return commonReturn;
}
public CommonReturn<T> ext(String appName, String port) {
this.setAppName(appName);
this.setPort(port);
return this;
}
}package com.hxstrive.service_demo.entity;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
/**
* 用户实体
* @author hxstrive.com
*/
@Data
@Builder
@ToString
public class User {
private Long id;
private String name;
private Integer age;
}package com.hxstrive.service_demo.exception;
/**
* 业务异常
* @author hxstrive.com
*/
public class BusinessException extends RuntimeException {
public BusinessException() {
}
public BusinessException(String message) {
super(message);
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public BusinessException(Throwable cause) {
super(cause);
}
}package com.hxstrive.service_demo.handler;
import com.hxstrive.service_demo.dto.CommonReturn;
import com.hxstrive.service_demo.exception.BusinessException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 全局异常处理
* @author hxstrive.com
* @since 1.0.0 2024/10/23 9:28
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseBody
public CommonReturn<?> handleBusinessException(BusinessException e) {
return CommonReturn.fail(e.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseBody
public CommonReturn<?> handleException(Exception e) {
return CommonReturn.fail("服务异常,稍后再试");
}
}package com.hxstrive.service_demo.controller;
import com.hxstrive.service_demo.entity.User;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.UUID;
/**
* 简单控制器
* @author hxstrive.com
*/
@RestController
@RequestMapping("/simple")
public class SimpleController {
@Value("${spring.application.name}")
private String appName;
@Value("${server.port}")
private String appPort;
@GetMapping("/hello")
public String hello(HttpServletRequest request) {
return "Hello World";
}
@GetMapping("/hello2")
public String hello2(@RequestParam("args") String[] args) {
return "Hello World, args=" + String.join("、", args);
}
@GetMapping("/info")
public String info() {
return "appName=" + appName + " appPort=" + appPort + " uuid=" + UUID.randomUUID().toString();
}
@GetMapping("/get")
public String get(@RequestParam("id") Long id) {
return "appName=" + appName + " appPort=" + appPort + " id=" + id;
}
@GetMapping("/param1")
public String param1(@RequestParam("token") String token) {
return "token=" + token;
}
@GetMapping("/param2")
public String param2(@RequestHeader("Authorization") String token) {
return "Authorization=" + token;
}
@PostMapping("/param3")
public String param3(@RequestBody String body) {
return "body=" + body;
}
@GetMapping("/header1")
public String header1(@RequestHeader("Custom-Header") String customHeader,
@RequestHeader("Authorization") String token,
@RequestHeader("Content-Type") String contentType) {
return "Custom-Header=" + customHeader +
"<br/>Authorization=" + token +
"<br/>Content-Type=" + contentType;
}
@PostMapping("/body1")
public Map<String,String> body1(@RequestBody Map<String,String> body) {
body.put("service_name", "ServiceDemo");
return body;
}
@PostMapping("/body2")
public User body2(@RequestBody User user) {
return user;
}
@GetMapping("/query")
public User query(@RequestParam("id") Long id, @RequestParam("name") String name, @RequestParam("age") Integer age) {
return User.builder().id(id).name(name).age(age).build();
}
@GetMapping("/interceptor")
public String interceptor(@RequestHeader("X-Forwarded-For") String xForwardedFor, @RequestParam("msg") String msg) {
return "xForwardedFor=" + xForwardedFor + "<br/>msg=" + msg;
}
@PostMapping("/encode")
public User encode(@RequestBody User user) {
System.out.println(user);
return user;
}
}package com.hxstrive.service_demo.controller;
import com.hxstrive.service_demo.dto.CommonReturn;
import com.hxstrive.service_demo.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* 用户控制器
* @author hxstrive.com
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
private final static List<User> USERS = new ArrayList<>();
static {
USERS.add(User.builder().id(1L).name("Tom").age(20).build());
USERS.add(User.builder().id(2L).name("Helen").age(30).build());
USERS.add(User.builder().id(3L).name("Bill").age(40).build());
}
@Value("${spring.application.name}")
private String appName;
@Value("${server.port}")
private String appPort;
// GET 请求,获取所有用户信息
@GetMapping("/getAllUsers")
public CommonReturn<List<User>> getAllUsers() {
log.info("getAllUsers()");
return CommonReturn.success(USERS).ext(appName, appPort);
}
// GET 请求,根据用户 ID 获取用户信息
@GetMapping("/getUserById")
public CommonReturn<User> getUserById(@RequestParam("id") Long id) {
log.info("getUserById() id={}", id);
return USERS.stream()
.filter(user -> user.getId().equals(id))
.findFirst()
.map(u -> CommonReturn.success(u).ext(appName, appPort))
.orElse(CommonReturn.fail("用户不存在"));
}
// POST 请求,创建新用户
@PostMapping("/createUser")
public CommonReturn<User> createUser(@RequestBody User user) {
log.info("createUser() user={}", user);
USERS.add(user);
return CommonReturn.success(user).ext(appName, appPort);
}
// PUT 请求,更新用户信息
@PutMapping("/updateUser")
public CommonReturn<User> updateUser(@RequestParam("id") Long id, @RequestBody User updatedUser) {
log.info("updateUser() id={}, updateUser={}", id, updatedUser);
for (int i = 0; i < USERS.size(); i++) {
if (USERS.get(i).getId().equals(id)) {
USERS.set(i, updatedUser);
return CommonReturn.success(updatedUser).ext(appName, appPort);
}
}
return CommonReturn.fail("更新用户信息失败");
}
// DELETE 请求,删除用户
@DeleteMapping("/deleteUser")
public CommonReturn<String> deleteUser(@RequestParam("id") Long id) {
log.info("deleteUser() id={}", id);
USERS.removeIf(user -> user.getId().equals(id));
return CommonReturn.success("删除成功").ext(appName, appPort);
}
}package com.hxstrive.service_demo.controller;
import com.hxstrive.service_demo.dto.CommonReturn;
import com.hxstrive.service_demo.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* Restful 风格的用户控制器
* @author hxstrive.com
*/
@Slf4j
@RestController
@RequestMapping("/user/restful")
public class UserRestfulController {
private final static List<User> USERS = new ArrayList<>();
static {
USERS.add(User.builder().id(1L).name("Tom").age(20).build());
USERS.add(User.builder().id(2L).name("Helen").age(30).build());
USERS.add(User.builder().id(3L).name("Bill").age(40).build());
}
@Value("${spring.application.name}")
private String appName;
@Value("${server.port}")
private String appPort;
// GET 请求,获取所有用户信息
@GetMapping("/getAllUsers")
public CommonReturn<List<User>> getAllUsers() {
log.info("getAllUsers()");
return CommonReturn.success(USERS).ext(appName, appPort);
}
// GET 请求,根据用户 ID 获取用户信息
@GetMapping("/{id}")
public CommonReturn<User> getUserById(@PathVariable Long id) {
log.info("getUserById() id={}", id);
return USERS.stream()
.filter(user -> user.getId().equals(id))
.findFirst()
.map((u) -> CommonReturn.success(u).ext(appName, appPort))
.orElse(CommonReturn.fail("用户不存在"));
}
// POST 请求,创建新用户
@PostMapping("/createUser")
public CommonReturn<User> createUser(@RequestBody User user) {
log.info("createUser() user={}", user);
USERS.add(user);
return CommonReturn.success(user).ext(appName, appPort);
}
// PUT 请求,更新用户信息
@PutMapping("/{id}")
public CommonReturn<User> updateUser(@PathVariable Long id, @RequestBody User updatedUser) {
log.info("updateUser() id={}, updateUser={}", id, updatedUser);
for (int i = 0; i < USERS.size(); i++) {
if (USERS.get(i).getId().equals(id)) {
USERS.set(i, updatedUser);
return CommonReturn.success(updatedUser).ext(appName, appPort);
}
}
return CommonReturn.fail("更新用户信息失败");
}
// DELETE 请求,删除用户
@DeleteMapping("/{id}")
public CommonReturn<String> deleteUser(@PathVariable Long id) {
log.info("deleteUser() id={}", id);
USERS.removeIf(user -> user.getId().equals(id));
return CommonReturn.success("删除成功");
}
}package com.hxstrive.service_demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 入口
* @author Administrator
*/
@RestController
@SpringBootApplication
public class OpenfeignServiceDemoApplication {
@Value("${spring.application.name}")
private String appName;
@Value("${server.port}")
private String appPort;
public static void main(String[] args) {
SpringApplication.run(OpenfeignServiceDemoApplication.class, args);
}
@GetMapping("/")
public String index() {
return "appName=" + appName + ", appPort=" + appPort;
}
@GetMapping("/hello")
public String hello() {
return "Hello World! appName=" + appName + ", appPort=" + appPort + "!";
}
}先启动前面创建的 Eureka 服务端,然后启动 service-demo 服务,成功启动后,状态如下图:

访问 http://localhost:8761 地址,查看 Eureka 管理页面,如下图:

如果能够正常看见该页面,且存在 SERVICE-DEMO 服务,则环境准备成功了。
此时,可以进行后续 Spring Cloud OpenFeign 的学习了……