JavaEE

JavaWeb了解之SpringMVC篇1

2022-11-03  本文已影响0人  平安喜乐698
目录
  前言
    1. MVC架构模式
    2. 三层架构
    3. SpringMVC
  1. 第一个SpringMVC项目(Hello World)
  2. 获取请求参数
  3. 在(request、session、application)域对象中共享数据
  4. 视图和视图解析器
  5. RESTful(REST风格)
  6. Converter类型转换器
  7. Formatter格式化器
  8. json数据转换(Java对象<--转换-->json数据)

SpringMVC:Spring提供的基于MVC设计模式的轻量级Web开发框架(本质:对Servlet的进一步封装)。

前言

  1. MVC(Model-View-Controller,模型-视图-控制器)

是一种架构模式,将界面和业务逻辑分离,使代码具有更高的可扩展性、可复用性、可维护性以及灵活性。

1. Model(模型层)
  应用的主体部分,由以下2部分组成:
  1. 实体类Bean(通常与数据库中的某个表对应)
    用于存储业务数据。
  2. 业务处理Bean(Service、Dao)
    用于处理业务逻辑、数据库访问。

2. View(视图层)    
  (由HTML、JSP、CSS、JavaScript等组成)页面,负责用户交互、展示数据。

3. Controller(控制器)  
  通常为Servlet,负责接收用户请求(让模型层处理数据,并将处理好的数据传给视图层,展示最终的页面给用户)。 
  本身并不做任何业务处理,只负责调度View层和Model层。
基于MVC的Web应用
大致工作流程如下:
  1. 用户发送请求到服务器。若请求路径与web.xml中配置的DispatcherServlet前端控制器的url-pattern相匹配,则该请求会被DispatcherServlet拦截。
  2. DispatcherServlet会读取SpringMVC.xml配置文件,通过组件扫描获取到所有的Controller控制器。将请求信息和所有控制器方法标识的@RequestMapping注解的value、method等属性值进行匹配,若匹配成功则将请求交给对应控制器方法进行处理。
  3. 控制器方法中调用相应的Model层处理请求,Model层处理完后将结果(模型数据)返回到控制器方法。
  5. 控制器方法返回一个字符串类型的逻辑视图名,会被视图解析器转为真正的视图,并将模型数据渲染到视图中返回给用户。

优点
  1. 降低代码耦合性
    在MVC模式中,三层之间相互独立、各司其职。
    一旦某一层的需求发生了变化,只需要更改相应层中的代码即可,而不会对其他层中的代码造成影响。
  2. 有利于分工合作
    在MVC模式中,将应用划分成三层,可以更好地实现开发分工(前端专注于视图层、熟悉业务的后台开发Model层、不熟悉业务的后台开发Controller层)。
  3. 提高可重用性
    在MVC模式中,多个视图可以共享同一个模型。

缺点
  1. 增加了系统结构和实现的复杂性
    对于简单的应用,如果也严格遵循MVC模式,按照模型、视图与控制器对系统进行划分,会增加系统结构的复杂性,并可能产生过多的更新操作,降低运行效率。
  2. 视图与控制器间的联系过于紧密
    虽然视图与控制器是相互分离的,但它们之间联系却是十分紧密的。视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了它们的独立重用。
  3. 视图对模型数据的低效率访问
    视图可能需要多次调用才能获得足够的显示数据。
    对未变化数据的不必要的频繁访问,也将损害操作性能。
  1. 三层架构
1. 表示层(UI)相当于:MVC模式的View层+Controller层
  负责用户交互、展示数据、接收用户请求,将请求交给业务逻辑层(BLL)和数据访问层(DAL)进行处理,把处理好的数据传给页面并展示给用户。

2. 业务逻辑层(BLL)相当于:MVC模式的Model层的一部分(不包含Dao和实体类)
  起到承上启下的作用,接收表示层传递来的请求,根据业务对数据进行处理。

3. 数据访问层(DAL)相当于:MVC模式的Model层的一部分(只包含了Dao接口及实现)
  用于实现数据库访问(增删改查等操作)。
基于三层架构的Web应用
  1. SpringMVC

Spring提供的基于MVC设计模式的轻量级Web开发框架(为表示层开发提供的一整套完备的解决方案)。

工作流程
  1. 用户通过浏览器发起一个HTTP请求,该请求会被DispatcherServlet(前端控制器)拦截;
  2. DispatcherServlet会调用HandlerMapping(处理器映射器);
  3. HandlerMapping负责找到具体的处理器(Handler)及拦截器,并以HandlerExecutionChain执行链的形式返回给DispatcherServlet。
  4. DispatcherServlet将执行链返回的Handler信息发送给HandlerAdapter(处理器适配器);
  5. HandlerAdapter根据Handler信息找到并执行相应的Handler(即:控制器方法)对请求进行处理;
  6. Handler执行完后会将一个ModelAndView对象(SpringMVC的底层对象,包括Model数据模型和View视图信息)返回给HandlerAdapter。
  7. HandlerAdapter接收到ModelAndView对象返回给DispatcherServlet。
  8. DispatcherServlet接收到ModelAndView对象后,会请求ViewResolver(视图解析器)对视图进行解析;
  9. ViewResolver解析完成后,会将View视图并返回给DispatcherServlet;
  10. DispatcherServlet接收到具体的View视图后,进行视图渲染,将Model中的模型数据填充到View视图中的request域,生成最终的View(视图);
  11. 视图负责将结果显示到浏览器(客户端)。

特点
  1. SpringMVC是Spring框架的子框架(可以与IoC容器等Spring其他功能无缝对接)。
  2. SpringMVC支持各种视图技术(如:JSP、Thymeleaf、JSP、FreeMaker等)。 
  3. SpringMVC基于原生的Servlet实现(通过功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理)。
  4. SpringMVC对表示层各细分领域需要解决的问题全方位覆盖,并提供一整套全面的解决方案。
  5. 代码简洁,提高开发效率。
  6. 采用松耦合、可插拔的组件结构(即插即用:想使用什么功能就配置相应组件)。高度可配置性、扩展性、灵活性。
  7. 性能卓著,适合大型、超大型互联网项目的开发。
  8. 支持注解、REST风格。
SpringMVC工作流程
常用组件 提供者 描述
DispatcherServlet 框架 前端控制器(最核心的组件,本质是一个Servlet),负责统一处理请求。SpringMVC的流程控制中心,控制整个流程的执行,对各个组件进行统一调度(降低组件之间的耦合性,有利于组件之间的拓展)。
HandlerMapping 框架 处理器映射器,根据请求的url、method等信息查找相应的 Handler(即控制器方法)。
Handler 开发员 处理器(Controller控制器方法),对用户的请求进行处理。
HandlerAdapter 框架 处理器适配器,根据HandlerMapping找到的Handler处理器调用具体的控制器方法。
ViewResolver 框架 视图解析器(常用:ThymeleafViewResolver、InternalResourceViewResolver),通过ModelAndView对象中的View信息将逻辑视图名解析为真正的视图View。
View 开发员 视图(View对象本身由框架提供,但视图所对应的前端页面需要开发员编写),将Model模型数据通过页面展示给用户。

可以看到,开发人员只需负责创建:控制器(及控制器方法)、html页面。

1. 第一个SpringMVC项目(Hello World)

1. 创建JavaWeb项目(Dynamic Web Project)
  选择Target runtime选项:设置本地的Tomcat服务器版本和JDK版本。
Eclipse左侧工具栏,点右键->New->Dynamic Web Project
填写项目名,点击Target runtime选项
配置Target runtime
配置Target runtime之后
勾选自动生成web.xml,点击Finish创建项目
项目结构
2. 添加依赖包(到webapp/WEB-INF/lib目录下)
  Spring框架相关
    1. 核心容器模块的4个依赖包(core、beans、context、expression)、commons-logging-xxx.jar
    2. AOP依赖包(spring-webmvc-aop.jar)
    3. SpringMVC依赖包(spring-web-xxx.jar、spring-webmvc-xxx.jar)
  Thymeleaf相关
    1. attoparser-xxx.jar
    2. slf4j-api-xxx.jar
    3. thymeleaf-xxx.jar
    4. thymeleaf-spring5-xxx.jar
    5. unbescape-xxx.jar

下载attoparser-xxx.jarslf4j-api-xxx.jarthymeleaf-xxx.jarthymeleaf-spring5-xxx.jarunbescape-xxx.jar

3. 配置DispatcherServlet
  在web.xml中,添加
    <!-- 配置SpringMVC的前端控制器(拦截用户的所有请求,进行统一处理) -->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 
            配置SpringMVC配置文件按的位置和名称(当配置文件不在WEB-INF目录下时需要配置)。项目启动时默认会从WEB-INF目录下寻找配置文件,默认的命名规则为{servlet-name}-servlet.xml。
        -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
        <!-- 
            如果希望项目启动时就加载并初始化该Servlet时 配置。 默认情况下,所有的Servlet(包括DispatcherServlet)都是在第一次调用时才会被加载(降低了项目启动时间,但增加了用户第一次请求所需的时间)。 
            load-on-startup元素取值规则如下: 
                1. 取值必须是一个整数; 
                2. 当值小于0或者没有指定时,则表示容器在该Servlet被首次请求时才会被加载; 
                3. 当值大于0或等于0时,表示容器在项目启动时就加载并初始化该Servlet,取值越小优先级越高; 
                4. 当取值相同时,容器就会自行选择顺序进行加载。 
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--
            设置springMVC的前端控制器所能处理(拦截)的请求的请求路径。 
            /会拦截所有请求(除了.jsp)。
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
4. 创建springMVC.xml配置文件(src目录下)
  web.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 开启组件扫描(会扫描包路径下的所有控制器类,包括子包) -->
    <context:component-scan base-package="com.sst.cx"></context:component-scan>
    <!-- 配置Thymeleaf视图解析器(配置为:映射到/WEB-INF/templates/目录下的.html文件) -->
    <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        <!-- 视图前缀 -->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!-- 视图后缀 -->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
</beans>
5. 创建Controller
  在com.sst.cx.controller下创建HelloController.java

package com.sst.cx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
    @RequestMapping("/")    
    public String sayHello() {
        // 逻辑视图名,真正的视图为:视图前缀+index+视图后缀(即:/WEB-INF/template/index.html)
        return "index";
    }
}

说明:
  1. @Controller注解:
    将一个普通类标识为Controller控制器类,类中的每一个处理请求的方法被称为“控制器方法”。控制器方法在处理完请求后,通常会返回一个字符串类型的逻辑视图名,通过视图解析器(在springMVC配置文件中设置视图解析器)将逻辑视图名解析为真正的视图页面,并响应给客户端展示。
  2. @RequestMapping注解:
    1. 用来标注控制器方法时:将请求地址和处理请求的控制器方法建立映射关系。DispatcherServelt拦截请求后会根据映射关系将请求分发给指定的控制器方法进行处理。
    2. 用来标注控制器类时:所有控制器方法的请求地址的父路径。
    常用属性:
      1. value属性
        设置(控制器方法关联的)请求地址(可匹配多个请求地址),会和控制器方法建立映射关联。
        例: 
          @RequestMapping(value = "/") 只有该属性时可简写为:@RequestMapping("/")  
          @RequestMapping(value = {"/register", "/login"})  
        支持Ant风格的通配符路径:
          1. ?  表示任意的单个字符。
          2. *  表示任意的 0 个或多个字符。
          3. **  表示任意的一层或多层目录(只能作为前缀)。
      2. name属性
        设置控制器方法的功能描述(用来干什么)
      3. method属性(默认支持所有方式)
        设置控制器方法支持的请求方式(常用:GET、POST、DELETE、PUT)。
        例:
          @RequestMapping(value = "/toUser",method = RequestMethod.GET)
          @RequestMapping(value = "/toUser",method = {RequestMethod.GET,RequestMethod.POST})
      5. params属性
        用于指定请求中的参数,只有当请求中携带了符合条件的参数时,控制器方法才会对该请求进行处理。4种方式:
        1. "param" 请求中必须携带名为param的参数。
        2. "!param" 请求中不能携带名为param的参数。
        3. "param=value" 请求中必须携带名为param的参数,且参数值必须为:value。
        4. "param!=value" 请求中不能携带参数:param=value。
        例:
          @RequestMapping(value = "/testParam", params = {"name=张三", "age=12"})
      6. headers属性
        用于设置请求中请求头信息,只有当请求中携带指定的请求头信息时,控制器方法才会处理该请求。4种方式:
        1. "header" 请求中必须携带请求头信息:header 。
        2. "!header" 请求中不能携带请求头信息:header 。
        3. "header=value" 请求中必须携带请求头信息:header=value。
        4. "header!=value" 请求中不能携带请求头信息:header=value。
        例
          @RequestMapping(value = "toUser",headers = {"Content-Type=application/json", "Referer=http://www.baidu.com"})
6. 在/WEB-INF/template/目录下创建index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
    <h1 th:text="世界你好"></h1>
    <!-- <a th:href="@{/register}">注册</a>   -->
</body>
</html>
7. 部署到Tomcat服务器中,并启动Tomcat服务器
在浏览器中访问http://localhost:8080/HelloSpringMVC/
项目结构
运行结果

2. 获取请求参数

可以通过设置控制器方法的形参来接收/获取用户请求中的参数(4种方式)。
  1. 为请求中的每个参数添加一个对应的同名形参(区分大小写)。
    SpringMVC会自动将请求参数封装到对应的形参中。当请求中包含多个同名的请求参数时:以字符串形式(将参数值以,分隔,如:"a,b")接收;或 以元素类型为字符串的数组形式(如:{"a","b"})接收。
    不适合参数过多的情况。
    例(浏览器输入http://localhost:8080/hello?name=zhangsan&age=12)
      @RequestMapping("/hello")
      public String requestParam(String name, Integer age) {
        System.out.println("nmae:" + name + "age: "+age);
        return "index";
      }
  2. 使用@RequestParam注解为请求中的参数和控制器方法的形参建立映射关系。
    SpringMVC会自动将请求参数封装到对应的形参中(通常用于解决方式1中不一致的情况)。
    @RequestParam注解的常用属性
      1. name属性(value属性的别名)等价于value属性
        设置请求中的参数名。
      2. value属性
        设置请求中的参数名。
      3. required属性
        设置请求参数名是否必须(默认为true),不包含时会报异常。并未要求其设置参数值。
      4. defaultValue属性
        设置请求参数的默认值。
    例
      @RequestMapping("/hello")
      public String requestParam(@RequestParam("name") String userName, @RequestParam("age") String userAge) {
        System.out.println("nmae:" + userName + "age: "+userAge);
        return "index";
      }
  3. 添加一个HttpServletRequest类型的形参。
    SpringMVC会自动将请求参数封装到HttpServletRequest对象中。
    例
      @RequestMapping("/hello")
      public String requestParam(HttpServletRequest request) {
        String name = request.getParameter("name");
        String age = request.getParameter("age");
        return "index";
      }
  4. 添加一个实体类类型的形参(推荐)。
    SpringMVC会自动将请求参数封装到该实体类对象的同名属性中。
    例
      @RequestMapping("/hello")
      public String requestParam(User user) {
        String name = user.getName();
        Integer age = user.getAge();
        return "index";
      }

解决获取请求参数的乱码问题
  在post请求中传递的参数为中文时,控制器方法获取到的参数值会出现乱码。
  解决:
    在web.xml文件中,添加(通常都会添加CharacterEncodingFilter来避免乱码)
    <!-- 请求和响应的字符串过滤器 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!-- 设置请求的编码 -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <!-- 设置响应的编码 -->
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

3. 在(request、session、application)域对象中共享数据

控制器方法处理请求时,会交给Model层处理,Model层将处理完请求后的结果(模型数据)返回给Controller。Controller在接收到Model层返回的模型数据后,将模型数据通过域对象(服务器在内存上创建的一块存储空间,用于不同动态资源之间的数据传递和共享)共享的方式传递给View视图进行渲染,最终返回给客户端展示。

常用的域对象共享数据方式:
  1. 使用原生ServletAPI向request、session、application域对象中共享数据
    会导致控制器与Servlet容器耦合度过高(不推荐)
    例(request)
      在控制器方法中添加一个HttpServletRequest类型的形参
      @RequestMapping("/")
      public String testServletAPI(HttpServletRequest request) {
        request.setAttribute("name", "张三");
        return "index";
      }
    例(session)
      在控制器方法中添加一个HttpSession类型的形参
      @RequestMapping("/")
      public String testServletAPI(HttpSession session) {
        session.setAttribute("name", "张三");
        return "index";
      }
    例(application)
      在控制器方法中添加一个HttpSession类型的形参,通过它获取到application域对象
      @RequestMapping("/")
      public String testServletAPI(HttpSession session) {
        ServletContext application = session.getServletContext();
        application.setAttribute("name", "张三");
        return "index";
      }
  2. 使用Model、Map、ModelMap向request域对象中共享数据
    例(Model)
      在控制器方法中添加一个Model类型的形参
      @RequestMapping("/")
      public String testModelMap(Model model) {
        model.addAttribute("name", "张三");
        return "index";
      }
    例(Map)
      在控制器方法中添加一个Map类型的形参
      @RequestMapping("/")
      public String testModelMap(Map<String, Object> map) {
        map.put("name", "张三");
        return "index";
      }
    例(ModelMap)
      在控制器方法中添加一个ModelMap类型的形参
      @RequestMapping("/")
      public String testModelMap(ModelMap modelMap) {
        modelMap.addAttribute("name", "张三");
        return "index";
      }
  3. 使用ModelAndView向request域对象中共享数据
    控制器方法支持ModelAndView、ModelMap、View、String多种类型的返回值,最终会被SpringMVC封装成一个ModelAndView对象。ModelAndView对象由model(模型数据:需要渲染到视图的数据)和 view(视图:通常为逻辑视图名)组成。
    ModelAndView的常用方法:
      1. 添加Model模型数据
        ModelAndView addObject(String attributeName, @Nullable Object attributeValue)   
        ModelAndView addObject(Object attributeValue)
        ModelAndView addAllObjects(@Nullable Map<String, ?> modelMap)
      2. 设置视图
        void setViewName(@Nullable String viewName)     
        void setView(@Nullable View view) 
    例
      在控制器方法中创建ModelAndView对象并返回。
      @RequestMapping("/")
      public ModelAndView testModelAndView() {
          ModelAndView mav = new ModelAndView();
          // 设置Model(向请求域共享数据)
          mav.addObject("name", "hello,张三");
          // 设置视图(实现页面跳转)
          mav.setViewName("index");
          return mav;
      }

4. 视图和视图解析器

SpringMVC的控制器方法支持ModelAndView、ModelMap、View、String多种类型的返回值,最终都会被封装成一个ModelAndView对象(由model模型数据和view视图组成)。通常View是逻辑视图名,需要通过视图解析器解析为真正的视图,并将model模型数据填入视图中。
SpringMVC的核心理念是将View视图与Model模型进行解耦,其工作重心聚焦在Model模型数据上,并不关心最终究竟采用何种视图技术(Thymeleaf、JSP、FreeMarker、Velocity、Excel等,根据需求自由选择)对模型数据进行渲染。

视图
  用于渲染页面:将Model模型数据填入页面中,最终生成HTML、JSP、Excel表格、Word文档、PDF文档、JSON数据等形式的文件,并展示给用户。

1. SpringMVC提供了一个View视图接口,接口中定义了以下2个方法:
  1. default String getContentType();   
    获取HTTP响应文件的类型(如:HTML、JSON、PDF等)。 
  2. void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;(视图的核心方法)   
    负责将Model模型数据渲染到视图中。
    参数说明:model(模型数据)、request(请求对象)、response(响应对象)。
2. 常用的View接口实现类(视图类)
  1. ThymeleafView(Thymeleaf视图)     
    当项目中使用的视图技术为Thymeleaf时,则需要使用该视图类。
  2. InternalResourceView(转发视图)
    实现请求的转发跳转。
  3. RedirectView(重定向视图)
    实现请求的重定向跳转。
  4. FreeMarkerView(FreeMarker视图)
  5. MappingJackson2JsonView(JSON视图)
  6. AbstractPdfView(PDF视图)

3. 视图分类
  1. 逻辑视图
    最大的特点:控制器方法返回的ModelAndView中的view不是真正的视图对象,而是一个字符串类型的逻辑视图名,需要通过一个ViewResolver视图解析器解析为真正的物理视图对象。
    方式1
      直接在控制器方法中返回字符串类型的逻辑视图名,然后通过Model、Map、ModelMap等对象携带Model模型数据到视图中。
      例
        @RequestMapping("/testView")
        public String testView(Model model) {
            model.addAttribute("product","模型数据")
            return "success";
        }
    方式2
      在控制器方法中通过ModelAndView提供的setViewName()方法设置逻辑视图名,然后通过ModelAndView的addObject()方法携带Model模型数据到视图中。
      例
        @RequestMapping("/testView")
        public ModelAndView testView() {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("productList");
            List<Product> productList = productService.getProductList();
            modelAndView.addObject(productList);
            return modelAndView;
        }
    注:对于Thymeleaf、JSP等类型的逻辑视图,其控制器方法返回的view并不是必须为字符串类型的逻辑视图名,也可以是一个真正的View视图对象(通过ModelAndView提供的方法构造),此时这个视图也不需要视图解析器的解析 而是直接渲染。
  2. 非逻辑视图
    控制方法返回的是一个真正的视图对象(不需要视图解析器解析,可直接渲染),而不是逻辑视图名。
    例(MappingJackson2JsonView将数据模型转换为JSON视图并展现给用户)
      @RequestMapping("/testJsonView")
      public ModelAndView testJsonView(Integer productId) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("url", "www.baidu.com");
        // 设置ModelAndView的View对象
        modelAndView.setView(new MappingJackson2JsonView());
        return modelAndView;
      }

4. 视图控制器标签
  如果控制器方法中只是简单返回一个逻辑视图名,而没有返回Model数据,则可使用<mvc:view-controller>视图控制器标签代替该方法。
  注意:在springMVC.xml中添加了视图控制器标签后,必须添加<mvc:annotation-driven />标签开启注解驱动,否则其他控制器方法的请求映射将全部失效。
  例:
    @RequestMapping("/addPage")
    public String addPage() {
        return "base/add";
    }
    等价于
    在springMVC.xml中使用<mvc:view-controller path="/addPage" view-name="base/add"></mvc:view-controller>
视图解析器(ViewResolver)
  提供了逻辑视图名与实际视图的映射关系,并负责将逻辑视图名解析为一个具体的视图对象。
  1. SpringMVC提供了ViewResolver视图解析器接口(视图解析器必须实现该接口)。
    public interface ViewResolver {
      @Nullable
      View resolveViewName(String viewName, Locale locale) throws Exception;
    }
  2. 常用的ViewResolver接口实现类(视图解析器)
    每一个解析器都对应一个视图技术
    1. BeanNameViewResolver     
      将逻辑视图名解析为 一个Bean(视图的名称就是Bean的id)。
    2. InternalResourceViewResolver     
      将逻辑视图名解析为 一个资源文件。
      如:"success.jsp" 会被映射为success.jsp文件。
    3. FreeMarkerViewResolver   
      将逻辑视图名解析为 一个FreeMarker模板文件。
    4. ThymeleafViewResolver    
      将逻辑视图名解析为 一个Thymeleaf模板文件。
  3. 使用某个视图解析器时
    只需在SpringMVC.xml配置文件中,将它以Bean组件的形式注入到Ioc容器中。否则SpringMVC会使用默认的InternalResourceViewResolver进行解析。
  4. 配置多个视图解析器
    对于不同类型的视图对象,需要在springMVC.xml中配置不同的视图解析器(根据order属性来指定解析优先级顺序,order越小优先级越高)来完成视图的实例化工作。
    SpringMVC会遍历所有视图解析器,并按照其优先级依次对逻辑视图名进行解析,直到解析成功并返回视图对象为止。


示例(同时配置Thymeleaf、JSP视图解析器):
<?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
       https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 开启组件扫描 -->
    <context:component-scan base-package="com.sst.cx"></context:component-scan>
    <!-- 配置 Thymeleaf 视图解析器 -->
    <bean id="viewResolver"
          class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <!-- 逻辑视图名的规则,以 th、base/ 开头的逻辑视图名才会被该解析器解析 -->
        <property name="viewNames" value="th*,base/*"/>
        <!-- 视图解析器的优先级,值越小,优先级越高 -->
        <property name="order" value="2"/>
        <!-- 定义视图文件的字符集 -->
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        <!-- 设置视图前缀  -->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!-- 设置视图后缀 -->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
    <!-- 配置 JSP 视图解析器 -->
    <bean id="viewResolver1" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 逻辑视图名的规则 -->
        <property name="viewNames" value="jsp/*"/>
        <!-- 视图解析器的优先级,值越小,优先级越高 -->
        <property name="order" value="1"/>
        <property name="viewClass"
                  value="org.springframework.web.servlet.view.InternalResourceView"/>
        <!-- 视图前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!-- 视图后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

静态资源映射

在SpringMVC.xml配置文件中,实现静态资源的映射(访问静态资源)。
  方式1
    <!-- 
      静态资源映射(用于配置静态资源的访问路径)
        location属性:本地静态资源文件所在目录。/js/**表示js目录及js子目录。
        mapping属性:访问静态资源的路径前缀。
    -->
    <mvc:resources mapping="/js/" location="/js/**"></mvc:resources>
  方式2
    <!--
      将静态资源交给Tomcat提供的默认Servlet进行处理。
      使用此标签时,必须设置<mvc:annotation-driven/>,否则只能访问静态资源,无法再访问其他请求。
    -->
    <mvc:default-servlet-handler/>

请求转发与重定向

请求转发
  在控制器方法中返回逻辑视图名时,使用"forward:"关键字进行请求转发操作(不会被SpringMVC配置的视图解析器解析,而是会将前缀“forward:”去掉,以剩余部分作为最终路径,经过相应的控制器方法处理后,交由视图解析器解析为最终显示的页面 展示到客户端)。
  方式1. 在控制器方法中返回: "forward:"关键字作为前缀的字符串。
    @RequestMapping("/testDispatcher")
    public String testDispatcher() {
        return "forward:/login";
    }
  方式2. 在控制器方法中返回:ModelAndView(逻辑视图名为: "forward:"关键字作为前缀的字符串)。
    @RequestMapping("/testDispatcher")
    public ModelAndView testDispatcher() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("forward:/login");  // 设置逻辑视图名
        return modelAndView;
    }

重定向
  在控制器方法中返回逻辑视图名时,使用"redirect:"关键字进行重定向操作。(不会被SpringMVC配置的视图解析器解析,而是会将前缀“redirect:”去掉,以剩余部分作为最终路径从客户端重新请求,经过相应的控制器方法处理后,交由视图解析器解析为最终显示的页面 展示到客户端)。
  方式1. 在控制器方法中返回: "redirect:"关键字作为前缀的字符串。
    @RequestMapping("/testDispatcher")
    public String testDispatcher() {
        return "redirect:/login";
    }
  方式2. 在控制器方法中返回:ModelAndView(逻辑视图名为: "redirect:"关键字作为前缀的字符串)。
    @RequestMapping("/testRedirect")
    public ModelAndView testDispatcher() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/login");  // 设置逻辑视图名
        return modelAndView;
    }

5. RESTful(REST风格)

REST(Resource Representational State Transfer)表现层资源状态转移
  描述了服务器与客户端的交互形式。
  1. Resource(资源)
    把Web项目部署到Tomcat服务器后,项目中的所有内容(类、html文件、css文件、js文件、数据库表、文本、图片、音频等)在都可以被称为这个服务器中的资源。而服务器则可以看作是由许许多多离散的资源组成的。
    这些资源都可以通过一个URI(统一资源标识符) 进行标识,任何对于该资源的操作都不能改变其URI。想要获取这个资源,只需访问它的URI即可。
  2. Representation(资源的表述)
    资源在某个特定时刻的状态的描述(资源的具体表现形式),可以有多种格式(如:html、xml、json、纯文本、图片、视频、音频等)。
    通常情况下,服务端与客户端资源的表述所有使用的格式往往是不同的(如:在服务端资源可能是数据库中的一段纯文本、一个xml文件、数据库中的一张表,而客户端则可能是表现为html页面、json、音频、视频)。
  3.State Transfer(资源状态转移)
    客户端与服务端进行交互时,资源从一种表现形式转换到另一种表现形式的过程。但是 HTTP协议是一种无状态协议(无法保存任何状态),因此如果客户端想要获取服务器上的某个资源,就必须通过某种手段让资源在服务器端发生“状态转化”,而这种状态转化又是建立在应用的UI表现层上的。这就是【表现层资源状态转移】的含义。

RESTful(REST风格)
  使用RESTful的Web服务称为REST服务(RESTful Web Services)。
  在传统的项目开发中,通常会将 操作资源的动词(由开发员定义、没有统一规范) 写进URL中。例(通过用户ID获取用户信息的请求):
    1. http://localhost:8080/helloSpringMVC/getUserById?id=1
    2. http://localhost:8080/helloSpringMVC/user/getById?id=1
    3. http://localhost:8080/helloSpringMVC/getUserInfo?id=1
    4. http://localhost:8080/helloSpringMVC/a/b?id=1
  RESTFul提倡使用统一的风格来设计URL,规则如下:
    1. URL只用来标识和定位资源,不得包含任何与操作相关的动词。
      例(访问和用户相关的资源)
        http://localhost:8080/helloSpringMVC/user
    2. 当请求中需要携带参数时,RESTFul允许将参数通过斜杠/拼接到URL中,而不是以前那样使用问号?拼接键值对的方式来携带参数。
      例
        http://localhost:8080/helloSpringMVC/user/1
    3. 客户端通过HTTP协议提供的四个表示操作方式的动词:GET(获取资源)、POST(新建资源)、PUT(更新资源) 和 DELETE(删除资源),实现对服务器端资源状态转移的描述。

  当前比较流行的互联网软件架构模式,利用HTTP协议的特性规定了一套统一的资源获取方式,以实现客户端与服务端的数据访问与交互。
  设计RESTFul接口(即:通过什么样的手段让资源在服务器端发生状态转移)

在SpringMVC项目中,通过@RequestMapping+@PathVariable注解的方式 使用RESTful。
  1. 通过@RequestMapping注解 设置路径中的参数。
    通过占位符{xxx}表示传递的参数。占位符的位置要与请求URL中参数的位置保持一致,否则会传错参数。
  2.通过@PathVariable注解 绑定参数
    将占位符{xxx}所表示的参数绑定到控制器方法的形参。
  3. 通过HiddenHttpMethodFilter对请求进行过滤
    浏览器默认只支持发送GET和POST请求。在web.xml中使用SpringMVC提供的HiddenHttpMethodFilter对请求进行过滤(将POST请求转换为PUT或DELETE请求)后则可处理PUT和DELETE请求。当为POST请求且存在_method参数值时,会将POST请求转换为_method参数指定的方法(PUT/DELETE)。
  例
    1. 在web.xml中,添加:
        <!-- 同时存在时,必须先配置CharacterEncodingFilter,再配置HiddenHttpMethodFilter -->
    <!-- 处理PUT和DELETE请求的过滤器 -->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    2. 控制器方法:
      @RequestMapping("/testRest/{id}/{username}")
      public String testRest(@PathVariable("id") String id, @PathVariable("username") String username) {
        System.out.println("id:" + id + ",username:" + username);
        return "success";
      }  
      // 新增商品
      @RequestMapping(value = "/product", method = RequestMethod.POST)
      public String addProduct(Product product) {
        productDao.addProduct(product);
        return "redirect:/products";
      }
      // 删除商品
      @RequestMapping(value = "/product", method = RequestMethod.DELETE)
      public String deleteProduct(String productId) {
        productDao.deleteProduct(productId);
        return "redirect:/products";
      }
      // 修改商品
      @RequestMapping(value = "/product", method = RequestMethod.PUT)
      public String updateProduct(Product product) {
        productDao.updateProduct(product);
        return "redirect:/products";
      }
      // 查看/回显商品信息(action参数为get时为查看;action参数为update时为修改页中回显数据)
      @RequestMapping("/product/{action}/{productId}")
      public String getProductList(@PathVariable("action") String action, @PathVariable("productId") String productId, Model model) {
        Product product = productDao.getProductById(productId);
        model.addAttribute("product", product);
        // 根据参数action判断跳转到商品详细信息页还是商品修改页
        if (action.equals("get")) {
            return "product_info";
        } else {
            return "product_update";
        }
      }
    3. html
      增
      <form th:action="@{/product}" method="post">
        ...
        <input type="submit" value="新增商品">
      </form>
      删
      <form th:action="@{/product}" method="post">
        <input type="hidden" name="_method" value="delete">
        ...
        <input type="submit" value="删除商品">
      </form>
      改
      <form th:action="@{/product}" method="post">
        <input type="hidden" name="_method" value="put">
        ...
        <input type="submit" value="修改商品">
      </form>
      查
      <a th:href="@{|/product/get/${product.getProductId()}|}">查看商品</a>
资源操作 传统方式的URL RESTFul的URL HTTP请求方式
获取资源 SELECT http://localhost:8080/helloSpringMVC/getUserById?id=1 http://localhost:8080/helloSpringMVC/user/1 GET
新增资源 INSERT http://localhost:8080/helloSpringMVC/saveUser http://localhost:8080/helloSpringMVC/user POST
更新资源 UPDATE http://localhost:8080/helloSpringMVC/updateUser http://localhost:8080/helloSpringMVC/user PUT
删除资源 DELETE http://localhost:8080/helloSpringMVC/deleteUser?id=1 http://localhost:8080/helloSpringMVC/user/1 DELETE

6. Converter类型转换器

通过一些注解,控制器方法就能得到各种类型的参数,这要归功于SpringMVC的类型转换机制。Spring提供了Converter类型转换器,在SpringMVC中它的作用是在控制器方法对请求进行处理前,先将请求中的参数转为指定的数据类型再传给控制器方法的形参。
SpringMVC会自动使用内置类型转换器对基本类型(int、long、float、double、boolean、char 等)、集合/数组类型 进行转换(请求参数类型要和接收参数类型相兼容,否则会报400错误)。

内置类型转换器---标量转换器

名称 作用
StringToBooleanConverter String 转 boolean
ObjectToStringConverter Object 转 String(调用对象的toString方法进行转换,可覆写该方法来自定义)
StringToNumberConverterFactory String 转 数字(如:Integer、Long等)
NumberToNumberConverterFactory 数字子类型(基本类型)转 数字类型(包装类型)
StringToCharacterConverter String 转 Character(取字符串中的第一个字符)
NumberToCharacterConverter 数字子类型 转 Character
CharacterToNumberFactory Character 转 数字子类型
StringToEnumConverterFactory String 转 枚举类型(通过Enum.valueOf方法)
EnumToStringConverter 枚举类型 转 String(返回枚举对象的name值)
StringToLocaleConverter String 转 java.util.Locale
PropertiesToStringConverter java.util.Properties 转 String(默认使用ISO-8859-1解码)
StringToPropertiesConverter String 转 java.util.Properties(默认使用ISO-8859-1编码)

内置类型转换器---集合、数组相关转换器

名称 作用
ArrayToCollectionConverter 任意数组 转 任意集合(List、Set)
CollectionToArrayConverter 任意集合 转 任意数组
ArrayToArrayConverter 任意数组 转 任意数组
CollectionToCollectionConverter 集合之间的类型转换
MapToMapConverter Map之间的类型转换
ArrayToStringConverter 任意数组 转 String
StringToArrayConverter 字符串 转 数组(默认以,分割,且去除字符串两边的空格)
ArrayToObjectConverter 任意数组 转 Object(如果目标类型和源类型兼容,直接返回源对象;否则返回数组的第一个元素并进行类型转换)
ObjectToArrayConverter Object 转 单元素数组
CollectionToStringConverter 任意集合(List、Set) 转 String
StringToCollectionConverter String 转 集合(默认以,分割,且去除字符串两边的空格)
CollectionToObjectConverter 任意集合 转 任意Object(如果目标类型和源类型兼容,直接返回源对象;否则返回集合的第一个元素并进行类型转换)
ObjectToCollectionConverter Object 转 单元素集合

自定义类型转换器

对于复杂类型的转换(如String 转 Date)和自定义格式数据的转换,则需要根据需求开发自定义类型转换器来进行转换。

步骤:
1. 创建自定义类型转换器类,实现 Spring提供的3个转换器接口中的任意一个。
   1. Converter<S,T>接口  
    使用了泛型(S表示原类型,T表示目标类型)。
    接口中定义了一个convert()方法,将原类型对象作为参数传入,进行转换之后返回目标类型对象。
  2. ConverterFactory<S,R>接口
    用于将同系列的多个Converter封装在一起。    
    如果希望将一种类型的对象转换为另一种类型及其子类对象(如:将String转为Number及Number的子类Integer、Double等类型),则需要一系列的Converter(如:StringToInteger、StringToDouble等)。
  3. GenericConverter接口     
    根据源类对象及目标类对象的上下文信息进行类型转换。
  例:
    // 自定义日期转换器
    public class MyDateConverter implements Converter<String, Date> {
        private String datePatten = "yyyy-MM-dd";
        @Override
        public Date convert(String source) {
            System.out.println("前端页面传递过来的时间为:" + source);
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePatten);
            try {
                return simpleDateFormat.parse(source);
            } catch (ParseException e) {
                throw new IllegalArgumentException("无效的日期格式,请使用正确的日期格式" + datePatten);
            }
        }
    }

2. 配置自定义类型转换器
  需要在SpringMVC.xml配置文件中对自定义类型转换器类进行配置才能生效。
  例:
    <!-- 注册自定义类型转换器,会覆盖默认注册的ConversionService(FormattingConversionServiceFactoryBean类型) -->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
    <!-- 配置自定义类型转换器 -->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.sst.cx.converter.MyDateConverter"></bean>
            </set>
        </property>
    </bean>

<mvc:annotation-driven/>(通常都需要在SpringMVC.xml配置文件中添加)说明:
  开启注解驱动来简化SpringMVC的相关配置:
    1. 自动向SpringMVC中注册3个Bean:RequestMappingHandlerMapping、RequestMappingHandlerAdapter(这2个组件:请求分发给控制器方法的必须组件;为读写json数据提供支持),ExceptionHandlerExceptionResolver(异常处理组件)。
    2. 默认注册了一个ConversionService(FormattingConversionServiceFactoryBean类型的Bean,给@DateTimeFormat、@NumberFormat注解提供支持)满足大多数的类型转换需求。

7. Formatter格式化器

开发中经常会涉及到一些需要进行格式化的数据(如:金额、日期),这些数据都要经过一定的格式化处理才能正常使用。
Spring提供了一个Formatter<T>接口(格式化转换器,将String类型的字符串转换为T任意类型),该接口继承了Printer<T>和Parser<T>接口(分别包含一个print方法和一个parse方法,所有的格式化转换器实现类都必须重写这两个方法)。
  public interface Formatter<T> extends Printer<T>, Parser<T> {}
  1. Printer<T>接口的print方法
    String print(T object, Locale locale); 
        将对象转为String类型的(按照一定的格式)字符串并返回。
        该方法中包含一个Locale类型的形参,可根据国家/语言进行定制。
  2. Parser<T>接口的parse方法    
    T parse(String text, Locale locale) throws ParseException;  
        将满足一定格式的字符串转为对象。
        该方法中包含一个Locale类型的形参,可根据国家/语言进行定制。

内置格式化转换器(格式化 日期Date类型、数值Number类型的数据)
  1. NumberFormatter    
    实现Number与String之间的解析与格式化。
  2. CurrencyFormatter  
    实现Number与String之间的解析与格式化(带货币符号)。
  3. PercentFormatter   
    实现Number与String之间的解析与格式化(带百分数符号)。
  4. DateFormatter  
    实现Date与String之间的解析与格式化。

1. 虽然Formatter与Converter存在一定的差异,但格式化转换本质上还是属于“类型转换”,因此在SpringMVC中Formatter(格式化转换器)实际上是委托给Converter机制实现的。
2. Spring提供了一个FormattingConversionService类(ConversionService类型转换器接口的实现类)具有类型转换功能、格式化转换功能。
3. SpringMVC为FormattingConversionService类提供了一个FormattingConversionServiceFactroyBean工厂类(用于在Spring上下文中构造 FormattingConversionService的实例,并且为@DateTimeFormat注解、@NumberFormat注解提供了支持)。
4. 在SpringMVC.xml配置文件中添加了<mvc:annotation-driven/>标签(项目启动时会自动初始化一个FormattingConversionServiceFactoryBean类型的实例:ConversionService),才能使用@DateTimeFormat注解、@NumberFormat注解 对 Date类型、Number类型的数据进行格式化转换。
1. @DateTimeFormat注解(日期格式化)
  用于标注java.util.Date、java.util.Calendar、java.long.Long等时间类型的数据。
  常用属性      
    1. pattern属性(String类型)  
      用于指定解析或格式化日期时间的模式(常用取值: yyyy-MM-dd、yyyy-MM-dd hh:mm:ss)。
    2. iso属性(DateTimeFormat.ISO类型)  
      用于指定解析或格式化日期时间的ISO模式,其取值有4种:
        DATE:yyyy-MM-dd
        TIME:hh:mm:ss:SSSZ
        DATE_TIME:yyyy-MM-dd hh:mm:ss:SSSZ
        NONE:无
    3. style属性(String类型)    
      用于指定日期时间的格式(默认值为SS)。
      该属性由两个字符组成,分别表示日期和时间的格式。
        S:短日期/时间格式
        M:中日期/时间格式
        L:长日期/时间格式
        F:完整日期/时间格式
  例:
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date date;
    <td th:text="${#dates.format(market.getDate(),'yyyy-MM-dd')}"></td>

2. @NumberFormat注解(数值格式化)
  用于标注数字基本类型(如:int、long等)、java.lang.Number类型的实例(如:BigDecimal、Integer等)。
  拥有两个互斥的属性:
    1. style属性(NumberFormat.Style类型)    
      用于指定数值的样式类型,其取值有以下4种:
        DEFAULT:默认类型
        NUMBER:正常数值类型
        PERCENT:百分数类型
        CURRENCY:货币数值类型
    2. pattern属性(String类型)  
      用于自定义数值的样式,如: #,###
  例:
    @NumberFormat(style = NumberFormat.Style.CURRENCY)
    private BigDecimal money;

8. json数据转换(Java对象<--转换-->json数据)

json(全称:JavaScript Object Notation)
  与xml相似,也是用来存储数据的(基于纯文本的数据格式)。但相比于xml,数据占用空间更小、解析速度更快。因此,使用json数据来进行前后台的数据交互。
  不仅能够传递String、Number、Boolean等简单类型的数据,还可以传递数组、Object对象等复杂类型的数据。

支持2种数据结构(可相互嵌套)
  1. 对象结构
    以“{”开始,以“}”结束,中间则由0个或多个以英文的逗号,分隔的key:value对构成。
    key必须为String类型,value可以是String、Number、Object、Array等数据类型。
    语法格式:
      {  
        key1:value1,
        key2:value2,
        ...
      }
    例:
    一个person对象包含姓名、密码、年龄等信息,使用json的表示形式如下:
      {
        "pname":"张三",
        "password":"123456",
        "page":12
      }
  2. 数组结构。 
    以“[”开始、以“]”结束,中间部分由0个或多个以英文的逗号,分隔的值列表组成。
    语法格式:
      [
        value1,
        value2,
        ...
      ]
    例:
    一个数组中包含了String、Number、Boolean、null多种类型的数据,使用json的表示形式如下:
      [
        "张三",
        123456789,
        true,
        null
      ]
json数据转换
  SpringMVC提供了一个默认的MappingJackson2HttpMessageConverter类,来处理浏览器与控制器类之间的json数据转换(Java对象和json数据的相互转换)。

步骤:
  1. 导入Jackson依赖包
    1. jackson-annotations-x.x.x.jar(json转换的注解包)
    2. jackson-core-x.x.x.jar(json转换的核心包)
    3. jackson-databind-x.x.x.jar(json转换的数据绑定包)
  2. springMVC.xml配置文件中,需要添加<mvc:annotation-driven/>开启注解驱动(会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter两个组件,为读写json数据提供支持)。
  3. json转换注解
    1. @RequestBody注解   
      标注控制器方法的形参。
      用于将请求体中的数据绑定到控制器方法的形参上(json转对象)。
    2. @ResponseBody注解  
      标注控制器方法
      用于将控制器方法的返回值,直接作为响应报文的响应体响应到
浏览器上(对象转json)。

例:
    @ResponseBody
    @RequestMapping(value = "/product", method = RequestMethod.POST)
    public Product addProduct(@RequestBody Product product) {
        productDao.addProduct(product);
        return product;
    }
    @ResponseBody
    @RequestMapping("/getProductList1")
    public List<Product> getProductList1() {
        List productList = productDao.getProductList();
        return productList;
    }

下载Jackson依赖包,搜索包->选择版本->点击bundle按钮

例

===》控制器

@Controller
public class ProductController {
    @Resource
    private ProductDao productDao;
    // 查看或回显商品信息,get:查看商品信息  update:为修改页回显的商品信息
    @ResponseBody
    @RequestMapping("/product/{productId}")
    public Product getProduct(@PathVariable("productId") String productId) {
        Product product = productDao.getProductById(productId);
        return product;
    }
    // 新增商品
    @ResponseBody
    @RequestMapping(value = "/product", method = RequestMethod.POST)
    public Product addProduct(@RequestBody Product product) {
        productDao.addProduct(product);
        return product;
    }
    // 删除指定的商品
    @RequestMapping(value = "/product", method = RequestMethod.DELETE)
    public String deleteProduct(String productId) {
        productDao.deleteProduct(productId);
        return "redirect:/products";
    }
    // 获取商品列表
    @ResponseBody
    @RequestMapping("/getProductList1")
    public List<Product> getProductList1() {
        List productList = productDao.getProductList();
        return productList;
    }
    // 修改商品信息
    @RequestMapping(value = "/edit-product")
    public String updateProduct(Product product) {
        productDao.updateProduct(product);
        return "redirect:/products";
    }
}


===》product_list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--引入 jquery-->
    <script type="text/javaScript"
            src="../../js/jquery-3.6.0.min.js " th:src="@{/js/jquery-3.6.0.min.js}"></script>
</head>
<body>
<table th:border="1" th:cellspacing="0" th:cellpadding="0" style="text-align: center;">
    <thead>
    <th>商品id</th>
    <th>商品名</th>
    <th>商品价格</th>
    <th>商品库存</th>
    <th>操作</th>
    </thead>
    <tbody th:id="tb">
    </tbody>
</table>
<br>
<!-- 作用:通过超链接控制表单的提交,将post请求转换为delete请求 -->
<form id="delete_form" method="post" th:action="@{/product}">
    <!-- HiddenHttpMethodFilter要求:必须传输_method请求参数,并且值为最终的请求方式 -->
    <input type="hidden" name="_method" value="delete"/>
    <input type="hidden" name="productId" th:id="form-id"/>
</form>
<button id="btn">新增商品</button>
<div id="addWindow">
    <label id="x" style="position: absolute;top:2px;left: 95%;font-size: 25px;">x</label>
    <h4>新增商品</h4>
    <form th:action="@{/product}" method="post">
        <table id="table-box" style="margin: auto">
            <tr>
                <td>商品 ID:</td>
                <td><input type="text" id="productId" name="productId" required></td>
            </tr>
            <tr>
                <td>商品名称:</td>
                <td><input type="text" id="productName" name="productName" required></td>
            </tr>
            <tr>
                <td>商品价格:</td>
                <td><input type="text" id="price" name="price" required></td>
            </tr>
            <tr>
                <td>商品库存:</td>
                <td><input type="text" id="stock" name="stock" required></td>
            </tr>
            <tr>
                <td>商品简介:</td>
                <td><textarea id="introduction" name="introduction" rows="10" cols="30"></textarea><br></td>
            </tr>
            <tr>
                <td colspan="2" align="center"><input id="addPro" type="submit" value="新增商品">
                    <input type="reset" id="reset" value="重置">
                </td>
            </tr>
        </table>
    </form>
</div>
<div id="backGround"></div>
<div id="editWindow">
    <label id="ex" style="position: absolute;top:2px;left: 95%;font-size: 25px;">x</label>
    <h4>修改商品</h4>
    <form th:action="@{/edit-product}" id="edit-form" method="post">
        <table id="edit-form-table" style="margin: auto">
            <tr>
                <td>商品 ID:</td>
                <td><input id="e-productId" type="text" name="productId" readonly></td>
            </tr>
            <tr>
                <td>商品名称:</td>
                <td><input id="e-productName" type="text" name="productName" required></td>
            </tr>
            <tr>
                <td>商品价格:</td>
                <td><input id="e-price" type="text" name="price" required></td>
            </tr>
            <tr>
                <td>商品库存:</td>
                <td><input id="e-stock" type="text" name="stock" required></td>
            </tr>
            <tr>
                <td>商品简介:</td>
                <td><textarea id="e-introduction" name="introduction" rows="10" cols="30"></textarea>
                </td>
            </tr>
            <tr>
                <td colspan="2" align="center"><input type="submit" value="修改商品信息"></td>
            </tr>
        </table>
    </form>
</div>
<div id="edit-backGround"></div>
<div id="lookWindow">
    <label id="lx" style="position: absolute;top:2px;left: 95%;font-size: 25px;">x</label>
    <h4>商品详情</h4>
    <table style="margin: auto">
        <tr>
            <td>商品 ID:</td>
            <td id="l-productId"></td>
        </tr>
        <tr>
            <td>商品名称:</td>
            <td id="l-productName"></td>
        </tr>
        <tr>
            <td>商品价格:</td>
            <td id="l-price"></td>
        </tr>
        <tr>
            <td>商品库存:</td>
            <td id="l-stock"></td>
        </tr>
        <tr>
            <td>商品简介:</td>
            <td id="l-introduction"></td>
        </tr>
    </table>
</div>
<div id="look-backGround"></div>
<script type="text/javaScript">
    $(document).ready(function () {
        // 点击新增商品,弹出新增商品弹窗
        $("#btn").click(function () {
            $("#addWindow").slideDown(300);
            $("#backGround").show();
        });
        $("#x").click(function () {
            $("#addWindow").slideUp(300);
            $("#backGround").hide();
            $("#reset").trigger("click")
        });
        $("#ex").click(function () {
            $("#editWindow").slideUp(300);
            $("#edit-backGround").hide();
        });
        $("#lx").click(function () {
            $("#lookWindow").slideUp(300);
            $("#look-backGround").hide();
        });
        $("#look-close").click(function () {
            $("#lookWindow").slideUp(300);
            $("#look-backGround").hide();
        });
        /**
         * 填完数据后,点击新增商品弹窗中的 新增商品 按钮,执行新增商品操作
         */
        $("#addPro").click(function () {
            add();
            $("#addWindow").slideUp(300);
            $("#backGround").hide();
            $("#reset").trigger("click")
        });
        $("#editPro").click(function () {
            $("#edit-form").submit();
            $("#editWindow").slideUp(300);
            $("#edit-backGround").hide();
            $("#e-reset").trigger("click")
        });
    });
    $(function () {
        $.ajax({
            // 请求路径
            url: "http://localhost:8080/helloSpringMVC/getProductList1",
            // 请求类型
            type: "get",
            // 定义发送请求的数据格式为JSON字符串
            contentType: "application/json;charset=utf-8",
            // 定义回调响应的数据格式为JSON字符串,该属性可以省略
            dataType: "json",
            // 成功响应的结果
            success: function (data) {
                if (data != null) {
                    var html = "";
                    for (var i = 0; i < data.length; i++) {
                        var product = data[i];
                        html += "<tr>" +
                            "<td>" + product.productId + "</td>" +
                            "<td>" + product.productName + "</td>" +
                            "<td>" + product.price + "</td>" +
                            "<td>" + product.stock + "</td>" +
                            "<td><button  onclick='lookPage(" + product.productId + ")'>查看商品</button>" +
                            "<button  onclick='editPage(" + product.productId + ")';>修改商品</button>" +
                            "<button  onclick='deletePro(" + product.productId + ")';>删除商品</button></td>" +
                            "</tr>"
                    }
                    $("#tb").html(html);
                }
            }
        });
    })
    function deletePro(productId) {
        var b = confirm("确认删除id 为" + productId + " 的商品?");
        if (b) {
            var delete_form = $("#delete_form");
            $("#form-id").val(productId);
            delete_form.submit();
        }
    }
    // 执行新增商品操作
    function add() {
        var productId = $("#productId").val();
        var productName = $("#productName").val();
        var price = $("#price").val();
        var introduction = $("#introduction").val();
        var stock = $("#stock").val();
        $.ajax({
            // 请求路径
            url: "http://localhost:8080/springmvc-json-demo/product",
            // 请求类型
            type: "post",
            data: JSON.stringify({
                productId: productId,
                productName: productName,
                price: price,
                stock: stock,
                introduction: introduction
            }), // 定义发送请求的数据格式为JSON字符串
            contentType: "application/json;charset=utf-8",
            // 定义回调响应的数据格式为JSON字符串,该属性可以省略
            dataType: "json",
            // 成功响应的结果
            success: function (data) {
                if (data != null) {
                    var product = data;
                    var html = "<tr>" +
                        "<td>" + product.productId + "</td>" +
                        "<td>" + product.productName + "</td>" +
                        "<td>" + product.price + "</td>" +
                        "<td>" + product.stock + "</td>" +
                        "<td><button onclick='lookPage(" + product.productId + ")'>查看商品</a>" +
                        "<button onclick='editPage(" + product.productId + ")';>修改商品</a>" +
                        "<button onclick='deletePro(" + product.productId + ")';>删除商品</a></td>" +
                        "</tr>";
                    $("#tb").append(html);
                }
            }
        });
    }
    function lookPage(productId) {
        $("#lookWindow").slideDown(300);
        $("#look-backGround").show();
        $.ajax({
            // 请求路径
            url: "http://localhost:8080/springmvc-json-demo/product/" + productId,
            // 请求类型
            type: "get",
            // 定义发送请求的数据格式为JSON字符串
            contentType: "application/json;charset=utf-8",
            // 定义回调响应的数据格式为JSON字符串,该属性可以省略
            dataType: "json",
            // 成功响应的结果
            success: function (data) {
                if (data != null) {
                    $("#l-productId").html(data.productId);
                    $("#l-productName").html(data.productName);
                    $("#l-price").html(data.price);
                    $("#l-stock").html(data.stock);
                    $("#l-introduction").html(data.introduction);
                }
            }
        });
    }
    // 打开商品修改页,并将商品信息回显到弹窗中
    function editPage(productId) {
        $("#editWindow").slideDown(300);
        $("#edit-backGround").show();
        $.ajax({
            // 请求路径
            url: "http://localhost:8080/springmvc-json-demo/product/" + productId,
            // 请求类型
            type: "get",
            // 定义发送请求的数据格式为JSON字符串
            contentType: "application/json;charset=utf-8",
            // 定义回调响应的数据格式为JSON字符串,该属性可以省略
            dataType: "json",
            // 成功响应的结果
            success: function (data) {
                if (data != null) {
                    var product = data;
                    $("#e-productId").val(data.productId);
                    $("#e-productName").val(data.productName);
                    $("#e-price").val(data.price);
                    $("#e-stock").val(data.stock);
                    $("#e-introduction").val(data.introduction);
                }
            }
        });
    }
</script>
<style type="text/css">
    #addWindow, #editWindow {
        display: none;
        position: absolute;
        top: 25%;
        left: 25%;
        width: 30%;
        height: 40%;
        padding: 20px;
        border: 3px solid #ccc;
        background-color: white;
        z-index: 2;
        overflow: auto;
    }
    #lookWindow {
        display: none;
        position: absolute;
        top: 25%;
        left: 25%;
        width: 30%;
        height: 30%;
        padding: 20px;
        border: 3px solid #ccc;
        background-color: white;
        z-index: 2;
        overflow: auto;
    }
    #backGround, #edit-backGround, #look-backGround {
        display: none;
        position: absolute;
        top: 0%;
        left: 0%;
        width: 100%;
        height: 1100px;
        background-color: black;
        z-index: 1;
        -moz-opacity: 0.8;
        opacity: .80;
        filter: alpha(opacity=88);
    }
    #x:hover, #lx:hover, #ex:hover {
        cursor: pointer;
        color: rgb(55, 198, 192);
    }
</style>
</body>
</html>
上一篇下一篇

猜你喜欢

热点阅读