系列文章
SSM之SpringMVC 01 —— SpringMVC原理及概念、Hello SpringMVC 注解版和配置版
SSM之SpringMVC 02 —— Controller和RestFul风格、转发和重定向、数据处理和乱码问题、Json
SSM之SpringMVC 03 —— 整合SSM(简单图书系统)
SSM之SpringMVC 04 —— Ajax、拦截器、文件上传和下载
文章目录
8、Ajax
8.1、简介
-
Ajax 即 Asynchronous JavaScript and XML (异步的JavaScript和XML)
-
Ajax是一种无需加载整个网页,只刷新部分网页的技术
-
Ajax不是一种新的编程语言,而是一种创建更快、更好、交互性更强的Web应用程序技术
-
例如百度的搜索框,在输入关键字时,JavaScript会把数据传到服务器,接着服务器会进行响应。(没有点击按钮,仅仅只是点了搜索框)
-
传统的网页,想要更新内容或提交表单必须重新加载整个网页
-
使用Ajax技术的网页,通过与服务器进行少量的数据交换就能实现异步局部更新
8.2、伪造Ajax
我们使用前端的iframe标签来模拟实现Ajax的效果。不太了解的可以看我另一篇博客:前端学习 01 —— HTML5 里面有讲iframe内联框架,同时还用到了DOM操作,动态修改前端,感兴趣就看另一篇 前端学习 03 —— JavaScript 02 里面有讲操作DOM对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fake Ajax</title>
<script>
function loadPage() {
let targetUrl = document.getElementById("url").value;
document.getElementById("iframePosition").src = targetUrl;
}
</script>
</head>
<body>
<div>
<span>请输入要加载的网址:</span>
<input type="text" id="url" value="https://www.baidu.com"/>
<input type="button" value="提交" onclick="loadPage()">
</div>
<div>
<h3>加载的网页:</h3>
<iframe id="iframePosition" style="width: 100%;height: 500px;"/>
</div>
</body>
</html>
如此,我们便可以在不重新加载整个页面的情况下,刷新部分内容。
8.3、jQuery.Ajax
-
jQuery库有提供Ajax,没必要用纯原生的JavaScript实现。(jQuery库不算框架,它只是包含了很多JavaScript的函数)
-
Ajax的核心是XMLHttpRequest(XHR),它提供了向服务器发送请求和解析服务器响应的接口,能够异步获取服务器数据(就是不需要整个页面刷新)
由于这里主要是后端开发,仅需要了解Ajax部分参数。
jQuery.ajax(...) 部分参数: url 请求地址 type 请求方式(GET、POST) data 要发送的数据 success 成功后执行的回调函数(全局) error 失败后执行的回调函数 beforeSend 发送请求前执行的回调函数 complete 完成后执行的回调函数 timeout 请求超时的时间 dataType 将服务器返回的数据转换为指定类型
下面我们在Spring中使用原始的HttpServletResponse处理
需要先下载jQuery包或者在线加载jQuery:https://www.jq22.com/jquery-info122
网盘里的各种环境目录下:https://pan.baidu.com/s/16fNpMkGeXZud-U58Z5y67g
提取码: q3eq
1、配置好spring-mvc和web.xml
spring-mvc.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置SpringMVC -->
<!-- 1. 扫描包 -->
<context:component-scan base-package="com.zcy.controller"/>
<!-- 2. 开启注解驱动 -->
<mvc:annotation-driven/>
<!-- 3. 静态资源默认Servlet配置 -->
<mvc:default-servlet-handler/>
<!-- 4. 配置JSP的视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- 注意:这里要用总的Spring配置文件 -->
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 用Spring自带的乱码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- session过期时间 15分钟 -->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
</web-app>
2、编写AjaxController.java,这里用原生的HttpServletResponse,将接收到的数据直接返回。
package com.zcy.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController//使类下所有方法的返回值都是字符串
@RequestMapping("/ajax")
public class AjaxController {
@PostMapping("/a1")
public void ajax1(String data, HttpServletResponse resp) throws IOException {
resp.getWriter().print(data);
}
}
3、index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
<script src="${pageContext.request.contextPath}/statics/js/jquery-3.5.1.min.js"></script>
<script>
function a1() {
//这里也可以写成$.post(url,data(可省略),success);
$.post({
//这里${}不是jQuery,是操作jsp内置变量
url:"${pageContext.request.contextPath}/ajax/a1",
//这里$("#xxx")是jQuery,通过id选择器选myData,val()是获取其值
data:{"data":$("#myData").val()},
success:function (data) {
alert(data);
}
})
}
</script>
</head>
<body>
<%-- onblur 失去焦点触发事件 --%>
<input type="text" placeholder="请输入数据" οnblur="a1()" id="myData">
</body>
</html>
结果:可以发现,当我们输入内容后,鼠标点击输入框外面(失去焦点),就会发送请求。注意下面的类型,XHR。
接下来用SpringMVC的方式实现
1、新建实体类User
package com.zcy.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//使用lombok省略实体类的set、get、toString等方法
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String username;
private String password;
}
2、AjaxController.java 新增方法
@PostMapping("/a2")
public List<User> ajax2(){
List<User> list = new ArrayList<User>();
list.add(new User("小白","123"));
list.add(new User("小黄","123"));
list.add(new User("小黑","123"));
System.out.println(list);
return list;//由于类上有@RestController注解,会将list转为json格式返回
}
3、前端页面:ajax.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script src="${pageContext.request.contextPath}/statics/js/jquery-3.5.1.min.js"></script>
<script>
$(function () {
$("#btn").click(function () {
//这里也可以写成$.post(url,data(可省略),success);
$.post({
url:"${pageContext.request.contextPath}/ajax/a2",
//data是从后端返回过来的json
success:function (data) {
//这里动态给表格增加内容,操作DOM。
let html = "";
for (let i = 0; i < data.length; i++){
html += "<tr>"+
"<td>"+data[i].username+"</td>"+
"<td>"+data[i].password+"</td>"+
"</tr>";
}
//通过id选择器给id为content的元素追加html内容
$("#content").html(html);
}
})
})
})
</script>
</head>
<body>
<input type="button" value="查看数据" id="btn"/>
<table border="1">
<thead>
<tr><td>用户名</td><td>密码</td></tr>
</thead>
<tbody id="content">
</tbody>
</table>
</body>
</html>
结果:
9.4、注册提示效果
当我们输入完账号或者密码时,Ajax直接就将数据发到后端,后端响应后,前端立即显示提示信息。
1、AjaxControlle新增方法
@PostMapping("/a3")
public String ajax3(String username, String password){
String message = "";
if (username != null){
if (username.equals("admin"))
message = "OK";
else
message = "ERROR";
}
if (password != null){
if (password.equals("123456"))
message = "OK";
else
message = "ERROR";
}
return message;
}
2、前端 ajax2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script src="${pageContext.request.contextPath}/statics/js/jquery-3.5.1.min.js"></script>
<script>
function username() {
$.post({
url:"${pageContext.request.contextPath}/ajax/a3",
data:{"username":$("#username").val()},
success:function (data) {
if (data.toString()==="OK")
$("#info1").css("color", "green");
else
$("#info1").css("color", "red");
$("#info1").html(data);
}
})
}
function password() {
$.post({
url:"${pageContext.request.contextPath}/ajax/a3",
data:{"username":$("#password").val()},
success:function (data) {
if (data.toString()==="OK")
$("#info2").css("color", "green");
else
$("#info2").css("color", "red");
$("#info2").html(data);
}
})
}
</script>
</head>
<body>
账号:<input type="text" id="username" οnblur="username()"/><span id="info1"></span><br/>
密码:<input type="text" id="password" οnblur="password()"/><span id="info2"></span>
</body>
</html>
9、拦截器
9.1、简介
SpringMVC的拦截器(Interceptor)类似于Servlet开发中的过滤器(Filter),用于对Controller进行预处理以及执行后处理。
- 拦截器是SpringMVC框架自身才有的,必须使用了SpringMVC框架才能用
- 过滤器是Servlet规范中的一部分,任何JavaWeb工程都能用,SpringMVC也可以用过滤器(前面Json乱码处理就是用了过滤器)
- 拦截器是AOP思想的具体应用,横切进去,不影响原有业务。
- 对于过滤器,在web.xml中将拦截器的url-pattern设置为/*后,可以对任何资源进行过滤。
- 对于拦截器,它只会拦截要访问Controller的请求,如果访问jsp/html/js等是不会拦截的。
9.2、自定义拦截器
1、MyInterceptor.java
package com.zcy.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
//返回值 确定该请求是否放行,若为false,则无法访问到后面的Controller
public boolean preHandle(
HttpServletRequest req, HttpServletResponse resp, Object handler
) throws Exception {
System.out.println("=====处理里前=====");
return true;
}
//一般postHandle和afterCompletion是来打印日志的
public void postHandle(
HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView modelAndView
) throws Exception {
System.out.println("=====处理后=====");
}
public void afterCompletion(
HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex
) throws Exception {
System.out.println("=====清理后=====");
}
}
2、InterceptorController.java
package com.zcy.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/interceptor")
public class InterceptorController {
@GetMapping("/i1")
public String interceptor1(){
System.out.println("执行InterceptorController");
return "Hello,Interceptor";
}
}
3、在spring-mvc.xml中配置拦截器
<!-- 拦截器配置:AOP的应用 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截的位置,/admin/**表示拦截/admin/下所有,这里是拦截项目下所有的 -->
<!-- 如果是/admin/*,则只拦截到/admin下一层,对于/admin/add/user就不会拦截 -->
<mvc:mapping path="/**"/>
<!-- 用哪个拦截器 -->
<bean class="com.zcy.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
结果:
9.3、用户登录拦截
现在用拦截器实现之前过滤器做的事,只有用户成功登录才能进入主页,如果注销后,直接输入主页的URL是无法进入的。
1、编写拦截器并配置
package com.zcy.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler
) throws Exception {
boolean flag = false;
String requestURI = request.getRequestURI();
if (requestURI.contains("toLogin"))//如果是去登录界面,则直接放行
return true;
if (requestURI.contains("login"))//如果是去登录界面后台的Controller,则放行
return true;
//说明用户已经登录过,放行。如果用户注销了,则为null,拦截。
if (request.getSession().getAttribute("userName") != null)
return true;
//拦截就会重定向到Home界面
request.getRequestDispatcher("/WEB-INF/jsp/home.jsp").forward(request, response);
return false;
}
}
spring-mvc.xml
<mvc:interceptor>
<!-- 拦截 /user/下所有请求 -->
<mvc:mapping path="/user/**"/>
<bean class="com.zcy.interceptor.LoginInterceptor"/>
</mvc:interceptor>
2、编写Controller
package com.zcy.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping("/user")
public class UserController {
//跳转到登陆界面
@RequestMapping("/toLogin")
public String toLogin(){
System.out.println("toLogin");
return "login";
}
//登录成功跳转到主页面
@RequestMapping("/login")
public String login(String username, String password, HttpSession session){
System.out.println("login");
if (username != null && username.length() > 0){
session.setAttribute("userName", username);
return "main";
}
else
return "login";
}
//跳转到主页面
@RequestMapping("/main")
public String main(){
return "main";
}
//注销
@RequestMapping("/logout")
public String logout(HttpSession session, Model model){
session.removeAttribute("userName");
model.addAttribute("message", "您已注销");
return "home";
}
}
3、编写前端
home.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>首页</h1>
<span>${message}</span>
<h2><a href="${pageContext.request.contextPath}/user/main">进入主页</a><h2/>
<h2><a href="${pageContext.request.contextPath}/user/toLogin">进入登录页面</a></h2>
</body>
</html>
main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Main</title>
</head>
<body>
<h1>主页面</h1>
<h3>欢迎用户:${userName}</h3>
<a href="${pageContext.request.contextPath}/user/logout">注销</a>
</body>
</html>
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>登录界面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username"><br/>
密 码:<input type="text" name="password"/><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
结果:在未登录或已注销情况下,无法进入主页。
10、文件下载和上传
文件下载和上传是项目中常见的功能,现在用SpringMVC来实现。
10.1、文件上传
前端表单必须设置为POST,并将enctype
设置为multipart/form-data
,只有这样浏览器才会把用户选择的文件以二进制方式发给服务器。
1、导包
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!--servlet-api导入高版本的-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
2、配置spring-mvc.xml,添加bean:multipartResolver(id必须为multipartResolver)
<!--文件上传配置-->
<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):将上传文件保存到一个目录文件中
3、前端页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%-- enctype="multipart/form-data"以二进制方式处理表单数据 --%>
<form action="/upload" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit" value="upload">
</form>
<a href="/download">点击下载</a>
</body>
</html>
4、Controller
package com.zcy.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
@RestController
@RequestMapping("/file")
public class FileController {
//方式一
//@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
//批量上传CommonsMultipartFile则为数组即可
@RequestMapping("/upload1")
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.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.Transto 来保存上传的文件
@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";
}
}
10.2、文件下载
文件下载步骤:
- 设置 response 响应头
- 读取文件 – InputStream
- 写出文件 – OutputStream
- 执行操作
- 关闭流 (先开后关)
@RequestMapping(value="/download")
public String downloads(HttpServletResponse response ,HttpServletRequest
request) throws Exception{
//要下载的图片地址
String path = request.getServletContext().getRealPath("/upload");
String fileName = "123.jpg";
//1、设置response 响应头
response.reset(); //设置页面不缓存,清空buffer
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 "ok";
}