关于 spring-boot-development
spring-boot-development 是指导 Spring Boot 应用开发的技能,涵盖自动配置、依赖注入、REST API、数据访问和安全配置。适合构建企业后端、微服务及数据驱动应用的开发者。
主要功能
- 01自动配置与依赖注入
- 02Spring MVC REST API
- 03Spring Data JPA 数据访问
- 04配置文件与环境配置
- 05Spring Security 认证授权
- 06测试与开发实践
适用场景
- 01构建企业 REST API
- 02开发连接数据库的应用
- 03配置认证与授权
- 04为服务编写单元及集成测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ manutej/ luxor-claude-marketplace --skill spring-boot-development安装后在 Claude Code 中输入/spring-boot-development确认已加载。
附带文件EXAMPLES.mdREADME.md
详细介绍
Spring Boot 开发技能
此技能基于官方 Spring Boot 文档,全面介绍如何使用自动配置、依赖注入、REST API、Spring Data、Spring Security 和企业级 Java 模式构建现代 Spring Boot 应用。
何时使用此技能
在以下情况下使用此技能:
- 构建企业级 REST API 和微服务
- 使用 Spring MVC 创建 Web 应用
- 使用 JPA 和数据库开发数据驱动型应用
- 使用 Spring Security 实现身份验证和授权
- 使用 Actuator 和监控构建可用于生产环境的应用
- 使用 Spring Boot 创建可扩展的后端服务
- 从传统 Spring 迁移到 Spring Boot
- 开发云原生应用
- 使用消息传递构建事件驱动系统
- 创建批处理应用
核心概念
自动配置
Spring Boot 会根据项目中添加的依赖自动配置应用,从而大幅减少样板配置。
自动配置的工作原理:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@SpringBootApplication 注解由以下注解组合而成:
@Configuration:将类标记为 Bean 定义的来源@EnableAutoConfiguration:启用 Spring Boot 的自动配置机制@ComponentScan:启用当前包及其子包中的组件扫描
条件自动配置:
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.url")
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
自定义自动配置:
// Exclude specific auto-configurations
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication {
// ...
}
// Or in application.properties
// spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
依赖注入
Spring 的 IoC(控制反转)容器负责管理对象的创建和依赖注入。
构造函数注入(推荐):
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// Constructor injection - recommended approach
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User createUser(User user) {
User saved = userRepository.save(user);
emailService.sendWelcomeEmail(saved);
return saved;
}
}
字段注入(不推荐):
@Service
public class UserService {
@Autowired // Avoid field injection
private UserRepository userRepository;
// Difficult to test and creates tight coupling
}
Setter 注入(可选依赖):
@Service
public class UserService {
private UserRepository userRepository;
private EmailService emailService;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired(required = false)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
组件构造型注解:
@Component // Generic component
public class MyComponent { }
@Service // Business logic layer
public class MyService { }
@Repository // Data access layer
public class MyRepository { }
@Controller // Presentation layer (web)
public class MyController { }
@RestController // REST API controller
public class MyRestController { }
Spring Web(REST API)
使用 Spring MVC 注解构建 RESTful Web 服务。
基本 REST 控制器:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody @Valid User user) {
User created = userService.save(user);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id,
@RequestBody @Valid User user) {
return userService.update(id, user)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (userService.delete(id)) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
请求映射形式:
@RestController
@RequestMapping("/api/products")
public class ProductController {
// Query parameters
@GetMapping("/search")
public List<Product> search(@RequestParam String name,
@RequestParam(required = false) String category) {
return productService.search(name, category);
}
// Multiple path variables
@GetMapping("/categories/{categoryId}/products/{productId}")
public Product getProductInCategory(@PathVariable Long categoryId,
@PathVariable Long productId) {
return productService.findInCategory(categoryId, productId);
}
// Request headers
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id,
@RequestHeader("Accept-Language") String language) {
return productService.find(id, language);
}
// Matrix variables
@GetMapping("/{id}")
public Product getProductWithMatrix(@PathVariable Long id,
@MatrixVariable Map<String, String> filters) {
return productService.findWithFilters(id, filters);
}
}
响应处理:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
// Return different status codes
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
Order created = orderService.create(order);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
// Custom headers
@GetMapping("/{id}")
public ResponseEntity<Order> getOrder(@PathVariable Long id) {
Order order = orderService.findById(id);
return ResponseEntity.ok()
.header("X-Order-Version", order.getVersion().toString())
.body(order);
}
// No content response
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
orderService.delete(id);
return ResponseEntity.noContent().build();
}
}
Spring Data JPA
Spring Data JPA 为数据库访问提供仓库抽象。
实体定义:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String name;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and setters
}
仓库接口:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Query method - Spring Data generates implementation
Optional<User> findByEmail(String email);
List<User> findByNameContaining(String name);
List<User> findByDepartmentId(Long departmentId);
// Custom JPQL query
@Query("SELECT u FROM User u WHERE u.email = ?1")
Optional<User> findByEmailQuery(String email);
// Named parameters
@Query("SELECT u FROM User u WHERE u.name LIKE %:name% AND u.department.id = :deptId")
List<User> searchByNameAndDepartment(@Param("name") String name,
@Param("deptId") Long deptId);
// Native SQL query
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
Optional<User> findByEmailNative(String email);
// Modifying query
@Modifying
@Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
int updateUserName(@Param("id") Long id, @Param("name") String name);
// Pagination and sorting
Page<User> findByDepartmentId(Long departmentId, Pageable pageable);
List<User> findByNameContaining(String name, Sort sort);
}
仓库用法:
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
public User save(User user) {
return userRepository.save(user);
}
public List<User> findAll() {
return userRepository.findAll();
}
public Page<User> findAll(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("name"));
return userRepository.findAll(pageable);
}
public boolean delete(Long id) {
if (userRepository.existsById(id)) {
userRepository.deleteById(id);
return true;
}
return false;
}
}
关系:
// One-to-Many
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
}
// Many-to-Many
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
配置
Spring Boot 使用 application.properties 或 application.yml 进行配置。
应用属性:
# Server configuration
server.port=8080
server.servlet.context-path=/api
# Database configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=user
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
# JPA configuration
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# Logging
logging.level.root=INFO
logging.level.com.example=DEBUG
logging.level.org.springframework.web=DEBUG
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# Custom properties
app.name=My Application
app.version=1.0.0
应用 YAML:
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: password
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: validate
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
logging:
level:
root: INFO
com.example: DEBUG
org.springframework.web: DEBUG
app:
name: My Application
version: 1.0.0
配置属性类:
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private String name;
private String version;
private Security security = new Security();
public static class Security {
private int tokenExpiration = 3600;
private String secretKey;
// Getters and setters
}
// Getters and setters
}
// Usage
@Service
public class MyService {
private final AppConfig appConfig;
public MyService(AppConfig appConfig) {
this.appConfig = appConfig;
}
public void printConfig() {
System.out.println("App: " + appConfig.getName());
System.out.println("Version: " + appConfig.getVersion());
}
}
特定环境的配置:
# application.properties (default)
spring.profiles.active=dev
# application-dev.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb_dev
logging.level.root=DEBUG
# application-prod.properties
spring.datasource.url=jdbc:postgresql://prod-server:5432/mydb_prod
logging.level.root=WARN
特定配置文件的 Bean:
@Configuration
public class DatabaseConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
return DataSourceBuilder.create().build();
}
}
Spring Security
在应用中实现身份验证和授权。
基本安全配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.httpBasic();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
内存身份验证:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder.encode("password"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder.encode("admin"))
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
数据库身份验证:
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getEmail())
.password(user.getPassword())
.roles(user.getRoles().toArray(new String[0]))
.build();
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomUserDetailsService userDetailsService;
public SecurityConfig(CustomUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public DaoAuthenticationProvider authenticationProvider(PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
}
JWT 身份验证:
@Component
public class JwtTokenProvider {
@Value("${app.security.jwt.secret}")
private String jwtSecret;
@Value("${app.security.jwt.expiration}")
private int jwtExpiration;
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration);
return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public Long getUserIdFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException | MalformedJwtException | ExpiredJwtException |
UnsupportedJwtException | IllegalArgumentException ex) {
return false;
}
}
}
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
private final CustomUserDetailsService customUserDetailsService;
public JwtAuthenticationFilter(JwtTokenProvider tokenProvider,
CustomUserDetailsService customUserDetailsService) {
this.tokenProvider = tokenProvider;
this.customUserDetailsService = customUserDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (jwt != null && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
API 参考
常用注解
Spring 核心注解:
@SpringBootApplication:主应用类@Component:通用组件@Service:服务层组件@Repository:数据访问层组件@Configuration:配置类@Bean:Bean 定义方法@Autowired:依赖注入@Value:注入属性值@Profile:基于配置文件的条件 Bean
Web 注解:
@RestController:REST API 控制器@Controller:MVC 控制器@RequestMapping:映射 HTTP 请求@GetMapping:映射 GET 请求@PostMapping:映射 POST 请求@PutMapping:映射 PUT 请求@DeleteMapping:映射 DELETE 请求@PatchMapping:映射 PATCH 请求@PathVariable:提取路径变量@RequestParam:提取查询参数@RequestBody:提取请求体@RequestHeader:提取请求头@ResponseStatus:设置响应状态
数据注解:
@Entity:JPA 实体@Table:表映射@Id:主键@GeneratedValue:自动生成的值@Column:列映射@OneToOne:一对一关系@OneToMany:一对多关系@ManyToOne:多对一关系@ManyToMany:多对多关系@JoinColumn:连接列@JoinTable:连接表
验证注解:
@Valid:启用验证@NotNull:字段不能为 null@NotEmpty:字段不能为空@NotBlank:字段不能为全空白@Size:字符串或集合的大小@Min:最小值@Max:最大值@Email:电子邮件格式@Pattern:正则表达式模式
事务注解:
@Transactional:启用事务管理@Transactional(readOnly = true):只读事务
安全注解:
@EnableWebSecurity:启用安全功能@PreAuthorize:方法级授权@PostAuthorize:方法执行后授权@Secured:基于角色的访问控制
异步与调度:
@EnableAsync:启用异步处理@Async:异步方法@EnableScheduling:启用调度@Scheduled:定时方法
工作流模式
REST API 设计模式
完整的 CRUD REST API:
// Entity
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Name is required")
private String name;
@NotBlank(message = "Description is required")
private String description;
@NotNull(message = "Price is required")
@Min(value = 0, message = "Price must be positive")
private BigDecimal price;
@NotNull(message = "Stock is required")
@Min(value = 0, message = "Stock must be positive")
private Integer stock;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and setters
}
// Repository
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByNameContaining(String name);
List<Product> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice);
}
// Service
@Service
@Transactional
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Transactional(readOnly = true)
public Page<Product> findAll(Pageable pageable) {
return productRepository.findAll(pageable);
}
@Transactional(readOnly = true)
public Optional<Product> findById(Long id) {
return productRepository.findById(id);
}
public Product create(Product product) {
return productRepository.save(product);
}
public Optional<Product> update(Long id, Product productDetails) {
return productRepository.findById(id)
.map(product -> {
product.setName(productDetails.getName());
product.setDescription(productDetails.getDescription());
product.setPrice(productDetails.getPrice());
product.setStock(productDetails.getStock());
return productRepository.save(product);
});
}
public boolean delete(Long id) {
return productRepository.findById(id)
.map(product -> {
productRepository.delete(product);
return true;
})
.orElse(false);
}
}
// Controller
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public Page<Product> getAllProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return productService.findAll(pageable);
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) {
Product created = productService.create(product);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(
@PathVariable Long id,
@Valid @RequestBody Product product) {
return productService.update(id, product)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
if (productService.delete(id)) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
异常处理模式
全局异常处理器:
// Custom exceptions
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
// Error response
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
// Constructors, getters, setters
}
// Global exception handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(
ResourceNotFoundException ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.NOT_FOUND.value(),
"Not Found",
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequest(
BadRequestException ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.BAD_REQUEST.value(),
"Bad Request",
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationErrors(
MethodArgumentNotValidException ex) {
Map<String, Object> errors = new HashMap<>();
errors.put("timestamp", LocalDateTime.now());
errors.put("status", HttpStatus.BAD_REQUEST.value());
Map<String, String> fieldErrors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
fieldErrors.put(error.getField(), error.getDefaultMessage())
);
errors.put("errors", fieldErrors);
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(
Exception ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal Server Error",
ex.getMessage(),
request.getDescription(false).replace("uri=", "")
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
数据库集成模式
完整的数据库设置:
// application.yml
/*
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: password
jpa:
hibernate:
ddl-auto: validate
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
*/
// Flyway migrations (db/migration/V1__Create_users_table.sql)
/*
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE INDEX idx_users_email ON users(email);
*/
// Entity with auditing
@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String password;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Getters and setters
}
// Enable JPA auditing
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}
测试模式
单元测试:
@SpringBootTest
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testFindById_Success() {
User user = new User();
user.setId(1L);
user.setEmail("[email protected]");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
Optional<User> result = userService.findById(1L);
assertTrue(result.isPresent());
assertEquals("[email protected]", result.get().getEmail());
verify(userRepository, times(1)).findById(1L);
}
@Test
void testFindById_NotFound() {
when(userRepository.findById(1L)).thenReturn(Optional.empty());
Optional<User> result = userService.findById(1L);
assertFalse(result.isPresent());
}
}
集成测试:
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@Test
void testCreateUser_Success() throws Exception {
User user = new User();
user.setEmail("[email protected]");
user.setName("Test User");
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.email").value("[email protected]"))
.andExpect(jsonPath("$.name").value("Test User"));
}
@Test
void testGetUser_Success() throws Exception {
User user = new User();
user.setEmail("[email protected]");
user.setName("Test User");
User saved = userRepository.save(user);
mockMvc.perform(get("/api/users/" + saved.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(saved.getId()))
.andExpect(jsonPath("$.email").value("[email protected]"));
}
@Test
void testGetUser_NotFound() throws Exception {
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound());
}
}
最佳实践
1. 使用构造函数注入
构造函数注入是推荐的依赖注入方式。
// Good - Constructor injection
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}
// Bad - Field injection
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
2. 对 API 请求/响应使用 DTO
不要通过 REST API 直接公开实体。
// DTO
public class UserDTO {
private Long id;
private String email;
private String name;
// No password field exposed
// Getters and setters
}
// Mapper
@Component
public class UserMapper {
public UserDTO toDTO(User user) {
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setEmail(user.getEmail());
dto.setName(user.getName());
return dto;
}
public User toEntity(UserDTO dto) {
User user = new User();
user.setEmail(dto.getEmail());
user.setName(dto.getName());
return user;
}
}
// Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
private final UserMapper userMapper;
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(userMapper::toDTO)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
3. 使用验证
始终验证输入数据。
// Entity with validation
@Entity
public class User {
@NotBlank(message = "Email is required")
@Email(message = "Email should 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;
}
// Controller
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
// Validation happens automatically
return ResponseEntity.ok(userService.save(user));
}
4. 正确使用事务
为服务方法设置适当的事务配置。
@Service
@Transactional
public class OrderService {
@Transactional(readOnly = true)
public List<Order> findAll() {
return orderRepository.findAll();
}
@Transactional
public Order createOrder(Order order) {
// Multiple database operations in one transaction
Order saved = orderRepository.save(order);
inventoryService.decreaseStock(order.getItems());
emailService.sendOrderConfirmation(saved);
return saved;
}
}
5. 使用分页
始终对大型数据集进行分页。
@GetMapping
public Page<Product> getProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return productService.findAll(pageable);
}
6. 全局处理异常
使用 @RestControllerAdvice 集中处理异常。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(ex.getMessage()));
}
}
7. 使用日志记录
在整个应用中实现适当的日志记录。
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public User createUser(User user) {
logger.info("Creating user with email: {}", user.getEmail());
try {
User saved = userRepository.save(user);
logger.info("User created successfully with id: {}", saved.getId());
return saved;
} catch (Exception e) {
logger.error("Error creating user: {}", e.getMessage(), e);
throw e;
}
}
}
8. 保护你的端点
实现适当的身份验证和授权。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer().jwt();
return http.build();
}
}
9. 使用数据库迁移
使用 Flyway 或 Liquibase 进行数据库版本控制。
-- V1__Create_users_table.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL
);
-- V2__Add_password_column.sql
ALTER TABLE users ADD COLUMN password VARCHAR(255);
10. 监控你的应用
使用 Spring Boot Actuator 进行监控。
# application.properties
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
示例
有关详细代码示例,请参阅 EXAMPLES.md,内容包括:
- 基础 Spring Boot 应用
- 带 CRUD 操作的 REST API
- 使用 JPA 集成数据库
- 自定义查询与规范
- 请求验证
- 异常处理
- 使用 JWT 进行身份验证
- 基于角色的授权
- 文件上传/下载
- 使用 Redis 进行缓存
- 异步处理
- 定时任务
- 多数据库配置
- Actuator 与监控
- Docker 部署
概述
此 Spring Boot 开发技能涵盖:
- 自动配置:根据依赖项自动配置
- 依赖注入:IoC 容器、构造函数注入、组件构造型注解
- REST API:控制器、请求映射、响应处理
- Spring Data JPA:实体、存储库、关系、查询
- 配置:属性、YAML、配置文件、自定义属性
- 安全:身份验证、授权、JWT、基于角色的访问控制
- 异常处理:全局异常处理、自定义异常
- 测试:单元测试、集成测试、MockMvc
- 最佳实践:DTO、验证、事务、分页、日志记录
- 生产就绪:Actuator、监控、数据库迁移、部署
这些模式和示例基于 Spring Boot 官方文档(可信度评分:7.5),体现了现代企业级 Java 开发实践。