关于 spring-framework-patterns
Spring Framework Patterns 是面向 Spring Framework 和 Spring Boot 开发者及代码审查者的参考技能,整理了 Spring 应用的架构、实现、安全与可维护性建议。
主要功能
- 01依赖注入实践
- 02Bean 生命周期与作用域
- 03REST 控制器设计
- 04Spring Data JPA 模式
- 05安全与异常处理
适用场景
- 01审查 Spring 应用代码
- 02规划 Spring Boot 架构
- 03实现或重构组件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ clostaunau/ holiday-card --skill spring-framework-patterns安装后在 Claude Code 中输入/spring-framework-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
checklists/code-review-checklist.mdexamples/README.mdSecurityConfigExample.javaUserServiceExample.javatemplates/GlobalExceptionHandler.java.templateREADME.mdRepository.java.templateRestController.java.templateService.java.template详细介绍
Spring Framework 模式
目的
此技能提供 Spring Framework 和 Spring Boot 开发的全面模式和最佳实践。在代码审查期间,它可作为参考指南,确保 Spring 应用遵循行业标准,易于维护、可扩展,并符合企业级 Java 规范。
适用场景:
- 审查 Spring/Spring Boot 应用的代码
- 设计 Spring Boot 应用架构
- 编写新的 Spring 组件(控制器、服务、仓库)
- 重构现有 Spring 应用
- 评估 Spring 配置和设置
- 向团队成员讲授 Spring 最佳实践
背景
Spring Framework 是企业级 Java 应用的事实标准。此技能记录了使用 Spring Boot 3.x+ 和 Spring 6.x+ 构建生产就绪应用的模式,重点包括:
- 模块化:通过合理分层清晰划分关注点
- 可维护性:代码易于理解和修改
- 可测试性:组件易于测试
- 性能:高效使用 Spring 功能
- 安全性:默认安全的模式
- 约定优于配置:利用 Spring Boot 自动配置
此技能供 uncle-duke-java Agent 在代码审查期间参考,也供开发者在实现 Spring 应用时参考。
先决条件
必备知识:
- Java 基础知识(Java 17+)
- 面向对象编程概念
- Spring 概念基础知识
- Maven 或 Gradle 基础知识
必备工具:
- JDK 17 或更高版本
- Spring Boot 3.x+
- Maven 或 Gradle
- IDE(IntelliJ IDEA、Eclipse、VS Code)
预期项目结构:
spring-boot-app/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/app/
│ │ │ ├── Application.java
│ │ │ ├── config/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ ├── model/
│ │ │ ├── dto/
│ │ │ ├── exception/
│ │ │ └── security/
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── application-dev.yml
│ │ ├── application-prod.yml
│ │ └── db/migration/
│ └── test/
│ └── java/
├── pom.xml (or build.gradle)
└── README.md
说明
任务 1:实施依赖注入最佳实践
1.1 构造函数注入(首选)
规则: 必须始终对必需依赖使用构造函数注入。生产代码中绝不使用字段注入。
✅ 良好示例:
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// Constructor injection - preferred approach
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User createUser(UserDTO dto) {
User user = new User(dto.getEmail());
User savedUser = userRepository.save(user);
emailService.sendWelcomeEmail(savedUser);
return savedUser;
}
}
优点:
- 依赖不可变(
final字段) - 依赖是必需的,没有它们就无法创建实例
- 易于测试,可在测试中注入模拟对象
- 测试中无需反射
- 构造函数清楚地列出了所有依赖
❌ 不良示例:
@Service
public class UserService {
// Field injection - DON'T DO THIS
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
public User createUser(UserDTO dto) {
User user = new User(dto.getEmail());
User savedUser = userRepository.save(user);
emailService.sendWelcomeEmail(savedUser);
return savedUser;
}
}
缺点:
- 无法声明为
final,依赖可变 - 可以在没有依赖的情况下创建实例(有 NullPointerException 风险)
- 难以测试,测试中需要反射或 Spring 上下文
- 隐藏依赖,无法清楚了解所需内容
- 违反封装原则
使用 Lombok(可接受):
@Service
@RequiredArgsConstructor // Generates constructor for final fields
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public User createUser(UserDTO dto) {
User user = new User(dto.getEmail());
User savedUser = userRepository.save(user);
emailService.sendWelcomeEmail(savedUser);
return savedUser;
}
}
1.2 使用 Setter 注入可选依赖
规则: 仅对可选依赖使用 Setter 注入。
✅ 良好示例:
@Service
public class NotificationService {
private final EmailService emailService; // Required
private SmsService smsService; // Optional
public NotificationService(EmailService emailService) {
this.emailService = emailService;
}
@Autowired(required = false)
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
public void notify(User user, String message) {
emailService.send(user.getEmail(), message);
if (smsService != null && user.getPhoneNumber() != null) {
smsService.send(user.getPhoneNumber(), message);
}
}
}
1.3 避免循环依赖
规则: 循环依赖表明设计存在问题。应进行重构,而不是使用 @Lazy。
❌ 不良示例:
@Service
public class OrderService {
@Autowired
@Lazy // Band-aid solution
private PaymentService paymentService;
}
@Service
public class PaymentService {
@Autowired
@Lazy // Band-aid solution
private OrderService orderService;
}
✅ 良好示例:
// Extract common logic to a new service
@Service
public class OrderProcessingService {
private final OrderRepository orderRepository;
private final PaymentRepository paymentRepository;
public OrderProcessingService(OrderRepository orderRepository,
PaymentRepository paymentRepository) {
this.orderRepository = orderRepository;
this.paymentRepository = paymentRepository;
}
public void processOrder(Order order, Payment payment) {
order.setStatus(OrderStatus.PROCESSING);
orderRepository.save(order);
payment.setOrderId(order.getId());
paymentRepository.save(payment);
}
}
@Service
public class OrderService {
private final OrderProcessingService processingService;
// ...
}
@Service
public class PaymentService {
private final OrderProcessingService processingService;
// ...
}
任务 2:了解 Bean 生命周期和作用域
2.1 Bean 作用域
可用作用域:
singleton(默认):每个 Spring 容器一个实例prototype:每次请求 Bean 时创建一个新实例request:每个 HTTP 请求一个实例(Web 应用)session:每个 HTTP 会话一个实例(Web 应用)application:每个 ServletContext 一个实例(Web 应用)
✅ 良好示例:
// Singleton (default) - stateless services
@Service
@Scope("singleton") // Can omit - it's default
public class UserService {
// Stateless - safe to share
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
// Prototype - stateful beans
@Component
@Scope("prototype")
public class ReportGenerator {
// Stateful - each user gets their own instance
private final List<String> reportLines = new ArrayList<>();
public void addLine(String line) {
reportLines.add(line);
}
public String generate() {
return String.join("\n", reportLines);
}
}
// Request scope - web layer
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String requestId;
private String userId;
// Getters and setters
}
❌ 不良示例:
// Singleton with mutable state - THREAD UNSAFE
@Service
public class UserService {
private User currentUser; // Shared across all requests - BAD!
public void processUser(User user) {
this.currentUser = user; // Race condition!
// Process...
}
}
2.2 Bean 生命周期回调
规则: 使用 @PostConstruct 进行初始化,使用 @PreDestroy 进行清理。
✅ 良好示例:
@Service
public class CacheService {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
private ScheduledExecutorService scheduler;
@PostConstruct
public void initialize() {
System.out.println("Initializing cache service...");
scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(this::cleanupExpiredEntries, 1, 1, TimeUnit.HOURS);
}
@PreDestroy
public void cleanup() {
System.out.println("Cleaning up cache service...");
cache.clear();
if (scheduler != null) {
scheduler.shutdown();
}
}
private void cleanupExpiredEntries() {
// Cleanup logic
}
}
2.3 组件构造型注解
规则: 使用最具体的构造型注解。
@Component // Generic Spring-managed component
@Service // Business logic layer
@Repository // Data access layer (adds exception translation)
@Controller // MVC controller (returns views)
@RestController // REST API controller (returns data)
@Configuration // Configuration class
✅ 良好示例:
@Repository // Data access - enables exception translation
public interface UserRepository extends JpaRepository<User, Long> {
}
@Service // Business logic
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
@RestController // REST API
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
}
@Configuration // Configuration
public class SecurityConfig {
// Configuration beans
}
任务 3:设计 REST API 控制器
3.1 控制器结构
规则: 保持控制器精简,将业务逻辑交给服务处理。
✅ 良好示例:
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping
public ResponseEntity<Page<UserDTO>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
Page<UserDTO> users = userService.findAll(pageable);
return ResponseEntity.ok(users);
}
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserRequest request) {
UserDTO created = userService.createUser(request);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
return userService.updateUser(id, request)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
优点:
- RESTful URL 结构
- 使用恰当的 HTTP 方法和状态码
- 支持分页和排序
- 使用 @Valid 进行验证
- 为已创建的资源设置 Location 标头
- 将逻辑委托给服务层
❌ 不良示例:
@RestController
public class UserController {
@Autowired
private UserRepository userRepository; // Controller accessing repository directly!
@GetMapping("/getUsers") // Non-RESTful URL
public List<User> getUsers() { // Returns entities, not DTOs
return userRepository.findAll(); // Business logic in controller
}
@PostMapping("/createUser") // Non-RESTful URL
public User createUser(@RequestBody User user) { // No validation
// Business logic in controller - BAD!
if (userRepository.findByEmail(user.getEmail()).isPresent()) {
throw new RuntimeException("Email exists"); // Poor error handling
}
return userRepository.save(user); // Returns 200 instead of 201
}
}
3.2 请求/响应模式
规则: 使用 DTO 定义 API 契约。切勿直接暴露实体。
✅ 正确示例:
// Request DTOs
public class CreateUserRequest {
@NotBlank(message = "Email is required")
@Email(message = "Email must be valid")
private String email;
@NotBlank(message = "Name is required")
@Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters")
private String name;
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
// Getters and setters
}
// Response DTOs
public class UserDTO {
private Long id;
private String email;
private String name;
private LocalDateTime createdAt;
private boolean active;
// No password field - security
// Getters and setters
}
// Service layer
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final UserMapper userMapper;
public UserDTO createUser(CreateUserRequest request) {
// Validate
if (userRepository.existsByEmail(request.getEmail())) {
throw new EmailAlreadyExistsException(request.getEmail());
}
// Map DTO to entity
User user = new User();
user.setEmail(request.getEmail());
user.setName(request.getName());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setActive(true);
user.setCreatedAt(LocalDateTime.now());
// Save
User saved = userRepository.save(user);
// Map entity to DTO
return userMapper.toDTO(saved);
}
}
3.3 HTTP 状态码
规则: 使用正确的 HTTP 状态码。
@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
// 200 OK - Successful GET/PUT
@GetMapping("/{id}")
public ResponseEntity<OrderDTO> getOrder(@PathVariable Long id) {
return orderService.findById(id)
.map(ResponseEntity::ok) // 200 OK
.orElse(ResponseEntity.notFound().build()); // 404 Not Found
}
// 201 Created - Successful POST
@PostMapping
public ResponseEntity<OrderDTO> createOrder(@Valid @RequestBody CreateOrderRequest request) {
OrderDTO created = orderService.createOrder(request);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created); // 201 Created
}
// 204 No Content - Successful DELETE
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
orderService.deleteOrder(id);
return ResponseEntity.noContent().build(); // 204 No Content
}
// 202 Accepted - Async processing
@PostMapping("/{id}/process")
public ResponseEntity<Void> processOrder(@PathVariable Long id) {
orderService.processOrderAsync(id);
return ResponseEntity.accepted().build(); // 202 Accepted
}
// 400 Bad Request - Validation failures (handled by @Valid)
// 401 Unauthorized - Not authenticated (handled by Security)
// 403 Forbidden - Not authorized (handled by Security)
// 404 Not Found - Resource doesn't exist
// 409 Conflict - Business rule violation
// 500 Internal Server Error - Unexpected errors
}
任务 4:使用 Spring Data JPA 实现数据访问层
4.1 Repository 接口
规则: 根据需求扩展适当的 Spring Data 接口。
✅ 正确示例:
// Simple CRUD - JpaRepository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Query methods - Spring Data generates implementation
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
List<User> findByActiveTrue();
// Custom query
@Query("SELECT u FROM User u WHERE u.createdAt > :date")
List<User> findRecentUsers(@Param("date") LocalDateTime date);
// Native query
@Query(value = "SELECT * FROM users WHERE email LIKE %:domain", nativeQuery = true)
List<User> findByEmailDomain(@Param("domain") String domain);
// Modifying query
@Modifying
@Query("UPDATE User u SET u.active = false WHERE u.lastLoginAt < :date")
int deactivateInactiveUsers(@Param("date") LocalDateTime date);
}
Repository 层级:
Repository<T, ID>- 标记接口,不包含方法CrudRepository<T, ID>- 基本 CRUD 操作PagingAndSortingRepository<T, ID>- 添加分页和排序JpaRepository<T, ID>- JPA 专属功能(flush、批量操作)
4.2 使用 Specifications 编写自定义查询
规则: 使用 Specifications 构建动态查询,而不是拼接查询字符串。
✅ 正确示例:
// Specification
public class UserSpecifications {
public static Specification<User> hasEmail(String email) {
return (root, query, cb) ->
email == null ? null : cb.equal(root.get("email"), email);
}
public static Specification<User> isActive() {
return (root, query, cb) -> cb.isTrue(root.get("active"));
}
public static Specification<User> createdAfter(LocalDateTime date) {
return (root, query, cb) ->
date == null ? null : cb.greaterThan(root.get("createdAt"), date);
}
public static Specification<User> nameLike(String name) {
return (root, query, cb) ->
name == null ? null : cb.like(cb.lower(root.get("name")), "%" + name.toLowerCase() + "%");
}
}
// Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}
// Service
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public Page<User> searchUsers(UserSearchCriteria criteria, Pageable pageable) {
Specification<User> spec = Specification.where(null);
if (criteria.getEmail() != null) {
spec = spec.and(UserSpecifications.hasEmail(criteria.getEmail()));
}
if (criteria.isActiveOnly()) {
spec = spec.and(UserSpecifications.isActive());
}
if (criteria.getCreatedAfter() != null) {
spec = spec.and(UserSpecifications.createdAfter(criteria.getCreatedAfter()));
}
if (criteria.getName() != null) {
spec = spec.and(UserSpecifications.nameLike(criteria.getName()));
}
return userRepository.findAll(spec, pageable);
}
}
4.3 分页和排序
规则: 列表端点始终应支持分页。
✅ 正确示例:
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping
public ResponseEntity<Page<UserDTO>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id,desc") String[] sort) {
// Parse sort parameters
List<Sort.Order> orders = Arrays.stream(sort)
.map(s -> {
String[] parts = s.split(",");
String property = parts[0];
Sort.Direction direction = parts.length > 1 && parts[1].equalsIgnoreCase("desc")
? Sort.Direction.DESC
: Sort.Direction.ASC;
return new Sort.Order(direction, property);
})
.toList();
Pageable pageable = PageRequest.of(page, size, Sort.by(orders));
Page<UserDTO> users = userService.findAll(pageable);
return ResponseEntity.ok(users);
}
}
4.4 事务管理
规则: 在服务方法上使用 @Transactional,而不是在 repository 方法上使用。
✅ 正确示例:
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentRepository paymentRepository;
private final EmailService emailService;
@Transactional
public OrderDTO createOrder(CreateOrderRequest request) {
// Create order
Order order = new Order();
order.setUserId(request.getUserId());
order.setStatus(OrderStatus.PENDING);
Order savedOrder = orderRepository.save(order);
// Create payment
Payment payment = new Payment();
payment.setOrderId(savedOrder.getId());
payment.setAmount(request.getAmount());
paymentRepository.save(payment);
// If email fails, transaction rolls back
emailService.sendOrderConfirmation(savedOrder);
return mapToDTO(savedOrder);
}
@Transactional(readOnly = true) // Optimization for read-only operations
public Optional<OrderDTO> findById(Long id) {
return orderRepository.findById(id)
.map(this::mapToDTO);
}
@Transactional(isolation = Isolation.SERIALIZABLE) // For critical operations
public void processPayment(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
// Process payment with highest isolation level
order.setStatus(OrderStatus.PAID);
orderRepository.save(order);
}
}
事务传播:
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED) // Default - join existing or create new
public void updateUser(User user) {
// Uses existing transaction or creates new
}
@Transactional(propagation = Propagation.REQUIRES_NEW) // Always create new transaction
public void auditLog(String action) {
// Independent transaction - commits even if parent rolls back
}
@Transactional(propagation = Propagation.MANDATORY) // Must have existing transaction
public void criticalOperation() {
// Throws exception if no transaction exists
}
}
任务 5:设计服务层
5.1 服务边界
规则: 服务应代表业务能力,而不是数据访问。
✅ 正确示例:
// Good service boundaries
@Service
@RequiredArgsConstructor
public class UserRegistrationService {
private final UserRepository userRepository;
private final EmailService emailService;
private final PasswordEncoder passwordEncoder;
@Transactional
public UserDTO register(RegistrationRequest request) {
// Validate
validateEmailNotExists(request.getEmail());
// Create user
User user = createUser(request);
User saved = userRepository.save(user);
// Send welcome email
emailService.sendWelcomeEmail(saved.getEmail());
return mapToDTO(saved);
}
private void validateEmailNotExists(String email) {
if (userRepository.existsByEmail(email)) {
throw new EmailAlreadyExistsException(email);
}
}
private User createUser(RegistrationRequest request) {
User user = new User();
user.setEmail(request.getEmail());
user.setName(request.getName());
user.setPassword(passwordEncoder.encode(request.getPassword()));
return user;
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional(readOnly = true)
public Optional<UserDTO> findById(Long id) {
return userRepository.findById(id).map(this::mapToDTO);
}
@Transactional(readOnly = true)
public Page<UserDTO> findAll(Pageable pageable) {
return userRepository.findAll(pageable).map(this::mapToDTO);
}
}
5.2 DTO 映射
规则: 将实体到 DTO 的映射逻辑集中在一处。
✅ 使用 MapStruct 的正确示例:
@Mapper(componentModel = "spring")
public interface UserMapper {
UserDTO toDTO(User user);
List<UserDTO> toDTOs(List<User> users);
@Mapping(target = "id", ignore = true)
@Mapping(target = "createdAt", ignore = true)
User toEntity(CreateUserRequest request);
}
// Usage in service
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
public UserDTO createUser(CreateUserRequest request) {
User user = userMapper.toEntity(request);
User saved = userRepository.save(user);
return userMapper.toDTO(saved);
}
}
✅ 不使用 MapStruct 的正确示例:
@Service
public class UserService {
private final UserRepository userRepository;
private UserDTO mapToDTO(User user) {
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setEmail(user.getEmail());
dto.setName(user.getName());
dto.setCreatedAt(user.getCreatedAt());
dto.setActive(user.isActive());
return dto;
}
private User mapToEntity(CreateUserRequest request) {
User user = new User();
user.setEmail(request.getEmail());
user.setName(request.getName());
return user;
}
}
任务 6:实现全局异常处理
6.1 自定义异常
规则: 为业务错误创建特定于领域的异常。
✅ 正确示例:
// Base exception
public abstract class BusinessException extends RuntimeException {
private final String errorCode;
public BusinessException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
// Specific exceptions
public class ResourceNotFoundException extends BusinessException {
public ResourceNotFoundException(String resourceName, Long id) {
super(String.format("%s with id %d not found", resourceName, id), "RESOURCE_NOT_FOUND");
}
}
public class EmailAlreadyExistsException extends BusinessException {
public EmailAlreadyExistsException(String email) {
super(String.format("Email %s already exists", email), "EMAIL_EXISTS");
}
}
public class InsufficientBalanceException extends BusinessException {
public InsufficientBalanceException(Long accountId) {
super(String.format("Insufficient balance in account %d", accountId), "INSUFFICIENT_BALANCE");
}
}
6.2 使用 @ControllerAdvice 实现全局异常处理器
规则: 使用 @ControllerAdvice 集中处理异常。
✅ 正确示例:
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
log.warn("Resource not found: {}", ex.getMessage());
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.NOT_FOUND.value())
.error("Not Found")
.message(ex.getMessage())
.errorCode(ex.getErrorCode())
.build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(EmailAlreadyExistsException.class)
public ResponseEntity<ErrorResponse> handleEmailAlreadyExists(EmailAlreadyExistsException ex) {
log.warn("Email already exists: {}", ex.getMessage());
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.CONFLICT.value())
.error("Conflict")
.message(ex.getMessage())
.errorCode(ex.getErrorCode())
.build();
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleValidationErrors(MethodArgumentNotValidException ex) {
log.warn("Validation failed: {}", ex.getMessage());
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
ValidationErrorResponse response = ValidationErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error("Validation Failed")
.message("Invalid request parameters")
.fieldErrors(errors)
.build();
return ResponseEntity.badRequest().body(response);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
log.error("Unexpected error occurred", ex);
ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.error("Internal Server Error")
.message("An unexpected error occurred")
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
// Error response DTOs
@Data
@Builder
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String errorCode;
}
@Data
@Builder
public class ValidationErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private Map<String, String> fieldErrors;
}
任务 7:实现 Spring Security
7.1 安全配置
规则: 使用安全配置类集中设置安全机制。
✅ 正确示例(Spring Security 6+):
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/public/**").permitAll()
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.requestMatchers("/api/v1/users/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.httpBasic(Customizer.withDefaults())
.formLogin(form -> form.disable())
.logout(logout -> logout
.logoutUrl("/api/v1/auth/logout")
.logoutSuccessHandler((request, response, authentication) ->
response.setStatus(HttpServletResponse.SC_OK))
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
}
7.2 JWT 实现
规则: 在 REST API 中使用 JWT 实现无状态身份验证。
✅ 正确示例:
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration:3600000}") // 1 hour default
private long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.toList());
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
return getClaimsFromToken(token).getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return !isTokenExpired(token);
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
private Claims getClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
private boolean isTokenExpired(String token) {
Date expiration = getClaimsFromToken(token).getExpiration();
return expiration.before(new Date());
}
}
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token != null && tokenProvider.validateToken(token)) {
String username = tokenProvider.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
7.3 方法安全
规则: 使用方法安全实现细粒度授权。
✅ 正确示例:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@PreAuthorize("hasRole('ADMIN')")
public List<UserDTO> findAll() {
return userRepository.findAll().stream()
.map(this::mapToDTO)
.toList();
}
@PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
public UserDTO findById(Long userId) {
return userRepository.findById(userId)
.map(this::mapToDTO)
.orElseThrow(() -> new ResourceNotFoundException("User", userId));
}
@PreAuthorize("hasAnyRole('ADMIN', 'MODERATOR')")
@PostAuthorize("returnObject.email == authentication.principal.username")
public UserDTO updateUser(Long id, UpdateUserRequest request) {
// Implementation
}
@Secured("ROLE_ADMIN")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
任务 8:实施测试策略
8.1 服务单元测试
规则: 使用模拟依赖隔离测试服务。
✅ 正确示例:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@Mock
private EmailService emailService;
@InjectMocks
private UserService userService;
@Test
void createUser_WithValidData_ShouldCreateUser() {
// Arrange
CreateUserRequest request = new CreateUserRequest();
request.setEmail("[email protected]");
request.setPassword("password123");
User user = new User();
user.setId(1L);
user.setEmail(request.getEmail());
when(userRepository.existsByEmail(request.getEmail())).thenReturn(false);
when(passwordEncoder.encode(request.getPassword())).thenReturn("encodedPassword");
when(userRepository.save(any(User.class))).thenReturn(user);
// Act
UserDTO result = userService.createUser(request);
// Assert
assertNotNull(result);
assertEquals(1L, result.getId());
assertEquals("[email protected]", result.getEmail());
verify(userRepository).existsByEmail(request.getEmail());
verify(passwordEncoder).encode(request.getPassword());
verify(userRepository).save(any(User.class));
verify(emailService).sendWelcomeEmail(any(User.class));
}
@Test
void createUser_WithExistingEmail_ShouldThrowException() {
// Arrange
CreateUserRequest request = new CreateUserRequest();
request.setEmail("[email protected]");
when(userRepository.existsByEmail(request.getEmail())).thenReturn(true);
// Act & Assert
assertThrows(EmailAlreadyExistsException.class, () ->
userService.createUser(request));
verify(userRepository).existsByEmail(request.getEmail());
verify(userRepository, never()).save(any(User.class));
}
}
8.2 使用 @SpringBootTest 进行集成测试
规则: 对需要完整上下文的集成测试使用 @SpringBootTest。
✅ 正确示例:
@SpringBootTest
@AutoConfigureMockMvc
@Transactional // Rollback after each test
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserRepository userRepository;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void createUser_WithValidData_ShouldReturn201() throws Exception {
// Arrange
CreateUserRequest request = new CreateUserRequest();
request.setEmail("[email protected]");
request.setName("Test User");
request.setPassword("password123");
// Act & Assert
mockMvc.perform(post("/api/v1/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(header().exists("Location"))
.andExpect(jsonPath("$.email").value("[email protected]"))
.andExpect(jsonPath("$.name").value("Test User"))
.andExpect(jsonPath("$.id").exists());
// Verify database
assertEquals(1, userRepository.count());
}
@Test
void createUser_WithInvalidEmail_ShouldReturn400() throws Exception {
// Arrange
CreateUserRequest request = new CreateUserRequest();
request.setEmail("invalid-email");
request.setName("Test User");
request.setPassword("password123");
// Act & Assert
mockMvc.perform(post("/api/v1/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.fieldErrors.email").exists());
}
}
8.3 使用 @DataJpaTest 测试 Repository
规则: 使用 @DataJpaTest 和内存数据库测试 Repository。
✅ 正确示例:
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Autowired
private TestEntityManager entityManager;
@Test
void findByEmail_WithExistingEmail_ShouldReturnUser() {
// Arrange
User user = new User();
user.setEmail("[email protected]");
user.setName("Test User");
entityManager.persist(user);
entityManager.flush();
// Act
Optional<User> found = userRepository.findByEmail("[email protected]");
// Assert
assertTrue(found.isPresent());
assertEquals("[email protected]", found.get().getEmail());
}
@Test
void findByEmail_WithNonExistingEmail_ShouldReturnEmpty() {
// Act
Optional<User> found = userRepository.findByEmail("[email protected]");
// Assert
assertFalse(found.isPresent());
}
}
8.4 使用 @WebMvcTest 测试控制器
规则: 无需完整上下文的控制器测试使用 @WebMvcTest。
✅ 正确示例:
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
@Test
void getUserById_WithExistingId_ShouldReturnUser() throws Exception {
// Arrange
UserDTO user = new UserDTO();
user.setId(1L);
user.setEmail("[email protected]");
when(userService.findById(1L)).thenReturn(Optional.of(user));
// Act & Assert
mockMvc.perform(get("/api/v1/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.email").value("[email protected]"));
verify(userService).findById(1L);
}
@Test
void getUserById_WithNonExistingId_ShouldReturn404() throws Exception {
// Arrange
when(userService.findById(999L)).thenReturn(Optional.empty());
// Act & Assert
mockMvc.perform(get("/api/v1/users/999"))
.andExpect(status().isNotFound());
}
}
任务 9:实现配置管理
9.1 外部配置
规则: 使用 application.yml 管理配置,并支持多个 profile。
✅ 正确的 application.yml:
spring:
application:
name: my-spring-app
profiles:
active: dev
datasource:
url: ${DB_URL:jdbc:postgresql://localhost:5432/myapp}
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:password}
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: validate
show-sql: false
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
cache:
type: caffeine
caffeine:
spec: maximumSize=500,expireAfterAccess=600s
server:
port: 8080
error:
include-message: always
include-binding-errors: always
include-stacktrace: on_param
include-exception: false
logging:
level:
root: INFO
com.example.app: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
# Custom application properties
app:
jwt:
secret: ${JWT_SECRET:default-secret-change-in-production}
expiration: 3600000
email:
from: [email protected]
features:
new-ui: false
application-dev.yml:
spring:
jpa:
show-sql: true
hibernate:
ddl-auto: update
logging:
level:
com.example.app: DEBUG
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
app:
features:
new-ui: true
application-prod.yml:
spring:
jpa:
show-sql: false
hibernate:
ddl-auto: validate
logging:
level:
root: WARN
com.example.app: INFO
app:
jwt:
expiration: 7200000 # 2 hours in production
9.2 @ConfigurationProperties
规则: 使用 @ConfigurationProperties 实现类型安全的配置。
✅ 正确示例:
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotNull
private Jwt jwt;
@NotNull
private Email email;
@NotNull
private Features features;
@Data
public static class Jwt {
@NotBlank
private String secret;
@Min(60000) // At least 1 minute
private long expiration;
}
@Data
public static class Email {
@Email
private String from;
}
@Data
public static class Features {
private boolean newUi;
}
// Getters and setters
}
// Enable configuration properties
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {
}
// Usage
@Service
@RequiredArgsConstructor
public class EmailService {
private final AppProperties appProperties;
public void sendEmail(String to, String subject, String body) {
String from = appProperties.getEmail().getFrom();
// Send email logic
}
}
任务 10:实现缓存
10.1 启用缓存
规则: 使用 Spring Cache 抽象实现声明式缓存。
✅ 正确示例:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager(
"users", "products", "orders"
);
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats());
return cacheManager;
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Cacheable(value = "users", key = "#id")
public Optional<UserDTO> findById(Long id) {
return userRepository.findById(id)
.map(this::mapToDTO);
}
@CachePut(value = "users", key = "#result.id")
public UserDTO updateUser(Long id, UpdateUserRequest request) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", id));
user.setName(request.getName());
User updated = userRepository.save(user);
return mapToDTO(updated);
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
@CacheEvict(value = "users", allEntries = true)
public void clearUserCache() {
// Cache cleared
}
}
10.2 缓存键策略
规则: 在复杂场景中使用显式缓存键。
✅ 正确示例:
@Service
public class ProductService {
// Simple key
@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
// ...
}
// Composite key
@Cacheable(value = "products", key = "#category + '-' + #priceRange")
public List<Product> findByCategoryAndPrice(String category, String priceRange) {
// ...
}
// Custom KeyGenerator
@Cacheable(value = "products", keyGenerator = "customKeyGenerator")
public List<Product> search(ProductSearchCriteria criteria) {
// ...
}
}
@Component("customKeyGenerator")
public class CustomKeyGenerator implements org.springframework.cache.interceptor.KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return target.getClass().getSimpleName() + "_"
+ method.getName() + "_"
+ Arrays.stream(params).map(String::valueOf).collect(Collectors.joining("_"));
}
}
任务 11:实现异步处理
11.1 启用异步处理
规则: 使用 @Async 执行非阻塞操作。
✅ 正确示例:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
@Service
@RequiredArgsConstructor
public class EmailService {
@Async("taskExecutor")
public void sendWelcomeEmail(User user) {
// Runs asynchronously
System.out.println("Sending email to: " + user.getEmail());
// Email sending logic
}
@Async("taskExecutor")
public CompletableFuture<EmailStatus> sendEmailWithResult(String to, String subject, String body) {
// Async with result
try {
// Send email
return CompletableFuture.completedFuture(EmailStatus.SENT);
} catch (Exception e) {
return CompletableFuture.completedFuture(EmailStatus.FAILED);
}
}
}
11.2 事件驱动架构
规则: 使用 Spring Events 解耦组件。
✅ 正确示例:
// Event
public class UserRegisteredEvent {
private final User user;
public UserRegisteredEvent(User user) {
this.user = user;
}
public User getUser() {
return user;
}
}
// Publisher
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;
@Transactional
public UserDTO register(RegistrationRequest request) {
User user = new User();
user.setEmail(request.getEmail());
User saved = userRepository.save(user);
// Publish event
eventPublisher.publishEvent(new UserRegisteredEvent(saved));
return mapToDTO(saved);
}
}
// Listeners
@Component
@Slf4j
public class UserRegistrationEventListener {
@Async
@EventListener
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleUserRegistered(UserRegisteredEvent event) {
log.info("User registered: {}", event.getUser().getEmail());
// Send welcome email
}
}
@Component
public class AuditEventListener {
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
// Log to audit trail
}
}
任务 12:实现 AOP
12.1 日志切面
规则: 使用 AOP 处理日志记录、审计和性能监控等横切关注点。
✅ 正确示例:
@Aspect
@Component
@Slf4j
public class LoggingAspect {
@Around("execution(* com.example.app.service.*.*(..))")
public Object logServiceMethods(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
log.debug("Entering {}.{}", className, methodName);
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
log.debug("Exiting {}.{} - Duration: {}ms", className, methodName, duration);
return result;
} catch (Exception e) {
log.error("Exception in {}.{}: {}", className, methodName, e.getMessage());
throw e;
}
}
@Before("@annotation(com.example.app.annotation.Audit)")
public void auditMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("Audit: Method {} called with args: {}", methodName, Arrays.toString(args));
}
}
// Custom annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {
}
// Usage
@Service
public class UserService {
@Audit
public UserDTO createUser(CreateUserRequest request) {
// Method will be audited
}
}
常见反模式
反模式 1:字段注入
❌ 错误示例:
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // Field injection
}
✅ 正确示例:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository; // Constructor injection
}
反模式 2:绕过服务层
❌ 错误:
@RestController
public class UserController {
@Autowired
private UserRepository userRepository; // Controller accessing repository directly
@GetMapping("/users")
public List<User> getUsers() {
return userRepository.findAll(); // Business logic in controller
}
}
✅ 正确:
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService; // Access through service layer
@GetMapping("/users")
public ResponseEntity<List<UserDTO>> getUsers() {
return ResponseEntity.ok(userService.findAll());
}
}
反模式 3:直接公开实体
❌ 错误:
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id); // Exposing entity
}
}
✅ 正确:
@RestController
public class UserController {
@GetMapping("/users/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id)); // Return DTO
}
}
反模式 4:在 Repository 方法上使用 @Transactional
❌ 错误:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Transactional // Don't put @Transactional on repository
@Query("UPDATE User u SET u.active = false WHERE u.id = :id")
void deactivate(@Param("id") Long id);
}
✅ 正确:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional // Put @Transactional on service methods
public void deactivateUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", id));
user.setActive(false);
userRepository.save(user);
}
}
反模式 5:不使用连接池
❌ 错误:
# No connection pooling configuration
spring:
datasource:
url: jdbc:postgresql://localhost:5432/myapp
✅ 正确:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/myapp
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
反模式 6:Controller 承担过多职责
❌ 错误:
@RestController
public class UserController {
@PostMapping("/users")
public User createUser(@RequestBody User user) {
// Validation in controller
if (user.getEmail() == null) {
throw new RuntimeException("Email required");
}
// Business logic in controller
if (userRepository.existsByEmail(user.getEmail())) {
throw new RuntimeException("Email exists");
}
// Data access in controller
User saved = userRepository.save(user);
// Email sending in controller
emailService.send(user.getEmail(), "Welcome!");
return saved;
}
}
✅ 正确:
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/users")
public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserRequest request) {
UserDTO created = userService.createUser(request);
return ResponseEntity.created(location).body(created);
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
@Transactional
public UserDTO createUser(CreateUserRequest request) {
// All business logic in service
validateEmail(request.getEmail());
User user = createUserEntity(request);
User saved = userRepository.save(user);
emailService.sendWelcomeEmail(saved);
return mapToDTO(saved);
}
}
反模式 7:在实体中存储业务逻辑
❌ 错误:
@Entity
public class Order {
@Autowired // Don't inject dependencies in entities!
private PaymentService paymentService;
public void process() {
// Business logic in entity - BAD!
if (this.total > 1000) {
paymentService.processLargeOrder(this);
} else {
paymentService.processSmallOrder(this);
}
}
}
✅ 正确:
@Entity
public class Order {
// Pure data model - no business logic
private Long id;
private BigDecimal total;
private OrderStatus status;
// Getters and setters
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentService paymentService;
private final OrderRepository orderRepository;
@Transactional
public void processOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new ResourceNotFoundException("Order", orderId));
if (order.getTotal().compareTo(BigDecimal.valueOf(1000)) > 0) {
paymentService.processLargeOrder(order);
} else {
paymentService.processSmallOrder(order);
}
}
}
检查清单
在代码审查期间使用此检查清单:
依赖注入
- 必需依赖使用构造函数注入
- 尽可能将字段声明为 final
- 不使用字段注入(在字段上使用 @Autowired)
- 不存在循环依赖
- 仅对可选依赖使用 Setter 注入
组件结构
- 使用正确的 stereotype 注解(@Service、@Repository、@Controller、@RestController)
- Controller 保持精简(委托给 Service)
- 业务逻辑位于服务层
- 仅通过 Repository 访问数据
- 分层恰当(Controller → Service → Repository)
REST API 设计
- URL 结构符合 RESTful 规范
- 使用正确的 HTTP 方法(GET、POST、PUT、DELETE)
- 使用正确的 HTTP 状态码
- 请求/响应使用 DTO(而非实体)
- 使用 @Valid 验证请求
- 列表端点支持分页
- 为已创建的资源设置 Location 标头
数据访问
- 使用合适的 Repository 接口(JpaRepository 等)
- 查询方法遵循 Spring Data 命名约定
- 在 Service 方法上使用 @Transactional(而非 Repository)
- 读取操作使用 readOnly=true
- 已配置连接池(HikariCP)
- 不存在 N+1 查询问题
配置
- 外部配置位于 application.yml
- 存在特定 Profile 的配置文件
- 使用 @ConfigurationProperties 实现类型安全的配置
- 敏感数据存储在环境变量中
- 为所有属性设置合理的默认值
异常处理
- 针对业务错误使用自定义异常
- 使用 @RestControllerAdvice 进行全局异常处理
- 针对不同异常使用恰当的 HTTP 状态码
- 正确处理验证错误
- 不捕获通用 Exception 后却不重新抛出
安全
- 在 @Configuration 类中配置安全设置
- 已配置密码编码器(BCrypt)
- 在需要的地方启用方法安全
- 已启用 CSRF 防护(或提供禁用理由)
- 为无状态身份验证正确实现 JWT
测试
- 使用模拟依赖为 Service 编写单元测试
- 使用 @SpringBootTest 编写集成测试
- 使用 @DataJpaTest 编写 Repository 测试
- 使用 @WebMvcTest 编写 Controller 测试
- 测试覆盖率 > 80%
性能
- 在适当情况下启用缓存
- 为长时间运行的操作进行异步处理
- 正确配置延迟加载
- 为频繁查询的字段添加数据库索引
代码质量
- 不存在代码重复
- 类、方法和变量使用有意义的名称
- 方法职责明确且长度适中
- 正确记录日志(不使用 System.out.println)
- 不保留已注释掉的代码
相关技能
- uncle-duke-java:使用此技能作为参考的 Java 代码审查 Agent
参考资料
官方文档
最佳实践指南
版本: 1.0 最后更新: 2025-12-24 维护者: 开发团队