从单体到微服务:Java开发者的第一步实战指南
大家好,我是团队的培训负责人,过去五年带过上百名应届生。每次新人入职,总有人问我:“微服务到底难不难?我连单体应用都没写明白。”其实,我当初学的时候也一样——被“服务注册”“熔断降级”这些词吓到过。但今天我想告诉你:只要动手做一遍,微服务没那么玄。
这篇教程专为零基础的你设计。我们将用最简单的 Java 代码,从一个“产品管理”的单体应用出发,亲手把它拆成两个微服务。过程中你会学到核心概念、避坑技巧,甚至能应对常见的面试题挑战。
为什么微服务值得学?
简单说:微服务是一种把大系统拆成多个小服务的架构方式。每个服务独立开发、部署、扩展。比如电商系统可以拆成“用户服务”“订单服务”“产品服务”。
- 优点:故障隔离(一个服务挂了不影响整体)、技术栈灵活(不同服务可用不同语言)、团队协作高效。
- 代价:复杂度上升(要处理网络调用、数据一致性等)。
💡 小提示:不是所有项目都需要微服务!如果你的应用只有 3 个接口,单体更合适。微服务适合业务复杂、团队规模大的场景。
环境准备:5 分钟搭好开发环境
我们需要以下工具(全是免费开源的):
| 工具 | 用途 | 安装建议 |
|---|---|---|
| JDK 17 | Java 运行环境 | 推荐 Adoptium Temurin |
| Maven | 项目依赖管理 | 官网下载后配置 PATH |
| IntelliJ IDEA Community | 开发 IDE | 免费版足够 |
| Spring Boot 3.x | 快速构建微服务 | 通过 start.spring.io 生成 |
创建第一个项目(产品服务)
- 访问 start.spring.io
- 填写:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.x
- Group:
com.example - Artifact:
product-service
- 添加依赖:Spring Web, Spring Data JPA, H2 Database
- 点击 “Generate”,解压后用 IDEA 打开
⚠️ 注意:别选错 Spring Boot 版本!3.x 要求 JDK 17+。
核心概念:用“产品管理”说清楚微服务
假设我们要做一个产品管理系统。在单体架构中,所有功能(增删改查产品)都在一个应用里。但在微服务中,我们可以只让这个应用负责“产品”相关逻辑。
关键角色解释(超简版)
- 服务提供者(Provider):提供 API 的服务,比如我们的
product-service。 - 服务消费者(Consumer):调用其他服务的 API,比如未来会有的
order-service。 - 服务注册中心:像“电话簿”,记录所有服务的地址(我们后面会用 Eureka 演示)。
- RESTful API:服务间通信的标准方式,用 HTTP + JSON。
🧠 我当初学的时候总混淆“服务”和“模块”。记住:一个微服务 = 一个独立运行的进程(jar 包),不是代码里的一个 package!
实战第一步:打造单体版“产品服务”
先别急着拆!我们先写出完整的单体应用,再改造它。
1. 定义产品实体
// src/main/java/com/example/productservice/Product.java
package com.example.productservice;
import jakarta.persistence.*;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
// 构造函数、getter/setter 省略(IDEA 可自动生成)
}
2. 创建 Repository
// src/main/java/com/example/productservice/ProductRepository.java
package com.example.productservice;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
3. 编写 Controller
// src/main/java/com/example/productservice/ProductController.java
package com.example.productservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductRepository productRepository;
@GetMapping
public List<Product> getAllProducts() {
return productRepository.findAll();
}
@PostMapping
public Product createProduct(@RequestBody Product product) {
return productRepository.save(product);
}
}
4. 配置 H2 内存数据库(方便测试)
在 application.properties 中添加:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
5. 启动并测试
运行 ProductServiceApplication.java,访问 http://localhost:8080/h2-console,用默认设置登录。
再用 curl 或 Postman 测试:
# 添加产品
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{"name":"笔记本电脑","price":5999}'
# 查询所有产品
curl http://localhost:8080/api/products
✅ 恭喜!你已经有一个能跑的单体应用了。
实战第二步:拆出第二个服务(订单服务)
现在,我们模拟“订单服务”需要查询产品信息。这意味着订单服务要远程调用产品服务。
创建订单服务
重复之前的步骤,在 start.spring.io 创建新项目 order-service,依赖同样选 Spring Web, Spring Data JPA, H2 Database。
在订单服务中调用产品服务
关键来了:如何让订单服务知道产品服务在哪?
方案 A:硬编码(不推荐,仅用于演示)
// OrderService 中直接写死地址
String productUrl = "http://localhost:8080/api/products/1";
// 用 RestTemplate 发起 HTTP 请求...
但这样一旦产品服务端口变了,订单服务就挂了。
方案 B:使用服务发现(推荐)
我们引入 Eureka 服务注册中心。
步骤 1:创建 Eureka Server
新建一个 Spring Boot 项目 eureka-server,只加 Eureka Server 依赖。
主类加上注解:
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
配置 application.yml:
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
启动后访问 http://localhost:8761,看到 Eureka 仪表盘就算成功。
步骤 2:让产品服务注册自己
在 product-service 的 pom.xml 加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
并在 application.properties 加:
spring.application.name=product-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
重启 product-service,刷新 Eureka 页面,你会看到 PRODUCT-SERVICE 出现在 Instances 列表中!
步骤 3:订单服务通过服务名调用
在 order-service 同样加入 Eureka Client 依赖,并配置:
spring.application.name=order-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
然后注入 RestTemplate 并启用服务发现:
@Configuration
public class AppConfig {
@Bean
@LoadBalanced // 关键!启用服务发现
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在 OrderController 中调用:
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test-product")
public String getProductFromService() {
// 注意:这里用服务名 product-service,不是 IP+端口!
String url = "http://product-service/api/products";
return restTemplate.getForObject(url, String.class);
}
}
启动 order-service,访问 http://localhost:8081/test-product(假设订单服务端口 8081),如果返回产品列表,说明微服务调用成功!
新手常见问题 & 避坑指南
Q1:为什么我的服务没注册到 Eureka?
- 检查
spring.application.name是否配置 - 确认 Eureka Server 地址是否正确(注意结尾有
/eureka/) - 查看控制台日志是否有
Registered instance字样
Q2:调用时报 “UnknownHostException: product-service”?
- 确保
RestTemplate加了@LoadBalanced注解 - 确认两个服务都已成功注册到 Eureka
Q3:H2 数据库的数据怎么没了?
- 因为用了
ddl-auto=create-drop,每次重启清空数据。生产环境用 MySQL 并设为validate或update。
Q4:微服务之间怎么传用户信息?
- 初学者可先用 Token 放在 Header 里透传。后续学 Spring Cloud Gateway + JWT。
面试题挑战:你能答对几道?
作为培训负责人,我常问新人这些问题:
微服务 vs 单体,什么时候该拆?
→ 答:当单体应用导致团队协作效率低、部署频繁冲突、技术栈无法演进时。服务注册中心的作用是什么?
→ 答:动态管理服务实例地址,实现服务发现与负载均衡。如果 product-service 挂了,order-service 怎么办?
→ 答:引入熔断机制(如 Resilience4j),返回兜底数据或错误提示。
下一步学习建议
你已经走出了最难的第一步!接下来:
- 深入 Spring Cloud Alibaba:用 Nacos 替代 Eureka(国内更流行)
- 学 API 网关:用 Spring Cloud Gateway 统一入口
- 解决分布式事务:了解 Seata 或 Saga 模式
- 监控与链路追踪:集成 Zipkin + Sleuth
🌟 最后送你一句话:微服务不是目标,而是手段。架构演进的核心永远是“解决当前业务痛点”。别为了用微服务而微服务!
动手敲完这篇教程的代码,你已经超过了 80% 只看不练的初学者。加油,未来的架构师!

评论 0