Spring MVC详解

Spring MVC详解:从原理到实战

前言

Spring MVC是Spring Framework的一个核心模块,是目前最流行的Java Web框架之一。它基于MVC设计模式,提供了一套完整的Web应用开发解决方案。本文将深入讲解Spring MVC的核心原理、关键组件和实战应用。

一、Spring MVC概述

1.1 MVC架构

MVC(Model-View-Controller)是一种软件架构模式,将应用分为三个核心组件:

markdown 复制代码
MVC架构图
┌─────────────────────────────────────────┐
│          浏览器(Browser)               │
└──────────────┬──────────────────────────┘
               │ HTTP Request
               ▼
┌─────────────────────────────────────────┐
│         Controller(控制器)             │
│  - 接收请求                              │
│  - 调用业务逻辑                          │
│  - 返回视图名称                          │
└──────┬──────────────────────┬───────────┘
       │                      │
       │ 调用                  │ 返回Model
       ▼                      ▼
┌──────────────┐      ┌──────────────────┐
│    Model     │      │      View        │
│  (模型)     │      │    (视图)       │
│  - 业务数据   │      │  - 渲染页面       │
│  - 业务逻辑   │      │  - 展示数据       │
└──────────────┘      └──────────────────┘

1.2 Spring MVC核心组件

markdown 复制代码
Spring MVC请求处理流程
┌─────────────────────────────────────────┐
│  1. DispatcherServlet                   │
│     - 前端控制器,统一处理请求            │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│  2. HandlerMapping                      │
│     - 根据URL查找Handler                 │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│  3. HandlerAdapter                      │
│     - 执行Handler(Controller方法)      │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│  4. Controller                          │
│     - 处理业务逻辑                       │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│  5. ViewResolver                        │
│     - 解析视图名称为具体视图              │
└────────────┬────────────────────────────┘
             ▼
┌─────────────────────────────────────────┐
│  6. View                                │
│     - 渲染并返回响应                     │
└─────────────────────────────────────────┘

二、快速开始

2.1 Maven依赖

xml 复制代码
<!-- Spring Boot Web Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Thymeleaf模板引擎(可选) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- 参数校验 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.2 第一个Controller

java 复制代码
/**
 * Hello World Controller
 */
@RestController
@RequestMapping("/api")
public class HelloController {

    /**
     * 简单的GET请求
     */
    @GetMapping("/hello")
    public String hello() {
        return "Hello, Spring MVC!";
    }

    /**
     * 路径参数
     */
    @GetMapping("/hello/{name}")
    public String helloWithName(@PathVariable String name) {
        return "Hello, " + name + "!";
    }

    /**
     * 请求参数
     */
    @GetMapping("/greet")
    public String greet(@RequestParam String name,
                       @RequestParam(defaultValue = "18") Integer age) {
        return String.format("Hello, %s! You are %d years old.", name, age);
    }

    /**
     * 返回JSON对象
     */
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        return new User(id, "张三", "zhangsan@example.com");
    }
}

@Data
@AllArgsConstructor
class User {
    private Long id;
    private String name;
    private String email;
}

2.3 配置类

java 复制代码
/**
 * Web MVC配置
 */
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 配置静态资源处理
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }

    /**
     * 配置视图解析器
     */
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    /**
     * 配置消息转换器
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new MappingJackson2HttpMessageConverter());
    }

    /**
     * 配置拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/public/**");
    }

    /**
     * 跨域配置
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

三、请求处理

3.1 请求映射注解

java 复制代码
/**
 * 请求映射注解详解
 */
@RestController
@RequestMapping("/api/products")
public class ProductController {

    /**
     * @GetMapping - 处理GET请求
     */
    @GetMapping
    public List<Product> getAllProducts() {
        return Arrays.asList(
            new Product(1L, "商品1", new BigDecimal("99.99")),
            new Product(2L, "商品2", new BigDecimal("199.99"))
        );
    }

    /**
     * @PostMapping - 处理POST请求
     */
    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        product.setId(System.currentTimeMillis());
        return product;
    }

    /**
     * @PutMapping - 处理PUT请求
     */
    @PutMapping("/{id}")
    public Product updateProduct(@PathVariable Long id,
                                @RequestBody Product product) {
        product.setId(id);
        return product;
    }

    /**
     * @DeleteMapping - 处理DELETE请求
     */
    @DeleteMapping("/{id}")
    public void deleteProduct(@PathVariable Long id) {
        System.out.println("删除商品: " + id);
    }

    /**
     * @PatchMapping - 处理PATCH请求
     */
    @PatchMapping("/{id}")
    public Product patchProduct(@PathVariable Long id,
                               @RequestBody Map<String, Object> updates) {
        Product product = new Product(id, "商品" + id, new BigDecimal("99.99"));
        // 应用部分更新
        return product;
    }

    /**
     * 多个HTTP方法
     */
    @RequestMapping(value = "/{id}/status",
                   method = {RequestMethod.GET, RequestMethod.POST})
    public String getStatus(@PathVariable Long id) {
        return "商品状态";
    }

    /**
     * 请求参数条件匹配
     */
    @GetMapping(params = "category=electronics")
    public List<Product> getElectronics() {
        return new ArrayList<>();
    }

    /**
     * 请求头条件匹配
     */
    @GetMapping(headers = "X-API-Version=1")
    public List<Product> getProductsV1() {
        return new ArrayList<>();
    }

    /**
     * Content-Type条件匹配
     */
    @PostMapping(consumes = "application/json")
    public Product createProductJson(@RequestBody Product product) {
        return product;
    }

    /**
     * Accept条件匹配
     */
    @GetMapping(value = "/{id}", produces = "application/json")
    public Product getProductJson(@PathVariable Long id) {
        return new Product(id, "商品" + id, new BigDecimal("99.99"));
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Product {
    private Long id;
    private String name;
    private BigDecimal price;
}

3.2 参数绑定

java 复制代码
/**
 * 参数绑定详解
 */
@RestController
@RequestMapping("/api/demo")
public class ParameterBindingController {

    /**
     * @PathVariable - 路径变量
     */
    @GetMapping("/users/{userId}/orders/{orderId}")
    public String getOrder(@PathVariable Long userId,
                          @PathVariable("orderId") Long id) {
        return "用户" + userId + "的订单" + id;
    }

    /**
     * @RequestParam - 请求参数
     */
    @GetMapping("/search")
    public String search(
        @RequestParam String keyword,                          // 必需参数
        @RequestParam(required = false) String category,       // 可选参数
        @RequestParam(defaultValue = "1") Integer page,       // 默认值
        @RequestParam(name = "size") Integer pageSize         // 参数重命名
    ) {
        return String.format("搜索: %s, 分类: %s, 页码: %d, 大小: %d",
                           keyword, category, page, pageSize);
    }

    /**
     * @RequestBody - 请求体
     */
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        return user;
    }

    /**
     * @RequestHeader - 请求头
     */
    @GetMapping("/info")
    public String getInfo(
        @RequestHeader("User-Agent") String userAgent,
        @RequestHeader(value = "Accept-Language", defaultValue = "zh-CN") String language
    ) {
        return "浏览器: " + userAgent + ", 语言: " + language;
    }

    /**
     * @CookieValue - Cookie值
     */
    @GetMapping("/session")
    public String getSession(@CookieValue("JSESSIONID") String sessionId) {
        return "会话ID: " + sessionId;
    }

    /**
     * @ModelAttribute - 模型属性
     */
    @PostMapping("/form")
    public User submitForm(@ModelAttribute User user) {
        return user;
    }

    /**
     * HttpServletRequest - 原生请求对象
     */
    @GetMapping("/request")
    public String handleRequest(HttpServletRequest request) {
        String method = request.getMethod();
        String uri = request.getRequestURI();
        return method + " " + uri;
    }

    /**
     * 数组参数
     */
    @GetMapping("/batch")
    public String batchDelete(@RequestParam Long[] ids) {
        return "删除: " + Arrays.toString(ids);
    }

    /**
     * List参数
     */
    @GetMapping("/list")
    public String getList(@RequestParam List<String> tags) {
        return "标签: " + tags;
    }

    /**
     * Map参数
     */
    @GetMapping("/map")
    public String getMap(@RequestParam Map<String, String> params) {
        return "参数: " + params;
    }

    /**
     * 自定义对象绑定
     */
    @GetMapping("/query")
    public String query(SearchQuery query) {
        return "查询: " + query;
    }
}

@Data
class SearchQuery {
    private String keyword;
    private String category;
    private Integer page;
    private Integer size;
}

3.3 参数校验

java 复制代码
/**
 * 参数校验
 */
@RestController
@RequestMapping("/api/validation")
@Validated
public class ValidationController {

    /**
     * 实体校验
     */
    @PostMapping("/users")
    public User createUser(@Valid @RequestBody UserDTO userDTO) {
        return new User(null, userDTO.getUsername(), userDTO.getEmail());
    }

    /**
     * 参数校验
     */
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable @Min(1) Long id) {
        return new User(id, "User" + id, "user@example.com");
    }

    /**
     * 分组校验
     */
    @PostMapping("/users/advanced")
    public User createUserAdvanced(
        @Validated(CreateGroup.class) @RequestBody UserDTO userDTO
    ) {
        return new User(null, userDTO.getUsername(), userDTO.getEmail());
    }

    /**
     * 异常处理
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleValidationException(
            MethodArgumentNotValidException ex) {

        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
            errors.put(error.getField(), error.getDefaultMessage())
        );

        return ResponseEntity.badRequest().body(errors);
    }
}

/**
 * 校验DTO
 */
@Data
class UserDTO {

    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$",
             message = "密码至少8位,包含大小写字母和数字")
    private String password;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;

    @Min(value = 18, message = "年龄必须大于等于18")
    @Max(value = 100, message = "年龄必须小于等于100")
    private Integer age;

    @NotNull(message = "手机号不能为空")
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
}

/**
 * 校验分组
 */
interface CreateGroup {}
interface UpdateGroup {}

四、响应处理

4.1 返回值类型

java 复制代码
/**
 * 响应处理
 */
@RestController
@RequestMapping("/api/response")
public class ResponseController {

    /**
     * 返回String(直接输出)
     */
    @GetMapping("/string")
    public String returnString() {
        return "Hello World";
    }

    /**
     * 返回对象(自动转JSON)
     */
    @GetMapping("/object")
    public User returnObject() {
        return new User(1L, "张三", "zhangsan@example.com");
    }

    /**
     * 返回List
     */
    @GetMapping("/list")
    public List<User> returnList() {
        return Arrays.asList(
            new User(1L, "张三", "zhangsan@example.com"),
            new User(2L, "李四", "lisi@example.com")
        );
    }

    /**
     * 返回Map
     */
    @GetMapping("/map")
    public Map<String, Object> returnMap() {
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "success");
        result.put("data", new User(1L, "张三", "zhangsan@example.com"));
        return result;
    }

    /**
     * 返回ResponseEntity(自定义状态码和响应头)
     */
    @GetMapping("/entity")
    public ResponseEntity<User> returnEntity() {
        User user = new User(1L, "张三", "zhangsan@example.com");
        return ResponseEntity
            .ok()
            .header("X-Custom-Header", "value")
            .body(user);
    }

    /**
     * 返回不同状态码
     */
    @PostMapping("/users")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        user.setId(System.currentTimeMillis());
        return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(user);
    }

    /**
     * 返回空响应
     */
    @DeleteMapping("/users/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        return ResponseEntity.noContent().build();
    }

    /**
     * 下载文件
     */
    @GetMapping("/download")
    public ResponseEntity<Resource> downloadFile() {
        ByteArrayResource resource = new ByteArrayResource("文件内容".getBytes());

        return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION,
                   "attachment; filename=\"file.txt\"")
            .body(resource);
    }

    /**
     * 重定向
     */
    @GetMapping("/redirect")
    public String redirect() {
        return "redirect:/api/response/string";
    }

    /**
     * 转发
     */
    @GetMapping("/forward")
    public String forward() {
        return "forward:/api/response/object";
    }
}

4.2 统一响应格式

java 复制代码
/**
 * 统一响应结果
 */
@Data
@AllArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "成功", data);
    }

    public static <T> Result<T> error(String message) {
        return new Result<>(500, message, null);
    }

    public static <T> Result<T> error(Integer code, String message) {
        return new Result<>(code, message, null);
    }
}

/**
 * 响应包装Advice
 */
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType,
                          Class<? extends HttpMessageConverter<?>> converterType) {
        // 判断是否需要包装
        return !returnType.getParameterType().equals(Result.class);
    }

    @Override
    public Object beforeBodyWrite(Object body,
                                 MethodParameter returnType,
                                 MediaType selectedContentType,
                                 Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                 ServerHttpRequest request,
                                 ServerHttpResponse response) {

        // 如果已经是Result类型,直接返回
        if (body instanceof Result) {
            return body;
        }

        // 包装为Result
        return Result.success(body);
    }
}

五、拦截器与过滤器

5.1 拦截器

java 复制代码
/**
 * 日志拦截器
 */
@Slf4j
public class LoggingInterceptor implements HandlerInterceptor {

    /**
     * 请求处理前
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        String method = request.getMethod();
        String uri = request.getRequestURI();
        log.info("请求开始: {} {}", method, uri);

        // 记录开始时间
        request.setAttribute("startTime", System.currentTimeMillis());

        return true;  // 返回true继续处理,false中断
    }

    /**
     * 请求处理后,视图渲染前
     */
    @Override
    public void postHandle(HttpServletRequest request,
                          HttpServletResponse response,
                          Object handler,
                          ModelAndView modelAndView) throws Exception {

        log.info("请求处理完成");
    }

    /**
     * 请求完成后
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler,
                               Exception ex) throws Exception {

        Long startTime = (Long) request.getAttribute("startTime");
        long duration = System.currentTimeMillis() - startTime;

        log.info("请求结束,耗时: {}ms", duration);

        if (ex != null) {
            log.error("请求异常", ex);
        }
    }
}

/**
 * 认证拦截器
 */
public class AuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler) throws Exception {

        // 获取Token
        String token = request.getHeader("Authorization");

        if (token == null || !validateToken(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("未授权");
            return false;
        }

        return true;
    }

    private boolean validateToken(String token) {
        // 验证Token
        return true;
    }
}

5.2 过滤器

java 复制代码
/**
 * 自定义过滤器
 */
@Component
@Order(1)
public class CustomFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("过滤器初始化");
    }

    @Override
    public void doFilter(ServletRequest request,
                        ServletResponse response,
                        FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        System.out.println("过滤器前置处理: " + req.getRequestURI());

        // 继续过滤器链
        chain.doFilter(request, response);

        System.out.println("过滤器后置处理");
    }

    @Override
    public void destroy() {
        System.out.println("过滤器销毁");
    }
}

/**
 * 使用FilterRegistrationBean注册过滤器
 */
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<CustomFilter> customFilter() {
        FilterRegistrationBean<CustomFilter> registration =
            new FilterRegistrationBean<>();

        registration.setFilter(new CustomFilter());
        registration.addUrlPatterns("/api/*");
        registration.setOrder(1);

        return registration;
    }
}

5.3 拦截器vs过滤器

css 复制代码
拦截器 vs 过滤器对比
┌──────────────┬───────────────┬──────────────┐
│  特性         │  拦截器        │  过滤器       │
├──────────────┼───────────────┼──────────────┤
│  实现原理     │  动态代理      │  函数回调     │
│  规范         │  Spring规范   │  Servlet规范  │
│  依赖         │  依赖Spring   │  不依赖Spring │
│  拦截范围     │  只拦截Controller│ 拦截所有请求│
│  访问Spring容器│  可以        │  不可以       │
│  执行顺序     │  在Filter之后 │  在Interceptor之前│
└──────────────┴───────────────┴──────────────┘

六、异常处理

6.1 全局异常处理

java 复制代码
/**
 * 全局异常处理器
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 处理参数校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValidationException(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
            errors.put(error.getField(), error.getDefaultMessage())
        );

        return Result.error(400, "参数校验失败:" + errors);
    }

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException ex) {
        log.error("业务异常: {}", ex.getMessage());
        return Result.error(ex.getCode(), ex.getMessage());
    }

    /**
     * 处理资源未找到异常
     */
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Result<?> handleResourceNotFoundException(ResourceNotFoundException ex) {
        return Result.error(404, ex.getMessage());
    }

    /**
     * 处理非法参数异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public Result<?> handleIllegalArgumentException(IllegalArgumentException ex) {
        return Result.error(400, "非法参数:" + ex.getMessage());
    }

    /**
     * 处理SQL异常
     */
    @ExceptionHandler(SQLException.class)
    public Result<?> handleSQLException(SQLException ex) {
        log.error("数据库异常", ex);
        return Result.error(500, "数据库错误");
    }

    /**
     * 处理所有未捕获的异常
     */
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception ex) {
        log.error("系统异常", ex);
        return Result.error(500, "系统错误:" + ex.getMessage());
    }
}

/**
 * 自定义业务异常
 */
@Data
public class BusinessException extends RuntimeException {
    private Integer code;

    public BusinessException(String message) {
        super(message);
        this.code = 500;
    }

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

/**
 * 资源未找到异常
 */
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

七、文件上传下载

7.1 文件上传

java 复制代码
/**
 * 文件上传Controller
 */
@RestController
@RequestMapping("/api/files")
public class FileUploadController {

    @Value("${upload.path:/tmp/uploads}")
    private String uploadPath;

    /**
     * 单文件上传
     */
    @PostMapping("/upload")
    public Result<String> uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            if (file.isEmpty()) {
                return Result.error("文件不能为空");
            }

            // 生成文件名
            String originalFilename = file.getOriginalFilename();
            String extension = originalFilename.substring(
                originalFilename.lastIndexOf("."));
            String filename = UUID.randomUUID().toString() + extension;

            // 保存文件
            Path path = Paths.get(uploadPath, filename);
            Files.createDirectories(path.getParent());
            file.transferTo(path.toFile());

            return Result.success(filename);

        } catch (IOException e) {
            return Result.error("文件上传失败:" + e.getMessage());
        }
    }

    /**
     * 多文件上传
     */
    @PostMapping("/batch-upload")
    public Result<List<String>> uploadFiles(
            @RequestParam("files") MultipartFile[] files) {

        List<String> filenames = new ArrayList<>();

        for (MultipartFile file : files) {
            if (!file.isEmpty()) {
                // 保存文件逻辑
                String filename = saveFile(file);
                filenames.add(filename);
            }
        }

        return Result.success(filenames);
    }

    /**
     * 带参数的文件上传
     */
    @PostMapping("/upload-with-data")
    public Result<FileUploadResponse> uploadWithData(
            @RequestParam("file") MultipartFile file,
            @RequestParam("title") String title,
            @RequestParam("description") String description) {

        String filename = saveFile(file);

        FileUploadResponse response = new FileUploadResponse();
        response.setFilename(filename);
        response.setTitle(title);
        response.setDescription(description);
        response.setSize(file.getSize());

        return Result.success(response);
    }

    private String saveFile(MultipartFile file) {
        try {
            String originalFilename = file.getOriginalFilename();
            String extension = originalFilename.substring(
                originalFilename.lastIndexOf("."));
            String filename = UUID.randomUUID().toString() + extension;

            Path path = Paths.get(uploadPath, filename);
            Files.createDirectories(path.getParent());
            file.transferTo(path.toFile());

            return filename;
        } catch (IOException e) {
            throw new RuntimeException("文件保存失败", e);
        }
    }
}

@Data
class FileUploadResponse {
    private String filename;
    private String title;
    private String description;
    private Long size;
}

7.2 文件下载

java 复制代码
/**
 * 文件下载Controller
 */
@RestController
@RequestMapping("/api/download")
public class FileDownloadController {

    @Value("${upload.path:/tmp/uploads}")
    private String uploadPath;

    /**
     * 文件下载
     */
    @GetMapping("/{filename}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
        try {
            Path path = Paths.get(uploadPath, filename);
            Resource resource = new UrlResource(path.toUri());

            if (!resource.exists()) {
                throw new ResourceNotFoundException("文件不存在: " + filename);
            }

            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .header(HttpHeaders.CONTENT_DISPOSITION,
                       "attachment; filename=\"" + filename + "\"")
                .body(resource);

        } catch (IOException e) {
            throw new RuntimeException("文件下载失败", e);
        }
    }

    /**
     * 在线预览
     */
    @GetMapping("/preview/{filename}")
    public ResponseEntity<Resource> previewFile(@PathVariable String filename) {
        try {
            Path path = Paths.get(uploadPath, filename);
            Resource resource = new UrlResource(path.toUri());

            if (!resource.exists()) {
                throw new ResourceNotFoundException("文件不存在");
            }

            // 根据文件类型设置Content-Type
            String contentType = Files.probeContentType(path);
            if (contentType == null) {
                contentType = "application/octet-stream";
            }

            return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType(contentType))
                .body(resource);

        } catch (IOException e) {
            throw new RuntimeException("文件预览失败", e);
        }
    }
}

八、实战案例

8.1 案例1:RESTful API设计

java 复制代码
/**
 * RESTful风格的用户管理API
 */
@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserRestController {

    @Autowired
    private UserService userService;

    /**
     * 获取用户列表
     * GET /api/users?page=1&size=10&keyword=zhang
     */
    @GetMapping
    public Result<Page<User>> getUserList(
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "10") Integer size,
            @RequestParam(required = false) String keyword) {

        Page<User> users = userService.findUsers(page, size, keyword);
        return Result.success(users);
    }

    /**
     * 获取单个用户
     * GET /api/users/1
     */
    @GetMapping("/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        if (user == null) {
            throw new ResourceNotFoundException("用户不存在: " + id);
        }
        return Result.success(user);
    }

    /**
     * 创建用户
     * POST /api/users
     */
    @PostMapping
    public Result<User> createUser(@Valid @RequestBody UserDTO userDTO) {
        User user = userService.createUser(userDTO);
        return Result.success(user);
    }

    /**
     * 更新用户
     * PUT /api/users/1
     */
    @PutMapping("/{id}")
    public Result<User> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UserDTO userDTO) {

        User user = userService.updateUser(id, userDTO);
        return Result.success(user);
    }

    /**
     * 部分更新用户
     * PATCH /api/users/1
     */
    @PatchMapping("/{id}")
    public Result<User> patchUser(
            @PathVariable Long id,
            @RequestBody Map<String, Object> updates) {

        User user = userService.patchUser(id, updates);
        return Result.success(user);
    }

    /**
     * 删除用户
     * DELETE /api/users/1
     */
    @DeleteMapping("/{id}")
    public Result<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return Result.success(null);
    }

    /**
     * 批量删除
     * DELETE /api/users?ids=1,2,3
     */
    @DeleteMapping
    public Result<Void> batchDelete(@RequestParam List<Long> ids) {
        userService.batchDelete(ids);
        return Result.success(null);
    }
}

@Data
class Page<T> {
    private Long total;
    private Integer page;
    private Integer size;
    private List<T> records;
}

8.2 案例2:异步请求处理

java 复制代码
/**
 * 异步请求处理
 */
@RestController
@RequestMapping("/api/async")
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    /**
     * Callable异步处理
     */
    @GetMapping("/callable")
    public Callable<String> handleCallable() {
        return () -> {
            Thread.sleep(2000);  // 模拟耗时操作
            return "Callable结果";
        };
    }

    /**
     * DeferredResult异步处理
     */
    @GetMapping("/deferred")
    public DeferredResult<String> handleDeferred() {
        DeferredResult<String> result = new DeferredResult<>(5000L);

        // 异步处理
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(2000);
                result.setResult("DeferredResult结果");
            } catch (InterruptedException e) {
                result.setErrorResult("处理失败");
            }
        });

        return result;
    }

    /**
     * CompletableFuture异步处理
     */
    @GetMapping("/future")
    public CompletableFuture<String> handleFuture() {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
                return "CompletableFuture结果";
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * SSE(Server-Sent Events)
     */
    @GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter handleSse() {
        SseEmitter emitter = new SseEmitter();

        CompletableFuture.runAsync(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    emitter.send("数据 " + i);
                    Thread.sleep(1000);
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });

        return emitter;
    }
}

8.3 案例3:内容协商

java 复制代码
/**
 * 内容协商(根据Accept返回不同格式)
 */
@Controller
@RequestMapping("/api/content")
public class ContentNegotiationController {

    /**
     * 返回JSON或XML
     */
    @GetMapping(value = "/user/{id}",
               produces = {MediaType.APPLICATION_JSON_VALUE,
                          MediaType.APPLICATION_XML_VALUE})
    @ResponseBody
    public User getUser(@PathVariable Long id) {
        return new User(id, "张三", "zhangsan@example.com");
    }

    /**
     * 返回HTML或JSON
     */
    @GetMapping("/users")
    public Object getUsers(@RequestHeader(value = "Accept",
                                        defaultValue = "text/html") String accept) {

        List<User> users = Arrays.asList(
            new User(1L, "张三", "zhangsan@example.com"),
            new User(2L, "李四", "lisi@example.com")
        );

        if (accept.contains("application/json")) {
            return new ResponseEntity<>(users, HttpStatus.OK);
        } else {
            return "users";  // 返回视图名称
        }
    }
}

九、最佳实践

9.1 RESTful设计原则

sql 复制代码
RESTful API设计规范
┌─────────────────────────────────────────┐
│  1. 使用名词而非动词                     │
│     ✓ GET /users                        │
│     ✗ GET /getUsers                     │
├─────────────────────────────────────────┤
│  2. 使用复数形式                        │
│     ✓ GET /users/1                      │
│     ✗ GET /user/1                       │
├─────────────────────────────────────────┤
│  3. 使用HTTP方法表示操作                 │
│     GET    - 查询                       │
│     POST   - 创建                       │
│     PUT    - 完整更新                   │
│     PATCH  - 部分更新                   │
│     DELETE - 删除                       │
├─────────────────────────────────────────┤
│  4. 使用HTTP状态码                      │
│     200 - 成功                          │
│     201 - 创建成功                      │
│     204 - 无内容                        │
│     400 - 客户端错误                    │
│     401 - 未授权                        │
│     404 - 未找到                        │
│     500 - 服务器错误                    │
├─────────────────────────────────────────┤
│  5. 版本控制                            │
│     /api/v1/users                       │
│     或使用请求头: Accept-Version: v1    │
└─────────────────────────────────────────┘

9.2 性能优化

java 复制代码
/**
 * 性能优化配置
 */
@Configuration
public class PerformanceConfig {

    /**
     * 配置HTTP缓存
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .setCacheControl(CacheControl.maxAge(7, TimeUnit.DAYS));
    }

    /**
     * 配置Gzip压缩
     */
    @Bean
    public FilterRegistrationBean<CommonsRequestLoggingFilter> gzipFilter() {
        // Spring Boot会自动配置Gzip
        return null;
    }
}

十、总结

核心知识点回顾

sql 复制代码
Spring MVC核心要点
│
├── 核心组件
│   ├── DispatcherServlet(前端控制器)
│   ├── HandlerMapping(处理器映射)
│   ├── Controller(控制器)
│   └── ViewResolver(视图解析器)
│
├── 请求处理
│   ├── @RequestMapping系列注解
│   ├── 参数绑定(@PathVariable/@RequestParam等)
│   ├── 参数校验(@Valid/@Validated)
│   └── 请求体处理(@RequestBody)
│
├── 响应处理
│   ├── 返回值类型
│   ├── ResponseEntity
│   ├── 统一响应格式
│   └── 内容协商
│
├── 拦截与过滤
│   ├── 拦截器(HandlerInterceptor)
│   └── 过滤器(Filter)
│
├── 异常处理
│   ├── @ExceptionHandler
│   ├── @ControllerAdvice
│   └── 全局异常处理
│
└── 高级特性
    ├── 文件上传下载
    ├── 异步请求处理
    ├── RESTful API
    └── 跨域配置

Spring MVC是构建Web应用的强大框架,掌握其核心原理和最佳实践,能够帮助我们开发出高质量的Web应用。


相关推荐
kkkkkkkkl241 小时前
springboot日志实现
java·spring boot
Sally_xy1 小时前
安装 Docker
java·docker·容器
w***H6501 小时前
SpringCloud-持久层框架MyBatis Plus的使用与原理详解
spring·spring cloud·mybatis
洛克大航海1 小时前
Maven 的下载安装配置教程
java·maven
即将进化成人机1 小时前
Spring Boot入门
java·spring boot·后端
苏打水com1 小时前
HTML/CSS 核心考点详解(字节跳动 ToB 中台场景)
java·前端·javascript
-大头.1 小时前
Spring批处理与任务管理全解析
java·linux·spring
科普瑞传感仪器1 小时前
基于六维力传感器的机器人柔性装配,如何提升发动机零部件装配质量?
java·前端·人工智能·机器人·无人机
她说..1 小时前
Java AOP完全指南:从原理到实战(全套知识点+场景总结)
java·开发语言·spring·java-ee·springboot