关于 spring-boot-patterns
面向 Spring Boot 开发者的实践模式技能,涵盖分层结构、常见实现与测试,适用于新建项目、编写功能和代码审查。
主要功能
- 01项目分层与结构建议
- 02REST 控制器最佳实践
- 03服务与仓储模式
- 04DTO、映射与异常处理
- 05配置与测试示例
适用场景
- 01搭建 Spring Boot 项目结构
- 02实现 REST API 与业务逻辑
- 03检查数据访问和异常处理
- 04编写控制器及集成测试
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ decebals/ claude-code-java --skill spring-boot-patterns安装后在 Claude Code 中输入/spring-boot-patterns确认已加载。
附带文件README.md
详细介绍
Spring Boot 模式技能
Spring Boot 应用的最佳实践和模式。
适用场景
- 用户说“创建控制器”/“添加服务”/“Spring Boot 帮助”
- 审查 Spring Boot 代码
- 搭建新的 Spring Boot 项目结构
项目结构
src/main/java/com/example/myapp/
├── MyAppApplication.java # @SpringBootApplication
├── config/ # Configuration classes
│ ├── SecurityConfig.java
│ └── WebConfig.java
├── controller/ # REST controllers
│ └── UserController.java
├── service/ # Business logic
│ ├── UserService.java
│ └── impl/
│ └── UserServiceImpl.java
├── repository/ # Data access
│ └── UserRepository.java
├── model/ # Entities
│ └── User.java
├── dto/ # Data transfer objects
│ ├── request/
│ │ └── CreateUserRequest.java
│ └── response/
│ └── UserResponse.java
├── exception/ # Custom exceptions
│ ├── ResourceNotFoundException.java
│ └── GlobalExceptionHandler.java
└── util/ # Utilities
└── DateUtils.java
控制器模式
REST 控制器模板
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor // Lombok for constructor injection
public class UserController {
private final UserService userService;
@GetMapping
public ResponseEntity<List<UserResponse>> getAll() {
return ResponseEntity.ok(userService.findAll());
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getById(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
@PostMapping
public ResponseEntity<UserResponse> create(
@Valid @RequestBody CreateUserRequest request) {
UserResponse created = userService.create(request);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<UserResponse> update(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
return ResponseEntity.ok(userService.update(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
控制器最佳实践
| 实践 | 示例 |
|---|---|
| 版本化 API | /api/v1/users |
| 使用复数名词 | /users 而非 /user |
| HTTP 方法 | GET=读取,POST=创建,PUT=更新,DELETE=删除 |
| 状态码 | 200=成功,201=已创建,204=无内容,404=未找到 |
| 验证 | 在请求体上使用 @Valid |
❌ 反模式
// ❌ Business logic in controller
@PostMapping
public User create(@RequestBody User user) {
user.setCreatedAt(LocalDateTime.now()); // Logic belongs in service
return userRepository.save(user); // Direct repo access
}
// ❌ Returning entity directly (exposes internals)
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userRepository.findById(id).get();
}
服务模式
服务接口 + 实现
// Interface
public interface UserService {
List<UserResponse> findAll();
UserResponse findById(Long id);
UserResponse create(CreateUserRequest request);
UserResponse update(Long id, UpdateUserRequest request);
void delete(Long id);
}
// Implementation
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true) // Default read-only
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
@Override
public List<UserResponse> findAll() {
return userRepository.findAll().stream()
.map(userMapper::toResponse)
.toList();
}
@Override
public UserResponse findById(Long id) {
return userRepository.findById(id)
.map(userMapper::toResponse)
.orElseThrow(() -> new ResourceNotFoundException("User", id));
}
@Override
@Transactional // Write transaction
public UserResponse create(CreateUserRequest request) {
User user = userMapper.toEntity(request);
User saved = userRepository.save(user);
return userMapper.toResponse(saved);
}
@Override
@Transactional
public void delete(Long id) {
if (!userRepository.existsById(id)) {
throw new ResourceNotFoundException("User", id);
}
userRepository.deleteById(id);
}
}
服务最佳实践
- 使用接口 + 实现类以便测试
- 在类级别使用
@Transactional(readOnly = true) - 在写入方法上使用
@Transactional - 抛出领域异常,而非通用异常
- 使用映射器(MapStruct)转换实体 ↔ DTO
仓库模式
JPA 仓库
public interface UserRepository extends JpaRepository<User, Long> {
// Derived query
Optional<User> findByEmail(String email);
List<User> findByActiveTrue();
// Custom query
@Query("SELECT u FROM User u WHERE u.department.id = :deptId")
List<User> findByDepartmentId(@Param("deptId") Long departmentId);
// Native query (use sparingly)
@Query(value = "SELECT * FROM users WHERE created_at > :date",
nativeQuery = true)
List<User> findRecentUsers(@Param("date") LocalDate date);
// Exists check (more efficient than findBy)
boolean existsByEmail(String email);
// Count
long countByActiveTrue();
}
仓库最佳实践
- 尽可能使用派生查询
- 单条结果使用
Optional - 检查是否存在时使用
existsBy,而非findBy - 除非必要,否则避免使用原生查询
- 使用
@EntityGraph优化数据获取
DTO 模式
请求/响应 DTO
// Request DTO with validation
public record CreateUserRequest(
@NotBlank(message = "Name is required")
@Size(min = 2, max = 100)
String name,
@NotBlank
@Email(message = "Invalid email format")
String email,
@NotNull
@Min(18)
Integer age
) {}
// Response DTO
public record UserResponse(
Long id,
String name,
String email,
LocalDateTime createdAt
) {}
MapStruct 映射器
@Mapper(componentModel = "spring")
public interface UserMapper {
UserResponse toResponse(User entity);
List<UserResponse> toResponseList(List<User> entities);
@Mapping(target = "id", ignore = true)
@Mapping(target = "createdAt", ignore = true)
User toEntity(CreateUserRequest request);
}
异常处理
自定义异常
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String resource, Long id) {
super(String.format("%s not found with id: %d", resource, id));
}
}
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
}
全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
log.warn("Resource not found: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.toList();
return ResponseEntity.badRequest()
.body(new ErrorResponse("VALIDATION_ERROR", errors.toString()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
log.error("Unexpected error", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred"));
}
}
public record ErrorResponse(String code, String message) {}
配置模式
应用属性
# application.yml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: ${DB_USER}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate # Never 'create' in production!
show-sql: false
app:
jwt:
secret: ${JWT_SECRET}
expiration: 86400000
配置属性类
@Configuration
@ConfigurationProperties(prefix = "app.jwt")
@Validated
public class JwtProperties {
@NotBlank
private String secret;
@Min(60000)
private long expiration;
// getters and setters
}
特定配置文件的配置
src/main/resources/
├── application.yml # Common config
├── application-dev.yml # Development
├── application-test.yml # Testing
└── application-prod.yml # Production
常用注解速查
| 注解 | 用途 |
|---|---|
@RestController | REST 控制器(组合了 @Controller + @ResponseBody) |
@Service | 业务逻辑组件 |
@Repository | 数据访问组件 |
@Configuration | 配置类 |
@RequiredArgsConstructor | Lombok:构造器注入 |
@Transactional | 事务管理 |
@Valid | 触发验证 |
@ConfigurationProperties | 将属性绑定到类 |
@Profile("dev") | 特定配置文件的 Bean |
@Scheduled | 定时任务 |
测试模式
控制器测试(MockMvc)
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldReturnUser() throws Exception {
when(userService.findById(1L))
.thenReturn(new UserResponse(1L, "John", "[email protected]", null));
mockMvc.perform(get("/api/v1/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"));
}
}
服务测试
@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {
@Mock
private UserRepository userRepository;
@Mock
private UserMapper userMapper;
@InjectMocks
private UserServiceImpl userService;
@Test
void shouldThrowWhenUserNotFound() {
when(userRepository.findById(1L)).thenReturn(Optional.empty());
assertThatThrownBy(() -> userService.findById(1L))
.isInstanceOf(ResourceNotFoundException.class);
}
}
集成测试
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
class UserIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@Autowired
private MockMvc mockMvc;
@Test
void shouldCreateUser() throws Exception {
mockMvc.perform(post("/api/v1/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name": "John", "email": "[email protected]", "age": 25}
"""))
.andExpect(status().isCreated());
}
}
速查卡
| 层 | 职责 | 注解 |
|---|---|---|
| 控制器 | HTTP 处理、验证 | @RestController、@Valid |
| 服务 | 业务逻辑、事务 | @Service、@Transactional |
| 仓库 | 数据访问 | @Repository、扩展 JpaRepository |
| DTO | 数据传输 | 带验证注解的 Record |
| 配置 | 配置 | @Configuration、@ConfigurationProperties |
| 异常 | 错误处理 | @RestControllerAdvice |
评论
支持 Markdown