SpringMVC与SpringBoot注解详解:从入门到实战
1. 引言:Spring生态中注解驱动开发的演进
在Java企业级应用开发领域,Spring框架已经成为事实上的标准。而注解驱动的开发模式极大简化了Spring应用的开发流程。随着Spring框架从传统的XML配置方式向注解配置方式演进,开发者可以更高效地构建稳健的Web应用程序。
Spring MVC作为Spring框架的Web模块,提供了一套完整的MVC实现,通过各种注解简化了HTTP请求处理、数据绑定和视图解析等操作。而Spring Boot在此基础上进一步简化了配置过程,通过自动配置和起步依赖,让开发者能够快速搭建生产级的Spring应用程序。
本文将深入剖析SpringMVC和SpringBoot中的常用注解,通过实际代码示例演示其用法,并探讨在实际项目中的最佳实践。无论您是Spring初学者还是有一定经验的开发者,都能从本文中获得有价值的见解。
2. Spring框架注解基础
2.1 Spring注解的发展历程
Spring框架从2.5版本开始引入注解支持,随后每个版本都增加了新的注解和功能。注解驱动的开发模式逐渐取代了传统的XML配置方式,使代码更加简洁、易读和易维护。
在Spring注解演进过程中,主要经历了以下几个阶段:
- Spring 2.5:引入了@Component、@Autowired等基本注解
- Spring 3.0:增加了@Configuration、@Bean等Java配置注解
- Spring 3.1:引入了@Profile等环境配置注解
- Spring 4.0:增强了条件化配置注解
- Spring Boot 1.0:推出了@SpringBootApplication等自动配置注解
2.2 Spring框架的核心注解
在深入探讨SpringMVC和SpringBoot特定注解之前,我们先了解Spring框架的核心注解,这些注解是整个Spring生态系统的基石:
// 组件扫描与配置类注解
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
// 组件标识注解
@Component
public class MyComponent {
// 类内容
}
@Service
public class MyService {
// 类内容
}
@Repository
public class MyRepository {
// 类内容
}
这些注解构成了Spring依赖注入和控制反转的基础。@Component是任何Spring管理组件的通用注解,而@Service、@Repository和@Controller是@Component的特殊化,它们在功能上相同,但通过命名表达了更明确的语义角色。
3. SpringMVC常用注解详解
3.1 控制器相关注解
3.1.1 @Controller注解
@Controller注解用于标记一个类作为Spring MVC的控制器。被@Controller注解的类实际上是一个特殊的@Component,会被组件扫描自动检测并注册为Spring应用上下文中的Bean。
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/list")
public String getUserList(Model model) {
List<User> users = userService.findAll();
model.addAttribute("users", users);
return "user-list";
}
}
在上面的示例中,@Controller注解表明这个类是一个Web控制器,它处理进入应用程序的HTTP请求。@RequestMapping注解在类级别上使用,为控制器中的所有处理方法提供了统一的URL路径前缀。
3.1.2 @RestController注解
@RestController是Spring 4.0引入的注解,它是一个组合注解,相当于@Controller和@ResponseBody的组合。使用@RestController注解的类,所有处理方法的返回值都会直接写入HTTP响应体,而不是解析为视图名称。
@RestController
@RequestMapping("/api/users")
public class UserApiController {
@Autowired
private UserService userService;
@GetMapping
public List<User> getUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
if (user != null) {
return ResponseEntity.ok(user);
} else {
return ResponseEntity.notFound().build();
}
}
}
@RestController是现代RESTful Web服务开发的首选注解,它简化了JSON/XML API的实现过程。
3.2 请求映射注解
3.2.1 @RequestMapping注解
@RequestMapping是Spring MVC中最基本且最灵活的请求映射注解,它可以用在类级别或方法级别,用于将HTTP请求映射到特定的处理器方法。
@Controller
@RequestMapping("/products")
public class ProductController {
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String getProductList(Model model) {
// 处理GET请求,返回视图名
return "product-list";
}
@RequestMapping(value = "/create", method = {RequestMethod.GET, RequestMethod.POST})
public String handleProductCreate(@RequestParam Map<String, String> params) {
// 处理GET和POST请求
if ("POST".equals(RequestContextUtils.getRequestMethod())) {
// 处理表单提交
return "redirect:/products/list";
}
// 显示表单
return "product-create";
}
@RequestMapping(value = "/details",
method = RequestMethod.GET,
params = "id",
headers = "Accept=application/json",
consumes = "application/json",
produces = "application/json")
@ResponseBody
public Product getProductDetails(@RequestParam("id") Long productId) {
// 复杂的请求映射条件
return productService.findById(productId);
}
}
@RequestMapping支持多种属性,可以精确控制请求映射的条件:
- value/path:指定映射的URL路径
- method:指定HTTP方法(GET、POST、PUT、DELETE等)
- params:要求请求必须包含某些参数或参数值
- headers:要求请求必须包含某些HTTP头
- consumes:指定处理请求的媒体类型(Content-Type)
- produces:指定响应的媒体类型
3.2.2 HTTP方法特定注解
Spring 4.3引入了更具体的HTTP方法映射注解,它们是@RequestMapping的快捷方式,使代码更加简洁:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}")
public Order getOrder(@PathVariable Long id) {
return orderService.findById(id);
}
@PostMapping
public Order createOrder(@RequestBody Order order) {
return orderService.save(order);
}
@PutMapping("/{id}")
public Order updateOrder(@PathVariable Long id, @RequestBody Order order) {
order.setId(id);
return orderService.update(order);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
orderService.deleteById(id);
return ResponseEntity.noContent().build();
}
@PatchMapping("/{id}/status")
public Order updateOrderStatus(@PathVariable Long id, @RequestParam String status) {
return orderService.updateStatus(id, status);
}
}
这些特定注解(@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping)使代码意图更加明确,减少了@RequestMapping注解中method属性的重复配置。
3.3 请求参数处理注解
3.3.1 @RequestParam注解
@RequestParam注解用于从HTTP请求中提取查询参数、表单数据等信息,并将其绑定到方法参数上。
@RestController
@RequestMapping("/api/books")
public class BookController {
@GetMapping("/search")
public List<Book> searchBooks(
@RequestParam("keyword") String keyword,
@RequestParam(value = "category", required = false, defaultValue = "all") String category,
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "20") int size) {
return bookService.search(keyword, category, page, size);
}
@PostMapping("/filter")
public List<Book> filterBooks(@RequestParam Map<String, String> filters) {
// 使用Map接收所有请求参数
return bookService.filter(filters);
}
}
@RequestParam的主要属性:
- value/name:请求参数名称
- required:参数是否必须(默认true)
- defaultValue:参数默认值
3.3.2 @PathVariable注解
@PathVariable注解用于从URL路径模板中提取值,并将其绑定到方法参数上,这对于RESTful风格的URL特别有用。
@RestController
@RequestMapping("/api/authors")
public class AuthorController {
@GetMapping("/{authorId}/books/{bookId}")
public Book getBook(@PathVariable Long authorId,
@PathVariable Long bookId) {
return bookService.findByAuthorAndBook(authorId, bookId);
}
@GetMapping("/{id}")
public Author getAuthor(@PathVariable("id") Long authorId) {
// 显式指定路径变量名称
return authorService.findById(authorId);
}
@GetMapping("/search/{category:[a-z-]+}")
public List<Author> getAuthorsByCategory(@PathVariable String category) {
// 使用正则表达式约束路径变量
return authorService.findByCategory(category);
}
}
3.3.3 @RequestBody注解
@RequestBody注解用于将HTTP请求体中的内容(通常是JSON或XML)绑定到方法参数上。
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
@PostMapping
public Employee createEmployee(@RequestBody Employee employee) {
return employeeService.save(employee);
}
@PostMapping("/batch")
public List<Employee> createEmployees(@RequestBody List<Employee> employees) {
return employeeService.saveAll(employees);
}
@PutMapping("/{id}")
public Employee updateEmployee(@PathVariable Long id,
@RequestBody Employee employee) {
employee.setId(id);
return employeeService.update(employee);
}
}
3.3.4 @RequestHeader和@CookieValue注解
@RequestHeader用于从HTTP请求头中提取值,@CookieValue用于从Cookie中提取值。
@RestController
@RequestMapping("/api/analytics")
public class AnalyticsController {
@GetMapping("/stats")
public AnalyticsData getStats(
@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value = "Authorization", required = false) String authToken,
@CookieValue(value = "sessionId", required = false) String sessionId) {
// 使用请求头和Cookie信息
return analyticsService.getData(userAgent, authToken, sessionId);
}
}
3.4 模型和视图注解
3.4.1 @ModelAttribute注解
@ModelAttribute注解具有多种用途,可以用于方法参数或方法级别,用于绑定请求参数到模型对象。
@Controller
@RequestMapping("/products")
public class ProductController {
// 在控制器方法执行前执行,用于准备模型数据
@ModelAttribute("categories")
public List<Category> getCategories() {
return categoryService.findAll();
}
@ModelAttribute("product")
public Product getProduct(@RequestParam(value = "id", required = false) Long id) {
if (id != null) {
return productService.findById(id);
}
return new Product();
}
@GetMapping("/create")
public String showCreateForm() {
return "product-form";
}
@PostMapping("/save")
public String saveProduct(@ModelAttribute("product") Product product,
BindingResult result) {
if (result.hasErrors()) {
return "product-form";
}
productService.save(product);
return "redirect:/products/list";
}
// @ModelAttribute在方法参数上的使用
@PostMapping("/update")
public String updateProduct(@ModelAttribute Product product) {
// 如果没有指定名称,则使用类型名称(首字母小写)
productService.update(product);
return "redirect:/products/list";
}
}
3.4.2 @SessionAttributes注解
@SessionAttributes注解用于在多个请求之间存储模型属性,通常用于在多个步骤的表单处理中保持状态。
@Controller
@RequestMapping("/multistep-form")
@SessionAttributes("formData")
public class MultiStepFormController {
@ModelAttribute("formData")
public FormData initializeFormData() {
return new FormData();
}
@GetMapping("/step1")
public String step1(@ModelAttribute("formData") FormData formData) {
return "step1";
}
@PostMapping("/step2")
public String step2(@ModelAttribute("formData") FormData formData) {
return "step2";
}
@PostMapping("/step3")
public String step3(@ModelAttribute("formData") FormData formData) {
return "step3";
}
@PostMapping("/complete")
public String complete(@ModelAttribute("formData") FormData formData,
SessionStatus status) {
// 处理表单数据
formService.process(formData);
// 清除会话属性
status.setComplete();
return "redirect:/multistep-form/success";
}
}
3.5 数据验证注解
Spring MVC与Bean Validation API(如Hibernate Validator)集成,提供了强大的数据验证功能。
public class User {
@NotNull(message = "ID不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄必须大于18岁")
@Max(value = 100, message = "年龄必须小于100岁")
private Integer age;
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$",
message = "密码必须包含大小写字母和数字,且长度至少8位")
private String password;
// 省略getter和setter
}
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody User user,
BindingResult result) {
if (result.hasErrors()) {
// 处理验证错误
Map<String, String> errors = new HashMap<>();
for (FieldError error : result.getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}
return ResponseEntity.badRequest().body(errors);
}
User savedUser = userService.save(user);
return ResponseEntity.ok(savedUser);
}
}
3.6 异常处理注解
3.6.1 @ExceptionHandler注解
@ExceptionHandler注解用于在控制器内处理特定类型的异常。
@Controller
@RequestMapping("/api/documents")
public class DocumentController {
@GetMapping("/{id}")
public ResponseEntity<Document> getDocument(@PathVariable Long id) {
Document document = documentService.findById(id);
if (document == null) {
throw new DocumentNotFoundException("文档不存在: " + id);
}
return ResponseEntity.ok(document);
}
// 处理控制器内的特定异常
@ExceptionHandler(DocumentNotFoundException.class)
public ResponseEntity<ErrorResponse> handleDocumentNotFound(DocumentNotFoundException ex) {
ErrorResponse error = new ErrorResponse("DOCUMENT_NOT_FOUND", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
// 处理多个异常类型
@ExceptionHandler({AccessDeniedException.class, SecurityException.class})
public ResponseEntity<ErrorResponse> handleAccessDenied(Exception ex) {
ErrorResponse error = new ErrorResponse("ACCESS_DENIED", "无权访问该资源");
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
}
3.6.2 @ControllerAdvice和@RestControllerAdvice注解
@ControllerAdvice和@RestControllerAdvice注解用于创建全局异常处理器,可以处理多个控制器的异常。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse("RESOURCE_NOT_FOUND", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationErrors(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toList());
ErrorResponse errorResponse = new ErrorResponse("VALIDATION_ERROR", "参数验证失败", errors);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
ErrorResponse error = new ErrorResponse("INTERNAL_SERVER_ERROR", "服务器内部错误");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
// 处理自定义业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
ErrorResponse error = new ErrorResponse(ex.getCode(), ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
}
3.7 跨域处理注解
3.7.1 @CrossOrigin注解
@CrossOrigin注解用于启用跨源资源共享(CORS),允许浏览器向不同域的服务器发起请求。
@RestController
@RequestMapping("/api/public")
@CrossOrigin(maxAge = 3600) // 允许所有源的跨域请求,缓存1小时
public class PublicApiController {
@GetMapping("/data")
@CrossOrigin(origins = "https://example.com") // 方法级别覆盖类级别配置
public PublicData getPublicData() {
return publicService.getData();
}
}
@RestController
@RequestMapping("/api/restricted")
@CrossOrigin(origins = {"https://trusted-domain.com", "https://another-trusted.com"},
allowedHeaders = {"Authorization", "Content-Type"},
methods = {RequestMethod.GET, RequestMethod.POST},
allowCredentials = "true",
maxAge = 1800)
public class RestrictedApiController {
@GetMapping("/secure-data")
public SecureData getSecureData() {
return secureService.getData();
}
@PostMapping("/submit")
public ResponseEntity<?> submitData(@RequestBody SubmissionData data) {
// 处理提交
return ResponseEntity.ok().build();
}
}
4. SpringBoot常用注解详解
4.1 应用配置注解
4.1.1 @SpringBootApplication注解
@SpringBootApplication是Spring Boot的核心注解,它是一个组合注解,包含了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解的功能。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 以上注解等价于以下配置
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(basePackages = "com.example")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@SpringBootApplication注解的主要功能:
- @SpringBootConfiguration:标识该类为配置类
- @EnableAutoConfiguration:启用Spring Boot的自动配置机制
- @ComponentScan:自动扫描并注册组件
4.1.2 条件化配置注解
Spring Boot提供了一系列条件化配置注解,用于根据特定条件启用或禁用配置:
@Configuration
public class ConditionalConfiguration {
// 当类路径下存在指定类时生效
@ConditionalOnClass(name = "com.example.SomeService")
@Bean
public SomeService someService() {
return new SomeService();
}
// 当类路径下不存在指定类时生效
@ConditionalOnMissingClass("com.example.AnotherService")
@Bean
public PlaceholderService placeholderService() {
return new PlaceholderService();
}
// 当指定Bean存在时生效
@ConditionalOnBean(DataSource.class)
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 当指定Bean不存在时生效
@ConditionalOnMissingBean(JdbcTemplate.class)
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 当指定属性具有特定值时生效
@ConditionalOnProperty(prefix = "app.feature", name = "enabled", havingValue = "true")
@Bean
public FeatureService featureService() {
return new FeatureService();
}
// 当表达式为true时生效
@ConditionalOnExpression("'${app.mode}' == 'production'")
@Bean
public ProductionService productionService() {
return new ProductionService();
}
}
4.2 配置属性注解
4.2.1 @ConfigurationProperties注解
@ConfigurationProperties注解用于将外部配置文件(如application.properties或application.yml)中的属性绑定到Java对象上。
@Component
@ConfigurationProperties(prefix = "app.database")
public class DatabaseProperties {
private String url;
private String username;
private String password;
private Pool pool = new Pool();
private Map<String, String> properties = new HashMap<>();
// 静态内部类用于嵌套属性
public static class Pool {
private int maxSize = 10;
private int minIdle = 2;
private long timeout = 30000;
// getter和setter
}
// getter和setter方法
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Pool getPool() { return pool; }
public void setPool(Pool pool) { this.pool = pool; }
public Map<String, String> getProperties() { return properties; }
public void setProperties(Map<String, String> properties) { this.properties = properties; }
}
// 在配置类中启用@ConfigurationProperties
@Configuration
@EnableConfigurationProperties(DatabaseProperties.class)
public class AppConfig {
// 配置内容
}
// 使用配置属性
@Service
public class DatabaseService {
private final DatabaseProperties properties;
public DatabaseService(DatabaseProperties properties) {
this.properties = properties;
}
public void printConfig() {
System.out.println("URL: " + properties.getUrl());
System.out.println("Pool Max Size: " + properties.getPool().getMaxSize());
}
}
对应的application.yml配置:
app:
database:
url: jdbc:mysql://localhost:3306/mydb
username: admin
password: secret
pool:
max-size: 20
min-idle: 5
timeout: 60000
properties:
cachePrepStmts: true
prepStmtCacheSize: 250
4.2.2 @Value注解
@Value注解用于直接注入配置属性值,适用于简单的属性注入场景。
@Service
public class NotificationService {
// 注入简单值
@Value("${app.notification.email.from}")
private String fromEmail;
// 注入默认值(当属性不存在时)
@Value("${app.notification.retry.count:3}")
private int retryCount;
// 注入系统属性
@Value("${java.home}")
private String javaHome;
// 注入表达式结果
@Value("#{systemProperties['user.name']}")
private String userName;
// 注入数组或列表
@Value("${app.notification.types:email,sms}")
private List<String> notificationTypes;
// 注入Map
@Value("#{${app.notification.settings}}")
private Map<String, String> notificationSettings;
public void sendNotification() {
// 使用注入的值
System.out.println("Sending from: " + fromEmail);
System.out.println("Retry count: " + retryCount);
}
}
对应的application.properties配置:
app.notification.email.from=noreply@example.com
app.notification.retry.count=5
app.notification.types=email,sms,push
app.notification.settings={'email.enabled':'true','sms.enabled':'false'}
4.3 自动配置与起步依赖
4.3.1 自定义自动配置
Spring Boot的自动配置是通过@Conditional注解和META-INF/spring.factories文件实现的。我们可以创建自定义的自动配置:
// 自定义配置类
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyServiceProperties properties) {
return new MyService(properties);
}
}
// 配置属性类
@ConfigurationProperties(prefix = "app.my-service")
public class MyServiceProperties {
private String endpoint;
private int timeout = 5000;
private boolean enabled = true;
// getter和setter
}
// 在META-INF/spring.factories中注册自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.MyServiceAutoConfiguration
4.3.2 自定义起步依赖
创建自定义起步依赖需要提供必要的代码和配置:
// 标记类,表示启用特定功能
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyFeatureConfiguration.class)
public @interface EnableMyFeature {
boolean enabled() default true;
}
// 配置类
@Configuration
public class MyFeatureConfiguration {
@Bean
@ConditionalOnMissingBean
public MyFeature myFeature() {
return new MyFeature();
}
}
// 在启动类上使用自定义注解
@SpringBootApplication
@EnableMyFeature
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.4 生产就绪特性注解
4.4.1 健康检查与监控
Spring Boot Actuator提供了生产就绪的特性,可以通过注解启用和配置:
@Component
public class CustomHealthIndicator implements HealthIndicator {
private final DatabaseService databaseService;
public CustomHealthIndicator(DatabaseService databaseService) {
this.databaseService = databaseService;
}
@Override
public Health health() {
try {
boolean isHealthy = databaseService.isHealthy();
if (isHealthy) {
return Health.up()
.withDetail("database", "connected")
.withDetail("timestamp", System.currentTimeMillis())
.build();
} else {
return Health.down()
.withDetail("database", "disconnected")
.withException(new RuntimeException("Connection failed"))
.build();
}
} catch (Exception e) {
return Health.down(e).build();
}
}
}
@Component
public class CustomMetrics {
private final MeterRegistry meterRegistry;
private final Counter requestCounter;
public CustomMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.requestCounter = Counter.builder("api.requests")
.description("API请求次数")
.tags("region", "china")
.register(meterRegistry);
}
public void recordRequest() {
requestCounter.increment();
}
}
4.4.2 定时任务与异步处理
Spring Boot提供了@Scheduled和@Async注解支持定时任务和异步处理:
@Service
@EnableScheduling
@EnableAsync
public class TaskService {
private static final Logger logger = LoggerFactory.getLogger(TaskService.class);
// 固定速率执行(每5秒执行一次)
@Scheduled(fixedRate = 5000)
public void scheduledTask() {
logger.info("固定速率任务执行: {}", System.currentTimeMillis());
}
// 固定延迟执行(上次任务完成后延迟3秒执行)
@Scheduled(fixedDelay = 3000)
public void delayedTask() {
logger.info("固定延迟任务执行: {}", System.currentTimeMillis());
}
// Cron表达式定时执行
@Scheduled(cron = "0 0/30 * * * ?")
public void cronTask() {
logger.info("Cron任务执行: {}", System.currentTimeMillis());
}
// 异步方法
@Async
public CompletableFuture<String> asyncTask(String input) {
logger.info("开始异步处理: {}", input);
// 模拟长时间运行的任务
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
String result = "处理完成: " + input.toUpperCase();
return CompletableFuture.completedFuture(result);
}
// 支持异常处理的异步方法
@Async
public CompletableFuture<String> asyncTaskWithException(String input) {
try {
if ("error".equals(input)) {
throw new IllegalArgumentException("无效输入");
}
String result = "成功处理: " + input;
return CompletableFuture.completedFuture(result);
} catch (Exception e) {
CompletableFuture<String> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
}
}
// 配置异步任务执行器
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsyncThread-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
5. 高级特性与最佳实践
5.1 注解的组合与元注解
在Spring中,我们可以创建组合注解(元注解),将多个注解的功能组合到一个自定义注解中:
// 元注解定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SpringBootApplication
@EnableCaching
@EnableAsync
@EnableScheduling
public @interface EnableAllFeatures {
String[] scanBasePackages() default {};
boolean enableCache() default true;
boolean enableAsync() default true;
boolean enableScheduling() default true;
}
// 使用组合注解
@EnableAllFeatures(
scanBasePackages = {"com.example", "com.common"},
enableCache = true,
enableAsync = true,
enableScheduling = true
)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// API版本控制组合注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(
produces = "application/json;charset=UTF-8",
headers = "X-API-Version=v1"
)
public @interface ApiV1 {
String value() default "";
RequestMethod[] method() default {};
}
// 使用API版本注解
@RestController
public class VersionedController {
@ApiV1("/users")
@GetMapping
public List<User> getUsersV1() {
// V1版本的实现
return userService.getUsers().stream()
.map(user -> {
UserV1 userV1 = new UserV1();
userV1.setId(user.getId());
userV1.setName(user.getFirstName() + " " + user.getLastName());
return userV1;
})
.collect(Collectors.toList());
}
}
5.2 自定义验证注解
我们可以创建自定义的验证注解来处理特定的业务逻辑验证:
// 自定义验证注解 - 密码强度验证
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordStrengthValidator.class)
@Documented
public @interface StrongPassword {
String message() default "密码强度不足";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int minLength() default 8;
boolean requireUppercase() default true;
boolean requireLowercase() default true;
boolean requireDigit() default true;
boolean requireSpecialChar() default true;
}
// 验证器实现
public class PasswordStrengthValidator implements ConstraintValidator<StrongPassword, String> {
private int minLength;
private boolean requireUppercase;
private boolean requireLowercase;
private boolean requireDigit;
private boolean requireSpecialChar;
@Override
public void initialize(StrongPassword constraintAnnotation) {
this.minLength = constraintAnnotation.minLength();
this.requireUppercase = constraintAnnotation.requireUppercase();
this.requireLowercase = constraintAnnotation.requireLowercase();
this.requireDigit = constraintAnnotation.requireDigit();
this.requireSpecialChar = constraintAnnotation.requireSpecialChar();
}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
if (password == null || password.length() < minLength) {
return false;
}
if (requireUppercase && !password.matches(".*[A-Z].*")) {
return false;
}
if (requireLowercase && !password.matches(".*[a-z].*")) {
return false;
}
if (requireDigit && !password.matches(".*\\d.*")) {
return false;
}
if (requireSpecialChar && !password.matches(".*[!@#$%^&*()].*")) {
return false;
}
return true;
}
}
// 使用自定义验证注解
public class UserRegistration {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
private String username;
@StrongPassword(
minLength = 10,
requireUppercase = true,
requireLowercase = true,
requireDigit = true,
requireSpecialChar = true,
message = "密码必须包含大小写字母、数字和特殊字符,且长度至少10位"
)
private String password;
@Email(message = "邮箱格式不正确")
private String email;
// getter和setter
}
// 在控制器中使用验证
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@PostMapping("/register")
public ResponseEntity<?> registerUser(@Valid @RequestBody UserRegistration registration,
BindingResult result) {
if (result.hasErrors()) {
Map<String, String> errors = new HashMap<>();
for (FieldError error : result.getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}
return ResponseEntity.badRequest().body(errors);
}
// 处理注册逻辑
User user = authService.register(registration);
return ResponseEntity.ok(user);
}
}
5.3 性能优化与注解最佳实践
5.3.1 注解使用的最佳实践
- 合理使用注解范围:
// 好的实践:明确指定注解的作用范围
@RestController // 明确表示这是REST控制器
@RequestMapping("/api/v1/users") // 明确指定API版本和路径
@Validated // 明确启用参数验证
public class UserApiController {
// 使用具体的HTTP方法注解
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable @Min(1) Long id) {
// 方法实现
}
// 使用合适的参数注解
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
// 方法实现
}
}
// 避免过度使用注解
// 不好的实践:在一个方法上使用过多注解
@GetMapping
@ResponseBody
@ApiOperation("获取用户列表")
@CrossOrigin
@Cacheable("users")
public List<User> getUsers(
@RequestParam @NotNull @Min(0) Integer page,
@RequestParam @NotNull @Min(1) @Max(100) Integer size) {
// 方法实现
}
- 合理使用懒加载:
@Service
public class HeavyService {
private final DataSource dataSource;
public HeavyService(DataSource dataSource) {
this.dataSource = dataSource;
}
// 使用@Lazy延迟初始化
@Bean
@Lazy
public ExpensiveObject expensiveObject() {
// 创建成本高的对象
return new ExpensiveObject();
}
}
// 配置类中的懒加载设置
@Configuration
@Lazy // 整个配置类延迟初始化
public class LazyConfig {
@Bean
@Lazy
public SomeService someService() {
return new SomeService();
}
}
- 合理使用缓存注解:
@Service
@CacheConfig(cacheNames = "users") // 类级别缓存配置
public class UserService {
// 缓存查询结果
@Cacheable(key = "#id", unless = "#result == null")
public User findById(Long id) {
// 数据库查询
}
// 缓存复杂查询
@Cacheable(key = "T(java.util.Objects).hash(#name, #email)")
public User findByNameAndEmail(String name, String email) {
// 复杂查询
}
// 更新缓存
@CachePut(key = "#user.id")
public User update(User user) {
// 更新操作
}
// 删除缓存
@CacheEvict(key = "#id")
public void deleteById(Long id) {
// 删除操作
}
// 条件化缓存
@Cacheable(key = "#id", condition = "#id > 10")
public User findByIdConditional(Long id) {
// 条件查询
}
}
6. 实战案例:构建一个完整的RESTful API
下面我们通过一个完整的案例来演示如何综合使用SpringMVC和SpringBoot注解构建一个RESTful API:
6.1 项目结构与配置
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── ecommerce/
│ │ ├── EcommerceApplication.java
│ │ ├── config/
│ │ ├── controller/
│ │ ├── service/
│ │ ├── repository/
│ │ ├── model/
│ │ └── dto/
│ └── resources/
│ ├── application.yml
│ └── static/
6.2 主应用类
package com.example.ecommerce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
@EnableCaching
@EnableAsync
public class EcommerceApplication {
public static void main(String[] args) {
SpringApplication.run(EcommerceApplication.class, args);
}
}
6.3 数据模型与DTO
// 实体类
@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "产品名称不能为空")
@Size(max = 100, message = "产品名称长度不能超过100个字符")
@Column(nullable = false, length = 100)
private String name;
@Size(max = 500, message = "产品描述长度不能超过500个字符")
@Column(length = 500)
private String description;
@DecimalMin(value = "0.0", inclusive = false, message = "价格必须大于0")
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
@Min(value = 0, message = "库存数量不能为负数")
@Column(nullable = false)
private Integer stockQuantity;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;
@CreationTimestamp
@Column(updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
// DTO类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProductDto {
@Null(message = "ID由系统自动生成")
private Long id;
@NotBlank(message = "产品名称不能为空")
@Size(max = 100, message = "产品名称长度不能超过100个字符")
private String name;
@Size(max = 500, message = "产品描述长度不能超过500个字符")
private String description;
@DecimalMin(value = "0.0", inclusive = false, message = "价格必须大于0")
private BigDecimal price;
@Min(value = 0, message = "库存数量不能为负数")
private Integer stockQuantity;
@NotNull(message = "分类不能为空")
private Long categoryId;
}
// API响应封装
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private String timestamp;
public static <T> ApiResponse<T> success(T data) {
return ApiResponse.<T>builder()
.success(true)
.data(data)
.timestamp(LocalDateTime.now().toString())
.build();
}
public static <T> ApiResponse<T> error(String message) {
return ApiResponse.<T>builder()
.success(false)
.message(message)
.timestamp(LocalDateTime.now().toString())
.build();
}
}
6.4 控制器实现
@RestController
@RequestMapping("/api/v1/products")
@Validated
@Api(tags = "产品管理", description = "产品相关操作")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
@ApiOperation("获取产品列表")
@Cacheable(value = "products", key = "T(java.util.Objects).hash(#page, #size, #keyword)")
public ResponseEntity<ApiResponse<Page<ProductDto>>> getProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long categoryId) {
Page<ProductDto> products = productService.findAll(page, size, keyword, categoryId);
return ResponseEntity.ok(ApiResponse.success(products));
}
@GetMapping("/{id}")
@ApiOperation("根据ID获取产品详情")
@Cacheable(value = "product", key = "#id")
public ResponseEntity<ApiResponse<ProductDto>> getProductById(
@PathVariable @Min(1) Long id) {
ProductDto product = productService.findById(id);
return ResponseEntity.ok(ApiResponse.success(product));
}
@PostMapping
@ApiOperation("创建新产品")
@ResponseStatus(HttpStatus.CREATED)
@CacheEvict(value = {"products", "product"}, allEntries = true)
public ResponseEntity<ApiResponse<ProductDto>> createProduct(
@Valid @RequestBody ProductDto productDto) {
ProductDto savedProduct = productService.save(productDto);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(savedProduct));
}
@PutMapping("/{id}")
@ApiOperation("更新产品信息")
@CacheEvict(value = {"products", "product"}, allEntries = true)
public ResponseEntity<ApiResponse<ProductDto>> updateProduct(
@PathVariable @Min(1) Long id,
@Valid @RequestBody ProductDto productDto) {
productDto.setId(id);
ProductDto updatedProduct = productService.update(productDto);
return ResponseEntity.ok(ApiResponse.success(updatedProduct));
}
@DeleteMapping("/{id}")
@ApiOperation("删除产品")
@ResponseStatus(HttpStatus.NO_CONTENT)
@CacheEvict(value = {"products", "product"}, allEntries = true)
public ResponseEntity<ApiResponse<Void>> deleteProduct(
@PathVariable @Min(1) Long id) {
productService.deleteById(id);
return ResponseEntity.noContent().build();
}
@PatchMapping("/{id}/stock")
@ApiOperation("更新产品库存")
@CacheEvict(value = {"products", "product"}, allEntries = true)
public ResponseEntity<ApiResponse<ProductDto>> updateStock(
@PathVariable @Min(1) Long id,
@RequestParam @Min(0) Integer quantity) {
ProductDto updatedProduct = productService.updateStock(id, quantity);
return ResponseEntity.ok(ApiResponse.success(updatedProduct));
}
}
6.5 全局异常处理
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiResponse<Void> handleResourceNotFound(ResourceNotFoundException ex) {
log.warn("资源未找到: {}", ex.getMessage());
return ApiResponse.error(ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Map<String, String>> handleValidationErrors(MethodArgumentNotValidException ex) {
log.warn("参数验证失败: {}", ex.getMessage());
Map<String, String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage,
(existing, replacement) -> existing
));
ApiResponse<Map<String, String>> response = ApiResponse.error("参数验证失败");
response.setData(errors);
return response;
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Map<String, String>> handleConstraintViolation(ConstraintViolationException ex) {
log.warn("约束违反: {}", ex.getMessage());
Map<String, String> errors = ex.getConstraintViolations()
.stream()
.collect(Collectors.toMap(
violation -> violation.getPropertyPath().toString(),
ConstraintViolation::getMessage
));
ApiResponse<Map<String, String>> response = ApiResponse.error("参数验证失败");
response.setData(errors);
return response;
}
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleBusinessException(BusinessException ex) {
log.warn("业务异常: {}", ex.getMessage());
return ApiResponse.error(ex.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponse<Void> handleGenericException(Exception ex) {
log.error("服务器内部错误: {}", ex.getMessage(), ex);
return ApiResponse.error("服务器内部错误,请稍后重试");
}
}
7. 总结
本文详细介绍了SpringMVC和SpringBoot中的常用注解,从基础的组件注册到高级的自定义注解,涵盖了Web开发中的各个方面。通过实际的代码示例,我们展示了如何合理使用这些注解来构建稳健、可维护的应用程序。
7.1 注解使用要点回顾
- SpringMVC注解专注于Web请求处理,包括控制器、请求映射、参数绑定等功能。
- SpringBoot注解简化了配置过程,提供了自动配置、条件化配置等高级特性。
- 组合注解可以帮助我们减少重复代码,提高开发效率。
- 自定义注解可以满足特定的业务需求,提高代码的可读性和可维护性。
7.2 最佳实践建议
- 合理选择注解:根据具体场景选择合适的注解,避免过度使用或滥用注解。
- 保持一致性:在项目中保持注解使用的一致性,建立统一的编码规范。
- 关注性能:合理使用缓存、懒加载等注解优化应用性能。
- 重视可测试性:使用注解时考虑代码的可测试性,避免过度耦合。
通过掌握这些注解的使用方法和最佳实践,开发者可以更加高效地构建Spring应用程序,提高开发效率和代码质量。随着Spring生态的不断发展,新的注解和功能会不断出现,开发者需要保持学习的态度,及时掌握最新的技术动态。
168万+

被折叠的 条评论
为什么被折叠?



