JavaEE

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. 文件上传
步骤:

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.jarcommons-io-x.x.x.jar

  1. 文件下载
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'>&nbsp;";
                        $("#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";
        }
    }
上一篇下一篇

猜你喜欢

热点阅读