SpringMVC02----响应数据和结果视图、文件上传、异常处理和拦截器

1.响应数据和结果视图

  • SpringMVC中的处理器方法的返回值用来指定页面跳转到哪个视图,处理器的返回值可以为String,void,ModelAndView对象。

1.1返回值为String类型:转发到字符串指定的URL

  • 控制器返回值为 String类型的时候,默认情况是返回逻辑视图名,然后被视图解析器解析为物理视图地址,最终底层通过请求转发将请求转发到对应页面。
  • 实例如下:
  • jsp代码:
<a href="user/testString">返回值为String</a><br>
  • 控制器代码:
    /**
     * 返回值类型为String
     */
    @RequestMapping("/testString")
    public String testString(Model model){
        System.out.println("testString方法执行了");
        User user = new User();
        user.setUsername("美羊羊");
        user.setPassword("789");
        user.setAge(20);
        model.addAttribute("user",user);

        //底层也采用的ModelAndView
        return "success";
    }

1.2返回值为void:转发到当前URL

  • 若处理器返回void,表示执行完处理器方法体内代码后,不进行请求转发,而直接转发到当前URL.若没有在web.xml中配置当前对应的url-pattern,则会返回404错误.
@RequestMapping("/testVoid")
    public void testVoid(Model model) {
		// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
		model.addAttribute("attribute_user", new User("张三", "123"));
        
        // 处理器没有返回值
        return;
    }
  • 上面的处理器方法就没有返回值,则会将请求转发到当前 项目域名/user/testVoid路径,若在web.xml中没有配置 项目域名/user/testVoid 对应的url-pattern,则会返回404错误
  • 如果既想使用 void返回值,同时又想对请求做出响应,比如请求转发或者重定向,那么我们就可以利用 Servlet 提供的原生 API 作为控制器方法的参数
    /**
     * 返回值类型为void
     */
    @RequestMapping("/testvoid")
    public void testvoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("testvoid方法执行了");
        //请求转发
        request.getRequestDispatcher("/WEB-INF/page/success.jsp").forward(request,response);

        //重定向
        //response.sendRedirect(request.getContextPath()+"/index.jsp");

        return;
    }

1.3返回值为 ModelAndView

  • ModelAndView对象是Spring提供的一个对象,可以用来调整具体的JSP视图,此对象的主要方法如下:
    • public ModelMap getModelMap(): 返回当前页面的ModelMap对象.
    • public ModelAndView addObject(Object attributeValue): 向当前页面的ModelMap对象中添加属性
    • public void setViewName(@Nullable String viewName): 指定返回视图,viewName会先被视图解析器处理解析成对应视图.
  • 具体实例如下:
  • jsp代码:
    <a href="user/testModelAndView">返回值为ModelAndView</a><br>
  • 控制器代码:
    /**
     * 返回值为ModelAndView
     */
    @RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        System.out.println("testModelAndView方法执行了");
        //创建ModelAndView对象
        ModelAndView mv = new ModelAndView();

        User user = new User();
        user.setUsername("美羊羊");
        user.setPassword("789");
        user.setAge(20);

        //把user对象存储到mv对象中,它底层也会把user存到request域中
        mv.addObject("user",user);

        //跳转到那个页面,会使用视图解释器
        mv.setViewName("success");
        return mv;
    }

1.4SpringMVC框架提供的请求转发和重定向

  • 要使用SpringMVC框架提供的请求转发,只需要在处理器方法返回的viewName字符串首加上forward:即可,要注意的是,此时forward:后的地址不能直接被视图解析器解析,因此要写完整的相对路径.示例如下:
	@RequestMapping("/testForward")
    public String testForward() {
        return "forward:/WEB-INF/pages/success.jsp";
    }
  • 在forward:要写完整的相对路径
  • 要使用SpringMVC框架提供的请求重定向,只需要在处理器方法返回的viewName字符串首加上redirect:即可,要注意的是,此时redirect:后的地址要写相对于ContextPath的地址.示例如下:
	@RequestMapping("/testRedirct")
    public String testRedirct() {
		return "redirect:/index.jsp";
    }
  • 这里的路径写法不用加项目名称,底层会帮我们加上。

1.5SpringMVC响应json数据

  • 在开发中,很多时候都是客户端提交 json 数据,服务器接收 json 数据并解析,然后生成 json 响应给客户端。这个时候我们就可以使用 @RequestBody@ResponseBody来完成 json 数据的交互。见代码
  • jsp代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Json 数据交互</title>
    <script src="js/jquery-1.8.2.min.js"></script>
    <script type="text/javascript">
        // 页面加载
        $(function () {
            // 绑定单击事件
            $("#btn").click(function () {
                // 发送ajax请求
                $.ajax({
                    url: "${pageContext.request.contextPath}/user/testAjax",
                    // 发送数据的 MIME 类型
                    contentType: "application/json;charset=utf-8",
                    // 发送数据为一个 json 串
                    data: '{"username":"鱼开饭","password":"123456","age":22}',
                    // 返回的数据类型
                    dataType: "json",
                    // 请求方式
                    type: "post",
                    // 成功回调函数
                    success: function (data) {
                        console.log(data);
                        console.log(data.username);
                        console.log(data.password);
                        console.log(data.age);
                    }
                });
            });
        });
    </script>
</head>
<body>
	<button id="btn">测试 ajax 请求 json 和响应 json</button>
</body>
</html>

  • 因为我们配置了前端控制器 DispatcherServlet,因此对于所有的请求都会进行拦截,包括静态资源。为了使引入的jquery.js不被拦截,我们需要在配置文件 springmvc.xml中配置不拦截静态资源
<!-- 设置静态资源不过滤 -->
<mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 -->
<mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 -->
<mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
  • location属性 : 指定 webapp 目录下的包
  • mapping属性 : 表示以 /static 开头的所有请求路径
  • 控制器代码:
/**
 * responsebody 加在方法上或者加在返回值前面都可以
 *
 * @param user 
 */
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user) {
    System.out.println("testAjax方法执行了...");
    System.out.println(user);

    // 模拟查询出新的用户
    user.setPassword("654321");
    user.setAge(10);

    return user;
}

2.SpringMVC实现文件上传

2.1传统的文件上传

  • 要实现文件上传,需要下面这些前提:
  • <form>表单的enctype属性取值必须是multipart/form-data(默认值是application/x-www-form-urlencoded),表示表单内容是分块的.这时request对象的getParameter()方法将失效.
  • <form>表单的method属性取值必须是post,因为get请求长度有限制.
  • 提供一个<input/>标签,用来选择上传文件.
    <h3>传统文件上传</h3>
    <form action="user/fileupload1" method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="upload1"><br>
        <input type="submit" value="上传">
    </form>
  • 引入文件上传的相关jar包:
<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>

  • 传统的JavaEE文件上传思路是通过解析request对象,获取表单中的上传文件项并执行保存.
   /**
     * 文传统件上传
     */
    @RequestMapping("fileupload1")
    public String fileupload1(HttpServletRequest request) throws Exception {
        System.out.println("文件上传");

        //使用fileupload组件完成上传
        //1.首先获取要上传的文件目录
        String path = request.getSession().getServletContext().getRealPath("/uploads");
        System.out.println(path);
        //2.创建file对象,一会向该路径下上传文件
        File file = new File(path);
        //判断路径是否存在,如果不存在就创建该路径
        if (!file.exists()){
            file.mkdirs();
        }
        //3.创建磁盘文件项工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload fileUpload = new ServletFileUpload(factory);
        //4.解析request对象
        List<FileItem> fileItems = fileUpload.parseRequest(request);
        //5.遍历
        for (FileItem fileItem:fileItems){
            //6.判断文件项是普通字段,还是要上传的文件
            if (fileItem.isFormField()){
                //普通字段
            }else {
                //上传文件项
                //7.获取上传文件的名称
                String filename = fileItem.getName();
                //把文件的名称设为唯一值
                String uuid = UUID.randomUUID().toString().replace("-","");
                filename = uuid + "_"  + filename;
                //8.上传文件
                fileItem.write(new File(file,filename));
                //9.删除临时文件
                fileItem.delete();
            }
        }
        return "success";
    }

2.2SpringMVC方式文件上传

  • 使用传统方式进行文件上传,我们需要自己解析 request分辨每一项究竟是普通表单项还是文件项,特别繁琐。而如果使用 SpringMVC,我们只需要配置一个文件解析器,由文件解析器来替我们解析,然后我们在控制器使用MultipartFile对象接收即可(该对象表示要上传的文件)
  • 实例如下:
    <h3>springMVC方式上传</h3>
    <form action="user/fileupload2" method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="upload2"><br>
        <input type="submit" value="上传">
    </form>
   /**
     * SpringMVC文件上传
     */
    @RequestMapping("fileupload2")
    public String fileupload2(HttpServletRequest request, MultipartFile upload2) throws Exception {
        System.out.println("springMVC文件上传");

        //1.首先获取要上传的文件目录
        String path = request.getSession().getServletContext().getRealPath("/uploads");
        System.out.println(path);
        //2.创建file对象,一会向该路径下上传文件
        File file = new File(path);
        //判断路径是否存在,如果不存在就创建该路径
        if (!file.exists()){
            file.mkdirs();
        }

        //3.获取上传文件的名称
        String filename = upload2.getOriginalFilename();
        //把文件的名称设为唯一值
        String uuid = UUID.randomUUID().toString().replace("-","");
        filename = uuid + "_"  + filename;
        //4.上传文件
        upload2.transferTo(new File(path,filename));

        return "success";
    }
  • 使用该方式上传文件时,要确保 MultipartFile对象名称与表单中 file组件的名称保持一致。如果不一致,则需要使用 @RequestParam 进行参数的绑定。譬如表单为:<input type="file" name="file"/>,则控制器方法应该为 test2(HttpServletRequest request, @RequestParam("file") MultipartFile upload)
  • 获取上传文件的文件名时,应该使用 getOriginalFilename(),不能使用 getName()getName()是获取表单中file组件的名称。
    如果要想实现多文件上传,那么需要在 file组件加上属性 multiple="multiple"(HTML5 的新属性),然后控制器用List<MultipartFile>接收即可。
  • 在 SpringMVC 配置文件中配置文件解析器
<!-- 配置文件解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 配置上传文件最大大小,字节(Byte)为单位。这里设置 10MB=10*1024KB=10*1024*1024B -->
    <property name="maxUploadSize" value="10485760"/>
    <!-- 设置编码,防止中文乱码 -->
    <property name="defaultEncoding" value="utf-8"/>
</bean>

2.3SpringMVC 跨服务器方式文件上传

  • 在实际开发中,我们可能会有好几台服务器,有的服务器用于部署应用,而有的服务器用于存储用户上传的文件。那么,此时我们如果需要跨服务器上传文件,就需要借助 jersey-clientjar包。
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
    <version>1.18.1</version>
</dependency>

  • 控制器代码:
    /**
     * 跨服务器文件上传
     */
    @RequestMapping("fileupload3")
    public String fileupload3(MultipartFile upload3) throws Exception {
        System.out.println("跨服务器文件上传文件上传");

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

        //2..获取上传文件的名称
        String filename = upload3.getOriginalFilename();
        //把文件的名称设为唯一值
        String uuid = UUID.randomUUID().toString().replace("-","");
        filename = uuid + "_"  + filename;
        //3.完成上传文件,跨服务器上传
        //3.1创建客户端对象
        Client client = Client.create();
        //3.2和文件服务器进行连接
        WebResource webResource = client.resource(path + filename);
        //3.上传
        webResource.put(upload3.getBytes());
        return "success";
    }
  • 想要测试跨服务器文件上传,需要部署两个 Tomcat 服务器,一个服务器部署应用,一个服务器用于接收文件。这里将存放文件的路径指定为接收文件项目的根目录下的 uploads文件夹。
  • 如果程序报 405 错误,那么应该在文件服务器的 Tomcat 的配置文件 conf\web.xml中加入以下配置,表示允许服务器进行读写操作(默认只读)
<servlet>
	<servlet-name>default</servlet-name>
	<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
	<init-param>
			<param-name>readonly</param-name>
			<param-value>false</param-value>
	</init-param>
</servlet>
  • 如果程序报 409 错误,那么应该在文件服务器的 target目录下创建 uploads文件夹
    在这里插入图片描述

3.SpringMVC 中的异常处理

  • 在开发中,一般我们都是Controller调用 ServiceService调用 Dao,在这个过程中,如果有某个地方出现了异常,那么异常都会被往上一层一层throws Exception(抛出)。Dao抛给 Service, Service抛给 DispatcherServlet,最后由 DispatcherServlet将异常交给 HandlerExceptionResolver(异常处理器)进行处理。
  • 编写自定义异常类,继承 Exception
/**
 * 自定义异常类
 */
public class SysException extends Exception{

    //存储提示信息
    private String message;

    public SysException(String message) {
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
  • 编写错误页面
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>出错啦。。</title>
</head>
<body>
    ${errorMsg}
</body>
</html>
  • 编写自定义异常处理器,需要实现 HandlerExceptionResolver接口
/**
 * 异常处理器
 */
public class SysExceptionResolver implements HandlerExceptionResolver {
    /**
     * 处理异常的业务逻辑
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param e             控制所抛出的exception对象
     * @return
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        //获取到异常对象
        SysException ex = null;
        if (e instanceof SysException){
            ex = (SysException)e;
        }else{
            ex = new SysException("系统正在维护中。。。");
        }
        //创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        //把异常信息存入到request域中
        mv.addObject("errorMsg",ex.getMessage());
        //设置跳转页面
        mv.setViewName("error");
        return mv;
    }
}
  • 在 SpringMVC 配置文件中配置异常处理器
    <!-- 配置异常处理器 -->
    <bean id="sysExceptionResolver" class="com.zut.exception.SysExceptionResolver"></bean>
  • 控制层测试代码:
@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/testException")
    public String testException() throws SysException {
        System.out.println("testException方法执行了。。。");
        //模拟异常
        try {
            int i = 1/0;
        } catch (Exception e) {
            //打印异常信息
            e.printStackTrace();
            //抛出自定义异常
            throw new SysException("查询数据库时出现错误。。。");
        }
        return "success";
    }
}

4.SpringMVC 中的拦截器

4.1拦截器概述

  • SpringMVC 中的 Interceptor(拦截器) 类似于 Servlet 中的 Filter(过滤器),拦截器是用于对 Controller(处理器)进行预处理和后处理的。和过滤器类似,过滤器可以定义过滤链,拦截器也可以定义拦截器链(Interceptor Chain),也就是将拦截器按一定的顺序联结成一条链,在访问被拦截的方法之前,拦截器链中的拦截器就会按之前定义的顺序进行拦截。

  • 过滤器和拦截器虽然比较类似,但是还是有区别的:

    • 过滤器是 Servlet 规范中的,也就是说,只要是 JavaWeb 工程就可以使用;而拦截器是 SpringMVC 框架的,只有使用了 SpringMVC 框架的工程才可以使用
    • 过滤器可以拦截所有的资源,也就是不仅可以拦截 Controller,其它的所有资源(像 jsp、js 这些静态资源)它都可以拦截;而拦截器是针对 Controller的,它只能拦截控制器。
    • 拦截器是 AOP 思想的一种实现方式
  • 如果想自定义拦截器,那么就需要实现 HandlerInterceptor接口,该接口中有 3 个 default方法:

    • boolean preHandle(..): 该方法在 Controller方法执行前调用。如果返回 true 就代表放行,执行下一个拦截器,如果没有拦截器,那就执行Controller的方法;如果返回 false 就代表不放行,此时可以使用转发或者重定向跳转到指定的页面。该方法是按拦截器定义顺序进行调用的。
    • void postHandle(..): 该方法在Controller方法执行后(但是在视图渲染之前)调用。此时我们可以通过 ModelAndView(模型和视图对象)来对模型数据或视图进行处理,也可以通过 HttpServletRequestHttpServletResponse进行转发或者重定向。该方法是按拦截器定义逆序进行调用的。
    • void afterCompletion(..): 该方法在 整个请求处理完毕后调用(也就是在视图渲染完毕后)。 可以在该方法中进行一些资源清理的操作(不能在该方法中进行页面的跳转了),相当于 try...catch...finally 中的 finally。该方法是按拦截器定义逆序进行调用的,而且只有 preHandle()返回 true 才调用。

4.2自定义拦截器

  • 创建自定义拦截器,实现 Interceptor接口并重写其中的方法
/**
 * 自定义拦截器
 */
public class MyInterceptor1 implements HandlerInterceptor {
    /**
     * 预处理  controller方法前执行
     * return true      放行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1执行了...前");
        return true;
    }

    /**
     *后处理   controller方法执行后,success.jsp执行之前
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor1执行了...后");
        return;
    }

    /**
     *success.jsp页面执行后,该方法会执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {

    }

}

  • 在 SpringMVC 配置文件中配置拦截器
    <!-- 配置拦截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- 要拦截的方法 -->
            <mvc:mapping path="/user/**"/>
            <!-- 不要拦截的方法 -->
<!--            <mvc:exclude-mapping path=""/>-->
            <!-- 配置拦截器对象 -->
            <bean id="myInterceptor1" class="com.zut.interceptor.MyInterceptor1"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
  • 编写控制器代码
@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/testInterceptor")
    public String testException() {
        System.out.println("testInterceptor方法执行了。。。");

        return "success";
    }
}
  • 编写jsp代码
<html>
<head>
    <title>拦截器</title>
</head>
<body>

    <h3>拦截器</h3>
    <a href="user/testInterceptor">拦截器</a>
</body>
</html>

4.3多个拦截器

  • 如果有多个拦截器,此时拦截器 1 的 preHandle方法返回 true,但是拦截器 2 的 preHandle方法返回 false,那么拦截器 1 的 afterCompletion的方法是否执行?
    • 因为我们上面说过,afterCompletion方法只有当 preHandle返回 true的时候执行。所以,拦截器 1 的afterCompletion方法会执行,而拦截器 2 的 afterCompletion方法不会执行
  • 如果某个拦截器在 postHandle方法中使用 requestresponse进行转发,那么最终的结果视图(这里是 success.jsp)还会不会执行?
    • 我们上面说过,postHandle方法是在控制器执行后,视图渲染前被调用。所以,当我们转发到某个页面时,浏览器会把这个页面展示出来,但是 postHandle方法还是会继续执行,也就是说 success.jsp还是会被执行到,只不过不会再被渲染展示了
  • 如果某个拦截器在 postHandle方法中不通过 request进行转发,而是使用 ModelAndView.setViewName("error"),那么最终的结果视图(这里是 success.jsp)还会不会执行?
    • 因为ModelAndView中存储的是结果视图,也就是控制器方法的返回值success,此时我们直接调用 setViewName()方法,就相当于修改了控制器方法的返回值,所以最终只会跳转到 error.jsp而不是 success.jsp
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值