在微服务架构的开发中,服务模块化和独立性的要求使得项目的依赖管理变得愈加复杂。每个服务可能依赖多个第三方库,并且随着时间的推移,库的版本更新往往会导致兼容性问题。对于开发者来说,确保所有服务中的依赖项版本一致、避免版本冲突、提升开发效率是一个重要的挑战。
为了解决这个问题,Spring Boot 3.3 引入了 BOM(Bill of Materials,材料清单)功能。BOM 可以帮助开发者通过一个统一的版本管理策略来解决微服务中的依赖冲突问题,使开发者能够更专注于业务逻辑开发,而不必为复杂的依赖管理操心。
在本文中,我们将深入探讨 BOM 在 Spring Boot 3.3 中的作用,并结合一个典型的微服务示例,展示如何通过 BOM 实现依赖管理的简化。我们将演示如何构建一个简单的库存管理系统(Inventory Management System),实现商品的增删查改(CRUD)操作,前端通过 jQuery 发起异步请求(AJAX)来与后端交互。通过此项目展示 Spring Boot BOM 如何在微服务架构下简化开发流程。
什么是材料清单(BOM)?
BOM(Bill of Materials)是一种用于声明和管理依赖库版本的机制。通过 BOM,项目的所有依赖库版本可以统一由 BOM 文件来定义和管理。BOM 的核心作用是提供一组经过测试和兼容的依赖库版本,避免开发者在项目中手动指定每个库的版本。
BOM 的特点如下:
- 统一版本控制:BOM 可以统一管理项目中的所有依赖项版本,从而避免不同库版本之间的冲突。
- 简化依赖声明:引入 BOM 后,开发者可以省略每个依赖项的版本声明,BOM 会自动提供版本信息。
- 保障项目稳定性:使用 BOM 可以确保依赖项之间的版本兼容性,减少不兼容问题导致的系统故障。
在 Spring Boot 中,BOM 是通过 dependencyManagement 元素来引入的,提供了对 Spring 生态系统和其他常用库的统一版本管理。
dependencyManagement 是 Maven 项目中用于管理依赖版本的一个关键元素。它主要用于定义一组共享的依赖版本,而不必在子模块中重复定义这些依赖的版本号。这对于大型项目,尤其是微服务架构中多个模块共享相同依赖时非常有用。
使用 dependencyManagement 可以集中管理依赖版本,确保在多模块项目中所有子模块依赖同一个版本的库,避免由于版本冲突导致的问题。
dependencyManagement 的主要功能
- 统一版本控制:通过在父 pom.xml 中定义依赖项及其版本号,所有子模块可以引用这些依赖项,而无需单独指定版本号。
- 灵活性:dependencyManagement 并不会自动将依赖引入子模块。它只提供版本信息,具体的依赖还需要在子模块中显式地声明。
- 避免重复:在多模块项目中,不需要在每个子模块的 pom.xml 文件中重复定义依赖项及其版本号,只需要在父模块的 pom.xml 文件中进行统一管理。
为什么要在微服务中使用 BOM?
在微服务架构中,多个服务通常使用相似的依赖库,但由于服务独立开发和部署,不同服务中可能会使用不同版本的库。这种版本差异可能会导致服务之间的兼容性问题,甚至引发系统故障。
使用 BOM 在微服务架构中有以下好处:
- 避免版本冲突:通过 BOM,所有微服务的依赖项可以使用相同版本的库,从而避免版本冲突导致的兼容性问题。
- 简化维护:在多个微服务项目中,如果依赖项版本发生变化,开发者只需在 BOM 中进行一次修改,所有服务都会自动继承新的依赖版本。
- 提升开发效率:BOM 提供了预先配置好的、经过测试的依赖版本组合,开发者可以专注于功能开发,而不必为版本管理分心。
运行效果:
图片
若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。
项目示例:库存管理系统
为了更好地演示 BOM 的作用,我们将开发一个典型的微服务示例——库存管理系统。该系统允许用户通过前端界面添加、删除、修改和查询商品的库存信息。系统包括以下主要功能:
- 前端:使用 Thymeleaf 模板引擎结合 jQuery 和 Bootstrap 实现界面交互。
- 后端:Spring Boot 提供 RESTful API 进行 CRUD 操作。
- 数据库:使用 H2 内存数据库进行数据存储。
项目 pom.xml 配置
在项目的 pom.xml 文件中,我们首先引入 Spring Boot 3.3 的 BOM 以管理项目依赖:
<?xml versinotallow="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.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>inventory-management</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>inventory-management</name>
<description>Demo project for Spring Boot</description>
<!-- 引入 Spring Boot 3.3 的 BOM -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.3.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- H2 数据库 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件 application.yml
我们使用 application.yml 来配置应用的端口和其他基础设置。
server:
port: 8080
app:
name: 库存管理系统
description: 使用 Spring Boot 3.3 BOM 简化开发
读取配置类
通过 @ConfigurationProperties 注解,我们可以将配置文件中的值映射到 Java 类中。
package com.icoderoad.inventory.management.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String description;
}
商品实体类
使用 Lombok 简化实体类的编写,定义商品的基本属性:
package com.icoderoad.inventory.management.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
private Long id;
private String name;
private int quantity;
private double price;
}
控制器和 API 实现
控制器提供增删查改的 RESTful API,并返回商品数据。示例代码如下:
package com.icoderoad.inventory.management.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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 com.icoderoad.inventory.management.entity.Product;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private List<Product> products = new ArrayList<>();
private AtomicLong counter = new AtomicLong();
@GetMapping
public List<Product> getAllProducts() {
return products;
}
@GetMapping("/{id}")
public Product getProductById(@PathVariable Long id) {
return products.stream().filter(p -> p.getId().equals(id)).findFirst().orElse(null);
}
@PostMapping
public Product addProduct(@RequestBody Product product) {
product.setId(counter.incrementAndGet());
products.add(product);
return product;
}
@PutMapping("/{id}")
public Product updateProduct(@PathVariable Long id, @RequestBody Product updatedProduct) {
Product product = products.stream().filter(p -> p.getId().equals(id)).findFirst().orElse(null);
if (product != null) {
product.setName(updatedProduct.getName());
product.setQuantity(updatedProduct.getQuantity());
product.setPrice(updatedProduct.getPrice());
}
return product;
}
@DeleteMapping("/{id}")
public void deleteProduct(@PathVariable Long id) {
products.removeIf(p -> p.getId().equals(id));
}
}
前端 Thymeleaf 模板及 jQuery 调用
前端页面通过 jQuery 发起 AJAX 请求,展示商品列表并支持增删改操作。
在 src/main/resources/templates 目录下创建 index.html 文件:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>库存管理系统</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
</head>
<body>
<div class="container mt-4">
<h1>库存管理系统</h1>
<!-- 添加商品按钮 -->
<button class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#productModal" id="addProductBtn">
添加商品
</button>
<!-- 商品表格 -->
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>商品名称</th>
<th>数量</th>
<th>价格</th>
<th>操作</th>
</tr>
</thead>
<tbody id="productTable"></tbody>
</table>
<!-- 商品模态框 -->
<div class="modal fade" id="productModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalTitle">添加商品</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="productForm">
<input type="hidden" id="productId">
<div class="mb-3">
<label for="productName" class="form-label">商品名称</label>
<input type="text" class="form-control" id="productName" required>
</div>
<div class="mb-3">
<label for="productQuantity" class="form-label">数量</label>
<input type="number" class="form-control" id="productQuantity" required>
</div>
<div class="mb-3">
<label for="productPrice" class="form-label">价格</label>
<input type="number" step="0.01" class="form-control" id="productPrice" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" id="saveProductBtn">保存</button>
</div>
</div>
</div>
</div>
</div>
<script>
// 加载商品列表
function loadProducts() {
$.getJSON('/api/products', function (data) {
let rows = '';
data.forEach(function (product) {
rows += `<tr>
<td>${product.id}</td>
<td>${product.name}</td>
<td>${product.quantity}</td>
<td>${product.price}</td>
<td>
<button class="btn btn-warning btn-sm edit-btn" data-id="${product.id}">编辑</button>
<button class="btn btn-danger btn-sm delete-btn" data-id="${product.id}">删除</button>
</td>
</tr>`;
});
$('#productTable').html(rows);
});
}
// 添加商品按钮
$('#addProductBtn').click(function () {
$('#modalTitle').text('添加商品');
$('#productForm')[0].reset();
$('#productId').val('');
});
// 保存商品(添加或编辑)
$('#saveProductBtn').click(function () {
const id = $('#productId').val();
const productData = {
name: $('#productName').val(),
quantity: $('#productQuantity').val(),
price: $('#productPrice').val()
};
if (id) {
// 编辑商品
$.ajax({
url: '/api/products/' + id,
type: 'PUT',
contentType: 'application/json',
data: JSON.stringify(productData),
success: function () {
$('#productModal').modal('hide');
loadProducts();
}
});
} else {
// 添加商品
$.ajax({
url: '/api/products',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(productData),
success: function () {
$('#productModal').modal('hide');
loadProducts();
}
});
}
});
// 编辑按钮点击事件
$(document).on('click', '.edit-btn', function () {
const id = $(this).data('id');
$.getJSON('/api/products/' + id, function (product) {
$('#modalTitle').text('编辑商品');
$('#productId').val(product.id);
$('#productName').val(product.name);
$('#productQuantity').val(product.quantity);
$('#productPrice').val(product.price);
$('#productModal').modal('show');
});
});
// 删除商品
$(document).on('click', '.delete-btn', function () {
const id = $(this).data('id');
$.ajax({
url: '/api/products/' + id,
type: 'DELETE',
success: function () {
loadProducts();
}
});
});
// 页面加载时加载商品列表
$(document).ready(function () {
loadProducts();
});
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
总结
通过 Spring Boot 3.3 的 BOM,我们成功简化了库存管理系统的依赖管理流程。在微服务开发中,BOM 的引入能够统一管理依赖项的版本,避免版本冲突,提升项目的可维护性。在前后端的代码示例中,我们展示了如何通过 RESTful API 实现增删查改操作,并结合 jQuery 和 Thymeleaf 实现简单而直观的用户界面。