3.1 Spring MVC架构
MVC模式概述
MVC(Model-View-Controller)是一种软件架构模式,将应用程序分为三个核心组件:
- Model(模型):负责数据和业务逻辑
- View(视图):负责用户界面展示
- Controller(控制器):负责处理用户输入和协调Model与View
Spring MVC工作流程
1. 用户请求 -> DispatcherServlet(前端控制器)
2. DispatcherServlet -> HandlerMapping(处理器映射器)
3. HandlerMapping -> Handler(处理器/Controller)
4. Handler -> HandlerAdapter(处理器适配器)
5. HandlerAdapter -> ViewResolver(视图解析器)
6. ViewResolver -> View(视图)
7. View -> 响应给用户
核心组件
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users")
public List<User> getAllUsers() {
return userService.findAll();
}
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
3.2 控制器开发
@Controller vs @RestController
// 传统Controller,返回视图名称
@Controller
public class PageController {
@RequestMapping("/home")
public String home(Model model) {
model.addAttribute("message", "Hello World");
return "home"; // 返回视图名称
}
}
// RESTful Controller,直接返回数据
@RestController
public class ApiController {
@GetMapping("/api/data")
public Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
data.put("message", "Hello World");
return data; // 直接返回JSON
}
}
请求映射注解
@RestController
@RequestMapping("/api/users")
public class UserController {
// GET请求
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
// GET请求,带路径参数
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.findById(id);
}
// POST请求
@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
// PUT请求
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userService.update(user);
}
// DELETE请求
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteById(id);
}
// PATCH请求
@PatchMapping("/{id}")
public User patchUser(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return userService.partialUpdate(id, updates);
}
}
请求参数处理
@RestController
public class ParameterController {
// 路径参数
@GetMapping("/users/{id}/posts/{postId}")
public Post getUserPost(@PathVariable Long id, @PathVariable Long postId) {
return postService.findByUserIdAndPostId(id, postId);
}
// 查询参数
@GetMapping("/users")
public List<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String name) {
return userService.findUsers(page, size, name);
}
// 请求头
@GetMapping("/profile")
public User getProfile(@RequestHeader("Authorization") String token) {
return userService.findByToken(token);
}
// Cookie
@GetMapping("/preferences")
public UserPreferences getPreferences(@CookieValue("sessionId") String sessionId) {
return userService.getPreferences(sessionId);
}
// 表单数据
@PostMapping("/login")
public ResponseEntity<String> login(
@RequestParam String username,
@RequestParam String password) {
boolean success = authService.authenticate(username, password);
return success ? ResponseEntity.ok("Login successful")
: ResponseEntity.status(401).body("Login failed");
}
}
请求体处理
@RestController
public class RequestBodyController {
// JSON请求体
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
// 验证请求体
@PostMapping("/users/validated")
public User createValidatedUser(@Valid @RequestBody User user) {
return userService.save(user);
}
// 部分更新
@PatchMapping("/users/{id}")
public User patchUser(
@PathVariable Long id,
@RequestBody Map<String, Object> updates) {
return userService.partialUpdate(id, updates);
}
// 文件上传
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("File is empty");
}
try {
String filename = fileService.store(file);
return ResponseEntity.ok("File uploaded: " + filename);
} catch (Exception e) {
return ResponseEntity.status(500).body("Upload failed: " + e.getMessage());
}
}
}
3.3 响应处理
ResponseEntity使用
@RestController
public class ResponseController {
// 基本响应
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user != null) {
return ResponseEntity.ok(user);
} else {
return ResponseEntity.notFound().build();
}
}
// 自定义状态码和头部
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = userService.save(user);
HttpHeaders headers = new HttpHeaders();
headers.add("Location", "/api/users/" + savedUser.getId());
return ResponseEntity
.status(HttpStatus.CREATED)
.headers(headers)
.body(savedUser);
}
// 错误响应
@GetMapping("/users/{id}/sensitive")
public ResponseEntity<?> getSensitiveData(@PathVariable Long id) {
if (!authService.hasPermission(id)) {
return ResponseEntity
.status(HttpStatus.FORBIDDEN)
.body(Map.of("error", "Access denied"));
}
return ResponseEntity.ok(userService.getSensitiveData(id));
}
}
统一响应格式
// 响应包装类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private long timestamp;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "Success", data, System.currentTimeMillis());
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null, System.currentTimeMillis());
}
}
// 使用响应包装
@RestController
public class UnifiedResponseController {
@GetMapping("/users")
public ApiResponse<List<User>> getUsers() {
List<User> users = userService.findAll();
return ApiResponse.success(users);
}
@PostMapping("/users")
public ApiResponse<User> createUser(@RequestBody User user) {
try {
User savedUser = userService.save(user);
return ApiResponse.success(savedUser);
} catch (Exception e) {
return ApiResponse.error(500, "Failed to create user: " + e.getMessage());
}
}
}
3.4 异常处理
全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理验证异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
ApiResponse<Map<String, String>> response =
ApiResponse.error(400, "Validation failed");
response.setData(errors);
return ResponseEntity.badRequest().body(response);
}
// 处理资源不存在异常
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(
ResourceNotFoundException ex) {
ApiResponse<Void> response = ApiResponse.error(404, ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(
BusinessException ex) {
ApiResponse<Void> response = ApiResponse.error(ex.getCode(), ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
// 处理系统异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleGenericException(Exception ex) {
log.error("Unexpected error occurred", ex);
ApiResponse<Void> response = ApiResponse.error(500, "Internal server error");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
// 自定义异常类
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
局部异常处理
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
// 控制器级别异常处理
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
}
3.5 数据验证
Bean Validation
// 实体类验证
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank(message = "Username cannot be blank")
@Size(min = 3, max = 20, message = "Username must be between 3 and 20 characters")
private String username;
@NotBlank(message = "Email cannot be blank")
@Email(message = "Email should be valid")
private String email;
@NotNull(message = "Age cannot be null")
@Min(value = 18, message = "Age must be at least 18")
@Max(value = 100, message = "Age must be less than 100")
private Integer age;
@Pattern(regexp = "^\\+?[1-9]\\d{1,14}$", message = "Phone number is invalid")
private String phone;
@Past(message = "Birth date must be in the past")
private LocalDate birthDate;
}
// 控制器中使用验证
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
User savedUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
}
自定义验证器
// 自定义验证注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
public @interface UniqueUsername {
String message() default "Username already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 验证器实现
@Component
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
@Autowired
private UserRepository userRepository;
@Override
public boolean isValid(String username, ConstraintValidatorContext context) {
if (username == null) {
return true; // 让@NotNull处理null值
}
return !userRepository.existsByUsername(username);
}
}
// 使用自定义验证
@Entity
public class User {
@NotBlank
@UniqueUsername
private String username;
// 其他字段...
}
分组验证
// 验证分组接口
public interface CreateGroup {}
public interface UpdateGroup {}
// 实体类分组验证
@Entity
@Data
public class User {
@Null(groups = CreateGroup.class, message = "ID must be null when creating")
@NotNull(groups = UpdateGroup.class, message = "ID cannot be null when updating")
private Long id;
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
private String username;
@NotBlank(groups = CreateGroup.class)
private String password;
}
// 控制器中使用分组验证
@RestController
public class UserController {
@PostMapping("/users")
public User createUser(@Validated(CreateGroup.class) @RequestBody User user) {
return userService.save(user);
}
@PutMapping("/users/{id}")
public User updateUser(
@PathVariable Long id,
@Validated(UpdateGroup.class) @RequestBody User user) {
user.setId(id);
return userService.update(user);
}
}
3.6 内容协商
多格式响应
@RestController
public class ContentNegotiationController {
@GetMapping(value = "/users/{id}",
produces = {MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE})
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@PostMapping(value = "/users",
consumes = {MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE})
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
配置内容协商
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.favorParameter(true)
.parameterName("format")
.ignoreAcceptHeader(false)
.useRegisteredExtensionsOnly(false)
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
}
3.7 跨域处理
@CrossOrigin注解
@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class ApiController {
@GetMapping("/data")
public Map<String, Object> getData() {
return Map.of("message", "Hello from API");
}
@CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST})
@PostMapping("/submit")
public ResponseEntity<String> submitData(@RequestBody Map<String, Object> data) {
return ResponseEntity.ok("Data submitted successfully");
}
}
全局CORS配置
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
// 或者通过WebMvcConfigurer配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000", "https://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
3.8 拦截器
自定义拦截器
@Component
public class LoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String requestURI = request.getRequestURI();
String method = request.getMethod();
logger.info("Request started: {} {}", method, requestURI);
// 记录请求开始时间
request.setAttribute("startTime", System.currentTimeMillis());
return true; // 继续处理请求
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
logger.info("Request processed successfully");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
String requestURI = request.getRequestURI();
int status = response.getStatus();
if (ex != null) {
logger.error("Request failed: {} - Status: {} - Duration: {}ms - Error: {}",
requestURI, status, duration, ex.getMessage());
} else {
logger.info("Request completed: {} - Status: {} - Duration: {}ms",
requestURI, status, duration);
}
}
}
// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/health", "/api/metrics");
}
}
认证拦截器
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 检查是否需要认证
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 检查方法或类上是否有@NoAuth注解
if (handlerMethod.hasMethodAnnotation(NoAuth.class) ||
handlerMethod.getBeanType().isAnnotationPresent(NoAuth.class)) {
return true;
}
}
// 获取token
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("Missing or invalid token");
return false;
}
token = token.substring(7); // 移除"Bearer "前缀
// 验证token
try {
if (jwtTokenUtil.isTokenValid(token)) {
String username = jwtTokenUtil.getUsernameFromToken(token);
request.setAttribute("currentUser", username);
return true;
} else {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("Invalid token");
return false;
}
} catch (Exception e) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("Token validation failed");
return false;
}
}
}
// 免认证注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoAuth {
}
3.9 过滤器
自定义过滤器
@Component
@Order(1)
public class RequestResponseLoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(RequestResponseLoggingFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 包装请求和响应以便读取内容
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(httpRequest);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpResponse);
long startTime = System.currentTimeMillis();
try {
// 执行请求
chain.doFilter(requestWrapper, responseWrapper);
} finally {
long duration = System.currentTimeMillis() - startTime;
// 记录请求信息
logRequest(requestWrapper, duration);
// 记录响应信息
logResponse(responseWrapper, duration);
// 复制响应内容到原始响应
responseWrapper.copyBodyToResponse();
}
}
private void logRequest(ContentCachingRequestWrapper request, long duration) {
String uri = request.getRequestURI();
String method = request.getMethod();
String queryString = request.getQueryString();
logger.info("Request: {} {} {} - Duration: {}ms",
method, uri, queryString != null ? "?" + queryString : "", duration);
// 记录请求体(仅对POST/PUT请求)
if ("POST".equals(method) || "PUT".equals(method)) {
byte[] content = request.getContentAsByteArray();
if (content.length > 0) {
String requestBody = new String(content, StandardCharsets.UTF_8);
logger.debug("Request Body: {}", requestBody);
}
}
}
private void logResponse(ContentCachingResponseWrapper response, long duration) {
int status = response.getStatus();
logger.info("Response: Status {} - Duration: {}ms", status, duration);
// 记录响应体
byte[] content = response.getContentAsByteArray();
if (content.length > 0) {
String responseBody = new String(content, StandardCharsets.UTF_8);
logger.debug("Response Body: {}", responseBody);
}
}
}
字符编码过滤器
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
FilterRegistrationBean<CharacterEncodingFilter> registrationBean = new FilterRegistrationBean<>();
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
registrationBean.setFilter(characterEncodingFilter);
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
}
3.10 文件上传下载
文件上传
@RestController
@RequestMapping("/api/files")
public class FileController {
@Value("${file.upload.path:/tmp/uploads}")
private String uploadPath;
// 单文件上传
@PostMapping("/upload")
public ResponseEntity<Map<String, String>> uploadFile(
@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.badRequest()
.body(Map.of("error", "File is empty"));
}
try {
// 检查文件类型
String contentType = file.getContentType();
if (!isValidFileType(contentType)) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Invalid file type"));
}
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
String filename = UUID.randomUUID().toString() + extension;
// 保存文件
Path uploadDir = Paths.get(uploadPath);
if (!Files.exists(uploadDir)) {
Files.createDirectories(uploadDir);
}
Path filePath = uploadDir.resolve(filename);
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
Map<String, String> response = new HashMap<>();
response.put("filename", filename);
response.put("originalName", originalFilename);
response.put("size", String.valueOf(file.getSize()));
response.put("url", "/api/files/download/" + filename);
return ResponseEntity.ok(response);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Upload failed: " + e.getMessage()));
}
}
// 多文件上传
@PostMapping("/upload/multiple")
public ResponseEntity<List<Map<String, String>>> uploadMultipleFiles(
@RequestParam("files") MultipartFile[] files) {
List<Map<String, String>> results = new ArrayList<>();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
// 调用单文件上传逻辑
ResponseEntity<Map<String, String>> result = uploadFile(file);
results.add(result.getBody());
}
}
return ResponseEntity.ok(results);
}
private boolean isValidFileType(String contentType) {
return contentType != null && (
contentType.startsWith("image/") ||
contentType.equals("application/pdf") ||
contentType.equals("text/plain")
);
}
private String getFileExtension(String filename) {
if (filename == null || filename.lastIndexOf('.') == -1) {
return "";
}
return filename.substring(filename.lastIndexOf('.'));
}
}
文件下载
@RestController
@RequestMapping("/api/files")
public class FileController {
// 文件下载
@GetMapping("/download/{filename}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
try {
Path filePath = Paths.get(uploadPath).resolve(filename);
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists() || !resource.isReadable()) {
return ResponseEntity.notFound().build();
}
// 获取文件的MIME类型
String contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// 在线预览
@GetMapping("/preview/{filename}")
public ResponseEntity<Resource> previewFile(@PathVariable String filename) {
try {
Path filePath = Paths.get(uploadPath).resolve(filename);
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists() || !resource.isReadable()) {
return ResponseEntity.notFound().build();
}
String contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "inline")
.body(resource);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
文件上传配置
# application.properties
# 文件上传配置
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=50MB
spring.servlet.multipart.file-size-threshold=2KB
# 自定义上传路径
file.upload.path=/app/uploads
总结
本章详细介绍了Spring Boot Web开发的核心内容:
- Spring MVC架构:理解MVC模式和工作流程
- 控制器开发:@Controller、@RestController、请求映射
- 参数处理:路径参数、查询参数、请求体处理
- 响应处理:ResponseEntity、统一响应格式
- 异常处理:全局异常处理、自定义异常
- 数据验证:Bean Validation、自定义验证器
- 内容协商:多格式响应支持
- 跨域处理:CORS配置
- 拦截器和过滤器:请求预处理和后处理
- 文件操作:文件上传下载功能
这些知识点构成了Spring Boot Web开发的基础,掌握这些内容可以开发出功能完整的Web应用程序。