SpringBoot常用注解详解
文章目录
- 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 {@Beanpublic MyService myService() {return new MyServiceImpl();}
}
详细解析:
@Configuration
类中的@Bean
方法会被Spring容器处理,创建并管理返回的对象实例。配置类本身也会作为Bean管理。
默认情况下,@Configuration
类使用CGLIB代理增强,确保@Bean
方法之间的引用始终返回同一个实例:
@Configuration
public class DatabaseConfig {@Beanpublic 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;}@Beanpublic 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 {@Beanpublic 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 {@Autowiredprivate DatabaseProperties dbProps;@Beanpublic 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 {@Autowiredprivate UserRepository userRepository;// 方法也可以使用@Autowired@Autowiredpublic 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 {@Autowiredprivate 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;@Autowiredpublic 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;@Autowiredpublic DefaultUserService(UserRepository userRepository) {// 将自动注入带有@Primary的MainUserRepositorythis.userRepository = userRepository;}
}
与@Qualifier结合:
@Primary
提供了系统默认值,而@Qualifier
允许在特定情况下覆盖默认行为:
@Service
public class SpecialUserService {private final UserRepository userRepository;@Autowiredpublic 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}") // 默认值为rootprivate 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 {@Autowiredprivate Environment environment;@Value("${db.driver}")private String driver;@Beanpublic 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;@Autowiredpublic 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.comport: 587username: user@example.compassword: secretssl: truerecipients:- admin@example.com- support@example.comheaders:X-Priority: HighX-Mailer: MyApp
与@Bean结合使用:
@Configuration
public class AppConfig {@Bean@ConfigurationProperties(prefix = "datasource")public HikariConfig hikariConfig() {return new HikariConfig();}@Beanpublic 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: MyApplicationdescription: Sample SpringBoot Applicationsecurity:enabled: truetoken-secret: abc123xyz789token-expiry-seconds: 7200
验证:
@Component
@ConfigurationProperties(prefix = "app.server")
@Validated
public class ServerProperties {@NotEmptyprivate 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 {@Beanpublic DataSource devDataSource() {// 返回开发环境的数据源配置}
}@Configuration
@Profile("production")
public class ProductionConfig {@Beanpublic 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 {@GetMappingpublic 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/listreturn "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/savereturn "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")@ResponseBodypublic ApiData getDataV1() {return new ApiData("v1");}
}
3.4 @GetMapping、@PostMapping等
@RequestMapping
的HTTP方法特定变体,让代码更加简洁明了。
语法:
@RestController
@RequestMapping("/api/products")
public class ProductController {// 等同于 @RequestMapping(method = RequestMethod.GET)@GetMappingpublic 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请求@PostMappingpublic 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")@ResponseBodypublic 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}")@ResponseBodypublic 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)@ResponseBodypublic 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)@ResponseBodypublic 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;}@ModelAttributepublic void addAttributes(Model model) {model.addAttribute("version", "1.0.0");model.addAttribute("lastUpdated", new Date());}
}
全局数据绑定器:
@ControllerAdvice
public class GlobalBindingInitializer {@InitBinderpublic 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 {@PersistenceContextprivate EntityManager entityManager;@Overridepublic User findById(Long id) {return entityManager.find(User.class, id);}@Overridepublic 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;@Autowiredpublic ProductRepositoryImpl(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Overridepublic 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;@Autowiredpublic OrderService(OrderRepository orderRepository,PaymentService paymentService,InventoryService inventoryService) {this.orderRepository = orderRepository;this.paymentService = paymentService;this.inventoryService = inventoryService;}@Transactionalpublic 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 {@Autowiredprivate AccountRepository accountRepository;@Transactionalpublic 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")@Modifyingint 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 {@Overrideprotected 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 {@Autowiredprivate UserService userService;@Autowiredprivate UserRepository userRepository;@Testpublic 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 {@Autowiredprivate MockMvc mockMvc;@MockBeanprivate UserService userService;@Testpublic 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"));}@Testpublic 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 {@Autowiredprivate UserRepository userRepository;@Autowiredprivate TestEntityManager entityManager;@Testpublic void testFindByEmail() {// givenUser user = new User();user.setUsername("testuser");user.setEmail("test@example.com");user.setPassword("password");entityManager.persist(user);entityManager.flush();// whenUser found = userRepository.findByEmail("test@example.com");// thenassertNotNull(found);assertEquals("testuser", found.getUsername());}@Testpublic void testSave() {// givenUser user = new User();user.setUsername("saveuser");user.setEmail("save@example.com");user.setPassword("password");// whenUser saved = userRepository.save(user);// thenassertNotNull(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 {@Autowiredprivate UserService userService;@MockBeanprivate UserRepository userRepository;@Testpublic void testFindById() {// givenUser user = new User();user.setId(1L);user.setUsername("testuser");when(userRepository.findById(1L)).thenReturn(Optional.of(user));// whenUser found = userService.findById(1L);// thenassertNotNull(found);assertEquals("testuser", found.getUsername());verify(userRepository).findById(1L);}@Testpublic void testFindByIdNotFound() {// givenwhen(userRepository.findById(99L)).thenReturn(Optional.empty());// when & thenassertThrows(UserNotFoundException.class, () -> {userService.findById(99L);});verify(userRepository).findById(99L);}
}
6.5 @SpyBean
创建并注入一个Spy对象,部分模拟Bean的行为。
语法:
@SpringBootTest
public class EmailServiceTest {@Autowiredprivate UserService userService;@SpyBeanprivate EmailService emailService;@Testpublic void testSendRegistrationEmail() {// givenUser user = new User();user.setUsername("newuser");user.setEmail("new@example.com");doNothing().when(emailService).sendEmail(anyString(), anyString(), anyString());// whenuserService.register(user);// thenverify(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 测试文档