springmvc简单使用案例(三)

HttpMessageConverter

HttpMessageConverter是Spring MVC中的核心接口,译为"HTTP消息转换器"。该接口包含多种实现类,每个实现类对应不同的消息转换机制。

转换器转换的是什么?

实现HTTP协议与Java对象之间的双向转换。如下图所示:

这段代码是我们之前经常编写的。请求体中的数据会通过HttpMessageConverter接口的实现类FormHttpMessageConverter转换为user对象。

如图所示,FormHttpMessageConverter的主要功能就是将请求协议数据转换为对应的Java对象。

再看下图:

这段代码是我们之前常用的实现方式:Controller返回的逻辑视图名称,会由视图解析器转换为物理视图名称并生成视图对象。随后,StringHttpMessageConverter将视图对象中的HTML字符串写入HTTP响应体,从而完成整个响应流程。

如上图所示,StringHttpMessageConverter的主要功能是将Java对象转换为响应协议数据

HttpMessageConverter是一个接口,SpringMVC为我们提供了丰富的实现类,每个实现类都具有独特的转换特性。

作为开发者,我们无需自行实现这些转换器,Spring MVC已经为我们完成了这部分工作。我们只需要根据具体的业务场景,选择合适的HTTP消息转换器即可。

那么如何选择呢?通过使用SpringMVC提供的各种注解,我们可以灵活地启用所需的消息转换器。

在HTTP消息转换器部分,我们需要重点掌握两个核心注解和两个关键类:

  • @ResponseBody

@ResponseBody 是 Spring MVC 中的一个核心注解,用于将控制器方法的返回值直接转换为 HTTP 响应体(Response Body),而不是将其解析为跳转的视图名称。它是前后端数据交互(尤其是前后端分离架构)中不可或缺的注解。

使用场景
  1. 前后端分离接口:返回 JSON/XML 数据给前端(如 Ajax 请求、移动端接口)。
  2. 返回纯文本:直接返回字符串、数字等简单类型。
1. 返回 Java 对象(自动转为 JSON)
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    // 方法返回 User 对象,@ResponseBody 会将其转为 JSON
    @GetMapping("/user")
    @ResponseBody
    public User getUser() {
        User user = new User();
        user.setId(1L);
        user.setUsername("张三");
        user.setEmail("zhangsan@test.com");
        return user;
    }
}

前端请求 /user 时,会收到 JSON 响应:

{"id":1,"username":"张三","email":"zhangsan@test.com"}
2. 返回字符串或简单类型
@Controller
public class TestController {

    // 返回纯文本
    @GetMapping("/hello")
    @ResponseBody
    public String sayHello() {
        return "Hello, Spring MVC!";
    }

    // 返回数字
    @GetMapping("/count")
    @ResponseBody
    public int getCount() {
        return 100;
    }
}

前端请求 /hello 会收到文本 Hello, Spring MVC!;请求 /count 会收到 100

注意事项
  • 依赖要求:若返回 Java 对象并希望转为 JSON,需确保项目中引入 Jackson 依赖(Spring Boot 已默认集成)。
  • 返回值类型:支持任意 Java 类型(对象、集合、基本类型等),转换器会自动处理。
  • 与 @RequestBody 区别@RequestBody 用于将请求体转换为 Java 对象(入参),@ResponseBody 用于将 Java 对象转换为响应体(出参),二者常配合使用。
  • @RequestBody

@RequestBody 是 Spring MVC 中的一个注解,用于将 HTTP 请求体(Request Body)的内容转换为 Java 对象,是前后端传递复杂数据(如 JSON、XML 等格式)的核心手段。

使用场景
  1. 前后端分离接口:前端通过 Ajax 发送 JSON 数据(如新增用户的表单数据),后端用 @RequestBody 接收并转换为 Java 对象。
  2. 复杂数据提交:提交包含嵌套结构、数组的请求体(如订单包含多个商品)。
1. 接收 JSON 请求体并转换为 Java 对象
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController //组合注解(@Controller+@ResponnseBody)
public class UserController {

    // 前端发送的 JSON 会被转换为 User 对象
    @PostMapping("/user")
    public Map<String, Object> addUser(@RequestBody User user) {
        // 处理 user 对象(如保存到数据库)
        return Map.of("success", true, "msg", "用户添加成功", "id", user.getId());
    }
}

前端发送的请求示例(Content-Type 需为 application/json):

{
  "username": "李四",
  "email": "lisi@test.com",
  "status": 1
}

后端 User 类需包含与 JSON 字段对应的属性及 getter/setter。

2. 接收复杂结构的请求体
// 订单实体类
class Order {
    private Long id;
    private String orderNo;
    private List<Product> products; // 嵌套的商品列表
    // getter/setter 省略
}

class Product {
    private Long productId;
    private String name;
    private Integer quantity;
    // getter/setter 省略
}

@RestController
public class OrderController {

    @PostMapping("/order")
    public Map<String, Object> createOrder(@RequestBody Order order) {
        // 处理订单逻辑
        return Map.of("success", true, "msg", "订单创建成功", "orderId", order.getId());
    }
}

前端发送的 JSON 示例:

{
  "orderNo": "ORD20251026001",
  "products": [
    { "productId": 1001, "name": "手机", "quantity": 2 },
    { "productId": 1002, "name": "耳机", "quantity": 1 }
  ]
}
注意事项
  • 请求头要求:前端发送请求时,需将 Content-Type 设置为 application/json(或对应格式的类型),否则 Spring 无法识别请求体格式。
  • 依赖要求:若解析 JSON,需确保项目中引入 Jackson 依赖(Spring Boot 已默认集成)。
  • 空值处理:若请求体与 Java 对象的字段不匹配,会抛出 HttpMessageNotReadableException,需确保前后端字段一致。
  • ResponseEntity

在Spring框架中,ResponseEntity 并不是一个注解,而是一个org.springframework.http.ResponseEntity),用于封装 HTTP 响应的完整信息,包括响应状态码(Status Code)、响应头(Headers)和响应体(Body)。它比 @ResponseBody 更灵活,能精确控制响应的各个部分。

1. 基础用法(设置状态码和响应体)
@Controller
public class UserController {
    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        if (user == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
        } else {
            return ResponseEntity.ok(user);
        }
    }
}
2. 自定义响应头和状态码
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;

@GetMapping("/file")
public ResponseEntity<byte[]> downloadFile() {
    byte[] fileContent = ...; // 读取文件内容
    HttpHeaders headers = new HttpHeaders();
    // 设置响应头:指定文件名和下载方式
    headers.add("Content-Disposition", "attachment; filename=\"test.txt\"");
    headers.add("Cache-Control", "no-cache");

    // 状态码 200 OK + 响应头 + 响应体(文件字节数组)
    return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
}
  • RequestEntity

RequestEntity 是 Spring 框架中的一个org.springframework.http.RequestEntity),用于封装 HTTP 请求的完整信息,包括请求方法(Method)、请求 URL、请求头(Headers)和请求体(Body)。它通常在控制器方法中作为参数使用,用于获取更全面的请求信息,比 @RequestBody 能获取更多元的请求数据(如请求方法、头信息等)

1. 基础用法(获取请求完整信息)
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RequestDemoController {

    @PostMapping("/request-info")
    public ResponseEntity<String> handleRequest(RequestEntity<User> requestEntity) {
        // 1. 获取请求方法(如 POST)
        String method = requestEntity.getMethod().name();
        
        // 2. 获取请求 URI
        String uri = requestEntity.getUrl().toString();
        
        // 3. 获取请求头(如 Content-Type)
        String contentType = requestEntity.getHeaders().getFirst("Content-Type");
        
        // 4. 获取请求体(已转换为 User 对象)
        User user = requestEntity.getBody();
        
        // 拼接响应信息
        String response = String.format(
            "方法: %s, URI: %s, Content-Type: %s, 接收用户: %s",
            method, uri, contentType, user.getUsername()
        );
        
        return ResponseEntity.ok(response);
    }
}

前端发送 POST 请求到 /request-info,携带 JSON 格式的 User 数据

{ "username": "张三", "age": 25 }

文件上传和文件下载

文件上传

在SpringMVC 6中不再需要添加以下依赖,而Spring 5及更早版本则需引入:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>

前端页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f5f7fa;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
        }

        .upload-container {
            background-color: white;
            padding: 2rem 3rem;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            width: 100%;
            max-width: 400px;
        }

        .upload-title {
            color: #333;
            text-align: center;
            margin-bottom: 1.5rem;
            font-size: 1.5rem;
        }

        .file-input-container {
            margin-bottom: 1.5rem;
        }

        .file-input {
            width: 100%;
            padding: 1rem;
            border: 2px dashed #ccc;
            border-radius: 4px;
            transition: border-color 0.3s;
        }

        .file-input:hover {
            border-color: #4a90e2;
        }

        .submit-btn {
            width: 100%;
            padding: 0.8rem;
            background-color: #4a90e2;
            color: white;
            border: none;
            border-radius: 4px;
            font-size: 1rem;
            cursor: pointer;
            transition: background-color 0.3s;
        }

        .submit-btn:hover {
            background-color: #3a7bc8;
        }

        .message {
            margin-top: 1rem;
            padding: 0.8rem;
            border-radius: 4px;
            text-align: center;
        }

        .success {
            background-color: #e8f5e9;
            color: #2e7d32;
        }

        .error {
            background-color: #ffebee;
            color: #c62828;
        }
    </style>
</head>
<body>
<div class="upload-container">
    <h2 class="upload-title">文件上传</h2>

    <!-- 显示后端返回的消息 -->
    <th:block th:if="${message != null}">
        <div class="message" th:classappend="${messageType == 'success' ? 'success' : 'error'}" th:text="${message}">
        </div>
    </th:block>

    <!-- 文件上传表单 -->
    <form th:action="@{/upload}" method="post" enctype="multipart/form-data">
        <div class="file-input-container">
            <input type="file" name="fileName" class="file-input" />
        </div>
        <input type="submit" value="上传文件" class="submit-btn" />
    </form>
</div>
</body>
</html>

效果如下:

web.xml里面增加上传文件配置信息

        <multipart-config>
            <!--设置单个支持最大文件的大小-->
            <max-file-size>102400</max-file-size>
            <!--设置整个表单所有文件上传的最大值-->
            <max-request-size>102400</max-request-size>
            <!--设置最小上传文件大小-->
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>

后端代码:

   @PostMapping("/upload")
    public String fileUp(@RequestParam("fileName") MultipartFile file, HttpServletRequest request) throws IOException {
        // 1. 校验文件是否为空
        if (file.isEmpty()) {
            return "文件为空";
        }

        // 2. 获取上传目录的真实路径(确保目录存在)
        String realPath = request.getServletContext().getRealPath("/upload");
        File uploadDir = new File(realPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs(); // 自动创建多级目录
        }

        // 3. 生成唯一文件名(避免覆盖)
        String originalFilename = file.getOriginalFilename();
        String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); // 扩展名(如 .jpg)
        String uniqueFileName = UUID.randomUUID().toString() + suffix;

        // 4. 保存文件(核心:用 transferTo 替代手动流操作)
        file.transferTo(new File(uploadDir, uniqueFileName)); // 直接传入目标文件对象
        return "ok";
    }

上传成功

建议:上传文件时,文件起名采用UUID。以防文件覆盖。

文件下载

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件下载</title>
    <!-- 引入Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- 引入Font Awesome -->
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">

    <style>
        .progress-animation {
            transition: width 0.3s ease-in-out;
        }
        .fade-in {
            animation: fadeIn 0.3s ease-in-out;
        }
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }
    </style>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col">
<div class="container mx-auto px-4 py-12 flex-grow flex flex-col items-center justify-center">
    <div class="w-full max-w-md">
        <h1 class="text-2xl font-bold text-center text-gray-800 mb-8">文件下载</h1>

        <!-- 下载表单 -->
        <form id="downloadForm" class="bg-white p-6 rounded-lg shadow-md">
            <div class="mb-4">
                <label for="fileName" class="block text-gray-700 mb-2">文件名称</label>
                <div class="relative">
                        <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
                            <i class="fa fa-file-text-o"></i>
                        </span>
                    <input
                            type="text"
                            id="fileName"
                            name="fileName"
                            placeholder="请输入要下载的文件名"
                            class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
                            required
                    >
                </div>
            </div>

            <button
                    type="submit"
                    class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition-colors flex items-center justify-center gap-2"
            >
                <i class="fa fa-download"></i>
                <span>下载文件</span>
            </button>
        </form>

        <!-- 提示信息 -->
        <div class="mt-4 text-center text-sm text-gray-500">
            <p>请输入正确的文件名(包含扩展名)以获取文件</p>
        </div>
    </div>
</div>

<!-- 下载进度提示 -->
<div id="progressBar" class="fixed bottom-4 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-3 rounded-md shadow-lg hidden fade-in max-w-md w-full">
    <div class="flex items-center gap-3">
        <div class="animate-spin">
            <i class="fa fa-circle-o-notch"></i>
        </div>
        <div class="flex-grow">
            <p id="progressText">正在准备下载...</p>
            <div class="w-full h-1 bg-gray-600 rounded-full mt-1 overflow-hidden">
                <div id="progressIndicator" class="h-full bg-blue-500 w-0 progress-animation"></div>
            </div>
        </div>
        <button id="cancelBtn" class="text-gray-300 hover:text-white">
            <i class="fa fa-times"></i>
        </button>
    </div>
</div>

<script>
    // 获取DOM元素
    const form = document.getElementById('downloadForm');
    const fileNameInput = document.getElementById('fileName');
    const progressBar = document.getElementById('progressBar');
    const progressText = document.getElementById('progressText');
    const progressIndicator = document.getElementById('progressIndicator');
    const cancelBtn = document.getElementById('cancelBtn');

    let progressInterval;
    let downloadWindow;

    // 表单提交处理
    form.addEventListener('submit', function(e) {
        e.preventDefault();
        const fileName = fileNameInput.value.trim();

        if (fileName) {
            startDownload(fileName);
        }
    });

    // 取消下载
    cancelBtn.addEventListener('click', function() {
        cancelDownload();
    });

    // 开始下载流程
    function startDownload(fileName) {
        // 显示进度条
        progressBar.classList.remove('hidden');
        progressText.textContent = `正在下载: ${fileName}`;
        progressIndicator.style.width = '0%';

        // 模拟进度更新
        let progress = 0;
        progressInterval = setInterval(() => {
            progress += Math.random() * 10;
            if (progress >= 90) {
                progress = 90; // 保持在90%直到实际下载开始
                clearInterval(progressInterval);

                // 构建下载URL,包含文件名参数
                const downloadUrl = 'download' + `?fileName=${encodeURIComponent(fileName)}`;

                // 打开下载链接
                downloadWindow = window.open(downloadUrl, '_blank');

                // 检查下载窗口是否被浏览器阻止
                if (!downloadWindow) {
                    progressText.textContent = '下载被浏览器阻止,请允许弹出窗口';
                    return;
                }

                // 完成进度显示
                setTimeout(() => {
                    progressIndicator.style.width = '100%';
                    progressText.textContent = `下载已开始: ${fileName}`;
                    setTimeout(() => {
                        progressBar.classList.add('hidden');
                    }, 1500);
                }, 500);
            }

            progressIndicator.style.width = `${progress}%`;
        }, 300);
    }

    // 取消下载
    function cancelDownload() {
        clearInterval(progressInterval);
        if (downloadWindow && !downloadWindow.closed) {
            downloadWindow.close();
        }
        progressBar.classList.add('hidden');
        progressIndicator.style.width = '0%';
    }
</script>
</body>
</html>

效果如下:

文件下载核心程序,使用ResponseEntity:

@GetMapping("/download")
    public ResponseEntity<byte[]> downloadFile(HttpServletResponse response, HttpServletRequest request) throws IOException {
        String fileName = request.getParameter("fileName");
        File file = new File(request.getServletContext().getRealPath("/upload") + "/"+fileName);
        // 创建响应头对象
        HttpHeaders headers = new HttpHeaders();
        // 设置响应内容类型
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        // 设置下载文件的名称
        headers.setContentDispositionFormData("attachment", file.getName());

        // 下载文件
        ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK);
        return entity;
    }

效果如下:

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值