SpringMVC 基础(二)

一、AJAX

1.1 简介

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML);

  • AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术;

  • AJAX 不是一种新的编程语言,而是一种用于创建更好更快,以及交互性更强的 Web 应用程序的技术;

  • 在 2005 年,Google 通过其 Google Suggest 使 AJAX 变得流行起来,Google Suggest 能够自动完成搜索单词;

  • Google Suggest 使用 AJAX 创造出动态性极强的 web 界面:当我们在谷歌的搜索框输入关键字时,JavaScript 会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表,如百度的搜索框:

  • 传统的网页(即不用 AJAX 技术的网页)想要更新内容或者提交一个表单,都需要重新加载整个网页;

  • 使用 AJAX 技术的网页,通过在后台服务器,进行少量的数据交换,就可以实现异步、局部更新;

  • 使用 AJAX,用户可以创建接近本地桌面应用的直接、高可用、更丰富、更动态的 Web 用户界面;

1.2 伪造 AJAX

  • 可以使用前端的一个标签,来伪造一个 ajax 的样子:iframe 标签

    • 新建模块,并导入 web 支持;
    • 配置 web.xml 及 springMVC 配置文件,运行 Tomcat 测试环境;
    • 编写 html 页面,使用 iframe 测试,感受下效果:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>iframe伪造ajax</title>
        <script>
            window.onload = function () {
                let myDate = new Date();
                document.getElementById("currentTime").innerText = myDate.getTime();
            };
    
            function LoadPage() {
                let url = document.getElementById("url").value;
                document.getElementById("iframe1").src = url;
            };
        </script>
    </head>
    <body>
    <div>
        <p>请输入要加载的地址:<span id="currentTime"></span></p>
        <p>
            <input id="url" type="text" value="https://juejin.cn/"/>
            <input type="submit" value="提交" onclick="LoadPage()"/>
        </p>
    </div>
    <div>
        <h3>加载页面位置:</h3>
        <iframe id="iframe1" style="width: 100%;height: 500px;"></iframe>
    </div>
    </body>
    </html>
    
    • 使用 IDEA 打开浏览器进行测试;

小结:

  • 利用 AJAX 可以实现:
    • 注册时,输入用户名,自动检测用户是否已经存在;
    • 登陆时,提示用户名,密码错误;
    • 删除数据行时,将行 ID 发送到后台,后台在数据库中删除,数据库删除成功后,在页面 DOM 中,将数据行也删除;
    • ...等等;

1.3 jQuery.ajax

  • jQuery 3.6.0 开发版 下载链接
  • Ajax 的核心是 XMLHttpRequest 对象(XHR):
    • XHR 为向服务器发送请求和解析服务器响应,提供了接口,能够以异步方式,从服务器获取新数据;
  • jQuery 提供多个与 AJAX 有关的方法;
  • 通过 jQuery AJAX 方法,能够使用 HTTP Get 和 HTTP Post,从远程服务器上请求文本、HTML、XML 或 JSON,同时能够把这些外部数据,直接载入网页的被选元素中;
  • jQuery(是一个库) 不是生产者,而是大自然搬运工;
  • jQuery Ajax 本质就是 XMLHttpRequest,对它进行了封装,方便调用:
jQuery.ajax(...)
/*
    部分参数:
        url:请求地址
        type:请求方式,GET、POST(1.9.0之后用method)
        headers:请求头
        data:要发送的数据
        contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8")
        async:是否异步
        timeout:设置请求超时时间(毫秒)
        beforeSend:发送请求前执行的函数(全局)
        complete:完成之后执行的回调函数(全局)
        success:成功之后执行的回调函数(全局)
        error:失败之后执行的回调函数(全局)
        accepts:通过请求头发送给服务器,告诉服务器当前客户端可接受的数据类型
        dataType:将服务器端返回的数据转换成指定类型
        "xml": 将服务器端返回的内容转换成xml格式
        "text": 将服务器端返回的内容转换成普通文本格式
        "html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。
        "script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式
        "json": 将服务器端返回的内容转换成相应的JavaScript对象
        "jsonp": JSONP 格式使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数
 */

测试一:HttpServletResponse 实现

  • 使用最原始的 HttpServletResponse 处理,最简单,最通用:
  • 配置 web.xml 和 springmvc 的配置文件:
    • 注意:静态资源过滤和注解驱动配置;
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置SpringMVC-->
    <!-- 1. 开启SpringMVC注解驱动,注意导入mvc的头文件-->
    <mvc:annotation-driven/>
    <!--2. 静态资源过滤-->
    <mvc:default-servlet-handler/>
    <!--3. 扫描包:Controller-->
    <context:component-scan base-package="com.study.controller"/>
    <!--4. 视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
  • 创建 Controll:AjaxController
// 不走视图页面,直接返回字符串
@RestController
public class AjaxController {
@RequestMapping("/a1")
    public void ajax1(String name, HttpServletResponse response) throws IOException {
        System.out.println("前端传递参数:" + name);
        if ("admin".equals(name)) {
            response.getWriter().println("true");
        } else {
            response.getWriter().println("false");
        }
    }
}
  • 导入 jQuery,可以使用在线的 CDN,也可以下载导入:
<%--<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>--%>
    <script src="${pageContext.request.contextPath}/static/js/jquery-3.6.0.js"></script>
  • 编写 index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
    <%--<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>--%>
    <script src="${pageContext.request.contextPath}/static/js/jquery-3.6.0.js"></script>
    <script>
        function a1() {
            // post、get都是调用的ajax方法,可以直接用 $.ajax
            $.post({
                url: "${pageContext.request.contextPath}/a1",
                // data:传递给后端的数据,键值对的形式 name对应后端的name
                data: {"name": $("#userName").val()},
                // data:后端返回的数据 status:状态
                success: function (data, status) {
                    console.log(data);
                    console.log(status);
                }
            });
        }
    </script>
</head>
<body>
<%--onblur:失去焦点触发事件--%>
用户名:<input type="text" id="userName" onblur="a1()"/>
</body>
</html>
  • 启动 Tomcat 测试:

    • 打开浏览器的控制台,当鼠标离开输入框时,可以看到发出了一个 ajax 的请求;
  • 流程分析:

测试二:SpringMVC、JSON 方式实现

  • 需要导入 Jackson 依赖:否则请求路径时,报错 406
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.2.2</version>
</dependency>
  • 创建实体类:user
    • 使用 lombok,需要事先导入依赖;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private int age;
    private String sex;
}
  • 在 Controller 类中添加方法:
    • 获取一个集合对象,展示到前端页面;
@RequestMapping("/a2")
public List<User> ajax2() {
    List<User> list = new ArrayList<>();
    list.add(new User("test01", 20, "男"));
    list.add(new User("test02", 21, "女"));
    list.add(new User("test03", 20, "男"));
    // 类中使用@RestController注解,将list转成json格式返回
    return list;
}
  • 前端页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <%--jQuery--%>
    <script src="${pageContext.request.contextPath}/static/js/jquery-3.6.0.js"></script>
    <script>
        $(function () {
            $("#btn").click(function () {
                /*
                    简写方式:
                    $.post(url,param[可以省略(传递给后端的参数)],success[回调函数])
                 */
                $.post("${pageContext.request.contextPath}/a2", function (data) {
                    console.log(data);
                    let html = "";
                    for (let i = 0; i < data.length; i++) {
                        <%-- jsp中需要转义es6模板字符串${}:\${} --%>
                        html += `<tr>
                        <td>\${data[i].name}</td>
                        <td>\${data[i].age}</td>
                        <td>\${data[i].sex}</td>
                        </tr>`
                    }
                    $("#content").html(html);
                });

                /*
                $.post({
                    url: "${pageContext.request.contextPath}/a2",
                    success:function (data){
                        let html = "";
                        for (let i = 0; i < data.length; i++) {
                            html += `<tr>
                        <td>\${data[i].name}</td>
                        <td>\${data[i].age}</td>
                        <td>\${data[i].sex}</td>
                        </tr>`
                        }
                        $("#content").html(html);
                    }
                });
                */
            })
        })
    </script>
</head>
<body>
<input type="button" id="btn" value="获取数据"/>
<table>
    <tr>
        <td>姓名</td>
        <td>年龄</td>
        <td>性别</td>
    </tr>
    <tbody id="content"></tbody>
</table>
</body>
</html>
  • 运行测试:

1.4 AJAX 注册提示

  • 在 Controller 类中,添加方法:
@RequestMapping("/a3")
public String ajax3(String name, String pwd) {
    String msg = "";
    if (name != null) {
        // admin:模拟数据库查询的数据
        if ("admin".equals(name)) {
            msg = "OK";
        } else {
            msg = "用户名有误";
        }
    }
    if (pwd != null) {
        // 123456:模拟数据库查询的数据
        if ("123456".equals(pwd)) {
            msg = "OK";
        } else {
            msg = "密码有误";
        }
    }
    // 类中使用@RestController注解,将msg转成json格式返回
    return msg;
}
  • 创建前端页面:login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>login</title>
    <script src="${pageContext.request.contextPath}/static/js/jquery-3.6.0.js"></script>
    <script>
        function a1() {
            $.post({
                url: "${pageContext.request.contextPath}/a3",
                data: {"name": $("#name").val()},
                success: function (data) {
                    if (data.toString() === "OK") {
                        $("#userInfo").css("color", "green");
                    }else {
                        $("#userInfo").css("color", "red");
                    }
                    $("#userInfo").html(data);
                }
            });
        }

        function a2() {
            $.post({
                url: "${pageContext.request.contextPath}/a3",
                data: {"pwd": $("#pwd").val()},
                success: function (data) {
                    if (data.toString() === "OK") {
                        $("#pwdInfo").css("color", "green");
                    }else {
                        $("#pwdInfo").css("color", "red");
                    }
                    $("#pwdInfo").text(data);
                }
            });
        }
    </script>
</head>
<body>
<p>
    <%--onblur:失去焦点触发事件--%>
    用户名:<input type="text" id="name" onblur="a1()"/>
    <span id="userInfo"></span>
</p>
<p>
    密码:<input type="text" id="pwd" onblur="a2()"/>
    <span id="pwdInfo"></span>
</p>
</body>
</html>
  • 处理 JSON 乱码:SpringMVC 配置文件中进行配置
<!--JSON乱码统一解决(固定代码)-->
<mvc:annotation-driven>
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <constructor-arg value="UTF-8"/>
        </bean>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                    <property name="failOnEmptyBeans" value="false"/>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
  • 运行测试:

1.5 获取 baidu 接口 Demo

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>JSONP百度搜索</title>
    <style>
        #q {
            width: 500px;
            height: 30px;
            border: 1px solid #ddd;
            line-height: 30px;
            display: block;
            margin: 0 auto;
            padding: 0 10px;
            font-size: 14px;
        }

        #ul {
            width: 520px;
            list-style: none;
            padding: 0;
            border: 1px solid #ddd;
            margin: -1px auto 0;
            display: none;
        }

        #ul li {
            line-height: 30px;
            padding: 0 10px;
        }

        #ul li:hover {
            background-color: #f60;
            color: #fff;
        }
    </style>
    <script>

        // 2.步骤二
        // 定义demo函数 (分析接口、数据)
        function demo(data) {
            let Ul = document.getElementById('ul');
            let html = '';
            // 如果搜索数据存在 把内容添加进去
            if (data.s.length) {
                // 隐藏掉的ul显示出来
                Ul.style.display = 'block';
                // 搜索到的数据循环追加到li里
                for (let i = 0; i < data.s.length; i++) {
                    // es6 模板字符串
                    html += `<li>${data.s[i]}</li>`
                    // html += '<li>' + data.s[i] + '</li>';
                }
                // 循环的li写入ul
                Ul.innerHTML = html;
            }
        }

        // 1.步骤一
        window.onload = function () {
            // 获取输入框和ul
            let Q = document.getElementById('q');
            let Ul = document.getElementById('ul');

            // 事件鼠标抬起时候
            Q.onkeyup = function () {
                // 如果输入框不等于空
                if (this.value != '') {
                    // ☆☆☆☆☆☆JSONPz重点☆☆☆☆☆☆
                    // 创建标签
                    let script = document.createElement('script');
                    // 给定要跨域的地址 赋值给src
                    // 这里是要请求的跨域的地址 百度搜索的跨域地址
                    script.src = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=' + this.value + '&cb=demo';
                    // 将组合好的带src的script标签追加到body里
                    document.body.appendChild(script);
                }
            }
        }
    </script>
</head>

<body>
<input type="text" id="q"/>
<ul id="ul">

</ul>
</body>
</html>

二、拦截器

2.1 概述

  • SpringMVC 的处理器,拦截器,类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理,可以自定义拦截器,实现特定的功能;
  • 过滤器与拦截器的区别:拦截器是 AOP 思想的具体应用;
  • 过滤器
    • servlet 规范中的一部分,任何 Java web 工程都可以使用;
    • url-pattern 中配置了 /* 之后,可以对所有要访问的资源,进行拦截;
  • 拦截器
    • 拦截器是 SpringMVC 框架自有的,只有使用了 SpringMVC 框架的工程才能使用;
    • 拦截器只会拦截访问控制器方法,如果访问的是 jsp、html、css、image、js,是不会进行拦截的;

2.2 自定义拦截器

  • 要自定义拦截器,必须实现 HandlerInterceptor 接口;
  • 创建新模块,并添加 web 支持;
  • 配置 web.xml 和 springmvc 配置文件;
  • 创建拦截器:config 目录下,创建 MyInterceptor
public class MyInterceptor implements HandlerInterceptor {
    /*
        如果返回true执行下一个拦截器
        如果返回false就不执行下一个拦截器
     */
    // 在请求处理的方法之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("------------处理前------------");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    // 在请求处理方法执行之后执行(一般用于处理日志)
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("------------处理后------------");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    // 在dispatcherServlet处理后执行,做清理工作
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("------------清理------------");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
  • 在 SpringMVC 的配置文件中,配置拦截器:
<!--拦截器配置-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--
            /**:包括路径及其子路径
            /admin/*:拦截的是/admin/add等等这种,/admin/add/user不会被拦截
            /admin/**:拦截的是/admin/下的所有
        -->
        <mvc:mapping path="/**"/>
        <!--bean配置的就是拦截器-->
        <bean class="com.study.config.MyInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>
  • 创建 Controller,接收请求:
@RestController
public class TestController {
    @RequestMapping("/interceptor")
    public String test2() {
        System.out.println("控制器中的方法执行了");
        return "hello";
}
  • 编写前端:index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <a href="${pageContext.request.contextPath}/interceptor">拦截器测试</a>
  </body>
</html>
  • 配置 Tomcat,启动测试:

2.3 验证用户是否登录 (认证用户)

实现思路:

  • 有一个登陆页面,需要写一个 controller 访问页面;
  • 登陆页面有一个提交表单的动作,需要在 controller 中处理,判断用户名、密码是否正确,如果正确,向 session 中写入用户信息,返回登陆成功信息;
  • 拦截用户请求,判断用户是否登陆,如果用户已经登陆,放行,如果用户未登陆,跳转到登陆页面;

测试:

  • 创建登陆页面:login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>首页</title>
</head>
<body>
<a href="${pageContext.request.contextPath}/user/toLogin">登录</a>
<a href="${pageContext.request.contextPath}/user/toSuccess">成功页面</a>
</body>
</html>
  • 创建 Controller 类,处理请求:
@Controller
@RequestMapping("/user")
public class LoginController {
    // 跳转到登录页面
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "login";
    }

    // 跳转到成功页面
    @RequestMapping("/toSuccess")
    public String toSuccess() {
        return "success";
    }

    // 登陆提交
    @RequestMapping("/login")
    public String login(HttpSession session, String username, String pwd) {
        // 把用户的信息存在Session中
        session.setAttribute("user", username);
        return "success";
    }

    // 退出登陆
    @RequestMapping("/logout")
    public String logout(HttpSession session) {
        // 移除Session
        session.removeAttribute("user");
        // 手动注销Session
        // session.invalidate();
        return "login";
    }
}
  • 创建登陆成功的页面:success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录成功</title>
</head>
<body>
<h3>登录成功页面</h3>
<hr>
${user}
<a href="${pageContext.request.contextPath}/user/logout">注销</a>
</body>
</html>
  • 在 index 页面上测试跳转,启动 Tomcat 测试,未登录也可以进入主页:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>首页</title>
</head>
<body>
<a href="${pageContext.request.contextPath}/user/toLogin">登录</a>
<a href="${pageContext.request.contextPath}/user/toSuccess">成功页面</a>
</body>
</html>
  • 创建登录拦截器:
public class LoginInterceptor implements HandlerInterceptor {
    // 在请求处理的方法之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 如果是登陆页面则放行
        if (request.getRequestURI().contains("toLogin")) {
            return true;
        }
        if (request.getRequestURI().contains("login")) {
            return true;
        }

        HttpSession session = request.getSession();

        // 如果用户已登陆也放行
        if (session.getAttribute("user") != null) {
            return true;
        }

        // 用户没有登陆跳转到登陆页面
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
        return false;
    }
}
  • 在 SpringMVC 的配置文件中,注册拦截器:
<!--拦截器配置-->
<mvc:interceptors>
    <mvc:interceptor>        
        <mvc:mapping path="/user/**"/>
        <!--bean配置的就是拦截器-->        
        <bean class="com.study.config.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  • 再次重启 Tomcat 测试,可以实现登录拦截功能;

三、文件上传和下载

3.1 概述

  • 文件上传是项目开发中最常见的功能之一,springMVC 可以很好的支持文件上传,但是 SpringMVC 上下文中,默认没有装配 MultipartResolver,因此,默认情况下,不能处理文件上传工作;
  • 如果想使用 Spring 的文件上传功能,则需要在上下文中配置MultipartResolver;
  • 前端表单要求:为了能上传文件,必须将表单的 method 设置为 POST,并将 enctype 设置为 multipart/form-data,只有在这样的情况下,浏览器才会把用户选择的文件,以二进制数据发送给服务器;
  • 表单中的 enctype 属性说明:
    • application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单,会将表单域中的值处理成 URL 编码方式;
    • multipart/form-data:以二进制流的方式,来处理表单数据,这种编码方式,会把文件域指定文件的内容,也封装到请求参数中,不会对字符编码;
    • text/plain:除了把空格转换为 + 号外,其他字符都不做编码处理,这种方式适用 直接通过表单发送邮件
<!--编码方式:文件上传-->
<form action="" enctype="multipart/form-data" method="post">
    <input type="file" name="file"/>
    <input type="submit">
</form>
  • 一旦设置了enctype 为 multipart/form-data,浏览器即会采用二进制流的方式,来处理表单数据,而对于文件上传的处理,则涉及在服务器端解析原始的 HTTP 响应;
  • 在 2003 年,Apache Software Foundation 发布了开源的 Commons FileUpload 组件,很快成为 Servlet/JSP 程序员上传文件的最佳选择;
    • Servlet3.0 规范已经提供方法来处理文件上传,但这种上传需要在 Servlet 中完成;
    • Spring MVC 提供了更简单的封装;
    • Spring MVC 为文件上传,提供了直接的支持,这种支持是用 即插即用 的 MultipartResolver 实现的;
    • Spring MVC 使用 Apache Commons FileUpload 技术实现了一个 MultipartResolver 实现类 CommonsMultipartResolver 因此,SpringMVC 的文件上传,还需要依赖 Apache Commons FileUpload 的组件

3.2 文件上传

  • 创建新模块,并添加 web 框架支持;
  • 配置 web.xml 及 SpringMVC 配置文件;
  • 配置 Tomcat,启动测试环境;
  • 导入文件上传的 jar 包,commons-fileupload 或 Maven 依赖:
<!--文件上传:自动导入commons-io-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
<!--servlet-api导入高版本的-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
  • 在 SpringMVC 配置文件中配置 bean:multipartResolver
    • id 必须为:multipartResolver,否则报400错误
<!--文件上传配置:id必须为:multipartResolver,否则报400错误-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 请求的编码格式,必须和jsp的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
    <property name="defaultEncoding" value="utf-8"/>
    <!-- 上传文件大小上限,单位为字节(10485760=10M) -->
    <property name="maxUploadSize" value="10485760"/>
    <property name="maxInMemorySize" value="40960"/>
</bean>
  • CommonsMultipartFile 的常用方法:
    • String getOriginalFilename():获取上传文件的原名;
    • InputStream getInputStream():获取文件流;
    • void transferTo(File dest):将上传文件,保存到一个目录文件中;
  • 编写前端页面:index.jsp
<form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
    <input type="file" name="file"/>
    <input type="submit" value="upload">
</form>
  • 创建 Controller 类:FileController

上传方式一:

@Controller
public class FileController {
    // 上传方式 1:
    // @RequestParam("file"):将name=file控件得到的文件封装成CommonsMultipartFile对象
    // 批量上传,CommonsMultipartFile则为数组即可
    @RequestMapping("/upload")
    public String fileUpload(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
        // 获取文件名:file.getOriginalFilename();
        String uploadFileName = file.getOriginalFilename();
        // 如果文件名为空,直接回到首页!
        if ("".equals(uploadFileName)) {
            return "redirect:/index.jsp";
        }
        System.out.println("上传文件名 : " + uploadFileName);
        // 上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        // 如果路径不存在,创建一个
        File realPath = new File(path);
        if (!realPath.exists()) {
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址:" + realPath);
        // 文件输入流
        InputStream is = file.getInputStream();
        // 文件输出流
        OutputStream os = new FileOutputStream(new File(realPath, uploadFileName));
        // 读取写出
        int len = 0;
        byte[] buffer = new byte[1024];
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
            os.flush();
        }
        os.close();
        is.close();
        return "redirect:/index.jsp";
    }
}
  • 测试上传文件;

上传方式二:

  • 采用 file.transferTo 来保存上传的文件;
  • 在 FileController 类中添加方法:
/*
    上传方式 2:
    采用file.transferTo来保存上传的文件
 */
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file,
                          HttpServletRequest request) throws IOException {
    // 上传路径保存设置
    String path = request.getServletContext().getRealPath("/upload");
    File realPath = new File(path);
    if (!realPath.exists()) {
        realPath.mkdir();
    }
    // 上传文件地址
    System.out.println("上传文件保存地址:" + realPath);
    // 通过CommonsMultipartFile的方法直接写文件(注意这个时候)
    file.transferTo(new File(realPath + "/" +
            file.getOriginalFilename()));
    return "redirect:/index.jsp";
}
  • 修改前端表单提交地址;
  • 运行测试;

3.3 文件下载

  • 文件下载步骤:

    • 设置 response 响应头;
    • 读取文件:InputStream;
    • 写出文件:OutputStream;
    • 执行操作;
    • 关闭流(先开后关);
  • 代码实现:

// 文件下载
@RequestMapping("/download")
public String downloads(HttpServletResponse response, HttpServletRequest request) throws Exception {
    // 要下载的图片地址
    String path = request.getServletContext().getRealPath("/upload");
    String fileName = "1.png";
    // 1. 设置response 响应头
    // 设置页面不缓存,清空buffer
    response.reset();
    // 字符编码
    response.setCharacterEncoding("UTF-8");
    // 二进制传输数据
    response.setContentType("multipart/form-data");
    // 设置响应头
    response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8"));
    File file = new File(path, fileName);

    // 2. 读取文件:输入流
    InputStream input = new FileInputStream(file);

    // 3. 写出文件:输出流
    OutputStream out = response.getOutputStream();
    byte[] buff = new byte[1024];
    int index = 0;

    // 4. 执行写出操作
    while ((index = input.read(buff)) != -1) {
        out.write(buff, 0, index);
        out.flush();
    }
    out.close();
    input.close();
    return null;
}
  • 创建对应目录及需要下载的文件:

  • 对应前端页面:

<a href="${pageContext.request.contextPath}/download">点击下载</a>
  • 运行测试;

四、SSM 回顾

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容