1. 整体目录结构
microservice-demo(父工程)
├── pom.xml(父POM,管理版本)
├── common(公共模块)
│ ├── pom.xml
│ └── src/main/java/com/demo/common/
│ ├── dto/User.java
│ └── api/UserApi.java
├── service-b(服务提供方,端口8081)
│ ├── pom.xml
│ ├── src/main/java/com/demo/serviceb/
│ │ ├── ServiceBApplication.java
│ │ └── controller/UserController.java
│ └── src/main/resources/application.yml
└── service-a(服务调用方,端口8080)
├── pom.xml
├── src/main/java/com/demo/servicea/
│ ├── ServiceAApplication.java
│ └── controller/OrderController.java
└── src/main/resources/application.yml
2. 父工程 pom.xml(根目录)
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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>microservice-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<!-- 引入 Spring Boot 父依赖,统一版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<!-- 管理 Spring Cloud 版本 -->
<properties>
<java.version>8</java.version>
<spring-cloud.version>2021.0.8</spring-cloud.version>
</properties>
<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>
<modules>
<module>common</module>
<module>service-b</module>
<module>service-a</module>
</modules>
</project>
3. common 模块(定义 API 契约)
common/pom.xml
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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>microservice-demo</artifactId>
<groupId>com.demo</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<dependencies>
<!-- 必备:包含 @FeignClient 注解 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 必备:包含 @GetMapping, @PathVariable 等注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 为了少写 getter/setter,引入 Lombok(可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
common/src/main/java/com/demo/common/dto/User.java
java
package com.demo.common.dto;
import lombok.Data;
@Data // 自动生成 getter/setter/toString
public class User {
private Long id;
private String name;
private String email;
}
common/src/main/java/com/demo/common/api/UserApi.java
java
package com.demo.common.api;
import com.demo.common.dto.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// name = "service-b" 对应 B 服务的 spring.application.name
// path = "/api" 统一前缀,方便 B 服务统一管理
// 这里暂时写死 url,方便不接注册中心直接运行(接入注册中心时删掉 url 即可)
@FeignClient(name = "service-b", url = "http://localhost:8081", path = "/api")
public interface UserApi {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}
4. service-b 模块(服务提供方,实现接口)
service-b/pom.xml
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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>microservice-demo</artifactId>
<groupId>com.demo</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-b</artifactId>
<dependencies>
<!-- 依赖 common 模块 -->
<dependency>
<groupId>com.demo</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</dependency>
<!-- web 容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
service-b/src/main/resources/application.yml
yaml
server:
port: 8081
spring:
application:
name: service-b # 必须和 @FeignClient(name = "service-b") 保持一致
service-b/src/main/java/com/demo/serviceb/ServiceBApplication.java
java
package com.demo.serviceb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
// 重点注意:B 服务是提供方,这里绝对不能加 @EnableFeignClients
public class ServiceBApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceBApplication.class, args);
}
}
service-b/src/main/java/com/demo/serviceb/controller/UserController.java
java
package com.demo.serviceb.controller;
import com.demo.common.api.UserApi;
import com.demo.common.dto.User;
import org.springframework.web.bind.annotation.RestController;
@RestController
// 关键点:实现 common 中定义的接口,自动继承 @GetMapping 路径
public class UserController implements UserApi {
@Override
public User getUserById(Long id) {
// 模拟数据库查询,返回假数据
User user = new User();
user.setId(id);
user.setName("张三(来自B服务)");
user.setEmail("zhangsan@service-b.com");
return user;
}
}
5. service-a 模块(服务调用方,使用 Feign)
service-a/pom.xml
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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>microservice-demo</artifactId>
<groupId>com.demo</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-a</artifactId>
<dependencies>
<!-- 依赖 common 模块 -->
<dependency>
<groupId>com.demo</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
service-a/src/main/resources/application.yml
yaml
server:
port: 8080
spring:
application:
name: service-a
service-a/src/main/java/com/demo/servicea/ServiceAApplication.java
java
package com.demo.servicea;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
// 必须加此注解,并指定扫描 common 模块中 Feign 接口所在的包
@EnableFeignClients(basePackages = "com.demo.common.api")
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
}
service-a/src/main/java/com/demo/servicea/controller/OrderController.java
java
package com.demo.servicea.controller;
import com.demo.common.api.UserApi;
import com.demo.common.dto.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private UserApi userApi; // 直接注入 common 里的接口
@GetMapping("/order/{id}/user")
public User getOrderUser(@PathVariable("id") Long id) {
// 看上去像本地调用,实际上发起了 HTTP 请求给 service-b
return userApi.getUserById(id);
}
}
6. 运行测试
- 先启动 service-b (右键运行
ServiceBApplication.main),控制台看到Started ServiceBApplication on port 8081。 - 再启动 service-a (右键运行
ServiceAApplication.main),控制台看到Started ServiceAApplication on port 8080。 - 打开浏览器或 Postman,访问:
http://localhost:8080/order/123/user
返回结果(JSON):
json
{
"id": 123,
"name": "张三(来自B服务)",
"email": "zhangsan@service-b.com"
}
7. 特别提示(再强调一遍)
| 检查项 | 说明 |
|---|---|
| B 服务启动类 | 绝对不要 加 @EnableFeignClients,否则它会尝试把自己注册成 Feign 客户端而报错。 |
| A 服务启动类 | 必须 加 @EnableFeignClients(basePackages = "com.demo.common.api"),否则 UserApi 无法被注入(报 NoSuchBeanDefinitionException)。 |
| common 依赖 | 如果 common 模块打包报错,记得先 mvn clean install 安装到本地仓库,再启动 A 和 B。 |
| 路径一致性 | B 服务的 UserController 由于实现了接口,不要再写 @RequestMapping 在类上,否则路径会变成双份(如 /api/api/users)。 |