springMVC框架学习安排

第二天

1. 响应数据和结果视图

1. 响应返回值分类

1. 响应String字符串
    @RequestMapping("testString")
    public String testString(Model model){
   
        User user = new User();
        user.setUname("liuzeyu");
        user.setAge(100);

        model.addAttribute("user",user);

        return "success";
    }
<%@page contentType="text/html; charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>入门成功</title>
</head>
<body>

<h2>执行成功</h2>

${
   requestScope.user.uname}<br>
${
   requestScope.user.age}

</body>
</html>

2. 响应void
<a href="user/testVoid">测试返回void</a>
    @RequestMapping("testVoid")
    public void testVoid() throws Exception {
   
        System.out.println("testVoid......");
    }

如果以上面的形式返回,则将报错404

出现此问题是无返回值时,springMVC默认把请求的路径当作转发的路径使用了,解决的办法有两个:

  1. 转发:
    @RequestMapping("testVoid")
    public void testVoid(HttpServletRequest request, HttpServletResponse response) throws Exception {
   
        System.out.println("testVoid......");
        request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
    }

回顾请求转发的特点:

1. 请求只发送一次
2. 浏览器地址栏路径不会改变
3. 只能转发服务内部的资源,这里是webapp下的资源,必须由 / 开头
  1. 重定向:
    @RequestMapping("testVoid")
    public void testVoid(HttpServletRequest request, HttpServletResponse response) throws Exception {
   
        System.out.println("testVoid......");
        response.sendRedirect(request.getContextPath()+"/index.jsp");
    }


回顾重定向特点:

1.浏览器地址栏会发送变化,如上图
2.可以重定向到服务器外部资源
3.重定向会发送两个请求,成功的话对应的状态码分别是302200
3. 响应ModelAndView
<a href="user/testModelAndView">测试返回ModelAndView</a>
@RequestMapping("testModelAndView")
public ModelAndView testModelAndView(){
   
    System.out.println("testModelAndView......");
    ModelAndView mv = new ModelAndView();
    User user = new User();
    user.setUname("liuzeyu");
    user.setAge(100);

    mv.addObject("user",user);
    mv.setViewName("success");  //设置转发视图
    return mv;
}
4. 使用forward和redirect进行页面响应跳转
<a href="user/testForwardAndRedirect">测试返回testForwardAndRedirect</a>
    @RequestMapping("testForwardAndRedirect")
    public String testForwardAndRedirect(){
   

        //return "forward:/WEB-INF/pages/success.jsp";
        return "redirect:/index.jsp";
        //return "redirect:/WEB-INF/pages/success.jsp"; //不能访问WEB-INF下的web文件
    }
5. 为什么redirect重定向不能访问WEB-INF目录下的内容?

答案还得从WEB-INF这个目录的作用说起

WEB-INF目录是Java的Web应用安全目录,客户端是无法访问的,只有服务器能访问。将一些页面放在这个目录下可以限制外部访问,提高安全性,如一些jsp,html页面。

原因:
既然是安全目录,客户端无法访问,那重定向当然是无法访问到的,因为重定向的特点是客户端没有直接请求服务器资源,而是二次请求服务器资源,因此是属于直接访问内部资源的,这是不被允许的。
然而请求转发是请求服务器去转发访问资源,因此服务器是可以访问资源的。

6. 响应json数据
  1. 加载jQuery文件(也不明白为什么本地会加载失败)
    <script src="http://libs.baidu.com/jquery/1.9.1/jquery.js"></script>
  1. 添加按钮和点击事件
<script>

    $(function () {
   
       $("#bt").click(function () {
   
           alert("按钮被点击了....")
           $.ajax({
   
               url:"user/testAjax",
               contentType:"application/json;charset=UTF-8",
               type:"post",
               data:'{"uname":"liuzeyu","age":"66"}',
               success:function (data) {
   
                   //接收服务器响应的数据
               }
           });
       });
    });
</script>
<button id="bt"> 发送异步请求</button>
  1. controller层接收
    @RequestMapping("testAjax")
    public void testAjax(@RequestBody String body){
   
        System.out.println(body);
    }

  1. controller层响应
    @RequestMapping("testAjax")
    public @ResponseBody User testAjax(@RequestBody User user){
   
        user.setAge(9999);   //修改年龄为9999
        System.out.println(user);
        return user;
    }
  1. 前端渲染
success:function (data) {
   
                   //接收服务器响应的数据
                   alert(data);
                   alert(data.uname);  //liuzeyu
                   alert(data.age);   //999
               }

2. 文件上传

1. 传统的文件上传方式

1. 文件上传的必要前提
A form 表单的 enctype 取值必须是:multipart/form-data
(默认值是:application/x-www-form-urlencoded)
enctype:是表单请求正文的类型
B method 属性取值必须是 Post(get的参数浏览器栏装不下)
C 提供一个文件选择域<input type=”file” />
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--
3. 借助第三方组件实现文件上传

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

<!--导入传统形式上传文件依赖的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>
4. 实现步骤
  1. 编写JSP页面
<h2>文件上传</h2>
<form action="user/testUpload1" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" value="文件" name="upload">
    <input type="submit" value="上传">
</form>
  1. controller层
@RequestMapping("/user")
@Controller
public class UserController {
   

    @RequestMapping("testUpload1")
    public String testUpload(HttpServletRequest request) throws Exception {
   
        System.out.println("testUpload1...");
        //使用fileupload组件完成文件上传
        //上传位置
        String realPath = request.getSession().getServletContext().getRealPath("/uploads/");
        //判断该路径是否存在
        File file = new File(realPath);
        if(!file.exists())
        {
   
            //创建文件夹
            file.mkdirs();
        }
        //解析request对象,获取上传文件选中
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        //解析request
        List<FileItem> fileItems = upload.parseRequest(request);
        //遍历
        for (FileItem item : fileItems) {
   
            //进行判断,当前的item是否是上传文件项
            if(item.isFormField()){
   
                //表示普通的表单项
            }else{
   
                //上传文件项
                //获取上传文件名称
                String name = item.getName();
                //把名字id设置成唯一值
                String uuid = UUID.randomUUID().toString().replace("-", "");
                name = uuid+ "_"+name;
                //完成文件上传
                item.write(new File(realPath,name));
                //删除临时文件
                item.delete();
            }
        }
        return "success";
    }
}

上传成功!

2. 使用springMVC文件上传方式

  1. 分析

  2. 搭建环境

<h2>文件上传2</h2>
<form action="user/testUpload2" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" value="文件" name="upload">
    <input type="submit" value="上传">
</form>
  @RequestMapping("testUpload2")
  public String testUpload2(HttpServletRequest request, MultipartFile upload) {
   
      return null;
  }
  1. 配置文件解析器
 <!--配置文件上传解析器-->
 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
     <!--设置上传文件的最小尺寸为5m-->
     <property name="maxUploadSize" value="5242880"></property>
 </bean>

  1. controller层代码
    @RequestMapping("testUpload2")
    public String testUpload2(HttpServletRequest request, MultipartFile upload) throws IOException {
   
        System.out.println("testUpload1...");
        //使用fileupload组件完成文件上传
        //上传位置
        String realPath = request.getSession().getServletContext().getRealPath("/uploads/");
        //判断该路径是否存在
        File file = new File(realPath);
        if(!file.exists())
        {
   
            //创建文件夹
            file.mkdirs();
        }

        //上传文件项
        //获取上传文件名称
        String name = upload.getOriginalFilename();
        //把名字id设置成唯一值
        String uuid = UUID.randomUUID().toString().replace("-", "");
        name = uuid+ "_"+name;
        //完成文件上传
        upload.transferTo(new File(realPath,name));
        return "success";
    }

其中很重要的一步:解析request对象获取文件上传项,交给了springMVC来做。

3. springMVC跨服务器文件上传

  1. 搭建omcat图片服务器,要修改HTTP端口号

  2. 实现springMVC跨服务器文件上传

     /** * 跨服务器器文件上传 * @param * @param upload * @return * @throws IOException */
        @RequestMapping("testUpload3")
        public String testUpload3(MultipartFile upload) throws IOException {
         
    
            System.out.println("testUpload3 跨服务器上传");
            String path = "http://localhost:9090/uploads/"; //图片服务器路径,需要手动创建一个/uplodas目录
            //上传文件项
            //获取上传文件名称
            String name = upload.getOriginalFilename();
            //把名字id设置成唯一值
            String uuid = UUID.randomUUID().toString().replace("-", "");
            name = uuid+ "_"+name;
            //创建客户端对象
            Client client = Client.create();
            //和图片服务器进行连接
            WebResource resource = client.resource(path + name);
    
            //上传文件
            resource.put(upload.getBytes());
    
            return "success";
        }
    

3. springMVC处理异常

存在的异常问题:

如果在dao层出现了异常,会抛向service层,再抛向…前端控制器。如果前端控制器再往浏览器抛,则用户一般看不懂,这显然对用于不友好,于是我们应该在前端控制处对异常进行处理,这个任务交给异常处理器,最后将处理好的异常发送给前端浏览器。

  1. jsp页面
<a href="user/testException">测试异常</a>
  1. controller层代码
@RequestMapping("/user")
@Controller
public class UserController {
   

    @RequestMapping("testException")
    public String testException() throws SysException {
   
        System.out.println("发生了异常....");

        try {
   
            int i = 1/0;
        } catch (Exception e) {
   
            e.printStackTrace();
            //抛出自定义的异常
            throw  new SysException("查询所有用户失败...");
        }
        return "success";
    }
}

  1. spring配置文件
<!--配置创建springIOC容器时要扫描的包-->
    <context:component-scan base-package="com.liuzeyu"></context:component-scan>

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

    <!--配置spring开启注解mvc的支持-->
    <mvc:annotation-driven></mvc:annotation-driven>
 <!--配置spring开启异常处理器的支持-->
    <bean id="sysException" class="com.liuzeyu.exception.SysExceptionResolver"></bean>
  1. web.xml
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <!--配置servlet-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <!--servlet启动后需要加载的spring配置文件-->
    <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>

  <!--处理post请求的中文乱码问题-->
  <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>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern> </filter-mapping> </web-app> 
  1. 自定义异常类
/** * Created by liuzeyu on 2020/4/29. * 自定义异常类 */
public class SysException extends Exception {
   

    private String message;

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

    public void setMessage(String message) {
   
        this.message = message;
    }

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

  1. 异常处理器
/** * Created by liuzeyu on 2020/4/29. * 异常类处理器 */
public class SysExceptionResolver implements HandlerExceptionResolver {
   
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                         HttpServletResponse httpServletResponse,
                                         Object o, Exception e) {
   
        SysException sysException = null;
        if(e instanceof SysException){
   
            sysException = (SysException)e;
        }else{
   
            sysException = new SysException("系统正在维护");
        }
        //创建ModleAndView对象
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg",sysException.getMessage());
        mv.setViewName("error");
        return mv;
    }
}

4. springMVC拦截功能

1. 拦截器的作用

springMVC的拦截器类似于servlet的Filter,用于对处理器进行预处理和后处理,用户可以自定义拦截器来实现特定的功能。
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
<mark>拦截器和过滤器的区别:</mark>

  1. 过滤器是servlet的规范中的一部分,任何web工程都可以使用,包括springMVC工程
  2. 拦截器是springMVC框架自身的,只有springMVC框架能使用
  3. 过滤器在配置了 /* 后,可以对访问的资源进行拦截
  4. 拦截器只会拦截controller里面的方法,如果要访问某某jsp,html,或者js资源,拦截器是不会进行拦截的,它也是AOP思想的应用。
    如果需要自定义拦截器,需要实现接口HandlerInterceptor接口

2. 拦截器的入门使用

  1. 自定义拦截器
public class MyInterceptor implements HandlerInterceptor{
   

/**预处理方法: preHandle:在controller方法执行执行的拦截器方法 返回值: true:表示可以放行以下要执行的controller方法 false:表示不能执行以行的controller方法 */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
        System.out.println("MyInterceptor....");
        return true;
    }
}
  1. 配置拦截器
    <mvc:interceptors>
        <mvc:interceptor>
            <!--拦截路径-->
            <mvc:mapping path="/user/*"/>
            <!--不拦截路径-->
            <!--<mvc:exclude-mapping path=""/>-->
            <!--配置拦截器对象-->
            <bean class="com.liuzeyu.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
  1. controller方法
    @RequestMapping("testInterceptor")
   public String  testInterceptor(){
   
        System.out.println("测试拦截器...");
        return "success";
   }

测试结果:

3. 拦截器的其它两个方法

  1. postHandle
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
        System.out.println("MyInterceptor....preHandle");
        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("MyInterceptor....postHandle");
        request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
    }

结果:

error.jsp

:拦截器处理转发后不会再去跳转controller里面的任何页面跳转。

  1. afterCompletion
/** * Created by liuzeyu on 2020/4/29. */
public class MyInterceptor implements HandlerInterceptor{
   

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
        System.out.println("MyInterceptor....preHandle");
        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("MyInterceptor....postHandle");
        request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
    }


    /** * 最后执行方法:通常用于是否资源,在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("MyInterceptor....afterCompletion");
    }
}

测试结果:

4. 拦截链执行顺序

  1. 配置拦截器2
/** * Created by liuzeyu on 2020/4/29. */
public class MyInterceptor2 implements HandlerInterceptor{
   

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
        System.out.println("MyInterceptor22222....preHandle");
        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("MyInterceptor2222....postHandle");
        //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
    }


    /** * 最后执行方法:通常用于是否资源,在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("MyInterceptor2222....afterCompletion");
    }
}

  1. 配置文件
 <mvc:interceptors>
        <mvc:interceptor>
            <!--拦截路径-->
            <mvc:mapping path="/user/*"/>
            <!--不拦截路径-->
            <!--<mvc:exclude-mapping path=""/>-->
            <!--配置拦截器对象-->
            <bean class="com.liuzeyu.interceptor.MyInterceptor"/>
        </mvc:interceptor>
        <!--再配置一个拦截器-->
        <mvc:interceptor>
            <!--拦截路径-->
            <mvc:mapping path="/**/"/>
            <!--不拦截路径-->
            <!--<mvc:exclude-mapping path=""/>-->
            <!--配置拦截器对象-->
            <bean class="com.liuzeyu.interceptor.MyInterceptor2"/>
        </mvc:interceptor>
    </mvc:interceptors>

  1. 执行顺序分析

    结果:

可见执行顺序如上,符合拦截链的执行流程。