JavaWeb了解之SpringMVC篇2
2022-11-03 本文已影响0人
平安喜乐698
目录
1. 拦截器
2. 异常处理
3. 文件的上传和下载
4. 注解配置
5. 国际化
1. 拦截器
类似于Servlet的过滤器,对用户请求进行拦截,在Controller控制器处理请求前、处理完请求后、甚至是渲染视图后,做相应的处理。
通过拦截器可实现:权限验证、记录请求信息日志、判断用户是否登录等功能。
拦截器使用的是可插拔式设计。如果需要某一拦截器,只需在配置文件中启用该拦截器即可;如果不需要这个拦截器,则只需要在配置文件中取消应用该拦截器即可。
步骤:
1. 定义拦截器
通过实现HandlerInterceptor接口的3个方法。
1. boolean preHandle();
该方法在控制器方法之前执行。
返回true时继续向下执行;返回false时中断后续的操作。
2. void postHandle();
该方法会在控制器方法调用之后、解析视图之前执行。
可以通过该方法对请求域中的Model模型数据和视图做进一步的修改。
3. void afterCompletion();
该方法会在整个请求完成后(即视图渲染结束之后)执行。
可以通过该方法实现资源清理、日志记录等工作。
定义完拦截器后,需要在SpringMVC.xml配置文件中进行配置才能生效。
例
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle 执行");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle 执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion 执行");
}
}
2. 配置拦截器
在SpringMVC.xml配置文件中使用<mvc:interceptors>标签(用来定义一组拦截器)及其子标签(<bean> 、<ref>、<mvc:interceptor>,可任意组合)对拦截器进行配置。
1. <bean>标签
用于定义一个【全局拦截器】,对所有的请求进行拦截。
例:
<!-- 配置拦截器 -->
<mvc:interceptors>
<bean class="com.sst.cx.interceptor.MyInterceptor"></bean>
</mvc:interceptors>
2. <ref>标签
用于定义一个【全局拦截器的引用】,对所有的请求进行拦截。
不能单独使用,需要和<bean>标签(在<mvc:interceptors>标签内外都可以)或@Component注解配合使用(以保证<ref>标签配置的拦截器是SpringIoc容器中的一个组件)。
例:
<!-- 将自定义的拦截器放到Ioc容器中 -->
<bean id="interceptor" class="com.sst.cx.interceptor.MyInterceptor"></bean>
<!-- 配置拦截器-->
<mvc:interceptors>
<ref bean="interceptor"></ref>
</mvc:interceptors>
3. <mvc:interceptor>标签(子标签:<mvc:mapping>、<mvc:exclude-mapping>、<bean>)
用于定义一个指定拦截路径的拦截器(配置拦截器拦截和不需要拦截的请求路径)。
1. <mvc:mapping>
通过path属性 配置拦截的请求路径。
2. <mvc:exclude-mapping>
通过path属性 配置不需要拦截的请求路径。
3. <bean>
定义一个指定了拦截路径的拦截器。
例:
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 拦截器1 -->
<mvc:interceptor>
<!-- 配置拦截器拦截的请求路径 -->
<mvc:mapping path="/**"/>
<!-- 配置拦截器不需要拦截的请求路径 -->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/"/>
<!-- 定义在<mvc:interceptors>标签下,表示拦截器只对指定的请求路径进行拦截 -->
<bean class="com.sst.cx.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
注意:在<mvc:interceptor>标签中,子标签必须按照上述的配置顺序进行编写,即 <mvc:mapping> → <mvc:exclude-mapping> → <bean> 的顺序,否则就会报错。
拦截器的执行流程
单个拦截器的执行流程
多个拦截器的执行流程(Interceptor1定义在Interceptor2前面)
单个拦截器的执行流程
1. 当请求的路径与拦截器拦截的路径相匹配时,程序会先执行拦截器类的preHandle()方法。若该方法返回值为true,则继续向下执行Controller控制器中的方法,否则将不再向下执行;
2. 执行控制器方法 对请求进行处理;
3. 执行拦截器的postHandle()方法(在该方法中,可对请求域中的Model模型数据和视图做进一步修改);
4. 通过DispatcherServlet的render()方法对视图进行渲染;
5. 执行拦截器的afterCompletion()方法(在该方法中,可以进行资源清理、日志记录等工作)。
多个拦截器的执行流程
通常项目中会有多个拦截器(来实现不同的功能)。拦截器的执行顺序和拦截器定义在配置文件中的顺序有关(preHandle()方法按照配置顺序执行;PostHandle()方法和afterCompletion()方法则按照配置顺序的反序执行)。
如果其中有拦截器的preHandle()方法返回了false,则各拦截器方法执行情况如下:
1. 第一个返回false的preHandle()方法以及它之前的所有拦截器的preHandle()方法都会执行。
2. 所有拦截器的postHandle()方法都不会执行。
3. 第一个返回false的preHandle()方法的拦截器(不包括该拦截器)之前的所有拦截器的afterComplation()方法都会执行。
拦截器示例(用户登录权限验证)
需求:
只有登录后的用户才能访问系统主页,若没有登录就直接访问主页,则拦截器会将请求拦截并跳转到登录页面,同时在登录页面中给出提示信息。
若用户登陆时,用户名或密码错误,则登录页也会显示相应的提示信息。
已登录的用户在系统主页点击“退出登录”时,跳转回登录页面。
用户登录流程
1. 创建JavaWeb项目(Dynamic Web Project),导入SpringMVC相关的依赖包。
2. 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">
<!-- 避免乱码 -->
<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>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置PUT、DELETE请求 过滤器 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.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>
</web-app>
3. 创建springMVC.xml(src目录下)
<?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">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.sst.cx"></context:component-scan>
<!-- 配置 Thymeleaf 视图解析器 -->
<bean id="viewResolver"
class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
<!-- 和业务逻辑无关的跳转映射 -->
<mvc:view-controller path="/" view-name="login"></mvc:view-controller>
<mvc:view-controller path="/index.html" view-name="login"></mvc:view-controller>
<mvc:view-controller path="/main" view-name="main"></mvc:view-controller>
<!-- 开启注解驱动 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 将静态资源交给Tomcat默认的Servlet 处理-->
<mvc:default-servlet-handler/>
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/"/>
<bean id="interceptor" class="com.sst.cx.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
</beans>
4. 创建User.java(在com.sst.cx.domain下)
package com.sst.cx.domain;
public class User {
private String userId;
private String userName;
private String password;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"userId='" + userId + '\'' +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
5. 创建LoginController.java(在com.sst.cx.controller下)
package com.sst.cx.controller;
import com.sst.cx.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
// 处理登录请求
@RequestMapping("/login")
public String login(User user, HttpServletRequest request) {
// 验证用户名和密码
if (user != null && "admin".equals(user.getPassword()) && "admin".equals(user.getUserName())) {
HttpSession session = request.getSession();
// 将用户信息放到 session 域中
session.setAttribute("loginUser", user);
// 重定向到主页
return "redirect:/main";
}
// 提示用户名或密码错误
request.setAttribute("msg", "用户名或密码错误");
return "login";
}
// 处理退出请求
@RequestMapping("/logout")
public String Logout(User user, HttpServletRequest request) {
// 设置session失效
request.getSession().invalidate();
return "redirect:/";
}
}
6. 创建LoginInterceptor.java(在com.sst.cx.interceptor下)
package com.sst.cx.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginInterceptor implements HandlerInterceptor {
// 控制器方法执行前,调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
// 未登录,返回登陆页
request.setAttribute("msg", "您没有权限进行此操作,请先登录!");
request.getRequestDispatcher("/").forward(request, response);
return false;
} else {
// 放行
return true;
}
}
}
7. 创建login.html(在webapp/WEB-INF/templates目录下)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登陆页</title>
</head>
<body>
<form th:action="@{/login}" method="post">
<table style="margin: auto">
<tr>
<td th:if="${not #strings.isEmpty(msg)}" colspan="2" align="center">
<p style="color: red;margin: auto" th:text="${msg}"></p>
</td>
</tr>
<tr>
<td>用户名:</td>
<td><input type="text" name="userName" required><br></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password" required><br></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="登陆">
<input type="reset" value="重置">
</td>
</tr>
</table>
</form>
</body>
</html>
8. 创建main.html(在webapp/WEB-INF/templates目录下)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>系统主页</title>
</head>
<body>
<h1 th:text="'欢迎您:'+${session.loginUser.getUserName()}" th:if="${not #strings.isEmpty(session.loginUser)}"></h1>
<a th:href="@{/logout}">退出登录</a>
</body>
</html>
9. 部署到Tomcat,并启动Tomcat
浏览器中访问http://localhost:8080/helloSpringMVC/main
2. 异常处理
应用运行中经常会不可避免地遇到各种可预知的、不可预知的异常,需要对这些异常进行处理以保证程序正常运行。SpringMVC提供了HandlerExceptionResolver异常处理器接口(可以对控制器方法执行过程中出现的各种异常进行处理)。
常用的实现类有以下4个(程序发生异常后会按照3 → 2 → 1的顺序,依次使用这3个默认异常处理器对异常进行解析,直到解析完成为止)。
1. DefaultHandlerExceptionResolver(处理 控制器处理请求时出现的异常)
提供了一个doResolveException()方法(返回类型为ModelAndView),该方法会在控制器方法出现指定异常时,生成一个新的包含了异常信息的ModelAndView对象并替换控制器方法的ModelAndView对象,以达到跳转到指定的错误页面来展示异常信息。
===》doResolveException()方法,源码如下
// 将SpringMVC项目中产生的各种异常转换为合适的code状态码。通过这些状态码,我们就可以进一步的确定发生异常的原因,以便于找到对应的问题。
@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, request, response, handler);
}
if (ex instanceof HttpMediaTypeNotSupportedException) {
return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, request, response, handler);
}
if (ex instanceof HttpMediaTypeNotAcceptableException) {
return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, request, response, handler);
}
……
} catch (Exception var6) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", var6);
}
}
return null;
}
例(在浏览器中直接http://localhost:8080/test使用get方式访问,报错405)
@Controller
public class ExceptionController {
@RequestMapping(value = "/test", method = RequestMethod.POST)
public String testDefaultHandlerExceptionResolver() {
return "success";
}
}
常见异常 | 状态码 | 描述 |
---|---|---|
HttpRequestMethodNotSupportedException | 405(Method Not Allowed) | HTTP请求方式不支持异常 |
HttpMediaTypeNotSupportedException | 415(Unsupported Media Type) | HTTP媒体类型不支持异常 |
HttpMediaTypeNotAcceptableException | 406(Not Acceptable) | HTTP媒体类型不可接受异常 |
BindException | 400(Bad Request) | 数据绑定异常 |
MissingServletRequestParameterException | 400(Bad Request) | 缺少参数异常 |
ConversionNotSupportedException | 500(Internal Server Error) | 数据类型转换异常 |
TypeMismatchException | 400(Bad Request) | 类型不匹配异常 |
HttpMessageNotReadableException | 400(Bad Request) | HTTP消息不可读异常 |
HttpMessageNotWritableException | 500(Internal Server Error) | HTTP消息不可写异常 |
更多的异常及其状态码映射,请参考org.springframework.http.HttpStatus |
2. ResponseStatusExceptionResolver(处理 自定义异常类)
1. 首先使用@ResponseStatus注解(3个属性)标注一个自定义异常类。
1. code属性(设置异常的状态码)value属性的别名,等价于value属性。
2. value属性(设置异常的状态码)。
3. reason属性(设置异常的原因或描述)。
2. 当程序运行时发生了这个自定义异常,ResponseStatusExceptionResolver会解析该异常,并将异常信息展示到错误页并返回给客户端。
例
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "自定义异常")
public class UserNotExistException extends RuntimeException {
}
@Controller
public class UserController {
@Resource
private UserDao userDao;
@RequestMapping("/login")
public String login(String userName, Model model) {
User user = userDao.getUserByUserName(userName);
if (user == null) {
throw new UserNotExistException(); // 抛出自定义异常
}
return "success";
}
}
3. ExceptionHandlerExceptionResolver(处理 控制器类内发生的异常)
1. 首先在Controller控制器类中使用@ExceptionHandler注解(使用value属性指定要处理的异常)来标注一个处理异常的方法(只能处理该控制器类内的异常)。
2. 当控制器方法出现指定异常时,调用该控制类中相应的@ExceptionHandler方法(即使用了@ExceptionHandler注解的方法)对异常进行处理。
3. 如果同一个控制器类内定义了多个使用@ExceptionHandler注解的异常处理方法,则根据继承关系 调用继承深度最浅的异常处理方法 对异常进行处理。
4. 如果在使用@ControllerAdvice注解的类中定义@ExceptionHandler方法(可以处理 应用中所有带有@RequestMapping注解的控制器方法 中的异常)则可实现全局异常处理。
例1
@Controller
public class ExceptionController2 {
// 控制器方法
@RequestMapping(value = "/testExceptionHandler")
public String testExceptionHandler() {
// 抛出ArithmeticException异常
System.out.println(10 / 0);
return "success";
}
// 使用@ExceptionHandler注解 定义一个异常处理方法
@ExceptionHandler(ArithmeticException.class)
public String handleException(ArithmeticException exception, Model model) {
// 将异常信息通过Model放到request域中,以便在页面中展示异常信息。
model.addAttribute("ex", exception);
return "error";
}
//
@ExceptionHandler(value = {RuntimeException.class})
public String handleException2(Exception exception, Model model) {
model.addAttribute("ex", exception);
return "error-2";
}
}
例2(全局异常处理)
@ControllerAdvice
public class ExceptionControllerAdvice {
@ExceptionHandler
public String exceptionAdvice(Exception exception, Model model) {
System.out.println("ExceptionControllerAdvice>>>>>>>>>>>>>>>>>>>");
model.addAttribute("ex", exception);
return "error-2";
}
}
4. SimpleMappingExceptionResolver(自定义的异常处理器,也能对所有异常进行统一处理)
1. 首先在SpringMVC.xml配置文件中,定义SimpleMappingExceptionResolver类型的Bean(配置异常类和错误页面的映射关系)。
2. 在webapp/WEB-INF/templates目录下,创建错误页。
例
1. 在SpringMVC.xml配置文件中,添加:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 配置 错误类型和错误页的映射 -->
<property name="exceptionMappings">
<props>
<!--
若应用运行时出现key中指定的(处理器方法执行过程中出现的)异常时,则跳转到指定错误页面。
-->
<prop key="com.sst.cx.exception.UserNotExistException">error-hello</prop>
</props>
</property>
<!-- exceptionAttribute属性:设置一个属性名(用于在请求域中共享异常信息) -->
<property name="exceptionAttribute" value="ex"></property>
</bean>
2. 在webapp/WEB-INF/templates目录下,创建error-hello.html错误页:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
error出错了!
<h4 th:text="${ex}"></h4>
</body>
</html>
3. 文件的上传和下载
- 文件上传
步骤:
1. 编写Form表单
form标签的method属性设置为post、enctype属性设置为multipart/form-data(浏览器会以二进制流的方式对表单数据进行处理,由服务端对文件上传的请求进行解析和处理)。
至少有一个type属性为file的input标签,该标签的multiple属性可实现同时选择多个文件进行上传。
例:
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="fileName" multiple="multiple"/>
<input type="submit" value="上传">
</form>
2. 配置文件解析器(MultipartResolver )
SpringMVC提供了MultipartResolver接口(文件解析器)来实现文件上传功能。
MultipartResolver接口有两个实现类:
1. StandardServletMultipartResolver
Servlet内置的上传功能,不需要依赖第三方JAR包。
仅支持Servlet 3.0及以上版本。
2. CommonsMultipartResolver
需要导入Apache的commons-fileupload依赖包。
不仅支持Servlet3.0及以上版本,还支持Servlet的旧版本。
例(CommonsMultipartResolver)
1. 在lib目录中导入依赖包:commons-io-x.x.x.jar、commons-fileupload-xx.xx.xx.jar。
2. 在SpringMVC.xml中,添加
<!-- 配置文件上传解析器,id必须为multipartResolver否则无法完成文件的解析和上传 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置上传文件的默认编码格式-->
<property name="defaultEncoding" value="UTF-8"></property>
<!--设置允许上传的最大长度(单位为字节)-->
<property name="maxUploadSize" value="1024000"></property>
<!--
其他常用属性
1. maxInMemorySize属性:读取文件到内存中的最大字节数。
2. resolveLazily属性:判断是否要延迟解析文件。
-->
</bean>
3. 创建控制器方法(添加一个MultipartFile接口类型的形参,该参数封装了被上传文件的信息) 进行文件上传处理
MultipartFile接口是InputStreamSource的子接口,常用的接口方法:
1. byte[] getBytes()
以字节数组的形式返回上传文件的内容。
2. String getContentType()
返回 上传文件的类型。
3. InputStream getInputStream()
以input流的形式返回上传文件的内容。
4. String getName()
返回请求参数的名称。
5. String getOriginalFillename()
返回上传文件的原始名(上传的客户端中的文件的名称)。
6. long getSize()
返回上传文件的大小(单位为字节)。
7. boolean isEmpty()
上传文件是否为空。
8. void transferTo(File destination)
将上传文件保存到目标目录下。
例:
@Controller
public class FileUploadController {
@RequestMapping("/uplaod")
public String upload(MultipartFile file) {
if (!file.isEmpty()) {
return "success";
}
return "error";
}
}
下载commons-fileupload-xx.xx.xx.jar、commons-io-x.x.x.jar
- 文件下载
1. 创建一个File对象(根据文件路径和文件名)
2. 设置响应头(文件的打开方式和下载方式)
3. 控制器方法中返回ResponseEntity对象(封装了File对象、响应头、状态码)。
类似于@ResponseBody 注解相似,都用来直接返回结果对象的。
详情见下方示例的DownLoadController控制器
示例(上传和下载)
===》file-upload.html(在webapp/WEB-INF/templates/目录下创建)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--引入 jquery-->
<script type="text/javaScript"
src="../../js/jquery-3.6.0.min.js" th:src="@{/js/jquery-3.6.0.min.js}"></script>
</head>
<body>
<form th:action="@{/student}" method="post" enctype="multipart/form-data">
<table style="margin: auto">
<tr>
<td th:if="${not #strings.isEmpty(msg)}" colspan="2" align="center">
<p style="color: red;margin: auto" th:text="${msg}"></p>
</td>
</tr>
<tr>
<td>学号:</td>
<td><input type="text" name="stuId" required><br></td>
</tr>
<tr>
<td>学生姓名:</td>
<td><input type="text" name="stuName" required><br></td>
</tr>
<tr>
<td>年龄:</td>
<td><input type="number" name="age" required><br></td>
</tr>
<tr>
<td>照片:</td>
<td><input type="file" id="chooseImage" name="photos" multiple="multiple" required><br>
<span id="img-div"></span></td>
</tr>
<input id="fileNameStr" type="hidden" name="fileNameStr"/>
<tr>
<td colspan="2" align="center">
<input type="submit" value="提交">
<input type="reset" value="重置">
</td>
</tr>
</table>
<!-- 保存用户自定义的背景图片 -->
<img id="preview_photo" src="" width="200px" height="200px">
</form>
<script type="text/javascript" th:inline="javascript">
/*<![CDATA[*/
ctxPath = /*[[@{/}]]*/ '';
/*]]>*/
</script>
<script type="text/javaScript">
$('#chooseImage').on('change', function () {
var filePath = $(this).val(), //获取到input的value,里面是文件的路径
fileFormat = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
// 检查是否是图片
if (!fileFormat.match(/.png|.jpg|.jpeg/)) {
alert('上传错误,文件格式必须为:png/jpg/jpeg');
return;
}
// 获取上传的文件
var arr = document.getElementById('chooseImage').files;
// 遍历文件
for (var i = 0; i < arr.length; i++) {
// 通过 FormData 将文件信息提交到后台
var formData = new FormData();
formData.append('photo', arr[i]);
$.ajax({
url: "http://localhost:8080/springmvc-file-demo/uploadPhoto",
type: "post",
data: formData,
contentType: false,
processData: false,
success: function (data) {
if (data.type == "success") {
// 在图片显示区显示图片
var html = "<img id='" + data.filename + "' src='" + ctxPath + data.filepath + data.filename + "' width='200px' height='200px'> ";
$("#img-div").append(html);
// 将文件路径赋值给 fileNameStr
var path = $("#fileNameStr").val();
if (path == "") {
$("#fileNameStr").val(data.filename);
} else {
$("#fileNameStr").val(path + "," + data.filename);
}
} else {
alert(data.msg);
}
},
error: function (data) {
alert("上传失败")
}
});
}
});
</script>
<style>
img[src=""], img:not([src]) {
opacity: 0;
}
</style>
</body>
</html>
===》success.html(在webapp/WEB-INF/templates/目录下创建)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>学生信息上传成功</h1>
<table>
<tr>
<td>学号:</td>
<td th:text="${student.getStuId()}"></td>
</tr>
<tr>
<td>姓名:</td>
<td th:text="${student.getStuName()}"></td>
</tr>
<tr>
<td>年龄:</td>
<td th:text="${student.getAge()}"></td>
</tr>
<tr>
<td>照片:</td>
<td th:each="p:${student.getPath()}">
<img th:src="${#servletContext.getContextPath()}+'/upload/'+${p}" width='200px' height='200px'/><br>
<a th:href="@{/downLoadFile(fileName=${p})}">点击下载图片</a>
</td>
</tr>
</table>
</body>
</html>
===》MultiFileController.java 上传文件控制器
@Controller
public class MultiFileController {
// 图片上传
@RequestMapping(value = "/uploadPhoto", method = RequestMethod.POST)
@ResponseBody
public Map<String, String> uploadPhoto(MultipartFile photo, HttpServletRequest request) {
Map<String, String> ret = new HashMap<String, String>();
if (photo == null) {
ret.put("type", "error");
ret.put("msg", "选择要上传的文件!");
return ret;
}
if (photo.getSize() > 1024 * 1024 * 10) {
ret.put("type", "error");
ret.put("msg", "文件大小不能超过10M!");
return ret;
}
// 获取文件后缀
String suffix = photo.getOriginalFilename().substring(photo.getOriginalFilename().lastIndexOf(".") + 1, photo.getOriginalFilename().length());
if (!"jpg,jpeg,gif,png".toUpperCase().contains(suffix.toUpperCase())) {
ret.put("type", "error");
ret.put("msg", "请选择jpg、peg、gif、png 格式的图片!");
return ret;
}
String realPath = request.getServletContext().getRealPath("/upload/");
System.out.println(realPath);
File fileDir = new File(realPath);
if (!fileDir.exists()) {
fileDir.mkdir();
}
String filename = photo.getOriginalFilename();
System.err.println("正在上传的图片为:" + filename);
String newFileName = UUID.randomUUID() + filename;
try {
// 将文件保存指定目录
photo.transferTo(new File(realPath + newFileName));
} catch (Exception e) {
ret.put("type", "error");
ret.put("msg", "保存文件异常!");
e.printStackTrace();
return ret;
}
ret.put("type", "success");
ret.put("msg", "上传图片成功!");
ret.put("filepath", "/upload/");
ret.put("filename", newFileName);
return ret;
}
// 提交学生信息
@RequestMapping(value = "/student", method = RequestMethod.POST)
public String uploadFile(Student student, Model model) {
String fileNameStr = student.getFileNameStr();
String[] split = fileNameStr.split(",");
List<String> list = new ArrayList<>();
for (String fileName : split) {
list.add(fileName);
}
student.setPath(list);
model.addAttribute("student", student);
return "success";
}
}
===》DownloadController.java 下载文件控制器
@Controller
public class DownLoadController {
// 文件下载
@RequestMapping("/downLoadFile")
public ResponseEntity<byte[]> downLoadFile(HttpServletRequest request, String fileName) throws IOException {
// 得到图片的实际路径
String realPath = request.getServletContext().getRealPath("/upload/" + fileName);
// 创建该图片的对象
File file = new File(realPath);
// 将图片数据读取到字节数组中
byte[] bytes = FileUtils.readFileToByteArray(file);
// 创建 HttpHeaders 对象设置响应头信息
HttpHeaders httpHeaders = new HttpHeaders();
// 设置图片下载的方式和文件名称
httpHeaders.setContentDispositionFormData("attachment", toUTF8String(fileName));
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
}
// 下载保存时使用中文文件名的字符编码转换方法
public String toUTF8String(String str) {
StringBuffer sb = new StringBuffer();
int len = str.length();
for (int i = 0; i < len; i++) {
// 取出字符中的每个字符
char c = str.charAt(i);
// Unicode码值为0~255时,不做处理
if (c >= 0 && c <= 255) {
sb.append(c);
} else { // 转换 UTF-8 编码
byte b[];
try {
b = Character.toString(c).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
b = null;
}
// 转换为%HH的字符串形式
for (int j = 0; j < b.length; j++) {
int k = b[j];
if (k < 0) {
k &= 255;
}
sb.append("%" + Integer.toHexString(k).toUpperCase());
}
}
}
return sb.toString();
}
}
===》Student.java
public class Student {
private String stuId;
private String stuName;
private Integer age;
// 用于接收上传的文件
private List<MultipartFile> photos;
// 上传文件名
private String fileNameStr;
// 已上传图片的路径集合
private List<String> path;
... 省略setter、getter方法
}
/*
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String login(User user, HttpServletRequest request, Model model) {
List<String> newFileNameList = new ArrayList<>();
List<MultipartFile> photos = user.getPhotos();
for (MultipartFile photo : photos) {
String realPath = request.getServletContext().getRealPath("/upload/");
System.out.println(realPath);
File fileDir = new File(realPath);
if (!fileDir.exists()) {
fileDir.mkdir();
}
String filename = photo.getOriginalFilename();
System.err.println("正在上传的图片为:" + filename);
String newFileName = UUID.randomUUID() + filename;
try {
// 将文件保存指定目录
photo.transferTo(new File(realPath + newFileName));
} catch (Exception e) {
e.printStackTrace();
}
newFileNameList.add(newFileName);
}
System.out.println(user);
model.addAttribute("type", "success");
model.addAttribute("user", user);
model.addAttribute("filePath", "/upload/");
model.addAttribute("fileNameList", newFileNameList);
return "success";
}
*/
4. 注解配置
除了使用传统的xml文件进行配置外,还可以使用全注解方式(取代web.xml、SpringMVC.xml)进行配置。
1. 使用初始化类替代web.xml
Servlet容器在启动时,会自动在类路径下查找实现了javax.servlet.ServletContainerInitializer接口的初始化类,来替代web.xml对Servlet容器的上下文进行配置(配置DispatcherServlet、)。
例
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
// 设置Spring的配置类
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
// 设置SpringMVC的配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
// 为DispatcherServlet指定映射规则(即:web.xml中的url-pattern)
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
// 添加过滤器
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceResponseEncoding(true);
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
}
}
===》SpringServletContainerInitializer类(ServletContainerInitializer接口的实现类)
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
……
}
}
说明:
1. @HandlesTypes注解:
获取所有实现了WebApplicationInitializer接口的类,并赋值给onStartup()方法的webAppInitializerClasses参数,由onStartup()方法调用WebApplicationInitializer实现类中的方法来实现对DispatcherServlet和SpringMVC的配置工作。
===》AbstractAnnotationConfigDispatcherServletInitializer(WebApplicationInitializer的实现类)DispatcherServelt的快速配置类
其常用方法如下:
1. protected abstract Class<?>[] getRootConfigClasses();
用于设置Spring的配置类。
2. protected abstract Class<?>[] getServletConfigClasses();
用于设置SpringMVC的配置类。
3. protected abstract String[] getServletMappings();
用于指定DispatcherServelt的映射规则(即:web.xml中的url-pattern)。
4. protected Filter[] getServletFilters()
用于添加各种过滤器(Filter)。
2. 使用配置类替代Spring的配置文件(Ioc容器)
使用@Configuration注解标注的普通类(被称为配置类),来替代Spring的配置文件。
在配置类中,可以定义多个使用@Bean注解标注的方法(等价于Spring配置文件中的<bean>标签)来将Java对象以Bean的形式交由Ioc容器管理。
例
@Configuration
public class SpringConfig {
// 定义Bean
// 方法名相当于<bean>标签的id属性
// 方法的返回值类型就相当于<bean>标签的class属性
// 若方法中存在形参,则该参数对象通常为Spring容器中的组件,Spring会按照类型或参数名称注入该参数对象。
@Bean
public Student student(){
Student student = new Student();
student.setStuId("1001");
student.setStuName("张三");
student.setAge(12);
return student;
}
}
等价于xml文件形式配置:
<bean id="student" class="com.sst.cx.entity.Student">
<property name="stuId" value="1001"></property>
<property name="stuName" value="张三"></property>
<property name="age" value="12"></property>
</bean>
3. 使用配置类替代SpringMVC的配置文件
使用@Configuration注解标注的普通类(被称为配置类),来替代SpringMVC的配置文件。
SpringMVC的配置组件(组件扫描、视图解析器、拦截器、类型转换器、异常解析器、文件上传解析器等)在配置类中的配置方式不同:
1. 实现WebMvcConfigurer接口(SpringBoot2.0之前是已废弃的WebMvcConfigurerAdapter抽象类)
通过在实现WebMvcConfigurer接口的配置类(使用@Configuration注解标注的普通类)中实现对应的接口方法来对SpringMVC部分组件(拦截器、格式化程序、视图控制器)进行配置。
例:
// 将静态资源文件交给默认Servlet处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
// 配置拦截器(对请求进行拦截处理)
@Override
public void addInterceptors(InterceptorRegistry registry) {
MyInterceptor myInterceptor = new MyInterceptor();
registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/");
}
// 配置视图控制器(路径<-->页面)
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("user");
}
// 配置异常处理器(异常<-->页面)
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.Exception", "error");
// 设置异常映射
exceptionResolver.setExceptionMappings(prop);
// 设置共享异常信息的键
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}
// 添加类型转换器和格式化器
@Override
public void addFormatters(FormatterRegistry registry) {
MyDateConverter myDateConverter = new MyDateConverter();
registry.addConverter(myDateConverter);
}
2. 使用@EnableWebMvc、@ComponentScan等注解(标注 配置类)
1. @ComponentScan("com.sst.cx")
等价于<context:component-scan base-package="com.sst.cx"></context:component-scan>
开启组件扫描(会扫描包路径下所有使用@Controller注解的类)
2. @EnableWebMvc
等价于<mvc:annotation-driven/>
开启SpringMVC注解驱动
3. 使用@Bean注解(在配置类中)
1. 配置Thymeleaf视图解析器
// 模板解析器
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext =
ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过 WebApplicationContext获取
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/"); // 视图前缀
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
// 模板引擎(并为模板引擎注入模板解析器)
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
// 视图解析器(并为视图解析器注入模板引擎)
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
等价于 xml文件形式配置Thymeleaf视图解析器。
2. 配置文件上传解析器
// 配置文件上传解析器
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
commonsMultipartResolver.setDefaultEncoding("UTF-8");
commonsMultipartResolver.setMaxUploadSize(1024*1024*10);
return commonsMultipartResolver;
}
等价于 xml文件形式配置文件上传解析器。
例
@Configuration
@ComponentScan("com.sst.cx")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
方式1中示例(实现WebMvcConfigurer接口方法)
方式3中示例(@Bean注解)
}
常用的WebMvcConfigurer接口方法 | 描述(返回值都是void) |
---|---|
configurePathMatch | HandlerMappings路径的匹配规则 |
configureContentNegotiation | 内容协商策略(一个请求路径返回多种数据格式)。 |
configureAsyncSupport | 配置异步请求处理相关参数 |
configureDefaultServletHandling | (将静态文件交给Servlet容器内置的默认Servlet处理)实现静态文件可以像Servlet一样被访问 |
addFormatters | 增加Converter转换器、Formatter格式化器 |
addInterceptors | 注册拦截器(对请求进行拦截处理) |
addResourceHandlers | 添加或修改静态资源映射(设置默认的静态资源目录) |
addCorsMappings | 配置跨域请求相关参数 |
addViewControllers | 添加和业务逻辑无关(直接简单跳转,不传递数据)的跳转(路径<-->页面) |
configureViewResolvers | 将Controller返回的视图名转换为具体的视图页面。 |
addArgumentResolvers | 自定义Controller方法参数类型,不会覆盖默认的处理(想覆盖,可直接去配置RequestMappingHandleAdapter)。 |
addReturnValueHandlers | 自定义Controller返回值类型,不会覆盖默认的处理(想覆盖,可直接去配置RequestMappingHandleAdapter)。 |
configureMessageConverters | 配置默认的消息转换器(转换 HTTP请求/响应) |
extendMessageConverters | 扩展消息转换器(通过此方式扩展则会新增;通过直接添加的方式会使默认的消息转换器列表失效;) |
configureHandlerExceptionResolvers | 配置异常处理器(异常<-->页面) |
extendHandlerExceptionResolvers | 扩展异常处理器列表 |
5. 国际化
为不同的国家/语言提供相应的页面和数据。
步骤:
1. 在SpringMVC.xml配置文件中,添加
<!--
配置国际化
1.ResourceBundleMessageSource:绑定相应的资源文件内容。
2.SessionLocaleResolver:将Locale对象(包含了国际化信息)存储在Session对象域中。
3.LocaleChangeInterceptor:用于手动切换语言环境。获取请求中的国际化信息(如:lang=zh_CN)并将其转换为Locale对象,以获取LocaleResolver对象对指定国际化资源文件进行解析。
-->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<!-- 配置国际化资源文件的基本名,多个时以,分隔 -->
<property name="basenames" value="messages"></property>
<!-- 配置国际化资源文件的编码 -->
<property name="defaultEncoding" value="UTF-8"></property>
<property name="cacheSeconds" value="0"></property>
</bean>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="en_US" />
</bean>
<mvc:interceptors>
<bean
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang"></property>
</bean>
</mvc:interceptors>
2. 创建国际化资源文件(在类路径下创建)
文件名格式为:基本名_语言_国家.properties
IDEA:打开任意一个国际化资源文件,切换为ResourceBundle模式(需要安装ResourceBundle插件),然后点击“+”号创建所需的国际化属性(需要进行国际化的字段)。
例(IDEA会自动识别国际化资源文件并自动添加Resouce Bundle目录)
messages.properties:默认
userName=用户名
password=密码
messages_zh_CN.properties:中文时生效
userName=用户名
password=密码
messages_en_US.properties:英语时生效
userName=userName
password=password
3. 在页面中获取国际化内容;
<h2 th:text="#{userName}"></h2>
<h2 th:text="#{password}"></h2>
4. 创建控制器方法来实现手动切换语言。
<a th:href="@{/localeChange(lang=en_US)}">英文</a>
<a th:href="@{/localeChange(lang=zh_CN)}">中文</a>
@Controller
public class I18nController {
@Resource
private ResourceBundleMessageSource messageSource;
// 切换语言环境
@RequestMapping("/localeChange")
public String localeChange(Locale locale) {
String userName = messageSource.getMessage("userName", null, locale);
String password = messageSource.getMessage("password", null, locale);
System.out.println(userName + "----" + password);
return "user";
}
}