Spring MVC学习笔记

Spring MVC

Spring MVC是目前主流的实现MVC模式的企业级开发框架,Spring框架的一个子模块,无需整合,开发起来更加便捷。

什么是MVC设计模式

将应用程序分为Controller、Model、View三层,Controller接收客户端请求,调用Model生成业务数据,传递给View。

Spring MVC就是对这套流程的封装,屏蔽很多底层代码,开放出接口,让开发者更加轻松、便捷的完成基于MVC模式的Web开发。

Spring MVC 的核心组件

  • DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥。
  • Handler:处理器,完成具体的业务逻辑,相当于Servlet或Action
  • HandlerMapping:DispatcherServlet接收到请求之后,通过HandlerMapping将不同的请求映射到不同的Handler。
  • HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。
  • HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)。
  • HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到JavaBean等。这些操作都是由HandlerAdaptec来完成,开发者只需将注意力集中在业务逻辑的处理上。DispatcherServlet通过HandlerAdapter执行不同的Handler。
  • ModelAndView:装载了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet。
  • ViewResolver:视图解析器,DispatcherServlet通过它将逻辑视图解析为物理视图,最终将渲染的结果返回给客户端。

Spring MVC工作原理

  1. 客户端请求被DispatcherServlet接收,向HandlerMapping索取Handler。

  2. HandlerMapping生成对应Handler和HandlerInterceptor,以HandlerExectionChain的形式返回给DispatcherServlet。

  3. DispatcherServlet通过HandlerAdapter调用Handler的方法完成业务逻辑的处理。

  4. Handler返回一个ModelAndView给DispatcherServlet。

  5. DispatcherServlet将获取的ModelAndView对象传给ViewResolver视图解析器。

  6. ViewResolver将逻辑视图解析为物理视图,返回一个View给DispatcherServlet。

  7. DispatcherServlet根据View进行视图渲染(将模型数据Model填充到视图View中)。

  8. DispatcherServlet将渲染后的结果响应给客户端。

    image-20200404132202970

Spring MVC流程非常复杂,实际开发中很简单,因为大部分的组件不需要开发者创建、管理,只需要通过配置文件的方式完成配置即可。真正需要开发者进行处理的只有Handler、View,

Spring MVC的特点

  • 清晰地角色划分
  • 灵活的配置功能
  • 提供了大量的控制器接口和实现类
  • 分离View层的实现
  • 国际化支持
  • 面向接口编程

如何使用Spring MVC

  • 创建Maven工程,pom.xml添加依赖:

    <dependencies>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.2.4.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.11</version>
          <scope>test</scope>
        </dependency>
    </dependencies>
    
  • 在main文件夹下新建java文件夹和resources文件夹,并分别设为Sources root和Resources Root。

  • 在web.xml中配置DispatcherServlet:

    <!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-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc.xml</param-value>
        </init-param>
      </servlet>
      <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    </web-app>
    
  • 在resources文件夹中新建springmvc.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:context="http://www.springframework.org/schema/context"
            xmlns:mvc="http://www.springframework.org/schema/mvc"
            xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--    自动扫描-->
        <context:component-scan base-package="com.wb"></context:component-scan>
    <!--    配置视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/"></property>
            <property name="suffix" value=".jsp"></property>
        </bean>
    </beans>
    
  • 创建Handler

    package com.wb.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/hello")
    public class HelloHandler {
        @RequestMapping("/index")
        public String index(){
            System.out.println("执行了index...");
            return "index";
        }
    }
    

Spring MVC注解

  • @RequestMapping

    Spring MVC通过@RequestMapping注解将URL请求与业务方法进行映射,在Handler的类定义处以及方法定义处都可以添加,相当于客户端多了一层访问路径。

    相关参数:

    1. value 指定URL请求的实际地址,是@RequestMapping的默认值。

      @RequestMapping("/index")
          public String index(){
              System.out.println("执行了index...");
              return "index";
          }
      

      等于

      @RequestMapping(value="/index")
          public String index(){
              System.out.println("执行了index...");
              return "index";
          }
      
    2. method 指定请求的method的类型(GET、POST、PUT、DELETE)

      @RequestMapping(value = "/index",method = RequestMethod.GET)
          public String index(){
              System.out.println("执行了index...");
              return "index";
          }
      

      上述代码表示index方法只能接收GET请求。

    3. params 指定请求中必须包含某些参数,否则无法调用该方法

      @RequestMapping(value = "/index",method = RequestMethod.GET,params = {"name","id=10"})
      public String index(){
          System.out.println("执行了index...");
          return "index";
      }
      

      上述代码表示请求中必须包含name和id两个参数,同时id的值必须为10。

      关于参数绑定,在方法的形参列表中,如果参数名和params中的参数名一致,则不需要添加注解;如果不一致,通过添加@RequestParam注解完成HTTP请求参数与业务方法形参的映射:

         @RequestMapping(value = "/index",method = RequestMethod.GET,params = {"name","id=10"})
          public String index(@RequestParam("name") String name1,@RequestParam("id") int id1){
              System.out.println(name1);
              System.out.println(id1);
              System.out.println("执行了index...");
              return "index";
          }
      

      在上述代码中可以自动完成参数的类型转换(通过HandlerAdapter,从String到int)。

      Spring MVC也支持RESTful风格的URL:

      传统类型:http://localhost:8080/hello/index?name=wangbin&id=10

      RESTful:http://localhost:8080/hello/index/wangbin/10

      @RequestMapping("/rest/{name}/{id}")
          public String rest(@PathVariable("name") String name,@PathVariable("id") int id){
              System.out.println(name);
              System.out.println(id);
              return "index";
          }
      

      通过 PathVariable 注解完成请求参数和形参的映射。

  • @Controller

    @Controller在类定义处添加,将该类交给IoC容器来管理(结合springmvc.xml的自动扫描配置使用),同时使其成为一个控制器,可以接收用户请求。

  • 映射Cookie

    Spring MVC可以通过映射直接在业务方法中获取Cookie的值:

    @RequestMapping("/cookie")
    public String cookie(@CookieValue(value = "JSESSIONID") String sessionId){
        System.out.println(sessionId);
        return "index";
    }
    
  • jsp页面的转发和重定向

    Spring MVC默认是以转发的形式响应。

    1. 转发

      return "forward:/index.jsp";
      

      等于

      return "index";
      
    2. 重定向

      return "redirect:/index.jsp";
      

Spring MVC数据绑定

数据绑定:在后端的业务方法中直接获取客户端HTTP请求中的参数,将请求参数映射到业务方法的形参中,Spring MVC中的数据绑定的工作是由HandlerAdapter来完成的。

  • 基本数据类型

    @RequestMapping("/base")
    @ResponseBody
    public String base(int id) {
        return id + "";
    }
    

    @ResponseBody 表示将Spring MVC会直接将业务方法的返回值响应给客户端,如果不加,则会将业务方法的返回值传递给DispatcherServlet,再由DispatcherServlet调用ViewResolver对返回值进行解析,映射到一个jsp资源。

    如果客户端没有对应参数,则会报错,因为基本数据类型不能接收null值。

  • 包装类

    @RequestMapping("/package")
    @ResponseBody
    public String package1(@RequestParam(value = "num",required = false,defaultValue = "0") Integer id){
        return id+"";
    }
    

    包装类可以接收null,当HTTP请求没有参数时,使用包装类定义形参不会抛异常。

    @RequestParam

    value=num:将HTTP请求中名为num的参数赋给形参id。

    required:设置value中的参数是否为必填项,false为非必填,true为必填。

    defaultValue:设置请求参数的缺省值。

  • 数组

    @RequestMapping("array")
    @ResponseBody
    public String array(String[] name){
        String str = Arrays.toString(name);
        return str;
    }
    

    上述方法中均含有@ResponseBody注解,因此可以将他们去掉,将类前的@Controller注解改为@RestController

    @RestController:表示该控制器会直接将业务方法的返回值响应给客户端,不进行视图解析。

    @Controller:表示该控制器的每一个业务方法的返回值都会交给视图解析器进行解析。如果只需要将数据响应给客户端,则需要在对应的业务方法定义处添加@ResponseBody

  • JavaBean

    Spring MVC会根据请求参数名和JavaBean属性名自动匹配,自动为对象填充属性值,同时支持级联属性。

    package com.wb.entity;
    
    import lombok.Data;
    
    @Data
    public class User {
        private long id;
        private String name;
        private Address address;
    }
    
    package com.wb.entity;
    
    import lombok.Data;
    
    @Data
    public class Address {
        private String value;
    }
    
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>注册</title>
    </head>
    <body>
    <form action="/hello/save" method="post">
        用户id:<input type="text" name="id"><br>
        用户名:<input type="text" name="name"><br>
        用户地址:<input type="text" name="address.value"><br>
        <input type="submit" value="注册">
    </form>
    </body>
    </html>
    
    @RequestMapping(value = "/save",method = RequestMethod.POST)
    public String save(User user){
        System.out.println(user);
        return "index";
    }
    

    如果出现中文乱码问题,只需在web.xml中添加Spring MVC自带的过滤器即可:

    <filter>
        <filter-name>encodingFilter</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>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
  • List

    Spring MVC不支持List类型的直接转换,需要对List集合进行包装。

    集合封装类:

    @Data
    public class UserList {
        private List<User> users;
    }
    

    JSP页面:

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>list</title>
    </head>
    <body>
    <form action="/data/list" method="post">
        用户1id:<input type="text" name="users[0].id"><br>
        用户1名:<input type="text" name="users[0].name"><br>
        用户2id:<input type="text" name="users[1].id"><br>
        用户2名:<input type="text" name="users[1].name"><br>
        用户3id:<input type="text" name="users[2].id"><br>
        用户3名:<input type="text" name="users[2].name"><br>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    

    业务方法:

    @RequestMapping("/list")
    public String list(UserList userList){
        StringBuffer stringBuffer = new StringBuffer();
        for(User user:userList.getUsers()){
            stringBuffer.append(user);
        }
        return stringBuffer.toString();
    }
    

    处理@ResponseBody返回给客户端浏览器出现中文乱码,在Springmvc.xml中配置消息转换器:

    <mvc:annotation-driven>
        <!-- 消息转换器-->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8"></property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    

    注:

    过滤器:设置请求request的编码,解决前端数据到后端乱码问题。

    消息转换器:设置响应response编码,解决后端数据到前端的乱码。

  • Map

    自定义封装类:

    @Data
    public class UserMap {
        private Map<String,User> users;
    }
    

    业务方法:

    @RequestMapping("/map")
    public String map(UserMap userMap){
        StringBuffer stringBuffer = new StringBuffer();
        for(String key:userMap.getUsers().keySet()){
            User user = userMap.getUsers().get(key);
            stringBuffer.append(user);
        }
        return stringBuffer.toString();
    }
    

    JSP页面:

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>map</title>
    </head>
    <body>
    <form action="/data/map" method="post">
        用户1id:<input type="text" name="users['a'].id"><br>
        用户1名:<input type="text" name="users['a'].name"><br>
        用户2id:<input type="text" name="users['b'].id"><br>
        用户2名:<input type="text" name="users['b'].name"><br>
        用户3id:<input type="text" name="users['c'].id"><br>
        用户3名:<input type="text" name="users['c'].name"><br>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    
  • JSON

    客户端发送JSON格式数据,直接通过Spring MVC绑定到业务方法的形参中。

    处理Spring MVC无法加载静态资源(js、css等),在web.xml中添加与资源格式对应配置即可:

    <servlet-mapping>
      <servlet-name>default</servlet-name>
      <url-pattern>*.js</url-pattern>
    </servlet-mapping>
    

    JSP页面:

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>json</title>
        <script type="text/javascript" src="js/jquery-3.4.1.js"></script>
        <script>
            $(function () {
                // 创建json对象
               let user = {
                   "id":1,
                   "name":"刘能"
               };
               // 向客户端发送json对象,并显示返回结果
               $.ajax({
                   url:"/data/json",
                   data:JSON.stringify(user),   //发送的数据
                   type:"post",
                   contentType:"application/json;charset=UTF-8", //设置数据的编码格式防止乱码
                   dataType:"json",   //返回数据的类型
                   success:function (data) {
                       alert(data.id+"---"+data.name);  //将返回的数据打印出来
                   }
               })
            });
        </script>
    </head>
    <body>
    
    </body>
    </html>
    

    业务方法:

    @RequestMapping("/json")
    public User json(@RequestBody User user){
        System.out.println(user);
        user.setId(123);
        user.setName("谢广坤");
        return user;
    }
    

    Spring MVC中的JSON和JavaBean的转换需要借助于fastjson,在pom.xml中引入相关依赖。

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.67</version>
    </dependency>
    

    在springmvc.xml中添加fastjson配置:

    <mvc:annotation-driven>
        <!-- 消息转换器 -->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8"></property>
            </bean>
            <!-- 配置fastjson -->
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"></bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    

Spring MVC模型数据解析

JSP四大作用域对应的内置对象:pageContext、request、session、application。

模型数据的绑定是由ViewResolver来完成的,实际开发中,我们需要先添加模型数据,再交给ViewResolver来绑定。

Spring MVC提供了以下几种方式添加模型数据:

  • Map
  • Model
  • ModelAndView
  • @SessionAttribute
  • ModelAttribute

将模型数据绑定到request域

  1. Map

    @RequestMapping("/map")
    public String map(Map<String,User> map) {
        User user = new User();
        user.setId(1L);
        user.setName("谢大脚");
        map.put("user",user);
        return "view";
    }
    

    在Handler中通过Map将数据存到request中,在JSP页面就可以使用:

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page isELIgnored="false" %>
    <html>
    <head>
        <title>map</title>
    </head>
    <body>
    ${requestScope.user}
    </body>
    </html>
    

    注意使用EL表达式时要加page指令。

  2. Model

    @RequestMapping("/model")
    public String model(Model model) {
        User user = new User();
        user.setId(2L);
        user.setName("刘一水");
        model.addAttribute("user",user);
        return "view";
    }
    
  3. ModelAndView

    通过ModelAndView有很多种方式设置:

    第一种:

    @RequestMapping("/modelAndView")
    public ModelAndView modelAndView(Model model) {
        User user = new User();
        user.setId(3L);
        user.setName("赵四");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("user",user);
        modelAndView.setViewName("view");
        return modelAndView;
    }
    

    第二种:

    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("user",user);
    View view = new InternalResourceView("/view.jsp");
    modelAndView.setView(view);
    

    第三种:

    View view = new InternalResourceView("/view.jsp");
    ModelAndView modelAndView = new ModelAndView(view);
    modelAndView.addObject("user",user);
    

    第四种:

    ModelAndView modelAndView = new ModelAndView("view");
    modelAndView.addObject("user",user);
    

    第五种:

    Map<String,User> map = new HashMap<>();
    map.put("user",user);
    ModelAndView modelAndView = new ModelAndView("view",map);
    

    第六种:

    Map<String,User> map = new HashMap<>();
    map.put("user",user);
    View view = new InternalResourceView("/view.jsp");
    ModelAndView modelAndView = new ModelAndView(view,map);
    

    第七种:

    ModelAndView modelAndView = new ModelAndView("view","user",user);
    

    第八种:

    View view = new InternalResourceView("/view.jsp");
    ModelAndView modelAndView = new ModelAndView(view,"user",user);
    
  4. HttpServletRequest

    @RequestMapping("/request")
    public String request(HttpServletRequest request){
        User user = new User();
        user.setId(4L);
        user.setName("王云");
        request.setAttribute("user",user);
        return "view";
    }
    
  5. @ModelAttribute

    定义一个方法,该方法返回模型数据,加上 @ModelAttrite 注解后,就可以将模型数据设置到 request 中,该方法先于其他业务方法执行。在业务方法中无需在添加模型数据,直接返回视图即可。

    @ModelAttribute
    public User getUser(){
        User user = new User();
        user.setId(3L);
        user.setName("刘大脑袋");
        return user;
    }
    

    同时也可以使用以下几种方式:

    @ModelAttribute
    public void getUser(Map<String,User> map){
        User user = new User();
        user.setId(3L);
        user.setName("刘大脑袋");
    }
    
    @ModelAttribute
    public void getUser(Model model){
        User user = new User();
        user.setId(3L);
        user.setName("刘大脑袋");
        model.addAttribute("user",user);
    }
    

将模型数据绑定到Session域

  1. 直接使用原生Servlet API

    @RequestMapping("/session")
    public String session(HttpServletRequest request){
        HttpSession httpSession = request.getSession();
        User user = new User();
        user.setId(5L);
        user.setName("谢永强");
        httpSession.setAttribute("user",user);
        return "view";
    }
    @RequestMapping("/session2")
    public String session2(HttpSession httpSession){
        User user = new User();
        user.setId(5L);
        user.setName("李大国");
        httpSession.setAttribute("user",user);
        return "view";
    }
    
  2. @SessionAttributes

    @SessionAttributes(value = {"user","address"})
    public class ViewHandler {
    }
    @SessionAttributes(types = {User.class, Address.class})
    
    @SessionAttributes(types = {User.class, Address.class})
    public class ViewHandler {
    }
    

    上述代码中,对于Viewhandler的所有业务方法:

    第一种:只要向request中添加了key=“user”、key=“address”的对象时,Spring MVC会将该数据添加到Session域中,key不变。

    第二种:只要向request域中添加了数据类型为User、Address对象时,Spring MVC会将该数据添加到Session中,key不变。

将模型数据绑定到application对象

@RequestMapping("/application")
public String application(HttpServletRequest request){
    User user = new User();
    user.setId(5L);
    user.setName("王小蒙");
    ServletContext application = request.getServletContext();
    application.setAttribute("user",user);
    return "view";
}

Spring MVC自定义数据转换器

数据转换器是指将客户端HTTP请求中的参数转换为业务方法中的形参,自定义表示开发者可以自主设计转换的方式,HandlerAdapter已经提供了通用的转换,String转int,String转double、表单数据的封装等,但是在特殊的业务场景下,HandlerAdapter无法进行转换,需要开发者自定义转换器。

例1:客户端输入日期如“2020-4-5”,自定义转换器将其转换为Date类型的对象。

  • 创建DateConverter转换器,实现Converter接口:

    package com.wb.converter;
    
    import org.springframework.core.convert.converter.Converter;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class DataConverter implements Converter<String, Date> {
        private String pattern;
    
        public DataConverter(String pattern) {
            this.pattern = pattern;
        }
    
        @Override
        public Date convert(String s) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.pattern);
            Date date = null;
            try {
                date = simpleDateFormat.parse(s);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return date;
        }
    }
    
  • 在spring.xml中配置转换器:

    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.wb.converter.DataConverter">
                    <constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
                </bean>
            </list>
        </property>
    </bean>
    <mvc:annotation-driven conversion-service="conversionService">
        <!-- 消息转换器 -->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8"></property>
            </bean>
            <!-- 配置fastjson -->
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"></bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    
  • JSP

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
        <head>
            <title>date</title>
        </head>
        <body>
            <form action="/converter/date" method="post">
                请输入日期:<input type="text" name="date">
                <input type="submit" value="提交">
            </form>
        </body>
    </html>
    
  • Handler

    @RestController
    @RequestMapping("/converter")
    public class ConverterHandler {
        @RequestMapping("/date")
        public String date(Date date){
            return date.toString();
        }
    }
    

例2:自定义Student类、客户端输入格式、转换器,将客户端传来的String类型数据转换成Student类。

  • StudentConverter

    package com.wb.converter;
    
    import com.wb.entity.Student;
    import org.springframework.core.convert.converter.Converter;
    
    public class StudentConverter implements Converter<String, Student> {
        @Override
        public Student convert(String s) {
            String[] args = s.split("-");
            Student student = new Student();
            student.setId(Long.parseLong(args[0]));
            student.setName(args[1]);
            student.setAge(Integer.parseInt(args[2]));
            return student;
        }
    }
    
  • Spring MVC.xml

    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.wb.converter.DataConverter">
                    <constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
                </bean>
                <bean class="com.wb.converter.StudentConverter"></bean>
            </list>
        </property>
    </bean>
    
  • JSP

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <form action="/converter/student" method="post">
        请输入学生信息:<input type="text" name="student">(id-name-age)<br>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    
  • Handler

    @RequestMapping("/student")
    public String student(Student student){
        return student.toString();
    }
    

Spring MVC REST

REST:Representational State Transfer,资源表现层状态转换,是目前比较主流的一种互联网软件架构,它结构清晰、标准规范、易于理解、便于扩展。

  • 资源(Resource)

    网络上的一个实体,一段文本、一张图片、一首歌曲、一段视频等等,就是一个具体的存在。可以用一个URI(统一资源标识符)指向它,每个资源都有一个特定的URI。要获取该资源时,只需要访问对应的URI即可。

  • 表现层(Representation)

    资源具体呈现出来的形式,比如文本可以用txt格式表示,也可以用HTML、XML、JSON等格式来表示。

  • 状态转换(State Transfer)

    客户端如果希望操作服务器中的某个资源,就需要通过某种方式让服务端发生状态转换,而这种转换是建立在表现层之上的,所以叫做“表现层状态转换。

特点:

  • URL更加简洁。
  • 有利于不同系统之间的资源共享,只需要遵守一定的规范,不需要进行其他配置即可实现资源共享。

如何使用?

REST具体操作就是HTTP协议中四个表示操作方式的动词分别对应CURD基本操作。

GET用来表示获取资源;

POST用来表示新建资源;

PUT用来表示修改资源;

DELETE用来表示删除资源。

  • Handler:

    @RestController
    @RequestMapping("/rest")
    public class RESTHandeler {
    
        @Autowired
        private StudentRepository studentRepository;
    
        @GetMapping("/findAll")
        public Collection<Student> findAll(HttpServletResponse response){
            response.setContentType("text/json;charset=UTF-8");  //解决中文乱码
            return studentRepository.findAll();
        }
    
        @GetMapping("/findById/{id}")
        public Student findById(@PathVariable("id") long id){
            return studentRepository.findById(id);
        }
    
        @PostMapping("/save")
        public void save(@RequestBody Student student){
            studentRepository.saveOrUpdate(student);
        }
    
        @PutMapping("/update")
        public void update(@RequestBody Student student){
            studentRepository.saveOrUpdate(student);
        }
    
        @DeleteMapping("/deleteById/{id}")
        public void deleteById(@PathVariable("id") long id){
            studentRepository.deleteById(id);
        }
    
    }
    
  • StudentRespository

    public interface StudentRepository {
        public Collection<Student> findAll();
        public Student findById(long id);
        public void saveOrUpdate(Student student);
        public void deleteById(long id);
    }
    
  • StudentRespositoryImpl

    @Repository
    public class StudentRepositoryImpl implements StudentRepository {
    
        private static Map<Long,Student> studentMap;
    
        static{
            studentMap = new HashMap<>();
            studentMap.put(1L,new Student(1L,"张三",22));
            studentMap.put(2L,new Student(2L,"李四",23));
            studentMap.put(3L,new Student(3L,"王五",24));
        }
    
        @Override
        public Collection<Student> findAll() {
            return studentMap.values();
        }
    
        @Override
        public Student findById(long id) {
            return studentMap.get(id);
        }
    
        @Override
        public void saveOrUpdate(Student student) {
            studentMap.put(student.getId(),student);
        }
    
        @Override
        public void deleteById(long id) {
            studentMap.remove(id);
        }
    }
    

Spring MVC文件上传下载

单文件上传

底层是使用Apache fileupload组件完成上传,Spring MVC对这种方式进行了封装。

  • 在pom.xml中引入依赖

    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.6</version>
    </dependency>
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.4</version>
    </dependency>
    
  • JSP

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
        <head>
            <title>Title</title>
        </head>
        <body>
            <form action="/file/upload" method="post" enctype="multipart/form-data">
                <input type="file" name="img">
                <input type="submit" value="上传">
            </form>
            <img src="${path}" alt="">
        </body>
    </html>
    

    注:

    input的type设置为file。

    form的method属性设置为post(get只能将文件名传给服务器)

    form的enctype属性设置为multipart/form-data(如果不设置只能将文件名传给服务器)

  • Handler

    @Controller
    @RequestMapping("/file")
    public class FileHandler {
        @PostMapping("/upload")
        public String upload(MultipartFile img, HttpServletRequest request){
            if(img.getSize()>0){
                //获取保存文件的file路径
                String path = request.getServletContext().getRealPath("file");
                //获取上传文件名
                String name = img.getOriginalFilename();
                File file = new File(path,name);
                try {
                    img.transferTo(file);
                    //保存上传后的文件路径到request中,在客户端就可以取出并显示
                    request.setAttribute("path","/file/"+name);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return "upload";
        }
    }
    
  • 需要在springmvc.xml中配置上传组件

    <!--配置上传组件-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
    

    注:bean的id是固定的,不能写错。

  • 要想客户端可以请求对应的图片资源,需要在web.xml中配置:

    <servlet-mapping>
      <servlet-name>default</servlet-name>
      <url-pattern>*.png</url-pattern>
    </servlet-mapping>
    

    类型与图片的类型一致。

多文件上传

  • 在pom.xml中添加jstl依赖:

    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    
  • JSP:

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ page isELIgnored="false" %>
    <html>
        <head>
            <title>Title</title>
        </head>
        <body>
            <form action="/file/uploads" enctype="multipart/form-data" method="post">
                file1: <input type="file" name="imgs"><br>
                file2: <input type="file" name="imgs"><br>
                file3: <input type="file" name="imgs"><br>
                <input type="submit" value="上传">
            </form>
            <c:forEach items="${files}" var="file">
                <img src="${file}" alt="">
            </c:forEach>
        </body>
    </html>
    
  • Handler

    @PostMapping("/uploads")
    public String uploads(MultipartFile[] imgs,HttpServletRequest request){
        List<String> files = new ArrayList<>();
        for (MultipartFile img:imgs){
            if(img.getSize()>0){
                //获取保存文件的file路径
                String path = request.getServletContext().getRealPath("file");
                //获取上传文件名
                String name = img.getOriginalFilename();
                File file = new File(path,name);
                try {
                    img.transferTo(file);
                    //保存上传后的文件路径
                    files.add("/file/"+name);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        request.setAttribute("files",files);
        return "uploads";
    }
    

文件下载

  • JSP

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
        <head>
            <title>Title</title>
        </head>
        <body>
            <a href="/file/download/1">1.png</a>
            <a href="/file/download/2">2.png</a>
            <a href="/file/download/3">3.png</a>
        </body>
    </html>
    
  • Handler

    @GetMapping("/download/{name}")
    public void download(@PathVariable String name, HttpServletRequest request, HttpServletResponse response){
        if(name!=null){
            name+=".png";
            String path = request.getServletContext().getRealPath("file");
            File file =new File(path,name);
            OutputStream outputStream = null;
            if(file.exists()){
                response.setContentType("application/forc-download");
                response.setHeader("Content-Disposition","attachment;filename="+name);
                try {
                    outputStream = response.getOutputStream();
                    outputStream.write(FileUtils.readFileToByteArray(file));
                    outputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if(outputStream!=null){
                        try {
                            outputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
    
            }
        }
    }
    

Spring MVC表单标签库

  • Handler

    @Controller
    @RequestMapping("/tag")
    public class TagHandler {
        @GetMapping("/get")
        public ModelAndView get(){
            ModelAndView modelAndView = new ModelAndView("tag");
            Student student = new Student(1L,"刘能",48);
            modelAndView.addObject("student",student);
            return modelAndView;
        }
    }
    
  • JSP

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page isELIgnored="false" %>
    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
    <html>
        <head>
            <title>Title</title>
        </head>
        <body>
            <h1>学生信息</h1>
            <form:form modelAttribute="student">
                学生ID:<form:input path="id"/><br>
                学生姓名:<form:input path="name"/><br>
                学生年龄:<form:input path="age"/><br>
            </form:form>
        </body>
    </html>
    

    注:

    1. 导入Spring MVC表单标签库的导入JSTL的语法类似,前缀prefix可以自定义,通常定义为form。

      <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
      
    2. 将form表单与模型数据进行绑定,通过modelAttribute属性完成绑定,将modelAttribute 的值设为模型数据对应的key值。

      <form:form modelAttribute="student">
      
    3. form表单完成绑定之后,将模型数据的值取出绑定到标签中。将标签path的属性值设置为模型对应的属性即可。

      学生ID:<form:input path="id"/><br>
      学生姓名:<form:input path="name"/><br>
      学生年龄:<form:input path="age"/><br>
      

常用的表单标签

  • form
<form:from modelAttribute="student"/>

渲染的是 HTML 中的<form></from>,通过 modelAttribute 属性绑定具体的模型数据。

  • input
<form:input path="name"/>

渲染的是 HTML 中的 <input type="text"/>,from 标签绑定的是模型数据,input 标签绑定的是模型数据中的属性值,通过 path 属性可以与模型数据中的属性名对应,并且支持及联操作。

<from:input path="address.name"/>
  • password
<form:password path="password"/>

渲染的是 HTML 中的 <input type="password"/>,通过 path 属性与模型数据的属性值进行绑定,password 标签的值不会在页面显示(有值而不显示)。

  • checkbox
<form:checkbox path="hobby" value="读书"/>
student.setFlag(false);
checkbox:<form:checkbox path="flag" value="flag"></form:checkbox><br/>

渲染的是 HTML 中的 <input type="checkbox"/>,通过 path 与模型数据的属性值进行绑定,可以绑定 boolean、数组和集合。

如果绑定 boolean 值,若该变量的值为 true,则表示该复选框选中,否则表示不选中。

如果绑定数组或者集合,数组/集合中的元素等于 checkbox 的 value 值,则选中。

student.setHobby(Arrays.asList("读书","看电影","玩游戏"));
modelAndView.addObject("student",student);
爱好:<form:checkbox path="hobby" value="摄影"></form:checkbox>摄影<br/>
<form:checkbox path="hobby" value="读书"></form:checkbox>读书<br/>
<form:checkbox path="hobby" value="听音乐"></form:checkbox>听音乐<br/>
<form:checkbox path="hobby" value="看电影"></form:checkbox>看电影<br/>
<form:checkbox path="hobby" value="旅游"></form:checkbox>旅游<br/>
<form:checkbox path="hobby" value="玩游戏"></form:checkbox>玩游戏<br/>
<input type="submit" value="提交"/>

以上代码选中的复选框有读书、看电影、玩游戏。

  • checkboxes
<form:checkboxes items=${student.hobby} path="selecHobby"/>

渲染的是 HTML 中的一组 <input type="checkbox"/>,是对 <form:checkbox/> 的一种简化,需要结合 items 和 path 属性来使用,items 绑定被遍历的集合或数组,path 绑定被选中的集合或数组,可以这样理解,items 为全部可选集合,path 为默认的选中集合。

student.setHobby(Arrays.asList("摄影","读书","听音乐","看电影","旅游","玩游戏"));
student.setSelectHobby(Arrays.asList("摄影","读书","听音乐"));
modelAndView.addObject("student",student);
爱好:<form:checkboxes path="selectHobby" items="${student.hobby}"/><br/>

需要注意的是 path 可以直接绑定模型数据的属性值,items 则需要通过 EL 表达式的形式从域对象中获取数据,不能直接写属性名。

  • radiobutton
<from:radiobutton path="radioId" value="0"/>

渲染的是 HTML 中的一个 <input type="radio"/>,绑定的数据与标签的 value 值相等则为选中,否则不选中。

student.setRadioId(1);
modelAndView.addObject("student",student);
radiobutton:<form:radiobutton path="radioId" value="1"/>radiobutton<br/>
  • radiobuttons
<form:radiobuttons itmes="${student.grade}" path="selectGrade"/>

渲染的是 HTML 中的一组 <input type="radio"/>,这里需要结合 items 和 path 两个属性来使用,items 绑定被遍历的集合或数组,path 绑定被选中的值,items 为全部的可选类型,path 为默认选中的选项,用法与 <form:checkboxes/> 一致。

Map<Integer,String> gradeMap = new HashMap<>();
gradeMap.put(1,"一年级");
gradeMap.put(2,"二年级");
gradeMap.put(3,"三年级");
gradeMap.put(4,"四年级");
gradeMap.put(5,"五年级");
gradeMap.put(6,"六年级");
student.setGradeMap(gradeMap);
student.setSelectGrade(3);
modelAndView.addObject("student",student);
学生年级:<form:radiobuttons items="${student.gradeMap}" path="selectGrade"/><br/>
  • select
<form:select items="${student.citys}" path="selectCity"/>

渲染的是 HTML 中的一个 <select/> 标签,需要结合 items 和 path 两个属性来使用,items 绑定被遍历的集合或数组,path 绑定被选中的值,用法与 <from:radiobuttons/>一致。

Map<Integer,String> cityMap = new HashMap<>();
cityMap.put(1,"北京");
cityMap.put(2,"上海");
cityMap.put(3,"广州");
cityMap.put(4,"深圳");
student.setCityMap(cityMap);
student.setSelectCity(3);
modelAndView.addObject("student",student);
所在城市:<form:select items="${student.cityMap}" path="selectCity"></form:select><br/>
  • options

form:select 结合 form:options 的使用,from:select 只定义 path 属性,在 form:select 标签内部添加一个子标签 form:options ,设置 items 属性,获取被遍历的集合。

所在城市:<form:select path="selectCity">
  				<form:options items="${student.cityMap}"></form:options>
				</form:select><br/>
  • option

form:select 结合 form:option 的使用,from:select 定义 path 属性,给每一个 form:option 设置 value 值,path 的值与哪个 value 值相等,该项默认选中。

所在城市:<form:select path="selectCity">
            <form:option value="1">杭州</form:option>
            <form:option value="2">成都</form:option>
            <form:option value="3">西安</form:option>
        </form:select><br/>
  • textarea

渲染的是 HTML 中的一个 <textarea/> ,path 绑定模型数据的属性值,作为文本输入域的默认值。

student.setIntroduce("你好,我是...");
modelAndView.addObject("student",student);
信息:<form:textarea path="introduce"/><br/>
  • errors

处理错误信息,一般用在数据校验,该标签需要结合 Spring MVC 的验证器结合起来使用。

Spring MVC数据校验

Spring MVC提供了两种数据校验方式:

  1. 基于Validator接口。
  2. 使用Annotation JSR - 303标准进行校验。

基于Validator接口的方式需要自定义Validator验证器,每一条数据的验证规则需要开发者手动完成,使用Annotation JSR - 303标准则不需要自定义验证器,通过注解的方式可以直接在实体类中添加每个属性的验证规则,这种方式更加方便,实际开发中推荐使用。

基于Validator接口

  • Account实体类

    package com.wb.entity;
    
    import lombok.Data;
    
    @Data
    public class Account {
        private String name;
        private String password;
    }
    
  • 自定义验证器AccountValidator,实现Validator接口

    package com.wb.validator;
    
    import com.wb.entity.Account;
    import org.springframework.validation.Errors;
    import org.springframework.validation.ValidationUtils;
    import org.springframework.validation.Validator;
    
    public class AccountValidator implements Validator {
    
        @Override
        public boolean supports(Class<?> aClass) {
            return Account.class.equals(aClass);
        }
    
        @Override
        public void validate(Object o, Errors errors) {
            ValidationUtils.rejectIfEmpty(errors,"name",null,"Name cannot be null");
            ValidationUtils.rejectIfEmpty(errors,"password",null,"Password cannot be null");
    }
    }
    
  • 在springmvc.xml中配置验证器

    <!--配置自定义验证器-->
    <bean id="accountValidator" class="com.wb.validator.AccountValidator"></bean>
    <mvc:annotation-driven conversion-service="conversionService" validator="accountValidator">
    
  • Handler

    @Controller
    @RequestMapping("/validator")
    public class ValidatorHandler {
        @GetMapping("/login")
        public String login(Model model){
            model.addAttribute("account",new Account());
            return "login";
        }
        @PostMapping("/login")
        public String login(@Validated Account account, BindingResult result){
            if(result.hasErrors()){
                return "login";
            }
            return "index";
        }
    }
    
  • JSP页面

    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page isELIgnored="false" %>
    <html>
        <head>
            <title>Title</title>
        </head>
        <body>
            <form:form modelAttribute="account" action="" method="post">
                姓名:<form:input path="name"></form:input><form:errors path="name"></form:errors><br>
                密码:<form:input path="password"></form:input><form:errors path="password"></form:errors><br>
                <input type="submit">
            </form:form>
        </body>
    </html>
    

Annotation JSR - 303标准

使用Annotation JSR - 303标准进行验证,需要导入支持这种标准的依赖jar文件,这里我们使用Hibernate Validator。

  • 在pol.xml中添加依赖

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.1.2.Final</version>
    </dependency>
    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>2.0.1.Final</version>
    </dependency>
    <dependency>
      <groupId>org.jboss.logging</groupId>
      <artifactId>jboss-logging</artifactId>
      <version>3.3.2.final</version>
    </dependency>
    
  • 通过注解的方式直接在实体类中添加验证规则

    package com.wb.entity;
    
    import lombok.Data;
    
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotEmpty;
    import javax.validation.constraints.Pattern;
    import javax.validation.constraints.Size;
    
    @Data
    public class Person {
        @NotEmpty(message = "用户名不能为空!")
        private String name;
        @Size(min = 6,max = 12,message = "密码必须介于6-12位!")
        private String password;
        @Email(regexp = "^\\w+@\\w+.(com|cn|net)$",message = "邮箱格式不正确!")
        private String email;
        @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "电话格式不正确!")
        private String phone;
    }
    
  • Handler

    @GetMapping("/regist")
    public String regist(Model model){
        model.addAttribute("person",new Person());
        return "regist2";
    }
    @PostMapping("/regist")
    public String regist(@Valid Person person, BindingResult result){
        if(result.hasErrors()){
            return "regist2";
        }
        return "index";
    }
    
  • 只要spring.xml配置了mvc:annotation-driven就可以,但是标签中不可以有validator属性;或者再写一个mvc:annotation-driven标签。

  • JSP

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page isELIgnored="false" %>
    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
    <html>
        <head>
            <title>Title</title>
        </head>
        <body>
            <form:form modelAttribute="person" action="/validator/regist" method="post">
                用户名:<form:input path="name"></form:input><form:errors path="name"></form:errors> <br>
                密码:<form:input path="password"></form:input><form:errors path="password"></form:errors> <br>
                邮箱:<form:input path="email"></form:input><form:errors path="email"></form:errors> <br>
                电话号码:<form:input path="phone"></form:input><form:errors path="phone"></form:errors> <br>
                <input type="submit" value="注册">
            </form:form>
        </body>
    </html>
    
  • 校验规则详解

    @Null 被注解的元素必须为null

    @NotNull 被注解的元素不能为null

    @Min(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值

    @Max(value) 被注解的元素必须是一个数字,其值必须小于于等于指定的最大值

    @Email 被注解的元素必须是电子邮箱地址

    @Pattern 被注解的元素必须符合对应的正则表达式

    @Length 被注解的元素的大小必须在指定的范围内

    @NotEmpty 被注解的字符串的值必须非空

    Null 和 Empty 是不同的结果,String str = null,str 是 null,String str = “”,str 不是 null,其值为空。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值