SpringMVC 基礎(chǔ)(二)

一、AJAX

1.1 簡介

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

  • AJAX 是一種在無需重新加載整個網(wǎng)頁的情況下,能夠更新部分網(wǎng)頁的技術(shù);

  • AJAX 不是一種新的編程語言,而是一種用于創(chuàng)建更好更快,以及交互性更強的 Web 應(yīng)用程序的技術(shù);

  • 在 2005 年,Google 通過其 Google Suggest 使 AJAX 變得流行起來,Google Suggest 能夠自動完成搜索單詞;

  • Google Suggest 使用 AJAX 創(chuàng)造出動態(tài)性極強的 web 界面:當我們在谷歌的搜索框輸入關(guān)鍵字時,JavaScript 會把這些字符發(fā)送到服務(wù)器,然后服務(wù)器會返回一個搜索建議的列表,如百度的搜索框:

  • 傳統(tǒng)的網(wǎng)頁(即不用 AJAX 技術(shù)的網(wǎng)頁)想要更新內(nèi)容或者提交一個表單,都需要重新加載整個網(wǎng)頁;

  • 使用 AJAX 技術(shù)的網(wǎng)頁,通過在后臺服務(wù)器,進行少量的數(shù)據(jù)交換,就可以實現(xiàn)異步、局部更新;

  • 使用 AJAX,用戶可以創(chuàng)建接近本地桌面應(yīng)用的直接、高可用、更豐富、更動態(tài)的 Web 用戶界面;

1.2 偽造 AJAX

  • 可以使用前端的一個標簽,來偽造一個 ajax 的樣子:iframe 標簽

    • 新建模塊,并導(dǎo)入 web 支持;
    • 配置 web.xml 及 springMVC 配置文件,運行 Tomcat 測試環(huán)境;
    • 編寫 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 打開瀏覽器進行測試;

小結(jié):

  • 利用 AJAX 可以實現(xiàn):
    • 注冊時,輸入用戶名,自動檢測用戶是否已經(jīng)存在;
    • 登陸時,提示用戶名,密碼錯誤;
    • 刪除數(shù)據(jù)行時,將行 ID 發(fā)送到后臺,后臺在數(shù)據(jù)庫中刪除,數(shù)據(jù)庫刪除成功后,在頁面 DOM 中,將數(shù)據(jù)行也刪除;
    • ...等等;

1.3 jQuery.ajax

  • jQuery 3.6.0 開發(fā)版 下載鏈接
  • Ajax 的核心是 XMLHttpRequest 對象(XHR):
    • XHR 為向服務(wù)器發(fā)送請求和解析服務(wù)器響應(yīng),提供了接口,能夠以異步方式,從服務(wù)器獲取新數(shù)據(jù);
  • jQuery 提供多個與 AJAX 有關(guān)的方法;
  • 通過 jQuery AJAX 方法,能夠使用 HTTP Get 和 HTTP Post,從遠程服務(wù)器上請求文本、HTML、XML 或 JSON,同時能夠把這些外部數(shù)據(jù),直接載入網(wǎng)頁的被選元素中;
  • jQuery(是一個庫) 不是生產(chǎn)者,而是大自然搬運工;
  • jQuery Ajax 本質(zhì)就是 XMLHttpRequest,對它進行了封裝,方便調(diào)用:
jQuery.ajax(...)
/*
    部分參數(shù):
        url:請求地址
        type:請求方式,GET、POST(1.9.0之后用method)
        headers:請求頭
        data:要發(fā)送的數(shù)據(jù)
        contentType:即將發(fā)送信息至服務(wù)器的內(nèi)容編碼類型(默認: "application/x-www-form-urlencoded; charset=UTF-8")
        async:是否異步
        timeout:設(shè)置請求超時時間(毫秒)
        beforeSend:發(fā)送請求前執(zhí)行的函數(shù)(全局)
        complete:完成之后執(zhí)行的回調(diào)函數(shù)(全局)
        success:成功之后執(zhí)行的回調(diào)函數(shù)(全局)
        error:失敗之后執(zhí)行的回調(diào)函數(shù)(全局)
        accepts:通過請求頭發(fā)送給服務(wù)器,告訴服務(wù)器當前客戶端可接受的數(shù)據(jù)類型
        dataType:將服務(wù)器端返回的數(shù)據(jù)轉(zhuǎn)換成指定類型
        "xml": 將服務(wù)器端返回的內(nèi)容轉(zhuǎn)換成xml格式
        "text": 將服務(wù)器端返回的內(nèi)容轉(zhuǎn)換成普通文本格式
        "html": 將服務(wù)器端返回的內(nèi)容轉(zhuǎn)換成普通文本格式,在插入DOM中時,如果包含JavaScript標簽,則會嘗試去執(zhí)行。
        "script": 嘗試將返回值當作JavaScript去執(zhí)行,然后再將服務(wù)器端返回的內(nèi)容轉(zhuǎn)換成普通文本格式
        "json": 將服務(wù)器端返回的內(nèi)容轉(zhuǎn)換成相應(yīng)的JavaScript對象
        "jsonp": JSONP 格式使用 JSONP 形式調(diào)用函數(shù)時,如 "myurl?callback=?" jQuery 將自動替換 ? 為正確的函數(shù)名,以執(zhí)行回調(diào)函數(shù)
 */

測試一:HttpServletResponse 實現(xiàn)

  • 使用最原始的 HttpServletResponse 處理,最簡單,最通用:
  • 配置 web.xml 和 springmvc 的配置文件:
    • 注意:靜態(tài)資源過濾和注解驅(qū)動配置;
<?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注解驅(qū)動,注意導(dǎo)入mvc的頭文件-->
    <mvc:annotation-driven/>
    <!--2. 靜態(tài)資源過濾-->
    <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>
  • 創(chuàng)建 Controll:AjaxController
// 不走視圖頁面,直接返回字符串
@RestController
public class AjaxController {
@RequestMapping("/a1")
    public void ajax1(String name, HttpServletResponse response) throws IOException {
        System.out.println("前端傳遞參數(shù):" + name);
        if ("admin".equals(name)) {
            response.getWriter().println("true");
        } else {
            response.getWriter().println("false");
        }
    }
}
  • 導(dǎo)入 jQuery,可以使用在線的 CDN,也可以下載導(dǎo)入:
<%--<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都是調(diào)用的ajax方法,可以直接用 $.ajax
            $.post({
                url: "${pageContext.request.contextPath}/a1",
                // data:傳遞給后端的數(shù)據(jù),鍵值對的形式 name對應(yīng)后端的name
                data: {"name": $("#userName").val()},
                // data:后端返回的數(shù)據(jù) status:狀態(tài)
                success: function (data, status) {
                    console.log(data);
                    console.log(status);
                }
            });
        }
    </script>
</head>
<body>
<%--onblur:失去焦點觸發(fā)事件--%>
用戶名:<input type="text" id="userName" onblur="a1()"/>
</body>
</html>
  • 啟動 Tomcat 測試:

    • 打開瀏覽器的控制臺,當鼠標離開輸入框時,可以看到發(fā)出了一個 ajax 的請求;
  • 流程分析:

測試二:SpringMVC、JSON 方式實現(xiàn)

  • 需要導(dǎo)入 Jackson 依賴:否則請求路徑時,報錯 406
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.2.2</version>
</dependency>
  • 創(chuàng)建實體類:user
    • 使用 lombok,需要事先導(dǎo)入依賴;
@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轉(zhuǎn)成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[可以省略(傳遞給后端的參數(shù))],success[回調(diào)函數(shù)])
                 */
                $.post("${pageContext.request.contextPath}/a2", function (data) {
                    console.log(data);
                    let html = "";
                    for (let i = 0; i < data.length; i++) {
                        <%-- jsp中需要轉(zhuǎn)義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="獲取數(shù)據(jù)"/>
<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:模擬數(shù)據(jù)庫查詢的數(shù)據(jù)
        if ("admin".equals(name)) {
            msg = "OK";
        } else {
            msg = "用戶名有誤";
        }
    }
    if (pwd != null) {
        // 123456:模擬數(shù)據(jù)庫查詢的數(shù)據(jù)
        if ("123456".equals(pwd)) {
            msg = "OK";
        } else {
            msg = "密碼有誤";
        }
    }
    // 類中使用@RestController注解,將msg轉(zhuǎn)成json格式返回
    return msg;
}
  • 創(chuàng)建前端頁面: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:失去焦點觸發(fā)事件--%>
    用戶名:<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亂碼統(tǒng)一解決(固定代碼)-->
<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函數(shù) (分析接口、數(shù)據(jù))
        function demo(data) {
            let Ul = document.getElementById('ul');
            let html = '';
            // 如果搜索數(shù)據(jù)存在 把內(nèi)容添加進去
            if (data.s.length) {
                // 隱藏掉的ul顯示出來
                Ul.style.display = 'block';
                // 搜索到的數(shù)據(jù)循環(huán)追加到li里
                for (let i = 0; i < data.s.length; i++) {
                    // es6 模板字符串
                    html += `<li>${data.s[i]}</li>`
                    // html += '<li>' + data.s[i] + '</li>';
                }
                // 循環(huán)的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重點☆☆☆☆☆☆
                    // 創(chuàng)建標簽
                    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 開發(fā)中的過濾器 Filter,用于對處理器進行預(yù)處理和后處理,可以自定義攔截器,實現(xiàn)特定的功能;
  • 過濾器與攔截器的區(qū)別:攔截器是 AOP 思想的具體應(yīng)用;
  • 過濾器
    • servlet 規(guī)范中的一部分,任何 Java web 工程都可以使用;
    • url-pattern 中配置了 /* 之后,可以對所有要訪問的資源,進行攔截;
  • 攔截器
    • 攔截器是 SpringMVC 框架自有的,只有使用了 SpringMVC 框架的工程才能使用;
    • 攔截器只會攔截訪問控制器方法,如果訪問的是 jsp、html、css、image、js,是不會進行攔截的;

2.2 自定義攔截器

  • 要自定義攔截器,必須實現(xiàn) HandlerInterceptor 接口;
  • 創(chuàng)建新模塊,并添加 web 支持;
  • 配置 web.xml 和 springmvc 配置文件;
  • 創(chuàng)建攔截器:config 目錄下,創(chuàng)建 MyInterceptor
public class MyInterceptor implements HandlerInterceptor {
    /*
        如果返回true執(zhí)行下一個攔截器
        如果返回false就不執(zhí)行下一個攔截器
     */
    // 在請求處理的方法之前執(zhí)行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("------------處理前------------");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    // 在請求處理方法執(zhí)行之后執(zhí)行(一般用于處理日志)
    @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處理后執(zhí)行,做清理工作
    @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>
  • 創(chuàng)建 Controller,接收請求:
@RestController
public class TestController {
    @RequestMapping("/interceptor")
    public String test2() {
        System.out.println("控制器中的方法執(zhí)行了");
        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 驗證用戶是否登錄 (認證用戶)

實現(xiàn)思路:

  • 有一個登陸頁面,需要寫一個 controller 訪問頁面;
  • 登陸頁面有一個提交表單的動作,需要在 controller 中處理,判斷用戶名、密碼是否正確,如果正確,向 session 中寫入用戶信息,返回登陸成功信息;
  • 攔截用戶請求,判斷用戶是否登陸,如果用戶已經(jīng)登陸,放行,如果用戶未登陸,跳轉(zhuǎn)到登陸頁面;

測試:

  • 創(chuàng)建登陸頁面: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>
  • 創(chuàng)建 Controller 類,處理請求:
@Controller
@RequestMapping("/user")
public class LoginController {
    // 跳轉(zhuǎn)到登錄頁面
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "login";
    }

    // 跳轉(zhuǎn)到成功頁面
    @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";
    }
}
  • 創(chuàng)建登陸成功的頁面: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 頁面上測試跳轉(zhuǎn),啟動 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>
  • 創(chuàng)建登錄攔截器:
public class LoginInterceptor implements HandlerInterceptor {
    // 在請求處理的方法之前執(zhí)行
    @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;
        }

        // 用戶沒有登陸跳轉(zhuǎn)到登陸頁面
        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 測試,可以實現(xiàn)登錄攔截功能;

三、文件上傳和下載

3.1 概述

  • 文件上傳是項目開發(fā)中最常見的功能之一,springMVC 可以很好的支持文件上傳,但是 SpringMVC 上下文中,默認沒有裝配 MultipartResolver,因此,默認情況下,不能處理文件上傳工作;
  • 如果想使用 Spring 的文件上傳功能,則需要在上下文中配置MultipartResolver;
  • 前端表單要求:為了能上傳文件,必須將表單的 method 設(shè)置為 POST,并將 enctype 設(shè)置為 multipart/form-data,只有在這樣的情況下,瀏覽器才會把用戶選擇的文件,以二進制數(shù)據(jù)發(fā)送給服務(wù)器;
  • 表單中的 enctype 屬性說明:
    • application/x-www=form-urlencoded:默認方式,只處理表單域中的 value 屬性值,采用這種編碼方式的表單,會將表單域中的值處理成 URL 編碼方式;
    • multipart/form-data:以二進制流的方式,來處理表單數(shù)據(jù),這種編碼方式,會把文件域指定文件的內(nèi)容,也封裝到請求參數(shù)中,不會對字符編碼;
    • text/plain:除了把空格轉(zhuǎn)換為 + 號外,其他字符都不做編碼處理,這種方式適用 直接通過表單發(fā)送郵件;
<!--編碼方式:文件上傳-->
<form action="" enctype="multipart/form-data" method="post">
    <input type="file" name="file"/>
    <input type="submit">
</form>
  • 一旦設(shè)置了enctype 為 multipart/form-data,瀏覽器即會采用二進制流的方式,來處理表單數(shù)據(jù),而對于文件上傳的處理,則涉及在服務(wù)器端解析原始的 HTTP 響應(yīng);
  • 在 2003 年,Apache Software Foundation 發(fā)布了開源的 Commons FileUpload 組件,很快成為 Servlet/JSP 程序員上傳文件的最佳選擇;
    • Servlet3.0 規(guī)范已經(jīng)提供方法來處理文件上傳,但這種上傳需要在 Servlet 中完成;
    • Spring MVC 提供了更簡單的封裝;
    • Spring MVC 為文件上傳,提供了直接的支持,這種支持是用 即插即用 的 MultipartResolver 實現(xiàn)的;
    • Spring MVC 使用 Apache Commons FileUpload 技術(shù)實現(xiàn)了一個 MultipartResolver 實現(xiàn)類 CommonsMultipartResolver 因此,SpringMVC 的文件上傳,還需要依賴 Apache Commons FileUpload 的組件;

3.2 文件上傳

  • 創(chuàng)建新模塊,并添加 web 框架支持;
  • 配置 web.xml 及 SpringMVC 配置文件;
  • 配置 Tomcat,啟動測試環(huán)境;
  • 導(dǎo)入文件上傳的 jar 包,commons-fileupload 或 Maven 依賴:
<!--文件上傳:自動導(dǎo)入commons-io-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
<!--servlet-api導(dǎo)入高版本的-->
<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屬性一致,以便正確讀取表單的內(nèi)容,默認為ISO-8859-1 -->
    <property name="defaultEncoding" value="utf-8"/>
    <!-- 上傳文件大小上限,單位為字節(jié)(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>
  • 創(chuàng)建 Controller 類:FileController

上傳方式一:

@Controller
public class FileController {
    // 上傳方式 1:
    // @RequestParam("file"):將name=file控件得到的文件封裝成CommonsMultipartFile對象
    // 批量上傳,CommonsMultipartFile則為數(shù)組即可
    @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);
        // 上傳路徑保存設(shè)置
        String path = request.getServletContext().getRealPath("/upload");
        // 如果路徑不存在,創(chuàng)建一個
        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 {
    // 上傳路徑保存設(shè)置
    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 文件下載

  • 文件下載步驟:

    • 設(shè)置 response 響應(yīng)頭;
    • 讀取文件:InputStream;
    • 寫出文件:OutputStream;
    • 執(zhí)行操作;
    • 關(guān)閉流(先開后關(guān));
  • 代碼實現(xiàn):

// 文件下載
@RequestMapping("/download")
public String downloads(HttpServletResponse response, HttpServletRequest request) throws Exception {
    // 要下載的圖片地址
    String path = request.getServletContext().getRealPath("/upload");
    String fileName = "1.png";
    // 1. 設(shè)置response 響應(yīng)頭
    // 設(shè)置頁面不緩存,清空buffer
    response.reset();
    // 字符編碼
    response.setCharacterEncoding("UTF-8");
    // 二進制傳輸數(shù)據(jù)
    response.setContentType("multipart/form-data");
    // 設(shè)置響應(yīng)頭
    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. 執(zhí)行寫出操作
    while ((index = input.read(buff)) != -1) {
        out.write(buff, 0, index);
        out.flush();
    }
    out.close();
    input.close();
    return null;
}
  • 創(chuàng)建對應(yīng)目錄及需要下載的文件:

  • 對應(yīng)前端頁面:

<a href="${pageContext.request.contextPath}/download">點擊下載</a>
  • 運行測試;

四、SSM 回顧

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容