文章目录
- 1. 前言
- 2. 核心注解
- 2.1 @SpringBootApplication
- 2.2 @Configuration
- 2.3 @EnableAutoConfiguration
- 2.4 @ComponentScan
- 2.5 @Bean
- 2.6 @Autowired
- 2.7 @Qualifier
- 2.8 @Primary
- 2.9 @Value
- 2.10 @PropertySource
- 2.11 @ConfigurationProperties
- 2.12 @Profile
- 3. Web开发相关注解
- 3.1 @Controller
- 3.2 @RestController
- 3.3 @RequestMapping
- 3.4 @GetMapping、@PostMapping等
- 3.5 @PathVariable
- 3.6 @RequestParam
- 3.7 @RequestBody
- 3.8 @ResponseBody
- 3.9 @ResponseStatus
- 3.10 @ExceptionHandler
- 3.11 @ControllerAdvice / @RestControllerAdvice
- 4. 数据访问相关注解
- 4.1 @Repository
- 4.2 @Entity
- 4.3 @Table
- 4.4 @Id和@GeneratedValue
- 4.5 @Column
- 4.6 @Transactional
- 4.7 Spring Data JPA相关注解
- 4.7.1 @Query
- 4.7.2 @Param
- 4.7.3 @Modifying
- 4.8 Mybatis相关注解
- 4.8.1 @Mapper
- 4.8.2 @Select、@Insert、@Update、@Delete
- 4.8.3 @Results和@Result
- 5. 安全相关注解
- 5.1 @EnableWebSecurity
- 5.2 @PreAuthorize和@PostAuthorize
- 5.3 @Secured
- 6. 测试相关注解
- 6.1 @SpringBootTest
- 6.2 @WebMvcTest
- 6.3 @DataJpaTest
- 6.4 @MockBean
- 6.5 @SpyBean
- 7. 总结
- 8. 参考资料
1. 前言
Spring Boot是基于Spring框架的快速开发套件,大量使用注解来简化配置和开发。本文档详细介绍SpringBoot中常用的注解,对每个注解的功能、使用场景和示例代码进行全面解析,帮助新手开发者快速掌握SpringBoot开发。
注解是Java代码的元数据,不会直接影响程序执行,但会被框架读取并用于控制应用行为。SpringBoot注解极大地减少了XML配置的繁琐,使代码更加简洁优雅。
2. 核心注解
2.1 @SpringBootApplication
这是SpringBoot最核心的注解,用于标记SpringBoot应用程序的入口类。它结合了三个注解的功能:
@Configuration
:将类标记为应用上下文的Bean定义源@EnableAutoConfiguration
:启用SpringBoot的自动配置机制@ComponentScan
:启用组件扫描,自动发现和注册Bean
语法:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
详细解析:
@SpringBootApplication
使用时可以自定义属性来调整行为:
@SpringBootApplication(
scanBasePackages = {"com.example.project", "org.example.another"},
exclude = {DataSourceAutoConfiguration.class},
excludeName = {"org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration"}
)
public class CustomizedApplication {
public static void main(String[] args) {
SpringApplication.run(CustomizedApplication.class, args);
}
}
scanBasePackages
:指定要扫描的包路径,默认只扫描注解所在类的包及其子包exclude
:排除特定的自动配置类excludeName
:通过类名排除特定的自动配置类
实践建议:
- 将
@SpringBootApplication
注解的类保持简洁,仅包含必要的启动代码 - 入口类通常放在根包(其他类的父包)下,这样默认组件扫描会涵盖所有项目类
- 如果需要自定义扫描范围或排除某些配置,使用注解的属性而不是创建单独的配置类
2.2 @Configuration
标记一个类作为Bean定义的来源,类似于传统Spring的XML配置文件。
语法:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
详细解析:
@Configuration
类中的@Bean
方法会被Spring容器处理,创建并管理返回的对象实例。配置类本身也会作为Bean管理。
默认情况下,@Configuration
类使用CGLIB代理增强,确保@Bean
方法之间的引用始终返回同一个实例:
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.postgresql.Driver");
ds.setUrl("jdbc:postgresql://localhost:5432/mydb");
ds.setUsername("user");
ds.setPassword("pass");
return ds;
}
@Bean
public JdbcTemplate jdbcTemplate() {
// 多次调用dataSource()将返回相同实例
return new JdbcTemplate(dataSource());
}
}
特殊选项:
@Configuration(proxyBeanMethods = false)
:使用lite模式,不代理方法,提高性能但不保证Bean引用的一致性
使用场景:
- 创建第三方库中类的Beans
- 定义复杂的Bean初始化逻辑
- 管理Bean之间的依赖关系
2.3 @EnableAutoConfiguration
启用SpringBoot的自动配置机制,根据类路径上的依赖自动配置Spring应用。
语法:
@EnableAutoConfiguration
public class MyConfig {
// ...
}
通常不直接使用,而是作为@SpringBootApplication
的一部分。
工作原理:
- 搜索类路径下所有的
META-INF/spring.factories
文件 - 查找
org.springframework.boot.autoconfigure.EnableAutoConfiguration
属性定义的配置类 - 应用这些配置类,但会根据条件注解(如
@ConditionalOnClass
)决定是否启用
自定义:
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class CustomAutoConfig {
// ...
}
2.4 @ComponentScan
配置组件扫描指令,自动发现并注册标有@Component
、@Service
、@Repository
、@Controller
等注解的类。
语法:
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// ...
}
详细解析:
可以指定多种扫描属性:
@ComponentScan(
basePackages = {"com.example.service", "com.example.repository"},
basePackageClasses = {UserService.class, ProductRepository.class},
includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*ServiceImpl"),
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = LegacyService.class)
)
public class DetailedScanConfig {
// ...
}
basePackages
:要扫描的包名basePackageClasses
:标记性类,将扫描这些类所在的包includeFilters
:指定包含哪些类excludeFilters
:指定排除哪些类useDefaultFilters
:是否使用默认过滤器(默认为true)
2.5 @Bean
在@Configuration
类中标记方法,告诉Spring该方法将返回一个应注册为Spring容器中Bean的对象。
语法:
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
详细属性:
@Configuration
public class DetailedBeanConfig {
@Bean(
name = "customBeanName",
initMethod = "init",
destroyMethod = "cleanup",
autowireCandidate = true
)
@Description("这是一个示例Bean")
@Scope("prototype")
public ComplexBean complexBean() {
return new ComplexBean();
}
}
name
/value
:指定Bean的名称(别名)initMethod
:初始化方法名称destroyMethod
:销毁方法名称(默认会自动检测close或shutdown方法)autowireCandidate
:是否作为自动装配的候选- 可与
@Scope
、@Description
、@Lazy
等注解组合使用
实践示例:
@Configuration
public class ServiceConfig {
@Autowired
private DatabaseProperties dbProps;
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(dbProps.getUrl());
config.setUsername(dbProps.getUsername());
config.setPassword(dbProps.getPassword());
config.setMaximumPoolSize(dbProps.getMaxPoolSize());
return new HikariDataSource(config);
}
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("users", "transactions");
}
}
2.6 @Autowired
自动装配依赖的Bean。Spring会在应用上下文中查找匹配的Bean并注入。
语法:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 方法也可以使用@Autowired
@Autowired
public void setMessageService(MessageService messageService) {
this.messageService = messageService;
}
}
详细用法:
- 构造函数注入(推荐):
@Service
public class ProductService {
private final ProductRepository repository;
private final PriceCalculator calculator;
@Autowired // 在Spring 4.3+,如果只有一个构造函数,@Autowired可以省略
public ProductService(ProductRepository repository, PriceCalculator calculator) {
this.repository = repository;
this.calculator = calculator;
}
}
- 字段注入:
@Controller
public class UserController {
@Autowired
private UserService userService;
@Autowired
@Qualifier("mainAuthenticator") // 当有多个同类型Bean时,指定具体实现
private Authenticator authenticator;
}
- 可选依赖:
@Component
public class OptionalDependencyExample {
@Autowired(required = false) // 依赖不存在也不会报错
private OptionalService optionalService;
public void doWork() {
if (optionalService != null) {
optionalService.perform();
} else {
// 备选逻辑
}
}
}
最佳实践:
- 使用构造函数注入,而不是字段注入
- 对于必需的依赖,构造函数注入可以确保在创建Bean时就完成注入
- 使用final字段防止依赖被修改
- 当有多个相同类型的Bean时,使用
@Qualifier
或@Primary
解决冲突
2.7 @Qualifier
当有多个相同类型的Bean时,@Qualifier
用于指定要注入的特定Bean。
语法:
@Component
public class PaymentProcessor {
@Autowired
@Qualifier("visaPaymentGateway")
private PaymentGateway paymentGateway;
}
对应的Bean定义:
@Component("visaPaymentGateway")
public class VisaPaymentGateway implements PaymentGateway {
// 实现
}
@Component("paypalPaymentGateway")
public class PayPalPaymentGateway implements PaymentGateway {
// 实现
}
构造函数参数限定:
@Service
public class CheckoutService {
private final PaymentGateway paymentGateway;
private final ShippingCalculator shippingCalculator;
@Autowired
public CheckoutService(
@Qualifier("visaPaymentGateway") PaymentGateway paymentGateway,
@Qualifier("internationalShipping") ShippingCalculator shippingCalculator) {
this.paymentGateway = paymentGateway;
this.shippingCalculator = shippingCalculator;
}
}
2.8 @Primary
标记首选的Bean,当存在多个相同类型的Bean时,优先注入带有@Primary
注解的Bean。
语法:
@Component
@Primary
public class MainUserRepository implements UserRepository {
// 优先使用的实现
}
@Component
public class SecondaryUserRepository implements UserRepository {
// 备用实现
}
自动注入时:
@Service
public class DefaultUserService {
private final UserRepository userRepository;
@Autowired
public DefaultUserService(UserRepository userRepository) {
// 将自动注入带有@Primary的MainUserRepository
this.userRepository = userRepository;
}
}
与@Qualifier结合:
@Primary
提供了系统默认值,而@Qualifier
允许在特定情况下覆盖默认行为:
@Service
public class SpecialUserService {
private final UserRepository userRepository;
@Autowired
public SpecialUserService(@Qualifier("secondaryUserRepository") UserRepository userRepository) {
// 明确指定使用非主要的实现
this.userRepository = userRepository;
}
}
2.9 @Value
注入外部化配置值,如属性文件、环境变量或系统属性中的值。
语法:
@Component
public class DatabaseSettings {
@Value("${database.url}")
private String databaseUrl;
@Value("${database.username:root}") // 默认值为root
private String username;
@Value("#{systemProperties['java.version']}") // SpEL表达式
private String javaVersion;
@Value("#{new java.util.Random().nextInt(100)}") // 计算值
private int randomNumber;
}
支持的用法:
- 属性占位符:
@Value("${app.timeout:30}") // 从配置中读取app.timeout,默认值为30
private int timeout;
- SpEL表达式:
@Value("#{environment['USER'] ?: 'Unknown'}") // 环境变量USER,如不存在则为'Unknown'
private String user;
@Value("#{configProperties.getProperty('app.max-connections')}") // 调用方法
private Integer maxConnections;
- 直接值:
@Value("true") // 布尔值
private boolean enabled;
@Value("100") // 整数
private int max;
- 数组和集合:
@Value("${app.servers:localhost:8080,localhost:8081}")
private String[] serverArray;
@Value("#{'${app.regions}'.split(',')}")
private List<String> regionList;
配置文件示例(application.properties):
database.url=jdbc:mysql://localhost:3306/myapp
database.username=admin
app.timeout=60
app.servers=server1.example.com,server2.example.com
app.regions=US,EU,ASIA
2.10 @PropertySource
指定属性文件的位置,用于加载外部配置。
语法:
@Configuration
@PropertySource("classpath:database.properties")
public class DatabaseConfig {
@Autowired
private Environment environment;
@Value("${db.driver}")
private String driver;
@Bean
public DataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName(environment.getProperty("db.driver"));
ds.setUrl(environment.getProperty("db.url"));
ds.setUsername(environment.getProperty("db.username"));
ds.setPassword(environment.getProperty("db.password"));
return ds;
}
}
多属性源:
@Configuration
@PropertySource({
"classpath:database.properties",
"classpath:application-${spring.profiles.active}.properties",
"file:/opt/config/external.properties"
})
public class MultiplePropertiesConfig {
// ...
}
自定义编码和工厂:
@Configuration
@PropertySource(
value = "classpath:i18n/messages_zh_CN.properties",
encoding = "UTF-8",
factory = YamlPropertySourceFactory.class
)
public class InternationalConfig {
// ...
}
使用环境:
@Component
public class DatabaseService {
private final Environment env;
@Autowired
public DatabaseService(Environment env) {
this.env = env;
}
public void printConfig() {
System.out.println("Driver: " + env.getProperty("db.driver"));
System.out.println("URL: " + env.getProperty("db.url"));
System.out.println("Timeout: " + env.getProperty("db.timeout", Integer.class, 30));
}
}
2.11 @ConfigurationProperties
将配置属性绑定到结构化的Java对象。
语法:
@Component
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
private String host;
private int port = 25; // 默认值
private String username;
private String password;
private boolean ssl = false;
private List<String> recipients = new ArrayList<>();
private Map<String, String> headers = new HashMap<>();
// Getters and setters...
}
对应的配置(application.yml):
app:
mail:
host: smtp.example.com
port: 587
username: user@example.com
password: secret
ssl: true
recipients:
- admin@example.com
- support@example.com
headers:
X-Priority: High
X-Mailer: MyApp
与@Bean结合使用:
@Configuration
public class AppConfig {
@Bean
@ConfigurationProperties(prefix = "datasource")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
@Bean
public DataSource dataSource(HikariConfig config) {
return new HikariDataSource(config);
}
}
嵌套属性类:
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String description;
private final Security security = new Security();
// Getters and setters...
public static class Security {
private boolean enabled;
private String tokenSecret;
private int tokenExpirySeconds = 3600;
// Getters and setters...
}
}
对应的配置:
app:
name: MyApplication
description: Sample SpringBoot Application
security:
enabled: true
token-secret: abc123xyz789
token-expiry-seconds: 7200
验证:
@Component
@ConfigurationProperties(prefix = "app.server")
@Validated
public class ServerProperties {
@NotEmpty
private String host;
@Min(1024)
@Max(65535)
private int port;
@Pattern(regexp = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")
private String ipAddress;
// Getters and setters...
}
启用配置属性支持:
在启动类上使用:
@SpringBootApplication
@EnableConfigurationProperties
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
2.12 @Profile
指定组件在特定的环境配置文件激活时才加载。
语法:
@Configuration
@Profile("development")
public class DevelopmentConfig {
@Bean
public DataSource devDataSource() {
// 返回开发环境的数据源配置
}
}
@Configuration
@Profile("production")
public class ProductionConfig {
@Bean
public DataSource prodDataSource() {
// 返回生产环境的数据源配置
}
}
在Bean级别使用:
@Configuration
public class AppConfig {
@Bean
@Profile("development")
public EmailService mockEmailService() {
return new MockEmailService();
}
@Bean
@Profile("production")
public EmailService smtpEmailService() {
return new SmtpEmailService();
}
}
使用表达式:
@Component
@Profile("!development") // 非开发环境
public class RealPaymentProcessor implements PaymentProcessor {
// 实现
}
@Component
@Profile("development | test") // 开发或测试环境
public class MockPaymentProcessor implements PaymentProcessor {
// 实现
}
设置激活的配置文件:
- application.properties 文件:
spring.profiles.active=development
- 命令行参数:
java -jar myapp.jar --spring.profiles.active=production
- 环境变量:
export SPRING_PROFILES_ACTIVE=production
java -jar myapp.jar
- 编程方式:
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.setAdditionalProfiles("production");
app.run(args);
}
3. Web开发相关注解
3.1 @Controller
标记一个类作为Spring MVC控制器,处理HTTP请求。
语法:
@Controller
public class UserController {
@GetMapping("/users")
public String listUsers(Model model) {
model.addAttribute("users", userService.findAll());
return "users/list"; // 返回视图名称
}
}
详细解析:
@Controller
注解的类会被自动检测并注册为Spring Bean。它主要用于处理视图和模型,默认方法返回视图名称。
3.2 @RestController
结合了@Controller
和@ResponseBody
,专门用于构建RESTful API。
语法:
@RestController
@RequestMapping("/api/users")
public class UserRestController {
@GetMapping
public List<User> getAllUsers() {
return userService.findAll(); // 直接返回对象,会自动转换为JSON
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
详细解析:
@RestController
中的方法返回值会直接序列化到HTTP响应体中,通常是JSON格式- 不需要在每个方法上添加
@ResponseBody
注解 - 适用于构建API而不是返回视图的应用
3.3 @RequestMapping
映射HTTP请求到控制器的方法。
语法:
@Controller
@RequestMapping("/users") // 类级别的基本路径
public class UserController {
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String listUsers(Model model) {
// 处理 GET /users/list
return "users/list";
}
@RequestMapping(
path = "/save",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.TEXT_HTML_VALUE
)
public String saveUser(User user) {
// 处理 POST /users/save
return "redirect:/users/list";
}
}
详细属性:
value
/path
:URL路径method
:HTTP方法(GET、POST、PUT、DELETE等)params
:请求必须包含的参数headers
:请求必须包含的头信息consumes
:请求的内容类型produces
:响应的内容类型
特殊用法:
@Controller
public class AdvancedMappingController {
// 多路径映射
@RequestMapping({"/home", "/index", "/"})
public String home() {
return "home";
}
// 参数条件
@RequestMapping(path = "/search", params = "type=user")
public String searchUsers() {
return "users/search";
}
// 请求头条件
@RequestMapping(path = "/api/data", headers = "X-API-VERSION=1")
@ResponseBody
public ApiData getDataV1() {
return new ApiData("v1");
}
}
3.4 @GetMapping、@PostMapping等
@RequestMapping
的HTTP方法特定变体,让代码更加简洁明了。
语法:
@RestController
@RequestMapping("/api/products")
public class ProductController {
// 等同于 @RequestMapping(method = RequestMethod.GET)
@GetMapping
public List<Product> getAllProducts() {
return productService.findAll();
}
// 等同于 @RequestMapping(path = "/{id}", method = RequestMethod.GET)
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
// POST请求
@PostMapping
public Product createProduct(@RequestBody Product product) {
return productService.save(product);
}
// PUT请求
@PutMapping("/{id}")
public Product updateProduct(@PathVariable Long id, @RequestBody Product product) {
product.setId(id);
return productService.update(product);
}
// DELETE请求
@DeleteMapping("/{id}")
public void deleteProduct(@PathVariable Long id) {
productService.deleteById(id);
}
// PATCH请求
@PatchMapping("/{id}")
public Product patchProduct(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return productService.partialUpdate(id, updates);
}
}
可用注解:
@GetMapping
:处理GET请求,通常用于获取资源@PostMapping
:处理POST请求,通常用于创建资源@PutMapping
:处理PUT请求,通常用于完全更新资源@DeleteMapping
:处理DELETE请求,通常用于删除资源@PatchMapping
:处理PATCH请求,通常用于部分更新资源
详细配置:
@GetMapping(
path = "/export",
params = "format=pdf",
produces = MediaType.APPLICATION_PDF_VALUE
)
public ResponseEntity<byte[]> exportPdf() {
byte[] content = generatePdfContent();
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", "report.pdf");
return new ResponseEntity<>(content, headers, HttpStatus.OK);
}
3.5 @PathVariable
将URL路径变量绑定到方法参数。
语法:
@GetMapping("/users/{id}/posts/{postId}")
public Post getUserPost(
@PathVariable Long id,
@PathVariable Long postId
) {
return postService.findByUserIdAndPostId(id, postId);
}
详细用法:
- 自定义变量名:
@GetMapping("/users/{userId}/roles/{roleId}")
public String assignRole(
@PathVariable("userId") Long id,
@PathVariable("roleId") Long roleId
) {
userService.assignRole(id, roleId);
return "redirect:/users";
}
- 可选路径变量:
@GetMapping({"/orders/{id}", "/orders"})
public List<Order> getOrders(@PathVariable(required = false) Long id) {
if (id != null) {
return Collections.singletonList(orderService.findById(id));
}
return orderService.findAll();
}
- Map中捕获所有变量:
@GetMapping("/users/{country}/{city}/{name}")
public User findUser(@PathVariable Map<String, String> pathVars) {
String country = pathVars.get("country");
String city = pathVars.get("city");
String name = pathVars.get("name");
return userService.findByCountryAndCityAndName(country, city, name);
}
注意事项:
- 路径变量名必须与URL路径占位符匹配,除非使用
name
属性明确指定 - 如果URL模板中的某个变量不是必需的,确保使用多个
@GetMapping
路径或将required
设置为false
- Spring会自动转换简单类型(如String、Integer、Long)的路径变量
3.6 @RequestParam
将请求参数绑定到方法参数。
语法:
@GetMapping("/search")
public List<Product> searchProducts(
@RequestParam String query,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return productService.search(query, page, size);
}
详细用法:
- 自定义参数名:
@GetMapping("/filter")
public List<User> filterUsers(
@RequestParam("q") String query,
@RequestParam("pg") int page
) {
return userService.filter(query, page);
}
- 可选参数:
@GetMapping("/products")
public List<Product> getProducts(
@RequestParam(required = false) String category,
@RequestParam(required = false) Double minPrice
) {
return productService.findByCriteria(category, minPrice);
}
- 设置默认值:
@GetMapping("/employees")
public List<Employee> getEmployees(
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String order
) {
return employeeService.findAllSorted(sortBy, order);
}
- 映射所有参数:
@PostMapping("/process")
public void processForm(@RequestParam Map<String, String> formParams) {
formService.process(formParams);
}
- 多值参数:
@GetMapping("/filter")
public List<Product> filterProducts(
@RequestParam List<String> categories,
@RequestParam(defaultValue = "0,100") List<Double> priceRange
) {
return productService.filterByCategoriesAndPriceRange(categories, priceRange);
}
URL示例:
/filter?categories=electronics&categories=books&priceRange=10&priceRange=50
3.7 @RequestBody
将HTTP请求体绑定到方法参数。
语法:
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
详细用法:
- 基本使用:
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest orderRequest) {
Order createdOrder = orderService.createOrder(orderRequest);
return ResponseEntity.created(URI.create("/orders/" + createdOrder.getId()))
.body(createdOrder);
}
- 请求验证:
@PostMapping("/register")
public User registerUser(@Valid @RequestBody UserRegistration registration) {
return userService.register(registration);
}
- 可选请求体:
@PutMapping("/users/{id}")
public User updateUser(
@PathVariable Long id,
@RequestBody(required = false) UserUpdateRequest updateRequest
) {
if (updateRequest == null) {
throw new BadRequestException("Request body is required");
}
return userService.update(id, updateRequest);
}
工作原理:
- 使用HTTP消息转换器(
HttpMessageConverter
)将请求体转换为对象 - 根据
Content-Type
头选择适当的转换器(如JSON转换为Java对象) - 可与Bean验证(
@Valid
)结合使用
常见问题:
- 客户端必须设置
Content-Type
头(如application/json
) - 请求体必须是有效的JSON/XML等(根据Content-Type)
- 如果请求体无法解析,Spring会返回400 Bad Request
- 默认情况下,
@RequestBody
是必需的,可以通过required=false
设置为可选
3.8 @ResponseBody
表示方法的返回值应该直接写入HTTP响应体,而不是视图名。
语法:
@Controller
public class ApiController {
@GetMapping("/api/data")
@ResponseBody
public Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
data.put("key", "value");
return data; // 自动转换为JSON并写入响应体
}
}
详细解析:
- 通常与
@Controller
一起使用,将方法返回值直接写入响应体 @RestController
已经包含了@ResponseBody
,无需再添加- 根据Accept头或配置选择适当的
HttpMessageConverter
进行序列化
自定义响应:
@Controller
public class FileController {
@GetMapping("/download/{filename}")
@ResponseBody
public ResponseEntity<byte[]> downloadFile(@PathVariable String filename) {
byte[] fileContent = fileService.getFileContent(filename);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", filename);
return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
}
}
3.9 @ResponseStatus
指定HTTP响应状态码。
语法:
@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@RequestBody User user) {
return userService.save(user);
}
详细用法:
- 方法级别:
@DeleteMapping("/users/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
- 类级别(适用于所有方法):
@RestController
@ResponseStatus(HttpStatus.OK)
public class HealthController {
@GetMapping("/health")
public Map<String, String> checkHealth() {
return Collections.singletonMap("status", "UP");
}
}
- 异常类:
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "User not found")
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long id) {
super("User with ID " + id + " not found");
}
}
当抛出此异常时,Spring会返回404状态码:
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
3.10 @ExceptionHandler
处理控制器中的异常。
语法:
@RestController
public class ProductController {
@GetMapping("/products/{id}")
public Product getProduct(@PathVariable Long id) {
Product product = productService.findById(id);
if (product == null) {
throw new ProductNotFoundException(id);
}
return product;
}
@ExceptionHandler(ProductNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, String> handleProductNotFound(ProductNotFoundException ex) {
Map<String, String> error = new HashMap<>();
error.put("error", "Product not found");
error.put("message", ex.getMessage());
return error;
}
}
详细用法:
- 处理多种异常:
@ExceptionHandler({ResourceNotFoundException.class, AccessDeniedException.class})
public ResponseEntity<ErrorResponse> handleResourceErrors(Exception ex) {
ErrorResponse error = new ErrorResponse();
if (ex instanceof ResourceNotFoundException) {
error.setStatus(HttpStatus.NOT_FOUND.value());
error.setMessage("Resource not found");
} else if (ex instanceof AccessDeniedException) {
error.setStatus(HttpStatus.FORBIDDEN.value());
error.setMessage("Access denied");
}
error.setTimestamp(new Date());
error.setDetails(ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.valueOf(error.getStatus()));
}
- 获取请求详情:
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleValidationError(
MethodArgumentNotValidException ex,
WebRequest request
) {
ValidationErrorResponse error = new ValidationErrorResponse();
error.setTimestamp(new Date());
error.setStatus(HttpStatus.BAD_REQUEST.value());
error.setMessage("Validation error");
error.setPath(request.getDescription(false));
// 提取验证错误
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.toList());
error.setErrors(errors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
作用范围:
- 控制器类内定义的
@ExceptionHandler
方法只能处理该控制器内抛出的异常 - 要处理全局异常,请使用
@ControllerAdvice
或@RestControllerAdvice
3.11 @ControllerAdvice / @RestControllerAdvice
定义全局异常处理器、绑定数据或初始化绑定器等,适用于所有@Controller
类。
语法:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseBody
public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse();
error.setStatus(HttpStatus.NOT_FOUND.value());
error.setMessage(ex.getMessage());
error.setTimestamp(new Date());
return error;
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public ErrorResponse handleGeneralError(Exception ex) {
ErrorResponse error = new ErrorResponse();
error.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
error.setMessage("An unexpected error occurred");
error.setTimestamp(new Date());
return error;
}
}
@RestControllerAdvice:
结合了@ControllerAdvice
和@ResponseBody
,类似于@RestController
:
@RestControllerAdvice
public class GlobalRestExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex) {
// 与上面相同,但不需要@ResponseBody
}
}
详细属性:
@ControllerAdvice(
basePackages = {"com.example.controllers"},
assignableTypes = {UserController.class, ProductController.class},
annotations = {RestController.class}
)
public class SpecificExceptionHandler {
// 只适用于指定包、类或注解的控制器
}
basePackages
:适用于指定包及其子包中的控制器assignableTypes
:适用于指定类型的控制器annotations
:适用于带有指定注解的控制器
全局数据绑定:
@ControllerAdvice
public class GlobalModelAttributes {
@ModelAttribute("globalSettings")
public Map<String, String> globalSettings() {
Map<String, String> settings = new HashMap<>();
settings.put("siteName", "My Awesome Site");
settings.put("copyrightYear", String.valueOf(Year.now().getValue()));
return settings;
}
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("version", "1.0.0");
model.addAttribute("lastUpdated", new Date());
}
}
全局数据绑定器:
@ControllerAdvice
public class GlobalBindingInitializer {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
// 禁止修改特定字段
binder.setDisallowedFields("id", "createdAt");
}
}
4. 数据访问相关注解
4.1 @Repository
标记数据访问层组件(DAO层)。
语法:
@Repository
public class JpaUserRepository implements UserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public User findById(Long id) {
return entityManager.find(User.class, id);
}
@Override
public List<User> findAll() {
return entityManager.createQuery("SELECT u FROM User u", User.class)
.getResultList();
}
}
详细解析:
@Repository
是@Component
的特化,用于数据访问对象- 自动被组件扫描检测并注册为Bean
- 提供数据访问异常转换,将特定于持久化技术的异常(如JDBC、JPA等)转换为Spring的
DataAccessException
层次结构
实际应用示例:
@Repository
public class ProductRepositoryImpl implements ProductRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public ProductRepositoryImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Product findById(Long id) {
try {
return jdbcTemplate.queryForObject(
"SELECT id, name, price, description FROM products WHERE id = ?",
new Object[]{id},
(rs, rowNum) -> {
Product product = new Product();
product.setId(rs.getLong("id"));
product.setName(rs.getString("name"));
product.setPrice(rs.getBigDecimal("price"));
product.setDescription(rs.getString("description"));
return product;
}
);
} catch (EmptyResultDataAccessException e) {
// 这里的异常已由Spring转换为DataAccessException层次结构
return null;
}
}
// 其他实现...
}
4.2 @Entity
将Java类标记为JPA实体。
语法:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
@Column(name = "email", unique = true, nullable = false)
private String email;
@Column(name = "password", nullable = false)
private String password;
@Column(name = "created_at")
@Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
// Getters and setters...
}
详细属性:
name
:实体名称,默认为类名- 需结合
@Table
、@Id
等JPA注解使用
关系映射:
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_number", nullable = false, unique = true)
private String orderNumber;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id", nullable = false)
private Customer customer;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
@ManyToMany
@JoinTable(
name = "order_promotions",
joinColumns = @JoinColumn(name = "order_id"),
inverseJoinColumns = @JoinColumn(name = "promotion_id")
)
private Set<Promotion> promotions = new HashSet<>();
// Getters and setters...
}
4.3 @Table
指定实体映射的数据库表。
语法:
@Entity
@Table(
name = "products",
schema = "inventory",
uniqueConstraints = {
@UniqueConstraint(columnNames = {"sku", "vendor_id"}),
@UniqueConstraint(columnNames = {"name", "vendor_id"})
},
indexes = {
@Index(name = "idx_product_name", columnList = "name"),
@Index(name = "idx_product_price", columnList = "price")
}
)
public class Product {
// Class implementation...
}
详细属性:
name
:表名,默认为实体类名schema
/catalog
:数据库schema或cataloguniqueConstraints
:唯一约束indexes
:表索引quoteStrategy
:包围标识符的策略(如表名、列名)
4.4 @Id和@GeneratedValue
@Id
标记实体的主键属性,@GeneratedValue
定义主键生成策略。
语法:
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 其他属性...
}
生成策略:
// 自增主键(MySQL、SQL Server、PostgreSQL)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 序列(Oracle、PostgreSQL)
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "employee_seq"
)
@SequenceGenerator(
name = "employee_seq",
sequenceName = "employee_sequence",
allocationSize = 1
)
private Long id;
// 表(通用)
@Id
@GeneratedValue(
strategy = GenerationType.TABLE,
generator = "employee_gen"
)
@TableGenerator(
name = "employee_gen",
table = "id_generator",
pkColumnName = "gen_name",
valueColumnName = "gen_value",
allocationSize = 1
)
private Long id;
// UUID(通用,需要手动设置)
@Id
@Column(length = 36)
private String id;
public Employee() {
this.id = UUID.randomUUID().toString();
}
4.5 @Column
指定实体属性映射的表列详情。
语法:
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(
name = "full_name",
nullable = false,
length = 100
)
private String fullName;
@Column(
name = "email",
unique = true,
nullable = false,
length = 150
)
private String email;
@Column(
name = "credit_limit",
precision = 10,
scale = 2
)
private BigDecimal creditLimit;
@Column(
name = "is_active",
columnDefinition = "BOOLEAN DEFAULT true"
)
private boolean active;
@Column(
name = "notes",
length = 1000
)
private String notes;
// Getters and setters...
}
详细属性:
name
:列名,默认为属性名unique
:是否唯一nullable
:是否允许为空insertable
/updatable
:是否包含在INSERT/UPDATE语句中columnDefinition
:列的SQL DDL片段length
:字符串列长度precision
/scale
:数值精度和小数位
4.6 @Transactional
标记事务方法或类。
语法:
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final InventoryService inventoryService;
@Autowired
public OrderService(
OrderRepository orderRepository,
PaymentService paymentService,
InventoryService inventoryService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.inventoryService = inventoryService;
}
@Transactional
public Order placeOrder(Order order) {
// 1. 保存订单
Order savedOrder = orderRepository.save(order);
// 2. 处理支付
paymentService.processPayment(order);
// 3. 更新库存
inventoryService.updateStock(order.getItems());
return savedOrder;
}
}
详细属性:
@Service
public class AdvancedTransactionService {
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
readOnly = false,
rollbackFor = {SQLException.class, DataAccessException.class},
noRollbackFor = {IllegalArgumentException.class},
label = "orderProcessing"
)
public void complexOperation() {
// 事务处理...
}
}
-
propagation
:事务传播行为REQUIRED
(默认):如果存在事务,则加入;否则创建新事务REQUIRES_NEW
:创建新事务,挂起当前事务SUPPORTS
:如果存在事务,则加入;否则以非事务方式执行NOT_SUPPORTED
:以非事务方式执行,挂起当前事务MANDATORY
:必须在事务中执行,否则抛出异常NEVER
:必须在非事务中执行,否则抛出异常NESTED
:如果存在事务,则创建嵌套事务;否则类似REQUIRED
-
isolation
:事务隔离级别DEFAULT
:使用数据库默认隔离级别READ_UNCOMMITTED
:最低隔离级别,可能读取未提交数据READ_COMMITTED
:只读取已提交数据,可能出现不可重复读REPEATABLE_READ
:确保相同查询返回相同结果,但可能出现幻读SERIALIZABLE
:最高隔离级别,完全串行化执行
-
timeout
:事务超时时间(秒) -
readOnly
:是否只读事务(只允许读操作) -
rollbackFor
/noRollbackFor
:指定回滚或不回滚的异常类型
示例场景:
@Service
public class BankingService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transfer(String fromId, String toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId)
.orElseThrow(() -> new AccountNotFoundException(fromId));
Account to = accountRepository.findById(toId)
.orElseThrow(() -> new AccountNotFoundException(toId));
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException(fromId);
}
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
accountRepository.save(from);
accountRepository.save(to);
// 如果这里抛出异常,整个转账操作会回滚
}
@Transactional(readOnly = true)
public BigDecimal getBalance(String accountId) {
return accountRepository.findById(accountId)
.map(Account::getBalance)
.orElseThrow(() -> new AccountNotFoundException(accountId));
}
}
4.7 Spring Data JPA相关注解
4.7.1 @Query
自定义查询语句。
语法:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.email = ?1")
User findByEmail(String email);
@Query(
value = "SELECT * FROM users WHERE last_name = :lastName",
nativeQuery = true
)
List<User> findByLastName(@Param("lastName") String lastName);
@Query("SELECT u FROM User u WHERE u.status = :status AND u.name LIKE %:namePart%")
List<User> findByStatusAndNameContaining(
@Param("status") UserStatus status,
@Param("namePart") String namePart);
@Query(value = "SELECT u FROM User u WHERE u.createdDate > :date")
List<User> findUsersCreatedAfter(@Param("date") Date date, Pageable pageable);
@Query(
value = "UPDATE User u SET u.status = :status WHERE u.lastLoginDate < :date"
)
@Modifying
int updateStatusForInactiveUsers(
@Param("status") UserStatus status,
@Param("date") Date date);
}
详细属性:
value
:JPQL查询语句(或nativeQuery=true时的SQL语句)nativeQuery
:是否使用原生SQL(默认false)countQuery
:用于分页查询的计数查询countProjection
:用于分页的计数投影
4.7.2 @Param
为@Query
中的命名参数提供映射。
语法:
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("SELECT p FROM Product p WHERE p.price BETWEEN :minPrice AND :maxPrice AND p.category = :category")
List<Product> findByPriceRangeAndCategory(
@Param("minPrice") BigDecimal minPrice,
@Param("maxPrice") BigDecimal maxPrice,
@Param("category") String category);
}
4.7.3 @Modifying
标记更新或删除操作的查询。
语法:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Modifying
@Transactional
@Query("UPDATE Order o SET o.status = :status WHERE o.id = :id")
int updateOrderStatus(@Param("id") Long id, @Param("status") OrderStatus status);
@Modifying
@Transactional
@Query("DELETE FROM Order o WHERE o.createdDate < :date")
int deleteOldOrders(@Param("date") Date date);
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE Product p SET p.price = p.price * :factor WHERE p.category = :category")
int updatePricesByCategory(@Param("category") String category, @Param("factor") double factor);
}
详细属性:
clearAutomatically
:查询后是否自动清除持久化上下文flushAutomatically
:查询前是否自动刷新持久化上下文
4.8 Mybatis相关注解
4.8.1 @Mapper
标记MyBatis映射器接口,用于与数据库交互。
语法:
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(@Param("id") Long id);
@Select("SELECT * FROM users")
List<User> findAll();
@Insert("INSERT INTO users(name, email, password) VALUES(#{name}, #{email}, #{password})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(User user);
@Update("UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}")
void update(User user);
@Delete("DELETE FROM users WHERE id = #{id}")
void delete(@Param("id") Long id);
}
4.8.2 @Select、@Insert、@Update、@Delete
MyBatis操作数据库的注解。
语法:
@Mapper
public interface OrderMapper {
@Select("SELECT * FROM orders WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "orderNumber", column = "order_number"),
@Result(property = "customer", column = "customer_id",
one = @One(select = "com.example.mapper.CustomerMapper.findById")),
@Result(property = "items", column = "id",
many = @Many(select = "com.example.mapper.OrderItemMapper.findByOrderId"))
})
Order findById(@Param("id") Long id);
@Insert({
"INSERT INTO orders(order_number, customer_id, order_date, total_amount) ",
"VALUES(#{orderNumber}, #{customer.id}, #{orderDate}, #{totalAmount})"
})
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Order order);
@Update({
"UPDATE orders ",
"SET status = #{status}, ",
"updated_at = #{updatedAt} ",
"WHERE id = #{id}"
})
int updateStatus(Order order);
@Delete("DELETE FROM orders WHERE id = #{id}")
int delete(@Param("id") Long id);
}
4.8.3 @Results和@Result
映射查询结果到Java对象。
语法:
@Mapper
public interface ProductMapper {
@Select("SELECT p.*, c.name as category_name FROM products p " +
"LEFT JOIN categories c ON p.category_id = c.id " +
"WHERE p.id = #{id}")
@Results({
@Result(property = "id", column = "id", id = true),
@Result(property = "name", column = "name"),
@Result(property = "price", column = "price"),
@Result(property = "description", column = "description"),
@Result(property = "categoryId", column = "category_id"),
@Result(property = "categoryName", column = "category_name"),
@Result(property = "tags", column = "id",
many = @Many(select = "com.example.mapper.TagMapper.findByProductId"))
})
Product findById(@Param("id") Long id);
}
5. 安全相关注解
5.1 @EnableWebSecurity
启用Spring Security的Web安全支持。
语法:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
5.2 @PreAuthorize和@PostAuthorize
基于表达式的方法级安全性注解。
语法:
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public List<User> getAllUsers() {
// 只有ADMIN角色才能访问
return userRepository.findAll();
}
@PreAuthorize("hasRole('ADMIN') or #username == authentication.principal.username")
public User getUser(String username) {
// ADMIN或当前用户本人可以访问
return userRepository.findByUsername(username);
}
@PostAuthorize("returnObject.owner == authentication.principal.username")
public Document getDocument(Long id) {
// 只有文档的拥有者才能获取到结果
return documentRepository.findById(id).orElse(null);
}
@PreAuthorize("hasPermission(#project, 'WRITE')")
public void updateProject(Project project) {
// 需要对项目有写权限
projectRepository.save(project);
}
}
5.3 @Secured
指定方法安全性所需的角色。
语法:
@Service
public class AdminService {
@Secured("ROLE_ADMIN")
public void deleteUser(Long userId) {
// 只有管理员可以删除用户
userRepository.deleteById(userId);
}
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public void updateUserStatus(Long userId, UserStatus status) {
// 管理员或经理可以更新用户状态
User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId));
user.setStatus(status);
userRepository.save(user);
}
}
6. 测试相关注解
6.1 @SpringBootTest
标记Spring Boot集成测试,加载完整的应用程序上下文。
语法:
@SpringBootTest
public class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
public void testCreateUser() {
User user = new User();
user.setUsername("testuser");
user.setEmail("test@example.com");
user.setPassword("password");
User savedUser = userService.create(user);
assertNotNull(savedUser.getId());
assertEquals("testuser", savedUser.getUsername());
assertEquals("test@example.com", savedUser.getEmail());
}
}
详细属性:
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"spring.datasource.url=jdbc:h2:mem:testdb", "spring.jpa.hibernate.ddl-auto=create-drop"},
classes = {TestConfig.class},
args = {"--debug"}
)
public class AdvancedIntegrationTest {
// ...
}
-
webEnvironment
:web环境配置MOCK
:加载WebApplicationContext并提供mock servlet环境RANDOM_PORT
:加载WebApplicationContext并提供实际servlet环境,使用随机端口DEFINED_PORT
:加载WebApplicationContext并提供实际servlet环境,使用定义的端口NONE
:加载ApplicationContext但不提供servlet环境
-
properties
:测试期间添加或覆盖的属性 -
classes
:明确指定要加载的配置类 -
args
:应用程序运行参数
6.2 @WebMvcTest
用于Controller层测试,仅加载Web层组件。
语法:
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
public void testGetUser() throws Exception {
User user = new User();
user.setId(1L);
user.setUsername("testuser");
user.setEmail("test@example.com");
when(userService.findById(1L)).thenReturn(user);
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.username").value("testuser"))
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
public void testCreateUser() throws Exception {
User user = new User();
user.setUsername("newuser");
user.setEmail("new@example.com");
User savedUser = new User();
savedUser.setId(2L);
savedUser.setUsername("newuser");
savedUser.setEmail("new@example.com");
when(userService.create(any(User.class))).thenReturn(savedUser);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"newuser\",\"email\":\"new@example.com\",\"password\":\"secret\"}"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(2))
.andExpect(jsonPath("$.username").value("newuser"));
}
}
6.3 @DataJpaTest
用于仅测试JPA组件(repositories)。
语法:
@DataJpaTest
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Autowired
private TestEntityManager entityManager;
@Test
public void testFindByEmail() {
// given
User user = new User();
user.setUsername("testuser");
user.setEmail("test@example.com");
user.setPassword("password");
entityManager.persist(user);
entityManager.flush();
// when
User found = userRepository.findByEmail("test@example.com");
// then
assertNotNull(found);
assertEquals("testuser", found.getUsername());
}
@Test
public void testSave() {
// given
User user = new User();
user.setUsername("saveuser");
user.setEmail("save@example.com");
user.setPassword("password");
// when
User saved = userRepository.save(user);
// then
assertNotNull(saved.getId());
User found = entityManager.find(User.class, saved.getId());
assertEquals("saveuser", found.getUsername());
assertEquals("save@example.com", found.getEmail());
}
}
6.4 @MockBean
创建并注入一个Mock对象,替换Spring容器中的Bean。
语法:
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@MockBean
private UserRepository userRepository;
@Test
public void testFindById() {
// given
User user = new User();
user.setId(1L);
user.setUsername("testuser");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
// when
User found = userService.findById(1L);
// then
assertNotNull(found);
assertEquals("testuser", found.getUsername());
verify(userRepository).findById(1L);
}
@Test
public void testFindByIdNotFound() {
// given
when(userRepository.findById(99L)).thenReturn(Optional.empty());
// when & then
assertThrows(UserNotFoundException.class, () -> {
userService.findById(99L);
});
verify(userRepository).findById(99L);
}
}
6.5 @SpyBean
创建并注入一个Spy对象,部分模拟Bean的行为。
语法:
@SpringBootTest
public class EmailServiceTest {
@Autowired
private UserService userService;
@SpyBean
private EmailService emailService;
@Test
public void testSendRegistrationEmail() {
// given
User user = new User();
user.setUsername("newuser");
user.setEmail("new@example.com");
doNothing().when(emailService).sendEmail(anyString(), anyString(), anyString());
// when
userService.register(user);
// then
verify(emailService).sendEmail(
eq("new@example.com"),
contains("Registration"),
contains("Welcome")
);
}
}
7. 总结
SpringBoot注解大大简化了Java企业级应用的开发,通过这些注解可以专注于业务逻辑而不是繁琐的配置。本文档详细介绍了SpringBoot中常用的核心注解、Web开发注解、数据访问注解以及安全测试注解,覆盖了从项目初始化到测试验证的整个开发流程。
通过学习和掌握这些注解,开发者可以更高效地构建健壮的SpringBoot应用。建议根据实际项目需求,选择合适的注解组合使用,并参考官方文档了解更多高级用法。
8. 参考资料
- Spring Boot 官方文档
- Spring Framework 官方文档
- Spring Data JPA 文档
- Spring Security 参考文档
- MyBatis Spring Boot Starter
- Spring Boot 测试文档