Spring Boot 开发专家
通用的、可迁移的 Spring Boot 开发 skill。不绑定特定机器、特定项目——任何 Spring Boot 工程师在任何电脑上都能直接用。
可迁移性设计
本 skill 遵循零硬编码原则:
| 原则 | 做法 |
|---|---|
| 路径 | 只用项目相对路径 src/main/java/...,绝不用绝对路径 |
| 端口 | 默认8080,可配 server.port,不做假设 |
| 数据库 | 用 spring.datasource.url 自动检测,不硬编码连接字符串 |
| 环境检测 | 用 java -version、mvn -v 检测工具链 |
| 包名 | 用占位符 ${basePackage} 表示,根据项目实际替换 |
项目初始化
方式一:Spring Initializr(推荐)
# 直接 curl 下载模板项目(任何机器都能跑)
curl -s https://start.spring.io/starter.zip \
-d type=maven-project \
-d language=java \
-d bootVersion=3.4.5 \
-d baseDir=my-project \
-d groupId=com.example \
-d artifactId=my-project \
-d name=MyProject \
-d description="Spring Boot project" \
-d packageName=com.example.demo \
-d packaging=jar \
-d javaVersion=17 \
-d dependencies=web,data-jpa,mysql,validation,lombok,devtools \
-o my-project.zip
方式二:Maven 手动创建
<!-- pom.xml 最小骨架 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
检测现有项目
# 判断是否 Spring Boot 项目
grep -rl "spring-boot-starter" pom.xml build.gradle 2>/dev/null
# 获取 Spring Boot 版本
mvn dependency:tree -Dincludes=org.springframework.boot 2>/dev/null | grep "spring-boot-starter"
标准分层架构
src/main/java/${basePackage}/
├── controller/ ← 接口层:@RestController, @RequestMapping
├── service/ ← 业务层:@Service, @Transactional
│ └── impl/ ← 实现类(接口-实现分离时)
├── repository/ ← 数据层:JpaRepository / MyBatis Mapper
├── entity/ ← 实体:@Entity, @Table
├── dto/ ← 数据传输对象(API 入参/出参)
├── vo/ ← 视图对象(返回给前端的)
├── config/ ← 配置类:@Configuration, @Bean
├── interceptor/ ← 拦截器:HandlerInterceptor
├── aspect/ ← 切面:@Aspect, @Around
├── exception/ ← 全局异常处理:@ControllerAdvice
├── util/ ← 工具类
└── constant/ ← 常量/枚举
包名约定: ${basePackage} = groupId + artifactId 的组合,如 com.example.demo
Controller 层规范
RESTful API 模板
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {
private final UserService userService;
@GetMapping
public Result<PageResult<UserVO>> list(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String keyword) {
return Result.ok(userService.listUsers(page, size, keyword));
}
@GetMapping("/{id}")
public Result<UserVO> getById(@PathVariable Long id) {
return Result.ok(userService.getById(id));
}
@PostMapping
public Result<Long> create(@Valid @RequestBody UserCreateDTO dto) {
return Result.ok(userService.create(dto));
}
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody UserUpdateDTO dto) {
userService.update(id, dto);
return Result.ok();
}
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable Long id) {
userService.delete(id);
return Result.ok();
}
}
统一返回格式
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private int code;
private String message;
private T data;
public static <T> Result<T> ok() { return new Result<>(200, "success", null); }
public static <T> Result<T> ok(T data) { return new Result<>(200, "success", data); }
public static <T> Result<T> fail(int code, String msg) { return new Result<>(code, msg, null); }
}
@Data
public class PageResult<T> {
private long total;
private int page;
private int size;
private List<T> records;
}
全局异常处理
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.fail(400, msg);
}
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusiness(BusinessException e) {
return Result.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<Void> handleUnknown(Exception e) {
log.error("Unhandled exception", e);
return Result.fail(500, "Internal server error");
}
}
Service 层规范
public interface UserService {
PageResult<UserVO> listUsers(int page, int size, String keyword);
UserVO getById(Long id);
Long create(UserCreateDTO dto);
void update(Long id, UserUpdateDTO dto);
void delete(Long id);
}
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Override
@Transactional(readOnly = true)
public PageResult<UserVO> listUsers(int page, int size, String keyword) {
Page<User> userPage = userRepository.findByKeyword(keyword, PageRequest.of(page - 1, size));
return new PageResult<>(
userPage.getTotalElements(), page, size,
userPage.getContent().stream().map(UserVO::from).toList()
);
}
@Override
@Transactional
public Long create(UserCreateDTO dto) {
if (userRepository.existsByEmail(dto.getEmail())) {
throw new BusinessException(400, "邮箱已被注册");
}
User user = User.from(dto);
return userRepository.save(user).getId();
}
// ... 其余方法
}
Repository 层规范
JPA
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByEmail(String email);
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword% OR u.email LIKE %:keyword%")
Page<User> findByKeyword(@Param("keyword") String keyword, Pageable pageable);
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id IN :ids")
int batchUpdateStatus(@Param("status") int status, @Param("ids") List<Long> ids);
}
MyBatis-Plus
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM user WHERE email = #{email}")
User selectByEmail(@Param("email") String email);
List<UserVO> selectPageWithOrders(@Param("keyword") String keyword, Page<User> page);
}
实体规范
@Entity
@Table(name = "user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String name;
@Column(nullable = false, unique = true, length = 100)
private String email;
@Column(nullable = false)
private Integer status = 1; // 1=正常 0=禁用
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
public static User from(UserCreateDTO dto) {
User user = new User();
BeanUtils.copyProperties(dto, user);
return user;
}
}
配置规范
application.yml 模板
server:
port: ${SERVER_PORT:8080}
spring:
application:
name: ${spring.application.name}
datasource:
url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai}
username: ${SPRING_DATASOURCE_USERNAME:root}
password: ${SPRING_DATASOURCE_PASSWORD:}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: validate # none/validate/update/create/create-drop
show-sql: false
open-in-view: false
redis:
host: ${SPRING_REDIS_HOST:localhost}
port: ${SPRING_REDIS_PORT:6379}
logging:
level:
root: INFO
${basePackage}: DEBUG
可迁移关键: 用环境变量覆盖默认值,不硬编码真实密码。
常用集成
Redis 缓存
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer())))
.build();
}
}
// 使用
@Service
public class UserService {
@Cacheable(value = "user", key = "#id")
public UserVO getById(Long id) { ... }
@CacheEvict(value = "user", key = "#id")
public void update(Long id, UserUpdateDTO dto) { ... }
}
定时任务
@Configuration
@EnableScheduling
public class ScheduleConfig {}
@Component
@Slf4j
public class ScheduledTasks {
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void cleanExpiredData() {
log.info("Cleaning expired data...");
}
@Scheduled(fixedRate = 60000) // 每60秒
public void heartbeat() {
log.debug("Heartbeat check");
}
}
AOP 日志
@Aspect
@Component
@Slf4j
public class LogAspect {
@Around("execution(* ${basePackage}.controller..*(..))")
public Object logController(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long elapsed = System.currentTimeMillis() - start;
log.info("{}.{} -> {}ms",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), elapsed);
return result;
}
}
测试规范
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldReturnUserList() throws Exception {
when(userService.listUsers(anyInt(), anyInt(), any()))
.thenReturn(new PageResult<>(1, 1, 20, List.of()));
mockMvc.perform(get("/api/v1/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200));
}
}
常用命令
# 启动(自动检测 profile)
mvn spring-boot:run
# 指定 profile
mvn spring-boot:run -Dspring-boot.run.profiles=dev
# 打包
mvn clean package -DskipTests
# 运行 jar
java -jar target/*.jar --server.port=8081
# 测试
mvn test
mvn test -Dtest=UserControllerTest
# 查看依赖树(定位版本冲突)
mvn dependency:tree | grep -E "conflict|omitted"
# 生成 OpenAPI 文档(springdoc)
# 启动后访问: http://localhost:8080/swagger-ui.html
性能调优清单
| 项目 | 配置 | 说明 |
|---|---|---|
| 连接池 | spring.datasource.hikari.maximum-pool-size=20 |
默认10,生产可调20-50 |
| JPA | spring.jpa.open-in-view=false |
避免长连接持有数据库连接 |
| Tomcat 线程 | server.tomcat.threads.max=200 |
默认200 |
| 日志 | 生产关闭 show-sql |
避免 IO 瓶颈 |
| 缓存 | 热点数据加 Redis 缓存 | TTL 按业务设 |
| 异步 | @EnableAsync + @Async |
非核心路径异步化 |
| 压缩 | server.compression.enabled=true |
减少带宽 |
常见问题排查
启动失败
# 看完整堆栈(不要只看最后几行)
mvn spring-boot:run 2>&1 | tee startup.log
grep "Caused by:" startup.log
90% 的启动失败原因:
1. 数据库连不上 → 检查 spring.datasource.url
2. 端口被占 → lsof -i :8080 或 netstat -ano | findstr 8080
3. Bean 冲突 → @Primary 或 @Qualifier 解决
4. 循环依赖 → 重构为 @Lazy 注入或用 setter 注入
5. 版本不兼容 → mvn dependency:tree 查冲突
接口 404
- 检查
@RequestMapping路径拼写 - 检查包扫描范围:启动类
@SpringBootApplication是否在上层包 - 检查
context-path:server.servlet.context-path配置
事务不回滚
// ❌ 同一个类内部调用不走代理
public void methodA() {
this.methodB(); // @Transactional 不生效
}
// ✅ 注入自己或移到另一个 Service
@Service
public class OtherService {
@Transactional
public void methodB() { ... }
}
环境检测脚本
#!/bin/bash
# check-env.sh — 在任何机器上检测 Spring Boot 开发环境
echo "Java: $(java -version 2>&1 | head -1)"
echo "Maven: $(mvn -v 2>&1 | head -1)"
echo "Gradle: $(gradle -v 2>&1 | head -1)"
echo "Spring Boot project: $(grep -l 'spring-boot-starter' pom.xml build.gradle 2>/dev/null || echo 'NOT FOUND')"
echo "Boot version: $(grep '<version>' pom.xml 2>/dev/null | head -1 | sed 's/.*<version>//;s/<\/version>//')"
echo "Database: $(grep 'spring.datasource.url' src/main/resources/application*.yml 2>/dev/null | head -1)"
echo "Port: $(grep 'server.port' src/main/resources/application*.yml 2>/dev/null | head -1)"
注意事项
- 本 skill 不绑定任何特定项目——所有路径都是相对路径
${basePackage}是占位符,使用时替换为项目实际根包名- 所有配置示例都通过了环境变量覆盖机制,不会泄露真实密码
- 代码示例遵循 Spring Boot 3.x + Java 17 标准
- 兼容 Maven 和 Gradle 两种构建工具