02-SpringMVC、响应数据结果视图、文件上传、异常解析器、拦截器

一、响应数据和结果视图
1.1、返回值分类
1.1.1、字符串
  controller 方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。 

//指定逻辑视图名,经过视图解析器解析为 jsp 物理路径:/WEB-INF/pages/success.jsp 
@RequestMapping("/testString")
    public String testString(Model model){
        System.out.println("testString...");
        User user = new User();
        user.setUsername("美美");
        user.setPassword("123");
        user.setAge(30);
        // model对象
        model.addAttribute("user",user);
        return "success";
    }
UserController

  视图解析器,将返回的字符串解析成一个路径。(在该视图解析器对象中指定了前后缀。)

<!--配置的视图解析器对象-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
</bean>
视图解析器


1.1.2、void
默认不会进行跳转 。
  默认会跳转到@RequestMapping(value="/initUpdate") initUpdate的页面。 
如果想进行跳转:
  1、使用request跳转页面
  2、通过response页面重定向
  3、通过response指定响应的结果,例如json数据:
    response.setCharacterEncoding("utf-8"); 
    response.setContentType("application/json;charset=utf-8"); 
    response.getWriter().write("json 串"); 

@RequestMapping("/testVoid")
    public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("testVoid方法执行了。。。");
        //1.编写请求转发的程序
//        request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
        
        //2.重定向
//        response.sendRedirect(request.getContextPath()+"/index.jsp");
        
        //设置i中文乱码解决
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        //3.直接进行响应
        response.getWriter().write("hello你好");
        
        return;
    }
UserController


1.1.3、ModelAndView
ModelAndView 是 SpringMVC 为我们提供的一个对象,该对象也可以用作控制器方法的返回值。 
<a href="user/testModelAndView">testModelAndView</a>

/**
     * 返回ModelAndView
     * @return
     */
    @RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        System.out.println("testModelAndView方法执行了...");
        ModelAndView mv = new ModelAndView();
        User user = new User();
        user.setUsername("tom");
        user.setPassword("123");
        user.setAge(30);
        
        // 把user对象存储到mv对象中,也会把user对象存入到request对象
        mv.addObject("user",user);
        //跳转到那个页面
        mv.setViewName("success");
        
        return mv;
    }
UserController

  注意:返回 ModelAndView 类型时,浏览器跳转只能是请求转发

1.2、转发和重定向
<a href="user/testRequestOrRedirect">testRequestOrRedirect</a>

/**
     * 测试转发和重定向
     * @return
     */
    @RequestMapping("/testRequestOrRedirect")
    public String testRequestOrRedirect(HttpServletRequest request){
        System.out.println("testRequestOrRedirect...");
        
        //1.转发
//        return "forward:/WEB-INF/pages/success.jsp";
        //2.重定向
//        return "redirect:"+request.getContextPath()+"/index.jsp";
        return "redirect:/index.jsp";
    }
UserController

1.2.1、forward 转发
return "forward:/WEB-INF/pages/success.jsp"; 
它相当于“request.getRequestDispatcher("url").forward(request,response)”。使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法。

1.2.2、Redirect 重定向
controller 方法提供了一个 String 类型返回值之后,它需要在返回值里使用:return "redirect:/index.jsp";
它相当于“response.sendRedirect(url)”。需要注意的是,如果是重定向到 jsp 页面,则 jsp 页面不能写在 WEB-INF 目录中,否则无法找到。
  --->WEB-INF是受保护的目录。只有请求转发才能访问到。

1.3、@ResponseBody 响应json数据
  该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的数据如:json,xml 等,通过 Response 响应给客户端 
  -->即:将Controller方法返回的对象--->json(xml)格式的数据(即一个json对象{username:"tom",password:"123",age:23})--->客户端
示例:
  Springmvc 默认用 MappingJacksonHttpMessageConverter 对 json 数据进行转换,需要加入 jackson 的包(或者是fastJson)。 

<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>
Jackson相关jar
<script src="js/jquery.min.js"></script>
    <script>
        $(function () {
          $("#btn").click(function () {
              // alert("btn ajax")
              $.ajax({
                  url:"user/testAjax",
                  contentType:"application/json;charset=utf-8",
                  data:'{"username":"hehe","password":"123","age":30}',
                  dataType:"json",
                  type:"post",
                  success:function (data) {
                      alert(data);
                      alert(data.username);
                  }
              });
          });
        });
</script>

<button id="btn">发送ajax的请求</button>
Response.jsp
@RequestMapping("/testAjax")
    public @ResponseBody User testAjax(@RequestBody User user){
        System.out.println("testAjax方法执行了...");
        //客户端发送ajax的请求,传的是json字符串
        user.setUsername("payn");
        user.setAge(100);
        
        return user;
    }
UserController

正确

错误


二、SpringMVC实现文件的上传
2.1、文件上传
2.1.1、必要前提
  A、form 表单的 enctype 取值必须是:multipart/form-data     
     (默认值是:application/x-www-form-urlencoded)     enctype:是表单请求正文的类型
  B、method 属性取值必须是 Post
  C、提供一个文件选择域<input type=”file” /> 
原理:
  因为表单(请求体中)默认是以键值对的形式提交的。
  multipart/form-data  把一个表单分成多个部分
  get 提交的方式大小是有限制的。post提交的大小没有限制。

2.1.2、原理分析
当 form 表单的 enctype 取值不是默认值后,request.getParameter() 将失效。

enctype=”application/x-www-form-urlencoded”时,form 表单的正文内容是: 
  key=value&key=value&key=value 
当 form 表单的 enctype 取值为 Mutilpart/form-data 时,请求正文内容就变成: 
  每一部分都是 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-- 


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

<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>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h3>传统文件上传</h3>

    <form action="user/fileupload1" method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="upload" /><br/>
        <input type="submit" value="上传" />
    </form>

</html>
index.jsp
@Controller
@RequestMapping("/user")
public class UserController {
    /**
     * 文件上传
     * @return
     */
    @RequestMapping("/fileupload1")
    public String fileuoload1(HttpServletRequest request) throws Exception {
        System.out.println("文件上传...");

        // 使用fileupload组件完成文件上传
        // 上传的位置
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        // 判断,该路径是否存在
        File file = new File(path);
        if(!file.exists()){
            // 创建该文件夹
            file.mkdirs();
        }
        System.out.println(path);
        System.out.println(file.getAbsolutePath());

        // 解析request对象,获取上传文件项
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 解析request
        List<FileItem> items = upload.parseRequest(request);
        System.out.println(items.size()+"---------------");
        // 遍历
        for(FileItem item:items){
            // 进行判断,当前item对象是否是上传文件项
            if(item.isFormField()){
                // 说明普通表单向
            }else{
                // 说明上传文件项
                // 获取上传文件的名称
                String filename = item.getName();
                // 把文件的名称设置唯一值,uuid
                String uuid = UUID.randomUUID().toString().replace("-", "");
                filename = uuid+"_"+filename;
                // 完成文件上传
                item.write(new File(path,filename));
                // 删除临时文件
                item.delete();
            }
        }

        return "success";
    }
}
UserController--->文件上传方式一


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

2.2.2、实现步骤
2.2.2.1、第一步:拷贝文件上传的 jar 包到工程的 lib 目录 

 <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>
添加依赖

2.2.2.2、第二步:编写jsp页面

<h3>传统文件上传</h3>

<form action="user/fileupload1" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="upload"/><br/>
        <input type="submit" value="上传" />
</form>
index.jsp

2.2.2.3、第三步:编写控制器

@RequestMapping("/fileupload2")
public String fileuoload2(HttpServletRequest request, MultipartFile upload) throws Exception {
    System.out.println("文件上传...");

    // 使用fileupload组件完成文件上传
    // 上传的位置
    String path = request.getSession().getServletContext().getRealPath("/uploads");
    // 判断,该路径是否存在
    File file = new File(path);
    if(!file.exists()){
        // 创建该文件夹
        System.out.println("创建文件夹");
        file.mkdirs();
    }
    System.out.println("file.exists---"+file.exists());
    System.out.println(file.getAbsolutePath());

    // 说明上传文件项
    // 获取上传文件的名称
    String filename = upload.getOriginalFilename();
    // 把文件的名称设置唯一值,uuid
    String uuid = UUID.randomUUID().toString().replace("-", "");
    filename = uuid+"_"+filename;
    // 完成文件上传
    upload.transferTo(new File(path,filename));

    return "success";
}
UserController--->文件上传方式二

2.2.2.4、配置文件解析器

<!--配置文件解析器对象-->
<bean id="multipartResolver" <!--id的值是固定的-- >
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--设置上传文件的最大尺寸为 5MB -- >
      <property name="maxUploadSize" value=">5242880" />
</bean>
springmvc配置文件解析器

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

2.3、springmv跨服务器方式的文件上传
2.3.1、分服务器的目的
在实际开发中,我们会有很多处理不同功能的服务器。例如: 
  应用服务器:负责部署我们的应用 
  数据库服务器:运行我们的数据库 
  缓存和消息服务器:负责处理大并发访问的缓存和消息 
  文件服务器:负责存储用户上传文件的服务器。
  (注意:此处说的不是服务器集群) 
分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。



2.3.2、准备两个tomcat服务器,并创建一个用于存放图片的web工程
在文件服务器的  CATALINA_HOME/conf/web.xml  配置中加入,允许读写操作。文件位置: 

<servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>readonly</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
</servlet>

加入此行的含义是:
  接收文件的目标服务器可以支持写入操作。

2.3.3、拷贝jar包

2.3.4、编写控制器实现上传图片

/**
     * 跨服务器文件上传
     * @return
     */
    @RequestMapping("/fileupload3")
    public String fileuoload3(MultipartFile upload) throws Exception {
        System.out.println("跨服务器文件上传...");

        // 定义上传文件服务器路径
        String path = "http://localhost:9090/uploads/";

        // 说明上传文件项
        // 获取上传文件的名称
        String filename = upload.getOriginalFilename();
        // 把文件的名称设置唯一值,uuid
        String uuid = UUID.randomUUID().toString().replace("-", "");
        filename = uuid+"_"+filename;

        // 创建客户端的对象
        Client client = Client.create();

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

        // 上传文件
        webResource.put(upload.getBytes());

        return "success";
    }
UserController--->跨服务器上传

2.3.5、编写jsp页面

 <h3>跨服务器文件上传</h3>

<form action="user/fileupload3" method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="upload" /><br/>
        <input type="submit" value="上传" />
</form>
index.jsp

2.3.6、配置解析器

 <!--配置文件解析器对象-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="10485760" />
</bean>
View Code

-->如果是使用第一种方式进行文件的上传,不需要加此配置文件,否则会报错。

三、SpringMVC中的异常处理
3.1、异常处理的思路
系统中异常包括两类:预期异常和运行时异常 RuntimeException,前者通过捕获异常从而获取异常信息, 后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。 系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由 springmvc 前端 控制器交由异常处理器进行异常处理,如下图: 

3.2、实现步骤
3.2.1、编写异常类和错误页面

@RequestMapping("/testException")
    public String testException() throws SysException {
        System.out.println("testException执行了。。。");

        try {
            int a = 10/0;
        } catch (Exception e) {
            //打印异常信息
            e.printStackTrace();
            //抛出自定义异常信息
            throw new SysException("查询所有的用户出现错误。。。");
        }

        return "success";
    }
UserController

3.2.2、自定义异常处理器

public class SysExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
        //获取到异常对象
        SysException e = null;
        if(ex instanceof SysException){
            e = (SysException) ex;
        }else {
            e = new SysException("系统正在维护...");
        }
        //创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        mv.addObject("erroMsg",e.getMessage());
        mv.setViewName("error");
        
        return mv;
    }
}
SysEcceptionResolver

3.2.3、配置异常处理器  --->在springmvc中

<!--配置异常处理器-->
<bean id="sysExceptionResolver" class="cn.itcast.exception.SysExceptionResolver"/>


四、SpringMVC中的拦截器
4.1、拦截器的作用
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
用户可以自己定义一些拦截器来实现特定的功能。
拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺 序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。 
  过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。 
  拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。 
  过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。 
  拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的。
它也是 AOP 思想的具体应用。
  要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。 

4.2、自定义拦截器的步骤

4.2.1、第一步:编写一个普通类实现HandlerInterceptor接口

public class MyInterceptor1 implements HandlerInterceptor{
    
    /**预处理  controller方法执行前
     * return true 放行,执行下一个拦截器,如果没有,执行controller中的方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1执行了...前1111");
        return true;
    }
    
    /**
     * 后处理方法,controller方法执行后,success.jsp执行之前
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor1执行了...后1111");
    }

    /**
     * success.jsp页面执行后,该方法会执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor1执行了...最后1111");
    }
}
MyInterceptor1
public class MyInterceptor2 implements HandlerInterceptor{
    
    /**预处理  controller方法执行前
     * return true 放行,执行下一个拦截器,如果没有,执行controller中的方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor2执行了...前2222");
        return true;
    }
    
    /**
     * 后处理方法,controller方法执行后,success.jsp执行之前
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor2执行了...后222");
    }

    /**
     * success.jsp页面执行后,该方法会执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor2执行了...最后2222");
    }
}
MyInterceptor2

4.2.2、第二步:配置拦截器

<!--配置拦截器-->
  <mvc:interceptors>
    <!--配置拦截器-->
    <mvc:interceptor>
      <!--配置要拦截的具体的方法-->
      <mvc:mapping path="/user/*"/>
      <!--不要拦截的方法-->
      <!--<mvc:exclude-mapping path=""/>-->
      <!--配置拦截器对象-->
      <bean class="cn.itcast.interceptor.MyInterceptor1"></bean>
    </mvc:interceptor>

    <!--配置第二个拦截器-->
    <mvc:interceptor>
      <mvc:mapping path="/**"/>
      <!--<mvc:exclude-mapping path=""/>-->
      <!--配置拦截器对象-->
      <bean class="cn.itcast.interceptor.MyInterceptor2"></bean>
    </mvc:interceptor>
  </mvc:interceptors>

4.2.3、测试运行结果


4.3、拦截器的细节
4.3.1、拦截器放行
放行的含义是指,如果有下一个拦截器就执行下一个,如果该拦截器处于拦截器链的最后一个,则执行控制器中的方法。
只有拦截器中的方法返回true的时候,程序才能继续执行。


4.3.2 拦截器中方法的说明

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}
HandlerInterceptor源码

preHandle方法:
如何调用:   
  按拦截器定义顺序调用   
何时调用:   
  只要配置了都会调用    
有什么用:
  如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回 true。 
  如果程序员决定不需要再调用其他的组件去处理请求,则返回 false。 

postHandle方法:
如何调用:   
  按拦截器定义逆序调用   
何时调用:   
   在拦截器链内所有拦截器返成功调用   
有什么用:   
  在业务处理器处理完请求后,但是 DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求 request 进行处理。 

afterCompletion方法:
如何调用:   
  按拦截器定义逆序调用  
何时调用:   
  只有 preHandle 返回 true 才调用   
有什么用:   
  在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。 

4.3.3、拦截器的作用路径

4.3.4、多个拦截器的执行顺序
  多个拦截器是按照配置的顺序决定的。 

4.4、正常流程测试

4.5、中断流程测试

4.6、拦截器的简单案例

posted @ 2019-01-07 22:15  payn  阅读(418)  评论(0)    收藏  举报