本文目录导读:

我将为您提供一个完整的Java服务注册与发现案例,使用Spring Cloud Netflix Eureka作为注册中心。
项目结构
service-registry-demo
├── pom.xml (父POM)
├── eureka-server (注册中心)
├── user-service (用户服务 - 服务提供者)
└── order-service (订单服务 - 服务消费者)
父POM配置
<?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.example</groupId>
<artifactId>service-registry-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>eureka-server</module>
<module>user-service</module>
<module>order-service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<properties>
<java.version>11</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>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
Eureka Server(注册中心)
eureka-server/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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>service-registry-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
EurekaServerApplication.java
package com.example.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
application.yml
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
# 注册中心本身不注册自己
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
# 关闭自我保护模式(开发环境)
enable-self-preservation: false
eviction-interval-timer-in-ms: 5000
User Service(服务提供者)
user-service/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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>service-registry-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>user-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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-actuator</artifactId>
</dependency>
</dependencies>
</project>
UserServiceApplication.java
package com.example.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
UserController.java
package com.example.user.controller;
import com.example.user.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@RestController
@RequestMapping("/api/users")
public class UserController {
@Value("${server.port}")
private String serverPort;
@Resource
private DiscoveryClient discoveryClient;
// 模拟数据库
private static final Map<Long, User> USER_DB = new ConcurrentHashMap<>();
static {
USER_DB.put(1L, new User(1L, "张三", "zhangsan@example.com"));
USER_DB.put(2L, new User(2L, "李四", "lisi@example.com"));
USER_DB.put(3L, new User(3L, "王五", "wangwu@example.com"));
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
log.info("从端口 {} 获取用户信息, id: {}", serverPort, id);
User user = USER_DB.get(id);
if (user != null) {
user.setServicePort(serverPort);
}
return user;
}
@GetMapping("/list")
public List<User> getAllUsers() {
log.info("从端口 {} 获取所有用户", serverPort);
return USER_DB.values().stream()
.peek(user -> user.setServicePort(serverPort))
.toList();
}
@PostMapping
public User createUser(@RequestBody User user) {
log.info("从端口 {} 创建用户: {}", serverPort, user);
user.setId((long) (USER_DB.size() + 1));
user.setServicePort(serverPort);
USER_DB.put(user.getId(), user);
return user;
}
/**
* 获取服务发现信息
*/
@GetMapping("/service-info")
public Map<String, Object> getServiceInfo() {
Map<String, Object> info = new HashMap<>();
info.put("serviceName", "USER-SERVICE");
info.put("port", serverPort);
List<ServiceInstance> instances = discoveryClient.getInstances("USER-SERVICE");
info.put("instances", instances.stream()
.map(instance -> Map.of(
"serviceId", instance.getServiceId(),
"host", instance.getHost(),
"port", instance.getPort(),
"uri", instance.getUri().toString()
))
.toList());
return info;
}
}
User.java
package com.example.user.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String email;
private String servicePort;
}
application.yml
server:
port: 8081
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
register-with-eureka: true
fetch-registry: true
instance:
instance-id: user-service-${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true
lease-renewal-interval-in-seconds: 5 # 心跳时间
lease-expiration-duration-in-seconds: 10 # 过期时间
management:
endpoints:
web:
exposure:
include: '*'
Order Service(服务消费者)
order-service/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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>service-registry-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>order-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
</project>
OrderServiceApplication.java
package com.example.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
/**
* RestTemplate 负载均衡配置
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* WebClient 负载均衡配置
*/
@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
OrderController.java
package com.example.order.controller;
import com.example.order.model.Order;
import com.example.order.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private static final Map<Long, Order> ORDER_DB = new ConcurrentHashMap<>();
static {
ORDER_DB.put(1L, new Order(1L, 101L, 200.50, "已支付"));
ORDER_DB.put(2L, new Order(2L, 102L, 350.00, "待发货"));
ORDER_DB.put(3L, new Order(3L, 103L, 150.75, "已完成"));
}
@Resource
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@Autowired
@Qualifier("webClientBuilder")
private WebClient.Builder webClientBuilder;
/**
* 获取订单详情(包含用户信息)
* 使用 RestTemplate 进行服务调用
*/
@GetMapping("/{id}/with-user")
public Map<String, Object> getOrderWithUser(@PathVariable Long id) {
Order order = ORDER_DB.get(id);
if (order == null) {
throw new RuntimeException("订单不存在: " + id);
}
log.info("获取订单: {},调用用户服务获取用户信息", order);
// 通过服务名调用用户服务(负载均衡)
String userServiceUrl = "http://USER-SERVICE/api/users/" + order.getUserId();
User user = restTemplate.getForObject(userServiceUrl, User.class);
Map<String, Object> result = new HashMap<>();
result.put("order", order);
result.put("user", user);
return result;
}
/**
* 使用 WebClient 进行响应式调用
*/
@GetMapping("/{id}/with-user-reactive")
public Map<String, Object> getOrderWithUserReactive(@PathVariable Long id) throws Exception {
Order order = ORDER_DB.get(id);
if (order == null) {
throw new RuntimeException("订单不存在: " + id);
}
// 使用 WebClient 异步调用
User user = webClientBuilder.build()
.get()
.uri("http://USER-SERVICE/api/users/" + order.getUserId())
.retrieve()
.bodyToMono(User.class)
.block();
Map<String, Object> result = new HashMap<>();
result.put("order", order);
result.put("user", user);
return result;
}
/**
* 发现并获取服务的所有实例
*/
@GetMapping("/discover-service")
public Map<String, Object> discoverService() {
Map<String, Object> result = new HashMap<>();
// 获取所有服务
List<String> services = discoveryClient.getServices();
result.put("services", services);
// 获取 USER-SERVICE 的所有实例
List<ServiceInstance> instances = discoveryClient.getInstances("USER-SERVICE");
result.put("userServiceInstances", instances.stream()
.map(instance -> Map.of(
"instanceId", instance.getInstanceId(),
"host", instance.getHost(),
"port", instance.getPort(),
"uri", instance.getUri().toString()
))
.toList());
return result;
}
/**
* 负载均衡测试 - 多次调用用户服务
*/
@GetMapping("/load-balance-test")
public List<Map<String, Object>> loadBalanceTest(@RequestParam(defaultValue = "5") int count) {
List<Map<String, Object>> results = new ArrayList<>();
for (int i = 0; i < count; i++) {
User user = restTemplate.getForObject("http://USER-SERVICE/api/users/" + (i % 3 + 1), User.class);
Map<String, Object> item = new HashMap<>();
item.put("requestId", i + 1);
item.put("user", user);
item.put("calledPort", user.getServicePort());
results.add(item);
}
return results;
}
}
Order.java
package com.example.order.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private Long id;
private Long userId;
private Double amount;
private String status;
}
User.java (在order-service中定义)
package com.example.order.model;
import lombok.Data;
@Data
public class User {
private Long id;
private String name;
private String email;
private String servicePort;
}
application.yml
server:
port: 8082
spring:
application:
name: order-service
cloud:
loadbalancer:
ribbon:
enabled: false
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
register-with-eureka: true
fetch-registry: true
instance:
instance-id: order-service-${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 10
测试步骤
1 启动服务
按照以下顺序启动服务:
# 1. 启动Eureka Server cd eureka-server mvn spring-boot:run # 2. 启动User Service cd user-service mvn spring-boot:run # 3. 启动Order Service cd order-service mvn spring-boot:run
2 验证服务注册
访问Eureka管理界面:http://localhost:8761
你应该能看到:
- USER-SERVICE 注册了一个实例
- ORDER-SERVICE 注册了一个实例
3 测试接口
# 1. 获取用户信息 curl http://localhost:8081/api/users/1 # 2. 获取订单及用户信息(使用RestTemplate) curl http://localhost:8082/api/orders/1/with-user # 3. 获取订单及用户信息(使用WebClient) curl http://localhost:8082/api/orders/1/with-user-reactive # 4. 查看服务发现信息 curl http://localhost:8082/api/orders/discover-service # 5. 负载均衡测试 curl "http://localhost:8082/api/orders/load-balance-test?count=5"
多实例测试
要测试负载均衡,可以启动多个User Service实例:
# 启动第一个实例(默认8081端口) mvn spring-boot:run # 启动第二个实例(指定8083端口) mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8083
核心概念说明
- 服务注册:服务启动时向Eureka Server注册自身信息
- 服务发现:服务消费者通过服务名从Eureka Server获取服务实例列表
- 心跳机制:服务每5秒向Eureka Server发送心跳保持连接
- 负载均衡:Spring Cloud LoadBalancer实现客户端负载均衡
- 服务下线:服务优雅退出时向Eureka Server发送注销请求
这个案例完整展示了Spring Cloud服务注册与发现的核心功能,可以作为微服务架构的基础模板使用。