Spring MVC

Spring MVC

目录

一、Spring MVC概述

1. Spring MVC特点

  • Spring MVC是一种实现MVC设计模式的轻量级Web框架

    MVC模式中,Web层主要利用Controller实现调度分发View实现展示内容,在Service层和Dao层中主要利用Model来处理业务逻辑,封装实体

    调度分发主要包括:接收请求、调用模型、转发到视图

  • 支持RESTful编程风格的请求

2. Spring MVC优势

  • 无需实现任何接口,通过一套注解就可以让一个Java类成为处理请求的控制器

  • 在Tomcat服务器上的Web应用中Spring MVC封装了原来Servlet中的共有行为(参数封装、视图转发),相比于直接使用一个BaseServlet来抽取这些共有行为,使用Spring MVC开发效率更高

    开发过程中使用前端控制器来处理这些共有行为,特有行为(如Cookie的设置和表单的校验)由其他的Servlet应用类或自定义类来完成

3. Spring MVC使用方式

  • Web工程执行流程

    调用目标资源就是将请求对象交给前端控制器进行处理,执行Controller完成后将目标地址返回值返回给前端控制器,前端控制器会将该地址返回给Tomcat引擎

    Spring MVC

  • Spring MVC使用步骤:

    1. 在web.xml中配置前端控制器DispatcherServlet

      前端控制器本质是一个Servlet,url-pattern设定为/是为了当收到请求后匹配到所有的访问路径(会匹配不带文件扩展名的所有访问路径),前端控制器收到请求后会对这种地址进行拦截,然后解析映射地址找到对应的处理器(执行@RequestMapping对应的业务方法)

      根据Servlet的生命周期,前端控制器同样会在第一次收到请求后对配置的类进行实例化和加载配置文件,需要在应用启动时就加载Spring MVC配置文件,就要配置load-on-startup

      <?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">
      
          <!--前端控制器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:spring-mvc.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>
      
    2. 编写Controller类和视图页面,使用注解配置Controller类中业务方法的映射地址

      @Controller用于生成该类的实例,需要使用实例来调用业务方法

      页面跳转的实现主要是请求转发,所以跳转后地址栏URL不发生改变

      @Controller
      @RequestMapping("/user")
      public class UserController {
          @RequestMapping("/quick")
          public String quick() {
              // 业务逻辑
              System.out.println("success to access springmvc!");
              // 页面跳转
              return "/WEB-INF/pages/success.jsp";
          }
      }
      
    3. 配置Spring MVC核心文件spring-mvc.xml

      添加web层的注解扫描,可以将Controller类生成实例放入web层的IOC容器中

      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:mvc="http://www.springframework.org/schema/mvc"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/mvc
             http://www.springframework.org/schema/mvc/spring-mvc.xsd
             http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context.xsd">
      
          <!--配置注解扫描-->
          <context:component-scan base-package="com.example.controller"/>
      </beans>
      

二、Spring MVC组件

1. Spring MVC组件概述

  • DispatcherServlet 前端控制器(Spring MVC主要组件)

    • 作用:DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求

      DispatcherServlet的存在降低了组件之间的耦合性

  • HandlerMapping 处理器映射器(Spring MVC三大组件之一)

    • 作用:HandlerMapping负责根据用户请求找到Handler,生成HandlerExecutionChain对象

      Spring MVC提供了不同的映射器 实现不同的映射方式

      例如:配置文件方式,实现接口方式,注解方式等

  • HandlerAdapter 处理器适配器(Spring MVC三大组件之一)

    • 作用:通过HandlerAdapter对handler进行执行

      适配器模式通过扩展适配器可以对更多类型的处理器进行执行

    • spring-mvc.xml中显式配置HandlerMapping和HandlerAdapter

      <!--处理器映射器-处理器适配器-->
      <!--进行功能增强:支持JSON读写-->
      <mvc:annotation-driven></mvc:annotation-driven>
      
  • Handler 处理器

    • 作用:由Handler对具体的用户请求进行处理
  • ViewResolver 视图解析器(Spring MVC三大组件之一)

    • 作用:ViewResolver负责将处理结果ModelAndView对象生成View视图

      ViewResolver首先根据逻辑视图名(业务方法中返回的文件名)解析成物理视图名(拼接前缀和后缀生成具体的页面地址),最后对View进行渲染将处理结果通过页面展示给用户

    • spring-mvc.xml中显式配置ViewResolver

      <!--视图解析器 ViewResolver-->
      <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>
      
  • View 视图

    • 作用:一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面

      Spring MVC提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。常用的视图就是jsp

2. Spring MVC执行流程

Spring MVC

  1. DispatcherServlet接收浏览器的请求

  2. DispatcherServlet调用HandlerMapping

    HandlerMapping根据注解或XML配置文件找到具体的Handler,将HandlerExecutionChain对象返回给DispatcherServlet

  3. DispatcherServlet调用HandlerAdapter

    HandlerAdapter经过适配会调用具体的Controller(后端控制器),Controller执行完成后HandlerAdapter会将结果ModelAndView对象返回给DispatcherServlet

  4. DispatcherServlet调用ViewReslover(同时将ModelAndView对象传入)

    ViewReslover解析后返回具体View对象给DispatcherServlet

  5. DispatcherServlet根据View对象进行渲染视图,并将渲染后的视图响应给用户

3. Spring MVC核心注解

  • @Controller

    作用:Spring MVC基于Spring容器,所以在进行Spring MVC操作时,需要将Controller存储到Spring容器中

    使用方式:添加在Controller类上

    使用该注解需要向Spring MVC配置文件中添加注解扫描,包名指定到controller下;

    Service层和Dao层的注解扫描需要单独添加到Spring配置文件下

    <!--spring-mvc.xml中配置注解扫描-->
    <context:component-scan base-package="com.example.controller"/>
    
    <!--applicationContext中配置注解扫描-->
    <context:component-scan base-package="com.example.service"/>
    <context:component-scan base-package="com.example.dao"/>
    
  • @RequestMapping

    作用:用于建立请求URL和处理请求方法之间的对应关系

    使用方式:添加在方法上或添加在Controller类上

    @Controller
    @RequestMapping("/user") //一级访问目录
    public class UserController {
        // http://localhost:8080/springmvc_quickstart/user/quick    /一级访问目录/二级访问目录
        /*
            path :作用等同于value,同样是设置方法的映射地址
            method:用来限定请求的方式 RequestMethod.POST:只能以post的请求方式访问该访问,其他请求方式会报错
            params:用来限定请求参数的条件 params={"accountName"} 表示请求参数中必须有accountName
         */
        @RequestMapping(path = "/quick",method = RequestMethod.GET,params = {"accountName"}) // 二级访问目录
        public String quick(Integer id){
            // 业务逻辑
            System.out.println("springmvc入门成功.....");
            // 视图跳转 逻辑视图名
            return "success";
        }
    }
    

三、Spring MVC请求

1. 通过自动映射匹配获取请求参数

  • 获取基本类型请求参数

    • Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配

    • 参数值会进行自动类型转换,自动的类型转换是指从String向其他类型的转换(如String转到Integer)

    • 示例Code

      <body>
          <a href="${pageContext.request.contextPath}/user/simpleParam?id=1&username=Jack">
              基本类型的请求
          </a>
      </body>
      
      // 业务方法中利用参数列表获取请求
      @RequestMapping("/simpleParam")
      public String simpleParam(Integer id, String username) {
          System.out.println(id);
          System.out.println(username);
          return "success";
      }
      
  • 获取对象类型请求参数

    • Controller中的业务方法参数的POJO属性名与请求参数的name一致,参数值会自动映射匹配

    • Tomcat8.5以上版本已经解决了GET请求的中文乱码问题,POST请求中文乱码需要手动添加过滤器解决

      <!--web.xml中配置全局过滤的filter-->
      <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>
      
    • 示例Code

      <form action="${pageContext.request.contextPath}/user/pojoParam" method="post">
          编号:<input type="text" name="id"><br>
          用户名:<input type="text" name="username"><br>
          <input type="submit" name="对象类型参数">
      </form>
      
      public class User { 
          Integer id; 
          String username; 
          // setter getter... 
      }
      
      @RequestMapping("/pojoParam")
      public String pojoParam(User user) {
          System.out.println(user);
          return "success";
      }
      
  • 获取数组类型请求参数

    • 使用场景:表单标签中type属性为checkbox,name属性相同而value属性不同,可以在业务方法参数列表中将参数设置为与name属性同名的数组(类型为checkbox的value属性类型),参数值会自动映射匹配

    • 示例Code

      <form action="${pageContext.request.contextPath}/user/arrayParam" method="post">
          编号:<input type="checkbox" name="ids" value="1">1<br>
          编号:<input type="checkbox" name="ids" value="2">2<br>
          编号:<input type="checkbox" name="ids" value="3">3<br>
          编号:<input type="checkbox" name="ids" value="4">4<br>
          <input type="submit" value="数组类型参数">
      </form>
      
      @RequestMapping("/arrayParam")
      public String arrayParam(Integer[] ids) {
          System.out.println(ids);
          System.out.println(Arrays.toString(ids));
          return "success";
      }
      
  • 获取集合类型请求参数

    • Controller中的业务方法参数的POJO属性名与请求参数的name一致,参数值会自动映射匹配

    • 示例Code

      <form action="${pageContext.request.contextPath}/user/queryParam" method="post">
          搜索关键字:<input type="text" name="keyword"><br>
      
          user对象:<input type="text" name="user.id" placeholder="编号"><br>
          user对象:<input type="text" name="user.username" placeholder="姓名"><br>
      
          list集合:<input type="text" name="userList[0].id" placeholder="编号"><br>
          list集合:<input type="text" name="userList[0].username" placeholder="姓名"><br>
          list集合:<input type="text" name="userList[1].id" placeholder="编号"><br>
          list集合:<input type="text" name="userList[1].username" placeholder="姓名"><br>
      
          Map集合:<input type="text" name="userMap['u1'].id" placeholder="编号"><br>
          Map集合:<input type="text" name="userMap['u1'].username" placeholder="姓名"><br>
          Map集合:<input type="text" name="userMap['u2'].id" placeholder="编号"><br>
          Map集合:<input type="text" name="userMap['u2'].username" placeholder="姓名"><br>
      
          <input type="submit" value="复杂类型">
      </form>
      
      public class QueryVo {
          private String keyword;
          private User user;
          private List<User> userList;
          private Map<String, User> userMap;
          
          // setter getter... 
      }
      
      @RequestMapping("/queryParam")
      public String queryParam(QueryVo queryVo) {
          System.out.println(queryVo);
          return "success";
      }
      

2. 自定义类型转换器

  • Spring MVC默认已经提供了一些常用的类型转换器

    例如:客户端提交的字符串转换成int型进行参数设置,日期格式类型要求为:yyyy/MM/dd否则会报错

  • 对于特定的格式的日期,需要使用Spring MVC提供的自定义类型转换器进行自定义处理

  • 实现步骤:

    1. 自定义类实现Converter接口,重写convert方法(将传入参数日期字符串转换成日期对象进行返回)
    2. 将自定义类注入ConversionServiceFactoryBean

3. 请求相关注解

  • @RequestParam

    • 使用场景:当请求的参数name名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显示的绑定

    • 示例Code

      <a href="${pageContext.request.contextPath}/user/findByPage?pageNo=2">
          分页查询
      </a>
      
      /*  @RequestParam() 注解 
      	defaultValue 设置参数默认值 
      	name 匹配页面传递参数的名称 
      	required 设置是否必须传递参数,默认值为true;如果设置了默认值,值自动改为false */
      @RequestMapping("/findByPage")
      public String findByPage(@RequestParam(name = "pageNo", defaultValue = "1", required = false) Integer pageNum, @RequestParam(defaultValue = "5") Integer pageSize) {
          System.out.println(pageNum);//2
          System.out.println(pageSize);//5
          return "success";
      }
      
  • @RequestHeader:

    • 使用场景:获取请求头中的数据

    • 示例Code

      // 为方法参数添加注解后,会将获取到的cookie作为实参赋值给方法形参
      @RequestMapping("/requestHead")
      public String requestHead(@RequestHeader("cookie") String cookie) {
          System.out.println(cookie);//JSESSIONID=8AF91A50907DADF7F4CD6721F0EE8CDA
          return "success";
      }
      
  • @CookieValue:

    • 使用场景:获取Cookie中的数据

    • 示例Code

      @RequestMapping("/cookieValue")
      public String cookieValue(@CookieValue("JSESSIONID") String jSessionId) {
          System.out.println(jSessionId);// B3BB44738FA51BC44C2EBDA334661C95
          return "success";
      }
      

4. 方法中获取Servlet API

  • Spring MVC支持使用原始Servlet API对象作为控制器方法的参数进行注入

  • 示例Code

    @RequestMapping("/servletAPI")
    public String servletAPI(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
        System.out.println(request);
        System.out.println(response);
        System.out.println(session);
        return "success";
    }
    

四、Spring MVC响应

1. 响应分类

  • 页面跳转的响应
    1. 业务方法中返回视图字符串
    2. 返回值为void的业务方法中使用原始Servlet API
    3. ModelAndView
  • 不进行跳转,向页面返回数据的响应
    1. 直接返回字符串数据,渲染数据到当前的路径下的页面上

      // 设置响应字符集,处理中文乱码
      response.setContentType("text/html;charset=utf-8");
      // 通过response直接响应数据
      response.getWriter().write("test");
      
    2. 将对象或集合转为JSON返回

2. 返回视图字符串进行页面跳转

  • 当返回字符串中带有forward或是redirect关键字,不会再走视图解析器进行前后缀的拼接,底层由Servlet API进行实现

    request.getRequestDispatcher("xxx").forward(request, response);
    response.sendRedirect(request.getContextPath() + "xxx");
    
  • 转发和重定向中需要向模型中设置属性值时,可以通过向Controller中的业务方法参数列表中添加Model对象参数,Spring MVC就会将该对象注入

    addAttribute方法底层使用了request.setAttribute("xxx", "xxx");

    转发后可以在在JSP上获取属性值(EL或方法调用),重定向后无法在JSP上获取属性值

    因为重定向过程中会将前面Request对象销毁,然后创建一个新的Request对象(两次请求),而Request作用范围只是一次请求

    model.addAttribute("xxx", "xxx");//作用范围:一次请求
    
  • 请求转发

    • 开发中一般使用返回逻辑视图字符串实现页面的跳转,该方式本质就是请求转发(一次请求地址栏不改变)

      逻辑视图:只有文件名,利用视图解析器的配置拼接前后缀以完成完整的路径(拼接后的就是实际视图URL)

      实际/物理视图:带WEB-INF的文件路径

    • 请求转发还可以使用返回forward:实际视图URL字符串

      // forward:转发相当于使用该方式进行请求转发
      request.getRequestDispatcher("url").forward(request,response)
      
      @RequestMapping("/forward")
      public String forward(Model model) {
          model.addAttribute("username", "Jack");
          return "forward:/WEB-INF/pages/success.jsp";
      }
      
    • 使用请求转发,既可以转发到JSP,也可以转发到其他的控制器方法

      @RequestMapping("/forward")
      public String forward(Model model) {
          model.addAttribute("username", "Jack");
          return "forward:/product/findAll";
      }
      
  • 重定向

    • 页面跳转后浏览器中的路径上会将Model中的数据拼接到URL地址上,但是无法在JSP上获取属性值

      Spring MVC

      redirect:后可以不添加工程路径,Spring MVC框架会自动拼接ContextPath(也叫工程路径)

      Spring MVC

    • 示例Code

      @RequestMapping("/redirect")
      public String redirect(Model model) {
          model.addAttribute("username", "Jeffrey");
          return "redirect:/index.jsp";
      }
      

3. 利用Servlet API进行页面跳转

  • 通过注入参数request、response对象到返回值为void的业务方法实现响应

    Spring MVC

  • 示例Code

    重定向过程中会将前面Request对象销毁,然后创建一个新的Request对象(两次使用不同的请求对象)

    /WEB-INF是安全目录,不允许外部请求(新的Request对象)直接访问该目录的资源,只可以进行服务器内部转发

    @RequestMapping("/returnVoid")
    public void returnVoid(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        // 1.通过response直接响应数据
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("test");
    
        // 2.设定请求属性
        request.setAttribute("username", "Lucy");
    
        // 3.通过request对象实现转发
        request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
    
        // 4.通过response对象实现重定向
        //response.sendRedirect(request.getContextPath() + "/index.jsp");
        
        // 5.HTTP Status 404 – Not Found
        //response.sendRedirect(request.getContextPath() + "/WEB-INF/pages/success.jsp");
    }
    

4. 利用ModelAndView进行页面跳转

  • 使用方式:在Controller中方法设定ModelAndView属性值,完成后返回ModelAndView对象

  • 运行流程:将ModelAndView对像设定viewName属性值(逻辑视图),将ModelAndView对象交给视图解析器解析成物理视图名(拼接前缀和后缀生成具体的页面地址),最后对View渲染将处理结果通过页面展示给用户

  • 示例Code

    使用ModelAndView对象的两种方式:生成局部变量、作为方法参数注入

    @RequestMapping("/returnModelAndView")
    public ModelAndView returnModelAndView() {
        ModelAndView modelAndView = new ModelAndView();
        // 设置模型数据(attributeName, attributeValue)
        modelAndView.addObject("username", "modelAndView-attributeValue");
        // 设置逻辑视图名称
        modelAndView.setViewName("success");
    
        return modelAndView;
    }
    
    @RequestMapping("/returnModelAndView2")
    public ModelAndView returnModelAndView(ModelAndView modelAndView) {
        // 设置模型数据(attributeName, attributeValue)
        modelAndView.addObject("username", "modelAndView-attributeValue2");
        // 设置逻辑视图名称
        modelAndView.setViewName("success");
    
        return modelAndView;
    }
    

5. 多个请求中实现共享数据

  • 问题:连续发送以下两个请求,attribute数据只在第一次请求时显示,说明一次Request无法共享数据

    @RequestMapping("/forward")
    public String forward(Model model) {
        model.addAttribute("username", "Jack");
        return "forward:/WEB-INF/pages/success.jsp";
    }
    
    @RequestMapping("/returnString")
    public String returnString() {
        return "success";
    }
    
  • 解决:如果在多个请求之间共用数据,则可以在控制器类上标注一个@SessionAttributes, 配置需要在

    session中存放的数据范围,Spring MVC将存放在model中对应的数据暂存到HttpSession中

    @SessionAttributes只能定义在类上

    @Controller
    @RequestMapping("/user")
    @SessionAttributes("username")//向request域中(model)存入key为username时,会同步到session中
    public class UserController {
    }
    

五、Spring MVC加载静态资源

  • 问题:当需要加载静态资源(如jQuery的.js文件)时,Spring MVC前端控制器DispatcherServlet的url-pattern配置的是/,代表对所有的资源进行处理(在Controller中匹配指定映射路径),这样就不会执行Tomcat内置的DefaultServlet处理静态资源,所以无法正常加载项目目录下的jQuery的.js文件

  • 解决:

    • 方式一:放行指定目录下的静态资源

      mapping:放行的映射路径,location:静态资源所在的目录

      <!--在springmvc配置文件中指定放行资源--> 
      <mvc:resources mapping="/js/**" location="/js/"/> 
      <mvc:resources mapping="/css/**" location="/css/"/> 
      <mvc:resources mapping="/img/**" location="/img/"/>
      
    • 方式二:在Spring MVC配置文件中开启DefaultServlet处理静态资源(放行所有的静态资源)

      <!--在springmvc配置文件中开启DefaultServlet处理静态资源--> 
      <mvc:default-servlet-handler/>
      

六、Ajax异步交互

  • 需要环境:配置jackson依赖,并且配置<mvc:annotation-driven/>支持json的读写

  • 实现步骤:

    1. 当使用Ajax提交并指定contentType为json形式时,通过HttpMessageConverter接口转换为对应的POJO对象

      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <html>
      <head>
          <title>Title</title>
      </head>
      <body>
          <%--引入jquery时避免使用自闭和标签--%>
          <script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
          <button id="btn1">ajax</button>
      
          <script>
              $("#btn1").click(function () {
                  let url = '${pageContext.request.contextPath}/user/ajaxRequest';
                  let data = '[{"id":1, "username":"jack"}, {"id":2, "username":"jeffrey"}]';
      
                  $.ajax({
                      type: 'POST',
                      url: url,
                      data: data,
                      contentType: 'application/json;charset=utf-8',
                      success: function (resp) {
                          alert(JSON.stringify(resp));
                      }
                  })
              })
          </script>
      </body>
      </html>
      
    2. 通过为Controller方法的形参前添加@RequestBody可以将JSON格式字符串转换为POJO对象

      在方法上添加@ResponseBody改变响应格式可以将POJO对象/集合转换为JSON格式数据

      @RequestMapping("/ajaxRequest")
      @ResponseBody
      public List<User> ajaxRequest(@RequestBody List<User> list) {
          System.out.println(list);
          return list;
      }
      

七、RESTful风格

  • RESTful风格多用于前后端分离项目开发,前端通过Ajax与服务器进行异步交互,处理器通常返回的是JSON数据

  • 示例Code

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <%--引入jquery时避免使用自闭和标签--%>
        <script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
        <button id="btn1">ajax</button>
    
        <script>
            $("#btn1").click(function () {
                let url = '${pageContext.request.contextPath}/user/ajaxRequest';
                let data = '[{"id":1, "username":"jack"}, {"id":2, "username":"jeffrey"}]';
    
                $.ajax({
                    type: 'POST',
                    url: url,
                    data: data,
                    contentType: 'application/json;charset=utf-8',
                    success: function (resp) {
                        alert(JSON.stringify(resp));
                    }
                })
            })
        </script>
    </body>
    </html>
    
    @Controller
    @RequestMapping("/restful1")
    public class RestfulController {
        /*
         *  根据id查询
         *  localhost:8080/项目名/restful/user/2 + get请求方式
         */
        @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
        @ResponseBody
        public String findById(@PathVariable Integer id) {
            // 调用service方法完成id为2的这条记录的查询
            return "findById: " + id;
        }
    }
    
    @RestController//组合注解:@Controller + @ResponseBody
    @RequestMapping("/restful2")
    public class RestfulController2 {
        /*
         *  根据id查询
         *  localhost:8080/项目名/restful/user/2 + get请求方式
         */
        // @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
        @GetMapping("/user/{id}")
        public String findById(@PathVariable Integer id) {
            // 调用service方法完成id为2的这条记录的查询
            return "findById: " + id;
        }
    
        /**
         *  新增方法
         */
        // @RequestMapping(value = "/user/", method = RequestMethod.POST)
        @PostMapping("/user")
        public String post() {
            return "post";
        }
    
        /**
         * 更新操作
         */
        @PutMapping("/user")
        public String put() {
            return "put";
        }
    
        /**
         * 删除方法
         */
        @DeleteMapping("/user/{id}")
        public String delete(@PathVariable Integer id) {
            return "delete: " + id;
        }
    }
    

八、文件上传

  • 单文件上传

    • 实现步骤:

      1. 导入fileupload和io坐标

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.3</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        
      2. 在spring-mvc.xml中配置文件上传解析器

        <!--文件上传解析器-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <!-- 设定文件上传的最大值为5MB,5*1024*1024 -->
            <property name="maxUploadSize" value="5242880"></property>
            <!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 -->
            <property name="maxInMemorySize" value="40960"></property>
        </bean>
        
      3. 编写文件上传代码

        <%@ page contentType="text/html;charset=UTF-8" language="java" %>
        <html>
        <head>
            <title>Title</title>
        </head>
        <body>
        <%--    文件上传三要素:--%>
        <%--    1.表单的提交方式必须是post--%>
        <%--    2.表单的enctype属性必须要修改成multipart/form-data--%>
        <%--    3.表单中必须要有文件上传项--%>
            <form action="${pageContext.request.contextPath}/fileupload" method="post" enctype="multipart/form-data">
                名称:<input type="text" name="username"><br>
                文件:<input type="file" name="filePic"><br>
                <input type="submit" value="单文件上传">
            </form>
        </body>
        </html>
        
        @Controller
        public class FileUploadController {
        //    单文件上传
            @RequestMapping("/fileupload")
            public String fileUpload(String username, MultipartFile filePic) throws IOException {
                //获取表单的提交参数,完成文件的上传
                System.out.println(username);
        
                //获取原始的文件上传名
                String originalFilename = filePic.getOriginalFilename();
                filePic.transferTo(new File("D:/upload/" + originalFilename));
        
                return "success";
            }
        }
        
  • 多文件上传

    • 示例Code

      <form action="${pageContext.request.contextPath}/filesupload" method="post" enctype="multipart/form-data">
          名称:<input type="text" name="username"><br>
          文件1:<input type="file" name="filePic"><br>
          文件2:<input type="file" name="filePic"><br>
          文件3:<input type="file" name="filePic"><br>
          <input type="submit" value="多文件上传">
      </form>
      
      @RequestMapping("/filesupload")
      public String fileUpload(String username, MultipartFile[] filePic) throws IOException {
          System.out.println(username);
          for (MultipartFile multipartFile : filePic) {
              String originalFilename = multipartFile.getOriginalFilename();
              multipartFile.transferTo(new File("D:/upload/" + originalFilename));
          }
          return "success";
      }
      

九、异常处理

1. 自定义异常处理

  • 系统的dao、service、controller出现异常都通过throws Exception向上抛出,最后由Spring MVC前端控制器交由异常处理器(HandlerExceptionResolver)进行异常处理,这是Spring MVC的异常处理机制

    当前方法捕获处理(try-catch),这种处理方式会造成业务代码和异常处理代码的耦合,所以Spring MVC会采用throws对异常进行处理

  • 自定义异常处理的作用:根据异常产生的不同可以自定义操作(如跳转页面)

  • 示例Code

    首先自定义异常类实现HandlerExceptionResolver接口,重写resolveException方法(定义异常处理)

    public class GlobalExceptionResolver implements HandlerExceptionResolver {
    
        /*
            Exception e: 实际抛出的异常对象
         */
    
        @Override
        public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
            // 具体的异常处理,产生异常后,会跳转到一个最终的异常页面
            ModelAndView modelAndView = new ModelAndView();
            // 设置模型数据,将异常信息放入error的attribute中
            modelAndView.addObject("error", e.getMessage());
            // 设置逻辑视图名称
            modelAndView.setViewName("error");
    
            return modelAndView;
        }
    }
    

    定义异常信息显示error.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h1>error!!!</h1>
        ${error}
    </body>
    </html>
    

    在spring-mvc.xml中添加自定义异常处理器bean标签

    <!--配置自定义异常处理器-->
    <bean id="globalExceptionResolver" class="com.example.exception.GlobalExceptionResolver">
    </bean>
    

    定义一个Controller中的业务方法测试异常处理

    @Controller
    public class ExceptionController {
        @RequestMapping("/testException")
        public String testException() {
            int i = 1 / 0;
            return "success";
        }
    }
    

2. Web的异常处理机制

  • 可以在web.xml中自定义500状态异常和404状态异常的跳转页面

  • 示例Code

    <error-page>
        <error-code>404</error-code>
        <location>/404.jsp</location>
    </error-page>
    
    <error-page>
        <error-code>500</error-code>
        <location>/500.jsp</location>
    </error-page>
    

十、拦截器

1. 拦截器概述

  • Spring MVC的Interceptor类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理后处理

  • Interceptor和filter的区别:

    • filter可以用于任何的Java Web工程,Interceptor只能使用在Spring MVC框架的工程中

    • filter可以在url-pattern中配置了/*后,可以拦截所有要访问的资源

      Interceptor只会拦截访问的控制器方法,不会拦截静态资源(jsp, html, css, image, js)

  • HandlerInterceptor中的抽象方法

    postHandle可以用于操作ModelAndView对象来重新设置视图

    Spring MVC

2. 拦截器案例

  • 示例Code

    编写HandlerInterceptor接口实现类

    public class MyInterceptor1 implements HandlerInterceptor {
        // preHandle:在目标方法执行之前进行拦截,return false:不放行
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle...");
            return true;
        }
    
        // postHandle:在目标方法执行之后,视图对象返回之前,执行的方法
        @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...");
        }
    }
    

    在spring-mvc.xml中配置拦截器

    <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"></mvc:mapping>
            <bean class="com.example.interceptor.MyInterceptor1"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
    

    编写Controller中的目标方法

    @Controller
    public class TargetController {
        @RequestMapping("/target")
        public String targetMethod() {
            System.out.println("目标方法执行了...");
            return "success";
        }
    }
    

    跳转的success.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h3>hello world</h3>
    
        <a href="${pageContext.request.contextPath}/user/findByPage?pageNo=2">
            分页查询
        </a>
    
        <% System.out.println("视图执行了...");%>
    </body>
    </html>
    

    控制台打印执行顺序

    Spring MVC

3. 拦截器链

  • 示例Code

    编写拦截器2实现HandlerInterceptor接口

    public class MyInterceptor2 implements HandlerInterceptor {
        // preHandle:在目标方法执行之前进行拦截,return false:不放行
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle2...");
            return true;
        }
    
        // postHandle:在目标方法执行之后,视图对象返回之前,执行的方法
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle2...");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("afterCompletion2...");
        }
    }
    

    在spring-mvc.xml中配置拦截器

    <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"></mvc:mapping>
            <bean class="com.example.interceptor.MyInterceptor1"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"></mvc:mapping>
            <bean class="com.example.interceptor.MyInterceptor2"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
    

    控制台打印执行顺序

    postHandle和afterCompletion执行顺序与拦截器的配置顺序相反

    Spring MVC

上一篇:【java提高】---细则(1)


下一篇:SpringBoot+Dubbo+Zookeeper实现简单的分布式架构