SpringMVC

作者: ttiga | 来源:发表于2021-07-23 14:03 被阅读0次

    SpringMVC环境的基本搭建

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

    什么是 MVC 设计模式?

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

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

    Spring MVC 的核心组件

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

    Spring MVC 的工作流程

    • 客户端请求被 DisptacherServlet 接收。
    • 根据 HandlerMapping 映射到 Handler。
    • 生成 Handler 和 HandlerInterceptor。
    • Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一并返回给 DisptacherServlet。
    • DispatcherServlet 通过 HandlerAdapter 调用 Handler 的方法完成业务逻辑处理。
    • Handler 返回一个 ModelAndView 给 DispatcherServlet。
    • DispatcherServlet 将获取的 ModelAndView 对象传给 ViewResolver 视图解析器,将逻辑视图解析为物理视图 View。
    • ViewResovler 返回一个 View 给 DispatcherServlet。
    • DispatcherServlet 根据 View 进行视图渲染(将模型数据 Model 填充到视图 View 中)。
    • DispatcherServlet 将渲染后的结果响应给客户端。
    image.png

    Spring MVC 流程非常复杂,但实际开发中很简单,因为大部分的组件不需要开发者创建、管理,只需要通过配置文件的方式完成配置即可,相当于框架已经把需要的组件给你了,你只需要通过一个配置文件告诉工程怎么协同工作就可以了,真正需要开发者进行处理的只有 Handler 、View。Model包括在Handler里了,Handler组件需要自己写是因为Handler里是写业务的例如: 注册,登录,验证业务等等,都是跟项目实际相关的,而SpringMVC作为工具来讲肯定不会帮你实现,因为他提供的都是通用的功能.同理View也是根据具体的业务和需求来决定怎么展示数据

    如何使用?

    • 创建 Maven 工程选择webapp模板,配置 pom.xml
      web相关的组件都放在webapp包下,在main下添加java和resources资源包
    <dependencies>
    <!--    添加springmvc的依赖-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.0.11.RELEASE</version>
        </dependency>
      </dependencies>
    
    • 在 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>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--  让DispatcherServlet读取springmvc.xml,添加初始化参数-->
        <init-param>
    <!--      上下文配置的路径-->
          <param-name>contextConfigLocation</param-name>
    <!--      classpath: 表示resources包的根目录,直接把springmvc的路径写进来就行了-->
          <param-value>classpath:springmvc.xml</param-value>
        </init-param>
      </servlet>
      <!--  添加Servlet映射-->
      <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
    <!--    拦截所有请求,所以写斜杠就行-->
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    <!--  还需要把springmvc自己的文件加进来,就是让DispatcherServlet来读取springmvc自己的配置文件-->
    </web-app>
    
    • 配置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-3.2.xsd">
    
    <!--    配置springmvc的相关配置-->
    <!--    自动扫描: 会自动地把相关组件扫到IOC中,所以在java包下写的任何类的组件都交给ioc来管理-->
        <context:component-scan base-package="com.tiga"></context:component-scan>
    <!--    配置视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--    比如将"index"字符串解析成物理视图,让他与index.jsp联系起来:结合前缀,后缀变成/index.jsp就是个完整路径-->
    <!--        配置页面的前缀,value值是斜杠,表示webapp的根目录-->
            <property name="prefix" value="/"></property>
    <!--        配置页面的后缀-->
            <property name="suffix" value=".jsp"></property>
        </bean>
        
    </beans>
    
    • 创建 Handler类
    // 控制器
    @Controller// 类似Component注解,也是可以把类交给ioc管理,只是多了控制器的功能
    public class HelloHandler {
        // 业务方法
        // 让客户端地址栏的请求与业务方法关联起来
        // 当地址栏输入index就调用index方法
        @RequestMapping(value = "/index")// 当访问index的时候就映射到index方法
        public String index(){
            System.out.println("执行了index..");
            // 返回一个逻辑视图,然后再结合视图解析器就可以找到index.jsp了
            return "index";
        }
    }
    
    • 执行流程(原理):

    启动服务器后,通过客户端(浏览器)地址栏发送index请求,会被服务器中映射的在web.xml中配置的DispatcherServlet捕获,index请求是通过RequestMapping注解映射的,捕获到index之后会根据HandlerMapping的参数映射找到对应的Handler控制器类,然后进到对应Handler类的方法里,方法调用是由SpringMVC完成,他会实例化Handler类,然后调用映射方法,方法返回一个逻辑视图,这个逻辑视图会返回到DispatcherServlet中,然后DispatcherServlet再把逻辑视图交给ViewResolver(视图解析器)解析,解析规则已在springmvc.xml中定义好了,根据前缀和后缀解析,解析后从一个逻辑视图路径变成一个物理视图路径:/index.jsp,因为部署到tomcat后web应用程序是在target包下的,所以这时候他就会在target包的web应用的根目录下找到index.jsp对应的物理视图返回给客户端,就能看到页面内容

    Spring MVC 注解

    • @RequestMapping

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

    • @Controller

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

    • @RequestMapping 相关参数

    1、value:指定客户端你 URL 请求的实际地址,是 @RequestMapping 的默认值。可以省略.

    @RequestMapping(value = "/index")// 当访问index的时候就映射到当前添加注解的方法
    // 等于@RequestMapping("/index")
        public String index(){
            System.out.println("执行了index..");
            // 返回一个逻辑视图,然后再结合视图解析器就可以找到index.jsp了
            return "index";
        }
    

    2、method:指定请求的 method 类型(HTTP请求类型),常用的: GET、POST、PUT、DELET。不添加method,则可以放行大部分类型的请求

    @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。

    • 在业务方法中获取到URL请求的参数
      在之前Servlet获取参数需要通过request对象的getparameter方法获取,现在不用手动获取,通过HandlerAdapter来完成数据处理,我们只需要在业务方法中添加想要的与params注解参数同名的形参,springmvc框架就会自动把请求的的参数自动赋给方法的形参

    • 体现HandlerAdapter的用途: 数据类型的转换
      关于参数绑定,在形参列表中通过添加 @RequestParam 注解完成 HTTP 请求参数与业务方法形参的映射。

      @RequestMapping(value = "/index",method = RequestMethod.GET,params = {"name","id=10"})// 当访问index的时候就映射到当前添加注解的方法
        // 获取的形参名必须要跟注解param属性的参数一致,否则会报异常:给形参赋了null值,因为之前是通过方法形参名与注解param属性参数名一致来赋值的,而他们名字不一致的时候就不会完成赋值
        // 若非要名字不同,就可以通过给形参加RequestParam注解来映射,名字相同可以不加注解
        public String index(@RequestParam("name") String str,@RequestParam("id") Integer age){
            System.out.println("执行了index..");
            System.out.println(age + ":" + str);
            // 返回一个逻辑视图,然后再结合视图解析器就可以找到index.jsp了
            return "index";
        }
    

    上述代码表示将请求的参数 name 和 id 分别赋给了形参 str 和 age ,同时自动完成了数据类型转换,将 “10” 转为了 int 类型的 10,再赋给 age,这些工作都是由 HandlerAdapter 来完成的。HandlerAdapter可以理解为你的助手或保姆,你希望在方法中用到哪些参数,直接在形参里添加就行,添加之后HandlerAdapter就自动帮你完成参数的赋值

    Spring MVC 也支持 RESTful 风格的 URL。REST是互联网软件架构的一种形式,通过这种形式就可以完成不同系统之间的数据交互,相当于是统一的规则

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

    REST风格url类型:http://localhost:8080/hello/index/zhangsan/10

    SpringMVC中解析RESTful风格的url参数类型

    // RESTful风格的URL请求类型参数解析
        // 如果要从url地址获取参数就要通过下面的写法解析,要在定义路径的时候就要把参数按以下格式写出来
        @RequestMapping("/rest/{name}/{id}")
        // 基于RESTful风格形参的参数不会和注解参数直接映射,要手动给形参加@PathVariable注解并给注解赋值为形参名才能映射
        // 因为RESTful风格的URL地址没有注明参数名
        public String rest(@PathVariable("name") String name,@PathVariable("id") int id){
            System.out.println(name);
            System.out.println(id);
            System.out.println("执行了index...");
            return "index";
        }
    

    通过 @PathVariable 注解完成请求参数与形参的映射。这就是SpringMVC对REST的支持

    • SpringMVC映射 Cookie

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

     @RequestMapping("/cookie")
        // 获取客户端的JSESSIONID的注解
        public String cookie(@CookieValue(value = "JSESSIONID") String sessionId) {
            System.out.println(sessionId);
            return "index";
        }
    
    • 使用 JavaBean(对象) 绑定参数

    Spring MVC 会根据请求参数名和 JavaBean 属性名进行自动匹配,自动为对象填充属性值,同时支持及联属性(对象里的属性是对象)。

    import lombok.Data;
    
    @Data
    public class Address {
        private String value;
    }
    
    import lombok.Data;
    
    @Data
    public class User {
        private long id;
        private String name;
        private Address address;
    }
    
    • 页面
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <form action="/hello/save" method="post">
    <%--    通过input标签的中的name属性来给javabean属性自动映射--%>
        用户id: <input type="text" name="id"><br>
        用户名: <input type="text" name="name"><br>
    <%--    这里的用户地址是address对象的属性值,不能直接写address,
    因为address是个对象不像name是个String类型的值,所以input标签只能添加给address对象里的属性
    所以标签name属性写成address.value就行,做一个级联--%>
    <%--    从User中的address属性 找到Address中的value--%>
    <%--这个表单添加数据的主体是User类,相当于给user对象的address属性添加值,
    address属性又是个对象,就相当于把值加给了user对象的address对象属性的value属性--%>
        地址: <input type="text" name="address.value"><br>
        <input type="submit" value="注册">
    </form>
    </body>
    </html>
    
    • 业务方法
    // 把注册表单和业务方法关联起来
        @RequestMapping(value = "/save", method = RequestMethod.POST)
        // HandlerAdapter自动封装数据,SpringMVC框架把注册表单中用户名和id分别赋给业务方法的形参
        // SpringMVC框架会根据请求参数名和 JavaBean 属性名进行自动匹配,也就是这时候是要求把id和name赋值给形参user对象的属性
        // 所以把表单的name分别赋值为user对象的属性名就能完成映射
        public String save(User user){
            System.out.println(user);
            return "index";
        }
    

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

    <!--    解决中文乱码问题,使用springmvc提供的过滤器-->
        <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>
    

    这样就完成java对象的绑定了

    • jsp页面的转发和重定向:
      转发: 服务器跳转,地址栏不变,同一次请求,在请求里传数据在转发的页面可以取出
      重定向: 客户端跳转,地址栏改变,相当于两次请求,在请求里传数据在重定向页面不能取出

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

    1.以转发形式响应页面

    // 转发形式响应页面(SpringMVC默认形式)
        @RequestMapping("/forward")
        public String forward() {
            return "forward:/index.jsp";
            // 等价于
            // return "index";
        }
    

    2..若想以重定向的形式响应

    // 重定向形式响应页面
        @RequestMapping("/redirect")
        public String redirect(){
            return "redirect:/index.jsp";
        }
    

    Spring MVC 数据绑定

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

    • HTTP请求参数绑定到基本数据类型参数里
    @Controller
    @RequestMapping("/data")
    public class DataBindHandler {
        @RequestMapping("/baseType")
        @ResponseBody// 不会把方法返回结果按照视图解析器解析,不会把他映射成jsp页面,而是把它的值直接返回到客户端
        public String baseType(Integer id) {
            // 不希望返回的值按照视图解析,而是把原本值返回到客户端
            return id + "";
        }
    }
    

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

    • 当形参的类型是基本数据类型时,但是客户端发送请求时没有给参数值,所以此时形参只能赋null,但是基本数据类型不能接收null,所以客户端会抛出异常,用包装类可以解决赋null值的问题,当请求中没传值也不会报错

    • 包装类

    // 包装类解决业务方法传入null值的问题,解决客户端抛异常
        @RequestMapping("/package")
        @ResponseBody
        public String packageType(Integer id) {
            return id + "";
        }
    
    包装类可以接收 null,当 HTTP 请求没有参数时,使用包装类定义形参的数据类型,程序不会抛出异常。

    @RequestParam注解中的几个属性

    value = "num":将 HTTP 请求中名为 num 的参数赋给业务方法的形参 id。

    requried:设置 num 是否为必填项,true 表示必填,false 表示非必填,默认是true,可省略。

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

    • HTTP请求参数绑定到数组中
    // 若当前Handler类只是对外提供数据服务,只返回model,则可以给类添加@RestController
    // 若Handler类要返回model和view则只能用@Controller
    @RestController// 表示把控制器里所有方法的返回值直接返回到客户端
    @RequestMapping("/data")
    public class DataBindHandler {
    @RequestMapping("/array")
        // @ResponseBody
        public String array(String[] name) {
            // 遍历数组,拼成字符串
            String str = Arrays.toString(name);
            return str;
        }
    }
    
    • @RestController 表示该控制器会直接将当前类的所有业务方法的返回值响应给客户端,不进行视图解析。
    • @Controller 表示该控制器的每一个业务方法的返回值都会交给视图解析器进行解析,如果只需要将数据响应给客户端,而不需要进行视图解析,则需要在对应的业务方法定义处添加 @ResponseBody。
    @RestController// 表示把控制器里所有方法的返回值直接返回到客户端
    @RequestMapping("/data")
    public class DataBindHandler {
    @RequestMapping("/array")
        // @ResponseBody
        public String array(String[] name) {
            // 遍历数组,拼成字符串
            String str = Arrays.toString(name);
            return str;
        }
    }
    

    等同于

    @Controller// 表示把控制器里所有方法的返回值按视图解析器解析再返回到客户端
    @RequestMapping("/data")
    public class DataBindHandler {
    @RequestMapping("/array")
        @ResponseBody
        public String array(String[] name) {
            // 遍历数组,拼成字符串
            String str = Arrays.toString(name);
            return str;
        }
    }
    
    • HTTP请求参数绑定到List集合中

    Spring MVC 不支持 List 类型的直接转换,需要对 List 集合进行包装。相当于只能识别实体类,所以只能把List集合类型作为实体类的属性封装成一个类

    • 集合封装类
    package com.southwind.entity;
    
    import lombok.Data;
    
    import java.util.List;
    
    @Data
    public class UserList {
        private List<User> users;
    }
    
    • JSP表单页面
    <%--
      Created by IntelliJ IDEA.
      User: Administrator
      Date: 2021/7/25
      Time: 16:27
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <form action="/data/list">
        <%--name属性不能直接与业务方法的userList对象直接映射,而是要和对象的属性映射
        并且userList的属性又是集合,所以不能直接与属性映射,要把input的值赋给集合里的一个对象的属性--%>
        用户1编号: <input type="text" name="users[0].id"><br>
        用户1名称: <input type="text" name="users[0].name"><br>
    <%--        address级联属性--%>
        用户1地址: <input type="text" name="users[0].address.value"><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>
    
    • 业务方法
    //http请求参数绑定到List集合中
        // 处理响应中文乱码问题: 通过修改response对象响应的编码,直接在形参里添加response对象,HandlerAdapter自动帮你创建
        // Serlvet的jar包在tomcat里,在创建完web工程会自动把tomcat的jar包依赖进去,现在maven工程不会依赖进来,所要手动导入Servlet包
        // 参数2: 指定返回的内容类型 ,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
        // @RequestMapping(value = "/list",produces = "text/html;charset=utf-8;")
        @RequestMapping("/list")
        public String list(UserList userList) {
            // Stringbuffer节省内存资源空间,提高效率,String浪费内存空间
            StringBuffer str = new StringBuffer();
            for (User user : userList.getUsers()){
                str.append(user);
            }
            return str.toString();
        }
    }
    
    • 处理 @ResponseBody 中文乱码方式一,在 springmvc.xml 中配置消息转换器。
    <!--    通过配置文件方式解决中文乱码问题,开发者无需在每个业务方法中处理中文乱码,
    只需交给springmvc同一进行管理,业务方法返回出去的数据会自动在这里处理,相当于有反向过滤器的功能-->
    <!--    mvc注解驱动-->
        <mvc:annotation-driven>
    <!--        消息转换器: 处理由@responseBody注解响应页面的中文乱码问题,默认注册设为true-->
            <mvc:message-converters register-defaults="true">
                <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                    <property name="supportedMediaTypes" value="text/html;charset=utf-8;"></property>
                </bean>
            </mvc:message-converters>
        </mvc:annotation-driven>
    

    方式二: 直接在@RequestMapping注解添加produces参数

    @RequestMapping(value = "/list",produces = "text/html;charset=utf-8;")
    
    • HTTP请求绑定数据都Map集合
    • 自定义Map集合封装类
    package com.tiga.entity;
    
    import lombok.Data;
    
    import java.util.Map;
    
    /**
     * @author tiga
     */
    @Data
    public class UserMap {
        private Map<String, User> users;
    }
    
    • 业务方法
    // http请求参数绑定到map集合中
        @RequestMapping("/map")
        public String map(UserMap users) {
            StringBuffer str = new StringBuffer();
            for (String key : users.getUsers().keySet()) {
                User user = users.getUsers().get(key);
                str.append(user);// 等同于user.toString(),把user转成String类型加入到str
            }
            return str.toString();
        }
    
    • jsp页面
      <%--
      Created by IntelliJ IDEA.
      User: Administrator
      Date: 2021/7/26
      Time: 10:35
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <form action="/data/map">
        <%--map集合的value按照key来存储而不是按照数组索引,
        也就是不能直接通过索引找到对应的value值,
        这里的key值可以随便定义,因为后台会根据定义的key取对应的value,key定义成什么都不影响取值--%>
        用户:1 <input type="text" name="users['a'].id"><br>
        用户:1 <input type="text" name="users['a'].name"><br>
        用户:1 <input type="text" name="users['a'].address.value"><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>
    
    • HTTP请求参数绑定数据到JSON中

    客户端发送JSON 格式的数据,直接通过 Spring MVC 绑定到业务方法的形参中。
    json相当于以一个对象的形式传过来,这是就需要借助ajax请求去处理

    如何使用ajax请求:
    1.导入jQuery.js文件到webapp工程下
    2.测试jQuery环境是否能用
    刚测试就报错

    image.png

    说明当前jQuery文件没有被识别出
    通过网络监听,发现jQuery文件找不到,所以加载不出来,这实际上是springmvc把它拦截了,因为在web.xml配置了前置控制器DispatcherSerlvet,而他会把所有请求拦截,包括加载jQuery.js静态资源,但是我们希望只是拦截逻辑请求

    类似于:

    image.png image.png

    而加载jQuery.js这种静态资源,不希望去映射,直接让他找对应物理资源就行了,所以jQuery.js请求不需要被前置控制器拦截,直接在web.xml中单独配置直接映射就行了

    处理 Spring MVC 无法加载静态资源,在 web.xml 中添加配置即可

    <!--    处理springmvc无法加载静态资源的问题,各种各样的资源都这样处理,jsp除外,他会自动识别-->
        <!-- 手动配置,单独处理jQuery的请求的映射,默认的方式,不交给前置控制器-->
        <servlet-mapping>
            <servlet-name>default</servlet-name>
            <url-pattern>*.js</url-pattern>
        </servlet-mapping>
    

    通过javascript发送json格式数据,表单是发送不了的

    JSP页面

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    <%--    导入jQuery环境--%>
        <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
        <script type="text/javascript">
            // 写初始化函数
            $(function (){
                // 发送json格式数据,类似于对象的东西
                // 定义一个json对象
                var user = {
                    "id":1,
                    "name":"张三"
                };
                    // 定义通过jQuery封装的ajax请求
                $.ajax({
                    // 要请求到达的地址
                    url: "/data/json",
                    // 把user变量转成json格式
                    data: JSON.stringify(user),
                    // 请求方式
                    type: "POST",
                    // 设置编码格式,处理中文乱码
                    contentType: "application/JSON;charset=utf-8",
                    // ajax接收json数据之后转成java对象,再json数据的形式再返回
                    dataType: "JSON",
                    // 回调函数,形参:后台返回的数据
                    success: function (data) {
                        alert(data.id + "---" + data.name);
                    }
                });
            });
        </script>
    </head>
    <body>
    
    </body>
    </html>
    

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

    <!--    添加解析json数据工具的依赖-->
    <!--    把json数据转成java对象-->
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.32</version>
        </dependency>
    

    springmvc.xml 添加 fastjson 配置。

    <!--    mvc注解驱动-->
        <mvc:annotation-driven>
    <!--        消息转换器: 处理由@responseBody注解响应页面的中文乱码问题,默认注册设为true-->
            <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>
    
    • json数据转换流程
      前端客户端把json数据传给后台,先通过fastjson工具把json数据转成java对象传入到业务方法形参,先把fastjson工具的依赖添加到pom.xml中,然后再在springmvc.xml中配置bean对象,配置完后,就可以在业务方法接收到json数据,json转成user,方法结束再返回回前端,又通过fastjson工具把user对象转成json数据.

    Spring MVC 模型数据解析

    JSP 四大作用域对应的内置对象:pageContext(不常用)、request(最常用)、session、application。数据从后台传到前台,需要传递,所以pageContext基本失效

    模型数据解析指的是 把模型数据绑定到域对象里,再从域对象里把域对象传到jsp,再从jsp把它解析出来

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

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

    • Map
    • Model
    • ModelAndView

    上面三个都是往request里加

    • @SessionAttribute: 把模型数据直接加到Session里
    • @ModelAttribute: 向application添加

    将模型数据绑定到 request 对象。

    1.通过Map添加:

    // 返回一个逻辑视图
        @RequestMapping("/map")
        // 此时的形参不是用来接收数据,而是往外传数据的,
        // 这个map对象实际上是springmvc框架里BindingAwareModelMap类的map对象
        public String map(Map<String,User> map) {
            User user = new User();
            user.setId(1L);
            user.setName("张三");
            // 向用户展示一个视图,需要user在视图进行展示,这叫模型数据解析
            // user对象被传到框架里的map,所以map里存的就是模型数据
            map.put("user", user);// key为 "key" value为 user对象
            // 对象会自动放到request域对象里,并会传到视图中,所以在request里取对象就行了
            // 把字符串传给dispatcherServlet,然后dispatcherServlet再结合viewResolver就可以进行解析了
            // 解析后就可以找到对应字符串的jsp页面,同时他还会把模型数据加进来,他会把map里的数据取出来,通过调用setAttribute方法
            // 存到request域对象,jsp页面存在的request的key和value分别与map的数据进行对应,所以在jsp就能直接取出
            return "view";
        }
    
    • 对应jsp页面
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%--解析EL表达式--%>
    <%@page isELIgnored="false" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        ${requestScope.user}
    </body>
    </html>
    

    2.通过Model添加:

    // model添加模型数据
        @RequestMapping("/model")
        public String model(Model model) {
            User user = new User();
            user.setId(2L);
            user.setName("李四");
            // 添加到model中
            model.addAttribute("user", user);
            // 返回视图后会把model里的数据存到request对象对应的值
            return "view";
        }
    
    • jsp页面同上

    3.通过ModelAndView添加到request对象:

      // 上面之前的model和view是分开的,model是存到map或model对象里,view是直接返回的
        // 所谓modelandview就是把view和model装到一块,然后把这完整对象进行返回
        // ModelAndView添加数据
        @RequestMapping("/modelAndView")
        public ModelAndView modelAndView() {
            User user = new User();
            user.setId(3L);
            user.setName("王五");
            // 创建一个modelAndview对象
            ModelAndView modelAndView = new ModelAndView();
            // 把model和view整合到对象中
            modelAndView.setViewName("view");
            modelAndView.addObject("user", user);
            // 直接返回对象
            return modelAndView;
        }
    
        // ModelAndView添加数据2
        @RequestMapping("/modelAndView2")
        public ModelAndView modelAndView2() {
            User user = new User();
            user.setId(4l);
            user.setName("孙七");
            // 创建modelAndView对象
            ModelAndView modelAndView = new ModelAndView();
            // 对象中添加模型
            modelAndView.addObject("user", user);
            // 创建一个View视图对象,传入视图信息,形参传入目标物理视图路径
            View view = new InternalResourceView("/view.jsp");
            // 对象中添加视图
            modelAndView.setView(view);
            return modelAndView;
        }
    
        // ModelAndView添加数据3
        @RequestMapping("/modelAndView3")
        public ModelAndView modelAndView3() {
            User user = new User();
            user.setId(5l);
            user.setName("周八");
            // 创建modelAndeView对象并传入目标物理视图名字
            ModelAndView modelAndView = new ModelAndView("view");
            // 只需要给对象添加模型就行
            modelAndView.addObject("user", user);
            return modelAndView;
        }
    
        // ModelAndView添加数据4
        @RequestMapping("/modelAndView4")
        public ModelAndView modelAndView4() {
            User user = new User();
            user.setId(5l);
            user.setName("周八");
            // 创建view对象,添加目标物理视图路径
            View view = new InternalResourceView("/view.jsp");
            // 把view对象传入到modelAndView对象中
            ModelAndView modelAndView = new ModelAndView(view);
            // modelAndView添加模型
            modelAndView.addObject("user", user);
            return modelAndView;
        }
    
        // ModelAndView添加数据5
        @RequestMapping("/modelAndView5")
        public ModelAndView modelAndView5() {
            User user = new User();
            user.setId(5l);
            user.setName("周八");
            // 创建一个map集合,把model存到map集合中
            Map<String, User> map = new HashMap<>();
            map.put("user", user);
            ModelAndView modelAndView = new ModelAndView("view", map);
            return modelAndView;
        }
    
        // ModelAndView添加数据6
        @RequestMapping("/modelAndView6")
        public ModelAndView modelAndView6() {
            User user = new User();
            user.setId(5l);
            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;
        }
    
        // ModelAndView添加数据7
        @RequestMapping("/modelAndView7")
        public ModelAndView modelAndView7() {
            User user = new User();
            user.setId(5l);
            user.setName("周八");
            // 创建modelAndView对象,构造器传入视图名,model的key,model的value
            ModelAndView modelAndView = new ModelAndView("view", "user", user);
            return modelAndView;
        }
    
        // ModelAndView添加数据8
        @RequestMapping("/modelAndView8")
        public ModelAndView modelAndView8() {
            User user = new User();
            user.setId(5l);
            user.setName("周八");
            View view = new InternalResourceView("/view.jsp");
            ModelAndView modelAndView = new ModelAndView(view, "user", user);
            return modelAndView;     
        }
    

    4.可以向原生的HttpServletRequest直接添加:

    直接向HttpServletRequest添加数据,上面都是间接向视图里的request域对象添加数据
    // 直接向HttpServletRequest添加数据,上面都是间接向视图里的request域对象添加数据
        @RequestMapping("/request")
        // 要用什么对象,直接在形参中写出来,springmvc框架会自动帮我们创建
        public String request(HttpServletRequest request) {
            User user = new User();
            user.setId(5l);
            user.setName("周八");
            // 把模型数据装到视图里的request域对象中
            request.setAttribute("user", user);
            return "view";
        }
    

    5.@ModelAttribute

    • 定义一个方法,该方法专门用来返回要填充到模型数据中的对象。
    // @ModelAttribute注解添加模型数据
        @ModelAttribute// 这个注解描述的方法会优先业务方法的执行,这时候就不需要在业务方法里处理模型数据
        // 无论在调用哪个业务方法之前一定会执行@ModelAttribute描述的方法
        public User getUser() {
            User user = new User();
            user.setId(1l);
            user.setName("张三");
            // 若要框架处理模型数据,就必须return,若不return,
            // 就要手动处理,方法返回值为void,通过给方法添加形参,
            // 在方法中处理模型数据
            return user;
        }
    
    • 方式2:在方法中处理模型数据,没有返回值,给方法添加形参
    @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")
        // 调用业务方法之前,会先执行@ModelAttribute描述的方法并返回对象,
        // 返回的对象作为当前业务方法的模型数据,所以在业务方法中就无需处理模型了,只需返回视图即可
        public String modelAttribute() {
            return "view";
        }
    

    将模型数据绑定到 session 对象

    1.直接向Session域对象添加数据

    // 原生方式向session里添加数据,和向request类似,session对象是从request对象中取出
        // 由于上面方法@ModelAttribute注解的原因,执行业务方法之前,request域对象就已经先获取到模型数据了,
        // 所以session域对象是另外重新获取模型数据的
        @RequestMapping("/session")
        public String session(HttpServletRequest request) {
            HttpSession session = request.getSession();
            User user = new User();
            user.setId(1l);
            user.setName("张三");
            session.setAttribute("user", user);
            return "view";
        }
    
        // 直接让框架创建好session域对象,直接获取session,不通过request间接获取session
        // 直接在方法形参添加session对象,
        @RequestMapping("/session2")
        public String session2(HttpSession session) {
            User user = new User();
            user.setId(1l);
            user.setName("张三");
            session.setAttribute("user", user);
            return "view";
        }
    

    2.@SessionAttributes注解添加模型数据到session域对象
    支持同时添加多个value值

    // 对所有业务方法生效,只要是业务方法中对注解参数值进行操作模型数据添加到request域对象中,他就会自动把模型数据加到session域对象中
    // 注解的value值与添加到模型里的key值对应
    // @SessionAttributes(value = "user")
    @SessionAttributes(value = {"user","address"})
    public class ViewHandler {
    }
    

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

    // 在任何业务方法里,只要注解参数对应类型的对象添加到域对象里,就自动加到session域对象中
    //@SessionAttributes(types = User.class) // 针对全局的,尽量不用,因为大多情况下只需向request添加而不需向session添加数据
    @SessionAttributes(types = {User.class,Address.class})
    public class ViewHandler {
    }
    

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

    将模型数据绑定到 application 对象,通过request间接绑定

    // 向application添加模型数据
        @RequestMapping("/application")
        // 因为ServletContext类没有提供无参构造器,所以HandlerAdapter不能创建application对象
        public String application(HttpServletRequest request) {
            ServletContext application = request.getSession().getServletContext();
            User user = new User();
            user.setId(1l);
            user.setName("张三");
            application.setAttribute("user", user);
            return "view";
        }
    

    Spring MVC 自定义数据转换器

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

    • 比如把String类型转换成日期类型

    客户端输入 String 类型的数据 "2020-03-03",自定义转换器将该数据转为 Date 类型的对象。

    • 创建 DateConverter 转换器,实现 Conveter 接口。实现接口就变成了控制器,若不实现就只是普通类.
    // 如何做到,传入数据为String类型,传出类型为Date类型
    // 直接在实现接口处指定传入传出泛型来规定转换和被转换类型
    public class DataConverter implements Converter<String, Date> {
        // 定义要转换日期的格式的变量
        String pattern = null;// 按照怎么样的格式转换
        // 需要通过框架传入到构造方法中,SpringIOC容器实例DataConverter的对象,就把在springmvc.xml中配置的日期格式传入,通过有参构造器来创建
        // 规定要转换前日期的格式
        public DataConverter(String pattern) {
            // 从外面传入要转换的日期格式,通过bean配置property传入
            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 对象-->
        <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <!--        定义属性 name属性是converters,因为自定义转换不仅仅是一个,可以配置多个-->
            <property name="converters">
    <!--            因为可以配置多个,所以一集合形式配合-->
                <list>
    <!--                配置的转换器的bean就是我们自定义的转换器类-->
                    <bean class="com.tiga.converter.DataConverter">
    <!--                    配置有参构造器参数,配置日期转换前的格式,value: 要转换的日期格式-->
                        <constructor-arg type="java.lang.String" value="YYYY-MM-DD"></constructor-arg>
                    </bean>
                </list>
            </property>
        </bean>
    
    <!--    在mvc注解驱动中注册转换器服务,配置转换器的id-->
        <mvc:annotation-driven conversion-service="conversionService">
    <!--        消息转换器: 处理由@responseBody注解响应页面的中文乱码问题,默认注册设为true-->
            <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>
    

    也可以不配置转换,直接

    写成simpledataformat sd=new simple data format(“yyyy-MM-dd”) 这样就不用配置了

    • jsp页面
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <form action="/converter/date" method="post">
    <%--    name属性和转换数据业务方法的形参名对应--%>
        转换日期: <input type="text" name="date">(yyyy-MM-dd)
        <br>
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    

    控制器类

    @RestController// 直接返回数据不通过视图解析器解析
    @RequestMapping("/converter")
    public class ConverterHandler {
        @RequestMapping("/date")
        public String date(Date date) {
            return date.toString();
        }
    }
    
    • String转成自定义类型
    String转Student

    StudentConverter(学生转换类)

    public class StudentConverter implements Converter<String, Student> {
        @Override
        public Student convert(String s) {
            // 通过参数中给定的字符将字符串分割成数组
            String[] args = s.split("-");
            // 创建学生对象
            Student student = new Student();
            // 把jsp获取到的参数添加到学生对象中
            student.setId(Long.parseLong(args[0]));
            student.setName(args[1]);
            student.setAge(Integer.parseInt(args[2]));
            return student;
        }
    }
    
    • 配置springmvc.xml
    <!--    配置自定义转换器 bean 对象-->
        <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <!--        定义属性 name属性是converters,因为自定义转换不仅仅是一个,可以配置多个-->
            <property name="converters">
    <!--            因为可以配置多个,所以一集合形式配合-->
                <list>
    <!--                配置的转换器的bean就是我们自定义的转换器类-->
                    <bean class="com.tiga.converter.DataConverter">
    <!--                    配置有参构造器参数,配置日期转换前的格式,value: 要转换的日期格式-->
                        <constructor-arg type="java.lang.String" value="YYYY-MM-DD"></constructor-arg>
                    </bean>
    <!--                配置Student转换器,通过空参构造器创建StudentConverter对象,无需配置构造器参数-->
                    <bean class="com.tiga.converter.StudentConverter">
                    </bean>
                </list>
            </property>
        </bean>
    
    <!--    在mvc注解驱动中注册转换器服务,配置转换器的id-->
        <mvc:annotation-driven conversion-service="conversionService">
    <!--        消息转换器: 处理由@responseBody注解响应页面的中文乱码问题,默认注册设为true-->
            <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>
    
    • jsp页面
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <%--    按照规定格式输入学生信息,转成Student类型--%>
    <form action="/converter/student" method="post">
    <%--    name属性和转换数据业务方法的形参名对应--%>
        请输入学生信息: <input type="text" name="student">(id-name-age)
        <input type="submit" value="提交">
    </form>
    </body>
    </html>
    
    • Handler
    @RestController// 直接返回数据不通过视图解析器解析
    @RequestMapping("/converter")
    public class ConverterHandler {
    @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)
      每发出一次请求就表示客户端和服务器进行的一次交互,http协议是无状态的协议,所以所有的状态都保存在服务端,因此,若客户端想操作服务端,就必须通过某种手段,让服务端发生状态转换

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

    相关文章

      网友评论

          本文标题:SpringMVC

          本文链接:https://www.haomeiwen.com/subject/hrmumltx.html