Spring MVC之——响应返回值、文件上传、异常处理、拦截器

响应返回值

String类型

controller 方法(SpringMvc的控制器方法)返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。

1
2
3
4
5
6
7
//指定逻辑视图名,经过视图解析器解析为 jsp 物理路径:/WEB-INF/pages/success.jsp
@RequestMapping("/testReturnString")
public String testReturnString() {
System.out.println("AccountController 的 testReturnString 方法执行了。。。。");
return "success";
}

Spring类型之——转发和重定向

  • forward 转发:controller 方法在提供了 String 类型的返回值之后,默认就是请求转发。也可以这样
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 转发
    * @return
    */
    @RequestMapping("/testForward")
    public String testForward() {
    System.out.println("AccountController 的 testForward 方法执行了。。。。");
    return "forward:/WEB-INF/pages/success.jsp";
    }

    需要注意的是,如果用了 formward:则路径必须写成实际视图 url,不能写逻辑视图。
    它相当于“request.getRequestDispatcher("url").forward(request,response)”
    使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法。

  • Redirect 重定向:contrller 方法提供了一个 String 类型返回值之后,它需要在返回值里使用:redirect
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 重定向
    * @return
    */
    @RequestMapping("/testRedirect")
    public String testRedirect() {
    System.out.println("AccountController 的 testRedirect 方法执行了。。。。");
    return "redirect:testReturnModelAndView";
    }

    它相当于“response.sendRedirect(url)”。需要注意的是,如果是重定向到 jsp 页面,则 jsp 页面不能写在 WEB-INF 目录中,否则无法找到。

void类型

我们知道 Servlet 原始 API 可以作为控制器中方法的参数:

1
2
3
4
@RequestMapping("/testReturnVoid")
public void testReturnVoid(HttpServletRequest request,HttpServletResponse response) throws Exception {
}

在 controller 方法形参上可以定义 request 和 response,使用 request 或 response 指定响应结果:

  • 1、使用 request 转向页面
    1
    request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
  • 2、也可以通过 response 页面重定向:
    1
    response.sendRedirect("testRetrunString")
  • 3、也可以通过 response 指定响应结果,例如响应 json 数据:
    1
    2
    3
    response.setCharacterEncoding("utf-8");
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().write("json 串");

ModelAndView类型

ModelAndView 是 SpringMVC 为我们提供的一个对象,该对象也可以用作控制器方法的返回值。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 返回 ModeAndView
* @return
*/
@RequestMapping("/testReturnModelAndView")
public ModelAndView testReturnModelAndView() {
ModelAndView mv = new ModelAndView();

//原理和ModeMap一样,都会存入request域中
mv.addObject("username", "张三");

//用于设置逻辑视图名称,这里通过视图解析器解析后的结果为:success.jsp
mv.setViewName("success");
return mv;
}

@ResponseBody注解响应json数据

  • 作用:该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的
    数据如:json,xml 等,通过 Response 响应给客户端
  • 注意:Springmvc 默认用 MappingJacksonHttpMessageConverter 对 json 数据进行转换,需要加入
    jackson 的包
  • maven坐标
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
    </dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
    </dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
    </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 响应 json 数据的控制器
*/
@Controller("jsonController")
public class JsonController {
/**
* 测试响应 json 数据
*/
@RequestMapping("/testResponseJson")
public @ResponseBody Account testResponseJson(@RequestBody Account account) {

//@RequestBody参数封装
//@ResponseBody对象转json

System.out.println("异步请求:"+account);
return account;
}
}

文件上传

文件上传的必要前提:

  • 1、form 表单的 enctype 取值必须是:multipart/form-data

    (默认值是:application/x-www-form-urlencoded)

    • enctype:是表单请求正文的类型
  • 2、method 属性取值必须是 Post
  • 3、提供一个文件选择域<input type=”file” />

文件上传的原理分析

当 form 表单的 enctype 取值不是默认值后,**request.getParameter()**将失效。

  • enctype=”application/x-www-form-urlencoded”时,form 表单的正文内容是:key=value&key=value&key=value
  • 当 form 表单的 enctype 取值为 Mutilpart/form-data 时,请求正文内容就变成:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    每一部分都是 MIME 类型描述的正文
    -----------------------------7de1a433602ac 分界符
    Content-Disposition: form-data; name="userName" 协议头
    aaa 协议的正文
    -----------------------------7de1a433602ac
    Content-Disposition: form-data; name="file";
    filename="C:\Users\zhy\Desktop\fileupload_demofile\b.txt"
    Content-Type: text/plain 协议的类型(MIME 类型)
    bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
    -----------------------------7de1a433602ac--

借助第三方组件实现文件上传

使用 Commons-fileupload 组件实现文件上传,需要导入该组件相应的支撑 jar 包:Commons-fileupload 和
commons-io。commons-io 不属于文件上传组件的开发 jar 文件,但Commons-fileupload 组件从 1.1 版本开始,它
工作时需要 commons-io 包的支持。

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>

传统方式的文件上传

传统方式的文件上传,指的是我们上传的文件和访问的应用存在于同一台服务器上。
并且上传完成之后,浏览器可能跳转。

  • 前端页面
    1
    2
    3
    4
    5
    <h3>文件上传</h3>
    <form action="user/fileupload" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="upload"/><br/>
    <input type="submit" value="上传文件"/>
    </form>
  • 编写Controller控制器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    /**
    * 文件上传
    * @throws Exception
    */
    @RequestMapping(value="/fileupload")
    public String fileupload(HttpServletRequest request) throws Exception {
    // 先获取到要上传的文件目录
    String path = request.getSession().getServletContext().getRealPath("/uploads");

    // 创建File对象,一会向该路径下上传文件
    File file = new File(path);

    // 判断路径是否存在,如果不存在,创建该路径
    if(!file.exists()) {
    file.mkdirs();
    }

    // 创建磁盘文件项工厂
    DiskFileItemFactory factory = new DiskFileItemFactory();
    ServletFileUpload fileUpload = new ServletFileUpload(factory);

    // 解析request对象
    List<FileItem> list = fileUpload.parseRequest(request);

    // 遍历
    for (FileItem fileItem : list) {
    // 判断文件项是普通字段,还是上传的文件
    if(fileItem.isFormField()) {
    }else {
    // 上传文件项
    // 获取到上传文件的名称
    String filename = fileItem.getName();

    // 上传文件
    fileItem.write(new File(file, filename));

    // 删除临时文件
    fileItem.delete();
    }
    }
    return "success";
    }

SpringMVC传传统方式的文件上传

SpringMVC框架提供了MultipartFile对象,该对象表示上传的文件,要求变量名称必须和表单file标签的
name属性名称相同。

  • 前端代码
    1
    2
    3
    4
    5
    <form action="/fileUpload" method="post" enctype="multipart/form-data">
    名称:<input type="text" name="picname"/><br/>
    图片:<input type="file" name="uploadFile"/><br/>
    <input type="submit" value="上传"/>
    </form>
  • Controller控制器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    /**
    * 文件上传的的控制器
    * @author 黑马程序员
    * @Company http://www.ithiema.com
    * @Version 1.0
    */
    @Controller("fileUploadController")
    public class FileUploadController {
    /**
    * 文件上传
    */
    @RequestMapping("/fileUpload")
    public String testResponseJson(String picname,MultipartFileuploadFile,HttpServletRequest request) throws Exception{
    //定义文件名
    String fileName = "";

    //1.获取原始文件名
    String uploadFileName = uploadFile.getOriginalFilename();

    //2.截取文件扩展名
    String extendName =uploadFileName.substring(uploadFileName.lastIndexOf(".")+1, nuploadFileName.length());

    //3.把文件加上随机数,防止文件重复
    String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();

    //4.判断是否输入了文件名
    if(!StringUtils.isEmpty(picname)) {
    fileName = uuid+"_" + picname +"." + extendName;
    }else {
    fileName = uuid+"_"+uploadFileName;
    }
    System.out.println(fileName);

    //2.获取文件路径
    ServletContext context = request.getServletContext();
    String basePath = context.getRealPath("/uploads");

    //3.解决同一文件夹中文件过多问题
    String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());

    //4.判断路径是否存在,不存在创建
    File file = new File(basePath+"/"+datePath);
    if(!file.exists()) {
    file.mkdirs();
    }

    //5.使用 MulitpartFile 接口中方法,把上传的文件写到指定位置
    uploadFile.transferTo(new File(file,fileName));
    return "success";
    }
    }

  • 配置文件解析器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 配置文件上传解析器 -->
    <bean id="multipartResolver" <!-- id 的值是固定的-->
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 设置上传文件的最大尺寸为 5MB -->
    <property name="maxUploadSize">
    <value>5242880</value>
    </property>
    </bean>

注意:

文件上传的解析器 id 是固定的,不能起别的名称,否则无法实现请求参数的绑定。(不光是文件,其他
字段也将无法绑定)

SpringMVC跨服务器方式文件上传

在实际开发中,我们会有很多处理不同功能的服务器。例如:

  • 应用服务器:负责部署我们的应用
  • 数据库服务器:运行我们的数据库
  • 缓存和消息服务器:负责处理大并发访问的缓存和消息
  • 文件服务器:负责存储用户上传文件的服务器。
    分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。

1、准备两个 tomcat 服务器,并创建一个用于存放图片的 web 工程

2. 实现SpringMVC跨服务器方式文件上传

  • 前端代码:
    1
    2
    3
    4
    5
    <h3>跨服务器的文件上传</h3>
    <form action="user/fileupload3" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="upload"/><br/>
    <input type="submit" value="上传文件"/>
    </form>
  • Controller控制器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /**
    * SpringMVC跨服务器方式的文件上传
    *
    * @param request
    * @return
    * @throws Exception
    */
    @RequestMapping(value="/fileupload3")
    public String fileupload3(MultipartFile upload) throws Exception {
    System.out.println("SpringMVC跨服务器方式的文件上传...");

    // 定义图片服务器的请求路径
    String path = "http://localhost:9090/day02_springmvc5_02image/uploads/";

    // 获取到上传文件的名称
    String filename = upload.getOriginalFilename();
    String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();

    // 把文件的名称唯一化
    filename = uuid+"_"+filename;

    // 向图片服务器上传文件
    // 创建客户端对象
    Client client = Client.create();

    // 连接图片服务器
    WebResource webResource = client.resource(path+filename);

    // 上传文件
    webResource.put(upload.getBytes());
    return "success";
    }
  • 配置解析器
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 配置文件上传解析器 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 设置上传文件的最大尺寸为 5MB -->
    <property name="maxUploadSize">
    <value>5242880</value>
    </property>
    </bean>

SpringMVC 中的异常处理

异常处理的思路

系统中异常包括两类:预期异常和运行时异常 RuntimeException,前者通过捕获异常从而获取异常信息,
后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。

系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由 springmvc 前端
控制器交由异常处理器进行异常处理,如下图:
SpringMVC异常解决思路.png

实现异常处理

  • 自定义异常
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 自定义异常
    */
    public class CustomException extends Exception {
    private String message;
    public CustomException(String message) {
    this.message = message;
    }

    public String getMessage() {
    return message;
    }
    }

  • 自定义异常处理器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * 自定义异常处理器
    * @author 黑马程序员
    * @Company http://www.ithiema.com
    * @Version 1.0
    */
    public class CustomExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    ex.printStackTrace();
    CustomException customException = null;

    //如果抛出的是系统自定义异常则直接转换
    if(ex instanceof CustomException){
    customException = (CustomException)ex;
    }else{
    //如果抛出的不是系统自定义异常则重新构造一个系统错误异常。
    customException = new CustomException("系统错误,请与系统管理 员联系!");
    }
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("message", customException.getMessage());
    modelAndView.setViewName("error");
    return modelAndView;
    }
    }

  • 配置异常处理器
    1
    2
    <!-- 配置自定义异常处理器 -->
    <bean id="handlerExceptionResolver" class="com.itheima.exception.CustomExceptionResolver"/>

SpringMVC 中的拦截器

  1. SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术。
  2. 可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链中的拦截器会按着定义的顺序执行。
  3. 拦截器过滤器的功能比较类似,有区别
    1. 过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术。
    2. 拦截器是SpringMVC框架独有的。
    3. 过滤器配置了/*,可以拦截任何资源。
    4. 拦截器只会对控制器中的方法进行拦截。
  4. 拦截器也是AOP思想的一种实现方式
  5. 想要自定义拦截器,需要实现HandlerInterceptor接口。

实现自定义拦截器

  • 创建类,实现HandlerInterceptor接口,重写需要的方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package cn.itcast.demo1;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.web.servlet.HandlerInterceptor;
    /**
    * 自定义拦截器1
    * @author rt
    */
    public class MyInterceptor1 implements HandlerInterceptor{
    /**
    * controller方法执行前,进行拦截的方法
    * return true放行
    * return false拦截
    * 可以使用转发或者重定向直接跳转到指定的页面。
    */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    System.out.println("拦截器执行了...");
    return true;
    }
    }
  • 在springmvc.xml中配置拦截器类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!-- 配置拦截器 -->
    <mvc:interceptors>
    <mvc:interceptor>
    <!-- 哪些方法进行拦截 -->
    <mvc:mapping path="/user/*"/>

    <!-- 哪些方法不进行拦截
    <mvc:exclude-mapping path=""/>
    -->

    <!-- 注册拦截器对象 -->
    <bean class="cn.itcast.demo1.MyInterceptor1"/>
    </mvc:interceptor>
    </mvc:interceptors>

HandlerInterceptor接口中的方法

  1. preHandle方法是controller方法执行前拦截的方法
    1. 可以使用request或者response跳转到指定的页面
    2. return true放行,执行下一个拦截器,如果没有拦截器,执行controller中的方法。
    3. return false不放行,不会执行controller中的方法。
  2. postHandle是controller方法执行后执行的方法,在JSP视图执行前。
    1. 可以使用request或者response跳转到指定的页面
    2. 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示。
  3. postHandle方法是在JSP执行后执行
    1. request或者response不能再跳转页面了

配置多个拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 哪些方法进行拦截 -->
<mvc:mapping path="/user/*"/>

<!-- 哪些方法不进行拦截
<mvc:exclude-mapping path=""/>
-->

<!-- 注册拦截器对象 -->
<bean class="cn.itcast.demo1.MyInterceptor1"/>
</mvc:interceptor>


<mvc:interceptor>
<!-- 哪些方法进行拦截 -->
<mvc:mapping path="/**"/>

<!-- 注册拦截器对象 -->
<bean class="cn.itcast.demo1.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>

ps:拦截器执行顺序按照配置的前后决定