Spring MVC

Spring MVC概述

SpringMVC是目前主流的实现MVC设计模式的框架,是Spring框架的一个分支产品,以Spring IoC容器为基础,并利用容器的特性来简化它的配置。SpringMVC相当于Spring的一个子模块,可以很好的和Spring结合起来进行开发,是Java Web开发者必须要掌握的框架。

Spring MVC的核心组件有:

  • DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥

  • Handler:处理器,完成具体的业务逻辑,相当于Servlet或Action

  • HandlerMapping:DispatcherServlet接收到请求后,通过HandlerMapping将不同的请求映射到不同的Handler

  • HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口

  • HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果需要额外设置拦截器,可以添加拦截器)

  • HandlerAdapter:处理适配器,Handler执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到Java Bean等,这些操作都是由HandlerAdapter来完成,开发者只需要将注意力集中在业务逻辑的处理上,DispatcherServlet通过HandlerAdapter执行不同的Handler

  • ModelAndView:装载了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet

  • ViewResolver:视图解析器,DispatcherServlet通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端

Spring MVC的特点:

  • 清晰的角色划分

  • 灵活的配置功能

  • 提供了大量的控制器接口和实现类

  • 分离View层的实现

  • 国际化支持

  • 面向接口编程

Spring MVC简单使用

  1. 创建maven工厂,添加相关依赖

       <dependencies>
         <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-webmvc</artifactId>
           <version>5.3.5</version>
         </dependency>
       </dependencies>
  2. 在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>
       
       <!-- 配置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:springmvc.xml</param-value>
         </init-param>
       </servlet>
       <servlet-mapping>
         <servlet-name>dispatcherServlet</servlet-name>
         <url-pattern>/</url-pattern>
       </servlet-mapping>
         
        <!-- 添加乱码过滤器 -->
       <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>
         
     </web-app>
  3. 在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:mvc="http://www.springframework.org/schema/mvc"
            xmlns:context="http://www.springframework.org/schema/context"
            xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                                 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                                 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
     
     
         <!-- 自动扫描 -->
         <context:component-scan base-package="com.jarreet"></context:component-scan>
     
         <!-- 配置视图解析器 -->
         <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
             <property name="prefix" value="/"></property>
             <property name="suffix" value=".jsp"></property>
         </bean>
     
     </beans>
  4. 创建Handler(Controller)

     package com.jarreet.controller;
     
     import org.springframework.stereotype.Controller;
     import org.springframework.web.bind.annotation.RequestMapping;
     
     @Controller
     public class HelloHandler {
     
         @RequestMapping(value = "/index")
         public String index(){
             System.out.println("执行了index");
             return "index";
        }
     }
  5. 配置Tomcat服务器,测试



Spring MVC执行流程

下图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现。

简要分析执行流程:

  1. DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。 我们假设请求的url为 : http://localhost:8080/SpringMVC/hello

    如上URL拆分成三部分:

    http://localhost:8080服务器域名

    SpringMVC部署在服务器上的web站点

    hello表示控制器

    通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。

  2. HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。

  3. HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:hello。

  4. HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等。

  5. HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。

  6. Handler让具体的Controller执行。

  7. Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。

  8. HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。

  9. DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。

  10. 视图解析器将解析的逻辑视图名传给DispatcherServlet。

  11. DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。

  12. 最终视图呈现给用户。

Spring MVC注解详解

  • @RequestMapping

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

    @RequestMapping有如下一些参数:

    • value:指定URL请求的实际地址,是@RequestMapping的默认值

    • method:指定请求的类型,如:RequestMethod.GET、RequestMethod.POST、RequestMethod.PUT、RequestMethod.DELETE

    • params:指定请求中必须包含某些参数,否则无法调用该方法,如params = {"id", name"}

    注意:若想要获得HTTP请求发来的参数,只需要将参数名写进controller的形参中就行了;若HTTP请求参数的参数名与形参列表不一致,则可以在形参列表中添加@RequestParam注解完成HTTP请求参数与业务形参的映射

    @RequestMapping还提供了一些实用的功能:

    • 映射 Cookie:

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

       @RequestMapping("/cookie")
       public String cookie(@CookieValue(value = "JSESSIONID") String sessionId){
        System.out.println(sessionId);
        return "index";
       }
    • 使⽤ JavaBean 绑定参数: Spring MVC 会根据请求参数名和 JavaBean 属性名进⾏⾃动匹配,⾃动为对象填充属性值,同时⽀持及联属性。

      实体类:

       package com.jarreet.entity;
       
       import lombok.Data;
       
       @Data
       public class Address {
           private String value;
       }
       package com.jarreet.entity;
       
       import lombok.Data;
       
       @Data
       public class User {
        private long id;
        private String name;
        private Address address;
       }

      JSP页面:

       <%@ page contentType="text/html;charset=UTF-8" language="java" %>
       <html>
       <head>
        <title>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>

      controller:

      @RequestMapping(value = "/save",method = RequestMethod.POST)
      public String save(User user){
      System.out.println(user);
      return "index";
      }
    • JSP 页面的转发和重定向:

      • 转发(SpringMVC默认方式):

        @RequestMapping("/forward")
        public String forward(){
        return "forward:/index.jsp";
        }
      • 重定向:

        @RequestMapping("/redirect")
        public String redirect(){
        return "redirect:/index.jsp";
        }
  • @Controller

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

  • @ResponseBody

    @ResponseBody表示 Spring MVC 会直接将业务⽅法的返回值响应给客户端,如果不加@ResponseBody 注解,Spring MVC 会将业务⽅法的放回值传递给 DispatcherServlet,再由DisptacherServlet 调⽤ ViewResolver 对返回值进⾏解析,映射到⼀个 JSP 资源。

Spring MVC数据绑定(前端数据 ➜ 后端)

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

  • 基本数据类型

    @RequestMapping("/baseType")
    @ResponseBody
    public String baseType(int id){
    return id+"";
    }
  • 包装类

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

    包装类可以接收 null,当 HTTP 请求没有参数时,使⽤包装类定义形参的数据类型,程序不会抛出异常。

    @RequestParam:

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

    requried:设置 num 是否为必填项,true 表示必填,false 表示⾮必填,可省略;

    defaultValue = “0”:如果 HTTP 请求中没有 num 参数,默认值为0。

  • 数组

    @RestController
    @RequestMapping("/data")
    public class DataBindHandler {
    @RequestMapping("/array")
    public String array(String[] name){
    String str = Arrays.toString(name);
    return str;
    }
    }

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

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

  • List集合

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

    集合封装类:

    package com.jarreet.entity;

    import lombok.Data;
    import java.util.List;

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

    JSP:

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

    controller:

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

    自定义封装类:

    package com.jarreet.entity;

    import lombok.Data;
    import java.util.Map;

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

    JSP:

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

    controller:

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

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

    若处理 Spring MVC ⽆法加载静态资源,在 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>Title</title>
    <script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
    <script type="text/javascript">
    $(function(){
    var user = {
    "id":1,
    "name":"张三"
    };
    $.ajax({
    url:"/data/json",
    data:JSON.stringify(user),
    type:"POST",
    contentType:"application/json;charset=UTF-8",
    dataType:"JSON",
    success:function(data){
    alter(data.id+"---"+data.name);
    }
    })
    });
    </script>
    </head>
    <body>

    </body>
    </html>

    controller:

    @RequestMapping("/json")
    public User json(@RequestBody User user){
    System.out.println(user);
    user.setId(6);
    user.setName("张六");
    return user;
    }

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

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

    springmvc.xml 配置消息转换器解决乱码问题并添加 fastjson 配置:

    <mvc:annotation-driven>
    <!-- 消息转换器 -->
    <mvc:message-converters register-defaults="true">
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
    <property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
    </bean>
    <!-- 配置fastjson -->
    <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"></bean>
    </mvc:message-converters>
    </mvc:annotation-driven>

Spirng MVC模型数据解析(后端处理数据 ➜ 前端)

JSP 四⼤作⽤域对应的内置对象:pageContext、request、session、application;

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

Spirng MVC提供了原生的servlet API来添加数据:如向session中添加数据

@RequestMapping("/session")
public String session2(HttpServletRequest request){
HttpSession session = request.getSession();
User user = new User();
user.setId(1L);
user.setName("张三");
session.setAttribute("user",user);
return "view";
}

其他如向reques、application添加数据与向session添加数据类似

Spring MVC 除了提供了原生的servlet API外还提供了以下⼏种⽅式添加模型数据:

  • Map

    controller:

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

    jsp:

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

    controller:

    @RequestMapping("/model")
    public String model(Model model){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    model.addAttribute("user", user);
    return "view";
    }

    jsp和Map相同

  • ModelAndView

    controller:ModelAndView支持多种方式返回视图

    @RequestMapping("/modelAndView")
    public ModelAndView modelAndView(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("user",user);
    modelAndView.setViewName("view");
    return modelAndView;
    }

    @RequestMapping("/modelAndView2")
    public ModelAndView modelAndView2(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("user",user);
    View view = new InternalResourceView("/view.jsp");
    modelAndView.setView(view);
    return modelAndView;
    }

    @RequestMapping("/modelAndView3")
    public ModelAndView modelAndView3(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView("view");
    modelAndView.addObject("user",user);
    return modelAndView;
    }

    @RequestMapping("/modelAndView4")
    public ModelAndView modelAndView4(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    View view = new InternalResourceView("/view.jsp");
    ModelAndView modelAndView = new ModelAndView(view);
    modelAndView.addObject("user",user);
    return modelAndView;
    }

    @RequestMapping("/modelAndView5")
    public ModelAndView modelAndView5(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    Map<String,User> map = new HashMap<>();
    map.put("user",user);
    ModelAndView modelAndView = new ModelAndView("view",map);
    return modelAndView;
    }

    @RequestMapping("/modelAndView6")
    public ModelAndView modelAndView6(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    Map<String,User> map = new HashMap<>();
    map.put("user",user);
    View view = new InternalResourceView("/view.jsp");
    ModelAndView modelAndView = new ModelAndView(view,map);
    return modelAndView;
    }

    @RequestMapping("/modelAndView7")
    public ModelAndView modelAndView7(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView("view","user",user);
    return modelAndView;
    }

    @RequestMapping("/modelAndView8")
    public ModelAndView modelAndView8(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    View view = new InternalResourceView("/view.jsp");
    ModelAndView modelAndView = new ModelAndView(view,"user",user);
    return modelAndView;
    }
  • @ModelAttribute

    • 定义⼀个⽅法,该⽅法专⻔⽤来返回要填充到模型数据中的对象。

      @ModelAttribute
      public User getUser(){
      User user = new User();
      user.setId(1L);
      user.setName("张三");
      return user;
      }
      @ModelAttribute
      public void getUser(Map<String,User> map){
      User user = new User();
      user.setId(1L);
      user.setName("张三");
      map.put("user",user);
      }
      @ModelAttribute
      public void getUser(Model model){
      User user = new User();
      user.setId(1L);
      user.setName("张三");
      model.addAttribute("user",user);
      }
    • 业务⽅法中⽆需再处理模型数据,只需返回视图即可。

      @RequestMapping("/modelAttribute")
      public String modelAttribute(){
      return "view";
      }
  • @SessionAttribute(此方法不常用,大多情况下还是采用原生的 servlet API )

    @SessionAttributes(value = {"user","address"})
    public class ViewHandler {
    }

    对于 ViewHandler 中的所有业务⽅法,只要向 request 中添加了 key = "user"、key = "address" 的对象时,Spring MVC 会⾃动将该数据添加到 session 中,保存 key 不变。

    @SessionAttributes(types = {User.class,Address.class})
    public class ViewHandler {
    }

    对于 ViewHandler 中的所有业务⽅法,只要向 request 中添加了数据类型是 User 、Address 的对象时,Spring MVC 会⾃动将该数据添加到 session 中,保存 key 不变。

Spring MVC自定义数据转化器

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

比如,客户端输⼊ String 类型的数据 "2019-03-03",⾃定义转换器将该数据转为 Date 类型的对象。

  • 创建 DateConverter 转换器,实现 Conveter 接⼝。

    package com.jarreet.converter;

    import org.springframework.core.convert.converter.Converter;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;

    public class DateConverter implements Converter<String, Date> {

    private String pattern;

    public DateConverter(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;
    }
    }
  • springmvc.xml 配置转换器

    <!-- 配置⾃定义转换器 -->
    <bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
    <list>
    <bean class="com.jarreet.converter.DateConverter">
    <constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
    </bean>
    </list>
    </property>
    </bean>

    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
  • jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    <title>Title</title>
    </head>
    <body>
    <form action="/converter/date" method="post">
    请输⼊⽇期:<input type="text" name="date"/>(yyyy-MM-dd)<br/>
    <input type="submit" value="提交"/>
    </form>
    </body>
    </html>
  • controller

    package com.jarreet.controller;

    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.Date;

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

Spring MVC REST

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

  • 资源(Resource)

    ⽹络上的⼀个实体,或者说⽹络中存在的⼀个具体信息,⼀段⽂本、⼀张图⽚、⼀⾸歌曲、⼀段视频等等,总之就是⼀个具体的存在。可以⽤⼀个 URI(统⼀资源标志符)指向它,每个资源都有对应的⼀个特定的 URI,要获取该资源时,只需要访问对应的 URI 即可。

  • 表现层(Representation)

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

  • 状态转换(State Transfer)

    客户端如果希望操作服务器中的某个资源,就需要通过某种⽅式让服务端发⽣状态转换,⽽这种转换是建⽴在表现层之上的,所有叫做"表现层状态转换"。

特点:

  • URL 更加简洁

  • 有利于不同系统之间的资源共享,只需要遵守⼀定的规范,不需要进⾏其他配置即可实现资源共享

如何使用:

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

GET ⽤来表示获取资源;

POST ⽤来表示新建资源;

PUT ⽤来表示修改资源;

DELETE ⽤来表示删除资源。

Controller:

package com.jarreet.controller;

import com.jarreet.entity.Student;
import com.jarreet.entity.User;
import com.jarreet.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;

@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);
}
}

StudentRepository:

package com.jarreet.repository;

import com.southwind.entity.Student;
import java.util.Collection;

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

StudentRepositoryImpl:

package com.jarreet.repository.impl;

import com.jarreet.entity.Student;
import com.jarreet.repository.StudentRepository;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

@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 文件上传下载

  1. 单文件上传

    底层是使⽤ Apache fifileupload 组件完成上传,Spring MVC 对这种⽅式进⾏了封装。

    • pom.xml添加依赖

      <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.5</version>
      </dependency>

      <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.3</version>
      </dependency>
    • jsp

      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <%@ page isELIgnored="false" %>
      <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}">
      </body>
      </html>

      注意点:input的type设置为file;form的method设置为post(get只能将文件名传给服务器);from 的 enctype 设置为 multipart-form-data(如果不设置只能将⽂件名传给服务器)

    • controller

      package com.jarreet.controller;

      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.multipart.MultipartFile;
      import javax.servlet.http.HttpServletRequest;
      import java.io.File;
      import java.io.IOException;

      @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.setAttribute("path","/file/"+name);
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      return "upload";
      }
      }
    • springmvc.xml

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

      <servlet-mapping>
      <servlet-name>default</servlet-name>
      <url-pattern>*.png</url-pattern>
      </servlet-mapping>
  2. 多文件上传

    • pom.xml添加依赖

      <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
      </dependency>

      <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
      </dependency>
    • jsp

      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <%@ page isELIgnored="false" %>
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <html>
      <head>
      <title>Title</title>
      </head>
      <body>
      <form action="/file/uploads" method="post" enctype="multipart/form-data">
      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}" width="300px">
      </c:forEach>
      </body>
      </html>
    • controller

      @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";
      }
  3. 下载

    • 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>
    • controller

      @GetMapping("/download/{name}")
      public void download(@PathVariable("name") 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("ContentDisposition","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();
      }
      }
      }
      }
      }
      }

Spirng MVC 表单标签库

  • controller

    @GetMapping("/get")
    public ModelAndView get(){
    ModelAndView modelAndView = new ModelAndView("tag");
    Student student = new Student(1L,"张三",22);
    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/>
    <input type="submit" value="提交"/>
    </form:form>
    </body>
    </html>
    1. JSP ⻚⾯导⼊ Spring MVC 表单标签库,与导⼊ JSTL 标签库的语法⾮常相似,前缀 prefifix 可以⾃定义,通常定义为 from。

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

      Handeler:modelAndView.addObject("student",student);
      JSP:<form:form modelAttribute="student">
    3. form 表单完成绑定之后,将模型数据的值取出绑定到不同的标签中,通过设置标签的 path 属性完成,将 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 表达式的形式从域对象中获取数据,不能直接写属性名。

  • rabiobutton

    <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 的验证器结合起来使⽤。

Spinrg MVC 数据校验

Spring MVC 提供了两种数据校验的⽅式:1、基于 Validator 接⼝。2、使⽤ Annotation JSR - 303 标准进⾏校验。

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

  1. 基于 Validator 接⼝

    • 实体类 Account

      package com.jarreet.entity;

      import lombok.Data;
      @Data
      public class Account {
      private String name;
      private String password;
      }
    • ⾃定义验证器 AccountValidator,实现 Validator 接⼝

      package com.jarreet.validator;

      import com.southwind.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,"姓名不能为空");
      ValidationUtils.rejectIfEmpty(errors,"password",null,"密码不能为空");
      }
      }
    • 控制器

      package com.jarreet.controller;

      import com.southwind.entity.Account;
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.validation.BindingResult;
      import org.springframework.validation.annotation.Validated;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestMapping;

      @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, BindingResultbindingResult){
      if(bindingResult.hasErrors()){
      return "login";
      }
      return "index";
      }
      }
    • springmvc.xml配置验证器

      <bean id="accountValidator" class="com.southwind.validator.AccountValidator">
      </bean> <mvc:annotation-driven validator="accountValidator"></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" %>
      <%@ taglib prefix="from" uri="http://www.springframework.org/tags/form" %>
      <html>
      <head>
      <title>Title</title>
      </head>
      <body>
      <form:form modelAttribute="account" action="/validator/login"method="post">
      姓名:<form:input path="name"/><from:errors path="name"></from:errors> <br/>
      密码:<form:input path="password"/><from:errors path="password"></from:errors><br/>
      <input type="submit" value="登录"/>
      </form:form>
      </body>
      </html>
  2. 使⽤ Annotation JSR - 303 标准进⾏验证,需要导⼊⽀持这种标准的依赖 jar ⽂件,这⾥我们使⽤Hibernate Validator。

    • pom.xml添加依赖

      <!-- JSR-303 -->
      <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>5.3.6.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.jarreet.entity;

      import lombok.Data;
      import org.hibernate.validator.constraints.Email;
      import org.hibernate.validator.constraints.NotEmpty;
      import javax.validation.constraints.Pattern;
      import javax.validation.constraints.Size;

      @Data
      public class Person {
      @NotEmpty(message = "⽤户名不能为空")
      private String username;
      @Size(min = 6,max = 12,message = "密码6-12位")
      private String password;
      @Email(regexp = "^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\\\.[a-zA-Z0-9-]+)*\\\\.[a-zA-Z0-9]{2,6}$",message = "请输⼊正确的邮箱格式")
      private String email;
      @Pattern(regexp = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\\\\\\\d{8}$",message = "请输⼊正确的电话")
      private String phone;
      }
    • ValidatorHandler

      @GetMapping("/register")
      public String register(Model model){
      model.addAttribute("person",new Person());
      return "register";
      }

      @PostMapping("/register")
      public String register(@Valid Person person, BindingResult bindingResult){
      if(bindingResult.hasErrors()){
      return "register";
      }
      return "index";
      }
    • springmvc.xml

      <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/register2"method="post">
      ⽤户名:<form:input path="username"></form:input><form:errors path="username"/><br/>
      密码:<form:password path="password"></form:password><form:errors path="password"/><br/>
      邮箱:<form:input path="email"></form:input><form:errors path="email"/><br/>
      电话:<form:input path="phone"></form:input><form:errors path="phone"/><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,其值为空

posted @ 2021-05-06 00:50  离渊灬  阅读(73)  评论(0)    收藏  举报