SpringMVC学习笔记

SpringMVC学习笔记

Spring MVC框架入门教程

ssm: MyBatis + Spring + SpringMVC

1. 回顾MVC

1.1 什么是 MVC

  • MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
  • 是将业务逻辑、数据、显示分离的方法来组织代码。
  • MVC 主要作用是降低了视图与业务逻辑间的双向耦合。
  • MVC 不是一种设计模式,MVC 是一种架构模式,当然不同的 MVC 存在差异。

Model(模型):数据类型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或 JavaBean 组件(包含数据和行为)。不过现在一般都分离开来: Value Object(数据 Dao)和服务层(行为 Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变)。处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。

最典型的MVC 就是 JSP + Servlet + JavaBean 的模式

image-20200512101727380

1.2 Model1时代

  • 在web早期的开发中,通常采用的是 Model1。

  • Model1中,主要分为两层,视图层和模型层

image-20200512105946239

Model1 的优点:架构简单,比较适合小型项目的开发。

Model1 的缺点:JSP 职责不单一,职责过重,不便于维护。

1.3 Model2时代

Model2 把一个项目分成三部分,包括视图、控制、模型。

image-20200512111319240

职责分析:

Controller:控制器

  1. 获取表单数据

  2. 调用业务逻辑

  3. 转向指定的页面

Model:模型

  1. 业务逻辑
  2. 保存数据的状态

View:视图

  1. 显示页面

Model1 和 Model2 的对比:

Model2 这样不仅提高了代码的复用率与项目的拓展性,而且大大降低了项目的维护成本。

Model1模式的实现比较简单,适用于快速开发小规模项目,Model1 中的 JSP 页面身兼 View 和 Controller 两种角色,将控制逻辑和表现逻辑混杂在一起,从而导致代码的重用性非常低,增加了应用的拓展性和维护的难度。Model2 消除了 Model1 的缺点。

1.4 回顾Servlet

  1. 新建一个 Maven 工程当作父工程,并在 pom.xml 中导入需要的依赖

        <dependencies>
            <!-- junit:测试 -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
            <!-- SpringMvc -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.3.RELEASE</version>
            </dependency>
            <!-- 开启Servlet支持 -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
            </dependency>
            <!-- 开启jsp支持 -->
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>jsp-api</artifactId>
                <version>2.2</version>
            </dependency>
            <!-- jackson -->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.10.2</version>
            </dependency>
            <!-- lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.10</version>
            </dependency>
        </dependencies>
    
  2. 建立一个 Module:SpringMVC-01-Servlet ,添加 Web app的支持

  3. 编写一个Servlet类:HelloServlet

    public class HelloServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 获取前端参数
            String method = req.getParameter("method");
            if ("add".equals(method)){
                req.getSession().setAttribute("msg","执行了add方法");
            }
            if ("delete".equals(method)){
                req.getSession().setAttribute("msg","执行了delete方法");
            }
            // 调用业务层
            // 视图转发或重定向
            req.getRequestDispatcher("WEB-INF/jsp/test.jsp").forward(req,resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doGet(req, resp);
        }
    }
    
  4. 在 web.xml 中配置 servlet

    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    
  5. 配置 Tomcat 服务器,并将工程项目打包发布

  6. 直接用 url 请求 Servlet

    http://localhost:8888/hello?method=add
    http://localhost:8888/hello?method=delete
    

MVC 框架要做哪些事情

  1. 将 url 映射到 java 类 或 java 方法
  2. 封装用户提交的数据
  3. 处理请求,调用相关的业务处理,封装响应数据
  4. 将响应的数据进行渲染 jsp/html 等表示层数据

说明:

常见的服务端 MVC 框架: Struts、Spring MVC、ASP.NET MVC、Zend Frameword、JSF;

常见前端 MVC 框架:vue、angularjs、react、backbone;

由 MVC 演化出了另外一些模式入:MVP、MVVM 等等

2. 什么是Spring MVC

2.1 概述

Spring MVC 是 Spring Framework 的一部分,是基于 Java 实现 MVC 的轻量级 Web 框架。

官方文档地址:https://docs.spring.io/spring/docs/5.3.0-SNAPSHOT/spring-framework-reference/web.html#spring-web

我们为什么要学习 Spring MVC呢?

Spring MVC 的特点:

  1. 轻量级、简单易学
  2. 高效、基于请求响应的 MVC 框架
  3. 与 Spring 兼容性好,无缝结合
  4. 约定优于配置
  5. 功能强大:RESTful、数据验证、格式化、本地化、主题等
  6. 简洁灵活

正是因为 SpringMVC 好,简单,边界,易学,天生和 Spring 无缝集成(使用 SpringIoC 和 Aop),使用约定优于配置,能够进行简单的 junit 测试,支持 Restful 风格,异常处理,本地化,国际化,数据验证,类型转换,拦截器等等,所以我们要学习。

最重要的一点还是用的人多,使用的公司多。

2.2 中心控制器

Spring 的 web 框架围绕 DispatcherServlet【调度Servlet】设计

DispatcherServlet 的作用是将请求分发到不同的处理器。从 Spring 2.5 开始,使用 Java 5 或者以上版本的用户可以采用基于注解形式进行开发,十分简洁,用户可以采用基于注解的 controller 声明方式。

Spring MVC 框架像许多其它 MVC 框架一样,以请求为驱动,围绕一个中心 Servlet 分派请求及提供其它功能,DispatcherServlet 是一个实际的 Servlet (它继承自 HttpServlet 基类)。

image-20200512133503366

(图片引自博客 https://blog.csdn.net/licwzy/article/details/81875635)

SpringMVC 的原理如下图所示:

当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器。再将结果返回给请求者。

image-20200512143002247

2.3 SpringMVC 原理

image-20200512143317781

(图引自狂神说公众号)

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

需要分析执行流程

  1. DispatcherServlet 表示前置控制器,是整个 SpringMVC 的控制中心。用户发出请求,DispatcherServlet 接收请求并拦截请求。

    我们假设请求的url为:http://localhost:8080/SpringMVC/hello

    如上url拆分成三部分:

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

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

    hello: 表示控制器

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

  2. HandlerMapping 为处理器映射。DispatcherServlet 调用HandelerMapping,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. 视图解析器将解析的逻辑视图结果,调用具体的视图。

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

3. 第一个SpringMVC项目

3.1 配置版

  1. 新建一个Moudle:SpringMVC-02-HelloMVC,添加 web 支持

  2. 导入SpringMVC依赖

  3. 配置 web.xml,注册 DispatcherServlet

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        
        <!-- 注册DispatcherServlet -->
        <servlet>
            <servlet-name>SpringMVC</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!-- 关联一个SpringMVC配置文件:【servlet-name】 springmvc-servlet.xml -->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:config/springmvc-servlet.xml</param-value>
            </init-param>
            <!-- 启动级别:1 -->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <!-- /  :匹配所有的请求(不包括.jsp) -->
        <!-- /* :匹配所有的请求(包括.jsp) -->
        <servlet-mapping>
            <servlet-name>SpringMVC</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
    </web-app>
    
  4. 编写我们要操作的业务 Controller,要么实现 Controller 接口,要么增加注解;需要返回一个 ModelAndView ,装数据,封视图

    public class HelloController implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
            // 创建 ModelAndView 对象
            ModelAndView mv = new ModelAndView();
            // 封装对象,放在 ModelAndView 中,Model
            mv.addObject("msg","HelloSpringMVC!");
            // 封装要跳转的视图,放在 ModelAndView 中,相当于 /WEB-INF/jsp/hello.jsp
             mv.setViewName("hello");
            return mv;
        }
    }
    
  5. 编写 SpringMVC 配置文件:springmvc-servlet.xml

    • 添加处理器映射器
    • 添加处理器适配器
    • 添加视图解析器
    • 将自己的类交给 SpringIoC 容器,注册 Bean
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 处理映射器 -->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!-- 处理器适配器 -->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
    
        <!--视图解析器:DispatcherServlet给他的ModelAndView-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!-- 前缀 -->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!-- 后缀 -->
            <property name="suffix" value=".jsp"/>
        </bean>
    
       <!-- 跳转的视图 -->
        <bean id="/hello" class="controller.HelloController"/>
    
    </beans>
    
  6. 创建需要跳转的 jsp 页面,并显示 ModelAndView 中存放的数据

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    ${msg}
    </body>
    </html>
    
  7. 配置 Tomcat 并启动测试

    在 url 地址栏中输入 http://localhost:8080/hello

可能遇到的问题,访问出现404,排查步骤:

  1. 查看控制台输出,看一下是不是缺少了什么 jar 包。
  2. 如果 jar 包存在,显示无法输出,就在 IDEA 的项目发布中,添加 lib文件夹,并添加对应的依赖
  3. 重启 Tomcat即可解决

image-20200512163312333

上面配置版的 SpringMVC项目,能更好地理解 SpringMVC 的原理。但是,在我们实际开发中并不会这么写。而是使用注解进行开发!

3.2 注解版

  1. 新建一个Moudle:SpringMVC-03-Hello-Annotation,添加 web 支持

  2. 导入依赖,并解决 Maven 资源过滤问题

    <!-- 解决 Maven 资源过滤问题 -->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
    
  3. 创建 SpringMVC 配置文件

    在 resource 目录下创建个 config 文件夹,并添加springmvc-servlet.xml 配置文件,配置的形式与 Spring 容器配置基本类似,为了支持基于注解的 IoC,配置了自动扫描包的功能,

    配置步骤:

    • 让 IoC 的注解生效
    • 静态资源过滤: HTML、JS、CSS、图片、视频 等
    • MVC 的注解驱动
    • 配置视图解析器

    具体配置信息如下:

    <?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">
    
        <!-- 自动扫描包,让指定包下的注解生效,由 IoC 容器同一管理 -->
        <context:component-scan base-package="com.xp.controller"/>
        <!-- 让 SpringMVC 处理静态资源 -->
        <mvc:default-servlet-handler/>
        <!--
        支持mvc注解驱动
        在 Spring 中一般采用 @RequestMapping 注解来完成映射关系
        要想使 @RequestMapping 注解生效
        必须向上下文中注册 DefaultAnnotationHandlerMapping 和一个 AnnotationMethodHandlerAdapter实例
        这两个实例分别在类级别和方法级别处理
        而 annotation-driver 配置帮助我们自动完成上述两个实例的注入
        -->
        <mvc:annotation-driven/>
    
        <!-- 视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!-- 前缀 -->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!-- 后缀 -->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    

    在视图解析器中,我们把所有的视图都存放在 /WEB-INF/ 目录下,这样可以保证视图安全,因为这个目录下的文件,客户端不能直接访问

  4. 配置 web.xml 文件

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!-- 注册 DispatcherServlet注解 -->
        <servlet>
            <servlet-name>SpringMVC</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:config/springmvc-servlet.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>SpringMVC</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
        
    </web-app>
    

    / 和 / 的区别:*

    < url-pattern > / </ url-pattern > 不会匹配到.jsp,只针对我们编写的请求;即.jsp不会进入 Spring 的 DispatcherServlet 类。

    < url-pattern > /* </ url-pattern > 会匹配*.jsp,会出现返回 JSP 视图时再次进入 Spring 的 DispatcherServlet 类,导致找不到对应的 controller 所以报404错。

    • 注意 web.xml 版本问题,要最新版
    • 注册 DispatcherServlet
    • 关联 SpringMVC 的配置文件
    • 启动级别为1
    • 映射路径为 / 【不要用/*,会404】
  5. 创建视图层 hello.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        ${msg}
    </body>
    </html>
    
  6. 创建 Controller

    编写一个 Java 控制类:HelloController

    @Controller
    @RequestMapping("hello")
    public class HelloController {
        // 真实的访问地址:项目名/hello/h1
        @RequestMapping("h1")
        public String hello(Model model){
            // 向模型中添加属性msg与值,可以在JSP页面中取出并渲染
            model.addAttribute("msg","HelloSpringMVCAnnotation!");
            // WEB-INF/jsp/hello.jsp
            return "hello";
        }
    }
    
    • @Controller 是为了让 Spring IoC容器初始化时自动扫描到并作为 Spring 中的一个组件

    • @RequestMapping 时为了映射请求路径,这里时因为类与方法上都有映射,所以访问的是项目名/hello/h1

    • 方法中声明 Model 类型的参数是为了把 Action 中的数据带到视图中

    • 方法返回的结果是视图的名称 hello,加上配置文件中的前后缀变成 WEB-INF/jsp/hello.jsp

  7. 启动 Tomcat 测试

小结

实现步骤其实非常简单:

  1. 新建一个 web 项目
  2. 导入相关 jar 包
  3. 编写 web.xml,注册 DispatcherServlet
  4. 编写 SpringMVC 配置文件
  5. 创建对应的控制类 controller
  6. 最后完善前端视图和 controller 之间的对应
  7. 测试运行调试

使用 SpringMVC 必须配置的三大件:

处理器映射器、处理器适配器、视图解析器

通常,我们只需要手动配置视图解析器,而处理器映射器处理器适配器只需要开启注解驱动即可,这样省去了大段的 xml 配置

4. RESTful 和 Controller

4.1 控制器 Controlle

控制器 Controller

  • 控制器负责提供访问应用程序的行为,通常通过接口定义或者注解定义两种实现方式。
  • 控制器负责解析用户的请求并将其转黄为一个模型。
  • 在 SpringMVC 中一个控制器类可以包含多个方法。
  • 在 SpringMVC 中,对于 Controller 的配置方式又很多种

Controller 有两种实现方式

  • 实现 Controller 接口
  • 使用注解

两种方式的对比:

  • 实现接口 Controller 定义控制器是较老的方法
  • 实现接口的缺点是:一个控制器中只有一个方法,如果要多个方法则需要定义多个 Controller;定义的方式比较麻烦
  • 使用注解能够减少配置,更简单

将类当作组件交由 Spring 托管的注解

@Component	// 普通类注解
@Controller	// controller层注解
@Repository	// dao层注解
@Service	// service层注解

4.2 @RequestMapping

  • @RequestMapping 注解用于映射 url 到控制器类或一个特定的处理程序方法。可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
@Controller
@RequestMapping("hello")
public class HelloController {

    @RequestMapping("h1")
    public String hello(Model model){
        model.addAttribute("msg","HelloSpringMVCAnnotation!");
        return "hello";
    }

}

上面代码hello方法的路径是 项目名/hello/h1

4.3 RESTful 风格

概念

RESTful 就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制。

功能

资源:互联网所有的事物都可以被抽象为资源。

资源操作:使用 POST、DELETE、PUT、GET,使用不同方法对资源进行操作。

分别对应 添加、删除、修改、查询。

传统方式操作资源:

通过不同的参数来实现不同的效果,方法单一: POST 和 GET。具体如下:

http://127.0.0.1/item/queryItem.action?id=1 查询,GET

http://127.0.0.1/item/saveItem.action 新增,POST

http://127.0.0.1/item/updateItem.action 更新,POST

http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST

使用RESTful操作资源:

可以通过不同的请求方式来实现不同的效果。具体如下:

http://127.0.0.1/item/1 查询,GET

http://127.0.0.1/item 新增,POST

http://127.0.0.1/item 更新,PUT

http://127.0.0.1/item/1 删除,DELETE

在原来的配置基础上,增加一个 Controller 类

使用 @PathVariable 注解,让方法参数的值对应绑定到一个 URI 模板变量上

@Controller
public class HelloController {

    @RequestMapping("/add/{a}/{b}")
    public String hello(@PathVariable int a,@PathVariable int b, Model model){
        int res = a+b;
        model.addAttribute("msg","结果为:"+res);
        return "hello";
    }
}

浏览器 url 访问及结果

image-20200513110631700

所有地址栏请求默认都是 HTTP GET 类型的。

方法级别的注释变体有如下几个

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

5. 数据处理及跳转

5.1 结果跳转方式

ModelAndView

设置 ModelAndView 对象,根据 view 的名称,和视图解析器跳到指定的页面。

页面:{视图解析器前缀} + viewName +{视图解析器后缀}

<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- 前缀 -->
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <!-- 后缀 -->
    <property name="suffix" value=".jsp"/>
</bean>

对应的 Controller 为:

public class HelloController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        // 创建 ModelAndView 对象
        ModelAndView mv = new ModelAndView();
        // 封装对象,放在 ModelAndView 中,Model
        mv.addObject("msg","HelloSpringMVC!");
        // 封装要跳转的视图,放在 ModelAndView 中,相当于 /WEB-INF/jsp/hello.jsp
         mv.setViewName("hello");
        return mv;
    }
}

5.2 ServletAPI

通过设置 ServletAPI,不需要视图解析器

  1. 通过 HttpServletResponse 进行输出
  2. 通过 HttpServletResponse 实现重定向
  3. 通过 HttpServletResponse 实现转发
@Controller
public class ResultGo {

   @RequestMapping("/result/t1")
   public void test1(HttpServletRequest req, HttpServletResponse rsp) throwsIOException {
       rsp.getWriter().println("Hello,Spring BY servlet API");
  }

   @RequestMapping("/result/t2")
   public void test2(HttpServletRequest req, HttpServletResponse rsp) throwsIOException {
       rsp.sendRedirect("/index.jsp");
  }

   @RequestMapping("/result/t3")
   public void test3(HttpServletRequest req, HttpServletResponse rsp) throwsException {
       //转发
       req.setAttribute("msg","/result/t3");
       req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,rsp);
  }

}

5.3 通过 SpringMVC 来实现转发和重定向

5.3.1 无需视图解析器

测试前,需要将视图解析器注释掉

默认地址是转发地址,加了前缀 foward:/ 也是转发,加了 redirect:/ 前缀代表则是重定向。

@Controller
public class TestController {
    @RequestMapping("test1")
    public String test1(Model model){
        model.addAttribute("msg","test1");
        // 转发
       return "/WEB-INF/jsp/test.jsp";
    }
    @RequestMapping("test2")
    public String test2(Model model){
        model.addAttribute("msg","test2");
        // 转发
        return "forward:/WEB-INF/jsp/test.jsp";
    }
    @RequestMapping("test3")
    public String test3(){
        // 重定向
        return "redirect:/index.jsp";
    }
}

重定向时需要注意一个点。重定向是客户端的,转发是服务端内部的。重定向是让客户端去访问重定向的地址。客户端是无权访问 WEB-INF 目录下资源。

若需要重定向到 WEB-INF 下,可以先定向到一个地址,然后由服务器内部去跳转

@RequestMapping("test3")
public String test3(){
    // 重定向到转发的url
    return "redirect:/toWebInf";
}

@RequestMapping("toWebInf")
public String toWebInf(){
    // 通过转发,可以访问WEB-INF目录下的资源
    return "/WEB-INF/jsp/test.jsp";
}

5.3.2 有视图解析器:

@Controller
@RequestMapping("/t2")
public class TestController2 {

    @RequestMapping("/test1")
    public String test1(Model model){
        model.addAttribute("msg","test1");
        // 转发
        return "test";
    }

    @RequestMapping("/test2")
    public String test2(Model model){
        model.addAttribute("msg","test2");
        // 转发,使用 forward 必须使用全限定名
        return "forward:/WEB-INF/jsp/test.jsp";
    }

    @RequestMapping("/test3")
    public String test3(){
        // 重定向
        return "redirect:/t2/toWebInf";
    }

    @RequestMapping("/toWebInf")
    public String toWebInf(){
        // 通过转发访问 WEB-INF 目录下的资源
        return "test";
    }

}

使用 forward:/ 进行转发和使用 redirect:/ 重定向 时,后面必须是全限定名

5.4 数据显示到前端

第一种:通过ModelAndView

和一开始的讲原理时的代码一样

public class ControllerTest1 implements Controller {

   public ModelAndView handleRequest(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) throws Exception {
       //返回一个模型视图对象
       ModelAndView mv = new ModelAndView();
       mv.addObject("msg","test");
       mv.setViewName("test");
       return mv;
  }
}

第二种:通过ModelMap

ModelMap

@Controller
@RequestMapping("user")
public class UserController {
    // 通过 http://localhost:8080/user/test1?name=xp 传递给后端
    @RequestMapping("test1")
    public String test1(String name, ModelMap model){
        // 接收前端对象
        System.out.println("name->"+name);
        // 返回数据给前端
        model.addAttribute("msg",name);
        // 跳转视图
        return "test";
    }
}

第三种:通过Model

Model

@Controller
@RequestMapping("user")
public class UserController {
    // 通过 http://localhost:8080/user/test2?name=xp 传递给后端
    @RequestMapping("test2")
    public String test1(String name, Model model){
        // 接收前端对象
        System.out.println("name->"+name);
        // 返回数据给前端
        model.addAttribute("msg",name);
        // 跳转视图
        return "test";
    }
}

对比

就对于新手而言,简单来说使用区别就是:

  • Model 只有寥寥几个方法,只适用于存储数据,简化了新手对于 Model 对象的操作和理解
  • ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性
  • ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转

当然,以后开发考虑的更多的是性能和优化,就不能单单仅限于此的了解

5.5 中文乱码问题

测试环境

  1. 创建一个 encoding.jsp,用来提交表单

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>解决乱码问题</title>
    </head>
    <body>
        <form action="${pageContext.request.contextPath}/encoding/test1" method="post">
            <input type="text" name="name"/>
            <input type="submit" value="提交"/>
        </form>
    </body>
    </html>
    
  2. 编写控制器 EncodingController 来接收前端提交的表单

    @Controller
    @RequestMapping("encoding")
    public class EncodingController {
        @PostMapping("test1")
        public String test1(String name, Model model){
            // 查看获取的前端提交的表单数据是否为乱码,方便定位问题
            System.out.println("name->"+name);
            // 获取前端提交的表单数据
            model.addAttribute("msg",name);
            return "test";
        }
    }
    
  3. 提交表单,查看控制台和浏览器页面输出

    image-20200513151536556

image-20200513151605405

​ 可以看出,在前端页面提交表单到后台接收前端表单数据的过程中,乱码就已经产生了!

解决办法

在 web.xml 中配置 SpringMVC 自带的编码过滤器,并重启 Tomcat 服务器

<!-- 编码过滤器:解决中文乱码问题 -->
<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>

但是我们发现,有些极端情况下,这个过滤器对 GET 的支持不好

处理方法:

  1. 修改 Tomcat 配置文件,设置编码

    <Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"
              connectionTimeout="20000"
              redirectPort="8443" />
    
  2. 自定义过滤器

    package com.xp.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.util.Map;
    
    /**
     * 解决get和post请求 全部乱码的过滤器
     */
    public class GenericEncodingFilter implements Filter {
    
        @Override
        public void destroy() {
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            //处理response的字符编码
            HttpServletResponse myResponse = (HttpServletResponse) response;
            myResponse.setContentType("text/html;charset=UTF-8");
    
            // 转型为与协议相关对象
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            // 对request包装增强
            HttpServletRequest myrequest = new MyRequest(httpServletRequest);
            chain.doFilter(myrequest, response);
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
    
    }
    
    //自定义request对象,HttpServletRequest的包装类
    class MyRequest extends HttpServletRequestWrapper {
    
        private HttpServletRequest request;
        //是否编码的标记
        private boolean hasEncode;
    
        //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
        public MyRequest(HttpServletRequest request) {
            super(request);// super必须写
            this.request = request;
        }
    
        // 对需要增强方法 进行覆盖
        @Override
        public Map getParameterMap() {
            // 先获得请求方式
            String method = request.getMethod();
            if (method.equalsIgnoreCase("post")) {
                // post请求
                try {
                    // 处理post乱码
                    request.setCharacterEncoding("utf-8");
                    return request.getParameterMap();
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            } else if (method.equalsIgnoreCase("get")) {
                // get请求
                Map<String, String[]> parameterMap = request.getParameterMap();
                if (!hasEncode) { // 确保get手动编码逻辑只运行一次
                    for (String parameterName : parameterMap.keySet()) {
                        String[] values = parameterMap.get(parameterName);
                        if (values != null) {
                            for (int i = 0; i < values.length; i++) {
                                try {
                                    // 处理get乱码
                                    values[i] = new String(values[i]
                                            .getBytes("ISO-8859-1"), "utf-8");
                                } catch (UnsupportedEncodingException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                    hasEncode = true;
                }
                return parameterMap;
            }
            return super.getParameterMap();
        }
    
        //取一个值
        @Override
        public String getParameter(String name) {
            Map<String, String[]> parameterMap = getParameterMap();
            String[] values = parameterMap.get(name);
            if (values == null) {
                return null;
            }
            return values[0]; // 取回参数的第一个值
        }
    
        //取所有值
        @Override
        public String[] getParameterValues(String name) {
            Map<String, String[]> parameterMap = getParameterMap();
            String[] values = parameterMap.get(name);
            return values;
        }
    }
    

6. JSON

引入 Jackson 依赖

<!-- jackson -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.2</version>
</dependency>

6.1 什么是JSON?

什么是JSON?

  • JSON(JavaScript Object Notation),翻译过来就是 JS 对象标记。是一种轻量级的数据交换格式,目前使用特别广泛。
  • 采用完全独立于编程语言的文本格式来存储和表示数据。
  • 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
  • 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

在 JavaScript 语言中,一切都是对象。因此,任何 JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。

6.2 JSON的格式

JSON 的格式:

  • 对象表示为键值对,数据由逗号分隔。
  • 花括号保存对象。
  • 方括号保存数组。

JSON 键值对 是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键值对组合中的键名卸载前面,并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:

{"name": "zhangsan"}
{"age": "18"}
{"sex": "男"}

很多人搞不清楚 JSON 和 JavaScript 对象的关系,甚至谁是谁都不清楚。其实,可以这么理解:

JSON 是 JavaScript 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。

var obj = {name:"zhangsan",age:18,sex:"男"};
var json = ‘{"name","zhangsan","age":18,"sex":"男"}’;

6.3 JSON 和 JavaScript 对象互转

要实现从 JSON 字符串转换为 JavaScript 对象,使用 JSON.parse() 方法:

var obj = JSON.parse(‘{"name","zhangsan","age":18,"sex":"男"}’);

要实现从 JavaScript 对象转换为 JSON 字符串,使用 JSON.stringify() 方法:

var json = JSON.stringify({name:"zhangsan",age:18,sex:"男"});

测试环境搭建

  1. 新建一个 module: SpringMVC-05-JSON,并添加web支持

  2. 在 web 目录下新建一个 json1.html,编写测试内容

    <!DOCTYPE html>
    <html lang="en">
    <head>
       <meta charset="UTF-8">
       <title>JSON</title>
    </head>
    <body>
    
    <script type="text/javascript">
       //编写一个js的对象
       var user = {
           name:"张三",
           age:18,
           sex:"男"
      };
       //将js对象转换成json字符串
       var str = JSON.stringify(user);
       console.log(str);
       
       //将json字符串转换为js对象
       var user2 = JSON.parse(str);
       console.log(user2.age,user2.name,user2.sex);
    
    </script>
    
    </body>
    </html>
    
  3. 在IDEA中使用浏览器打开,查看控制台输出

    image-20200513205935055

6.4 Controller返回JSON数据

Jackson: 目前比较好的 JSON 解析工具

当然工具不止这一种,比如还有阿里的 fastjson ,谷歌的 gson 等等

我们这里使用 Jackson,具体操作如下:

  1. 导入 Jackson 的依赖

    <!-- jackson -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.2</version>
    </dependency>
    
  2. 配置 web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!-- 注册DispatcherServlet -->
        <servlet>
            <servlet-name>SpringMVC</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:config/springmvc-servlet.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>SpringMVC</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <!-- 编码过滤 -->
        <filter>
            <filter-name>encoding</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>forceEncoding</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>encoding</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    </web-app>
    
  3. 创建 springmvc-servlet.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 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.xp.controller"/>
        <!-- 静态资源过滤 -->
        <mvc:default-servlet-handler/>
        <!-- 开启mvc注解驱动支持 -->
        <mvc:annotation-driven/>
    
        <!-- 视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <!-- 前缀 -->
            <property name="prefix" value="/WEB-INF/jsp"/>
            <!-- 后最 -->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    
  4. 编写实体类 User

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        public String name;
        public String age;
        public String sex;
    }
    
  5. 编写控制器 JsonController

    @Controller
    public class JsonController {
        @RequestMapping(path = "/json1", produces = "application/json;charset=utf-8")
        @ResponseBody // 添加ResponseBody注释,它就不会走视图解析器,会直接返回一个字符串
        public String json1() throws JsonProcessingException {
            ObjectMapper objectMapper = new ObjectMapper();
            User user = new User("zhangsan", "18", "男");
            return objectMapper.writeValueAsString(user);
        }
    }
    

    @ResponseBody :该注解可以添加在方法或类上,添加该注解后,该类或该方法就不会走视图解析器,会直接返回一个字符串。

    在前后端分离开发中,一般都使用 @ResponseBody 注解,十分方便。

    如果在 @RequestMapping 注解中不配置 produces 属性的话,就会产生中文乱码。

    image-20200513211558172

    当然,还有一种解决乱码一劳永逸的方法

    在 springmvc-servlet.xml 配置文件中增加如下配置

    <!-- 开启mvc注解驱动支持 -->
    <mvc:annotation-driven>
        <!-- 解决jackson乱码问题 -->
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                        <property name="failOnEmptyBeans" value="false"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    

使用这种方法解决乱码问题后,就不需要在 @RequestMapping 注解中配置 produces 属性

输出集合

@RequestMapping("/json3")
public String json3() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    User user1 = new User("张三1", "18", "男");
    User user2 = new User("张三2", "18", "男");
    User user3 = new User("张三3", "18", "男");
    User user4 = new User("张三4", "18", "男");
    List<User> users = new ArrayList<>();
    users.add(user1);
    users.add(user2);
    users.add(user3);
    users.add(user4);
    String str = objectMapper.writeValueAsString(users);
    return str;
}

输出时间对象

@RequestMapping("/json4")
public String json4() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String format = sdf.format(date);
    return objectMapper.writeValueAsString(format);
}

6.5 fastjson

fastjson.jar 是阿里开发的一款专门用于 Java 开发的包。可以方便的实现 json 对象与 JavaBean 对象的转换,实现 JavaBean 对象与 json 字符串的转换,实现 json 对象与 json 字符串的转换。

导入 fastjson 依赖

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.68</version>
    </dependency>
</dependencies>

fastjson 三个主要的类:

JSONObject 代表 json 对象

  • JSONObject 实现了 Map 接口
  • JSONObject 对应 json 对象,通过各种形式的 get() 方法可以获取 json 对象中的数据,也可以利用诸如 size(),isEmpty() 等方法获取键值对的个数和判断是否未空。其本质是通过实现 Map 接口并调用接口中的方法完成的。

JSONArray 代表 json 对象数组

  • 内部是有 List 接口中的方法来完成操作

JSON 代表 JSONObject 和 JSONArray 的转化

  • JSON 类源码分析与使用
  • 仔细观察这些方法,主要是实现 json 对象,json 对象数组,javabean 对象,json 字符串之间的相互转化

代码测试

public class FastJsonDemo {
   public static void main(String[] args) {
       //创建一个对象
       User user1 = new User("张三1号", 3, "男");
       User user2 = new User("张三2号", 3, "男");
       User user3 = new User("张三3号", 3, "男");
       User user4 = new User("张三4号", 3, "男");
       List<User> list = new ArrayList<User>();
       list.add(user1);
       list.add(user2);
       list.add(user3);
       list.add(user4);

       System.out.println("*******Java对象 转 JSON字符串*******");
       String str1 = JSON.toJSONString(list);
       System.out.println("JSON.toJSONString(list)==>"+str1);
       String str2 = JSON.toJSONString(user1);
       System.out.println("JSON.toJSONString(user1)==>"+str2);

       System.out.println("\n****** JSON字符串 转 Java对象*******");
       User jp_user1=JSON.parseObject(str2,User.class);
       System.out.println("JSON.parseObject(str2,User.class)==>"+jp_user1);

       System.out.println("\n****** Java对象 转 JSON对象 ******");
       JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2);
       System.out.println("(JSONObject) JSON.toJSON(user2)==>"+jsonObject1.getString("name"));

       System.out.println("\n****** JSON对象 转 Java对象 ******");
       User to_java_user = JSON.toJavaObject(jsonObject1, User.class);
       System.out.println("JSON.toJavaObject(jsonObject1, User.class)==>"+to_java_user);
  }
}

7. AJAX

7.1 简介

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
  • AJAX 时一种无需重新加载整个网页的情况下,能够更新部分网页的技术。
  • AJAX 不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的 Web 应用程序的技术。
  • 在 2005 年,Google 通过其 Google Suggest 使得 AJAX 变得流行起来,Google Suggest 能够自动帮你完成搜索单词。
  • Google Suggest 使用 AJAX 创造出动态性极强的 web 界面;当你在谷歌的搜索框输入关键字时,JavaScript 会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表。
  • 就和国内百度的搜索框一样。
  • 传统的网页(不用 AJAX 技术的网页),想要更新内容或者提交一个表单,都需要重新加载整个网页。而网页的很多内容是并不需要改变的。
  • 使用 AJAX 技术的网页,通过在后台服务器进行少量的数据交换,就可以实现异步局部更新整个网页。
  • 使用 AJAX ,用户可以创建接近本地桌面应用的直接、高可用、更丰富、更动态的 Web 用户界面。

使用AJAX可以做:

  • 注册时,输入用户名自动检测用户是否已经存在。
  • 登陆时,提示用户名密码错误
  • 删除数据行时,将行ID发送到后台,后台在数据库中删除,数据库删除成功后,在页面 DOM 中将数据行也删除
  • 等等。。

7.2 iframe标签

iframe,可以算是伪造的 AJAX,也可以实现页面局部刷新。虽然,iframe 标签的使用比 AJAX 要简单得多,但其安全性以及用户体验性并不如人意。

下面,我们来使用 iframe 标签实现页面的局部刷新

  1. 创建一个新的工程项目:SpringMVC-06-AJAX,添加 web 框架

  2. 在 index.jsp 中编写 iframe 标签

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
      <head>
        <title>iframeTest</title>
        <script>
          // iframe 加载url
          function loadUrl(url) {
            var iframe = document.getElementById("iframe");
            iframe.src = url;
          }
        </script>
      </head>
      <body>
      <%-- 点击按钮后,iframe 加载页面 --%>
      <div>
        <button id="iframeBtn" onclick="loadUrl('iframe.jsp')">点我加载 iframe.jsp</button>
      </div>
      <iframe src="" id="iframe"></iframe>
      </body>
    </html>
    
  3. 在 web 目录下创建 iframe.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h1>这是 iframe.jsp</h1>
    </body>
    </html>
    
  4. 测试是否页面局部加载成功

    image-20200515220355919

    如果我们让 loadUrl() 的参数是百度网址时,也可以跳转到百度的页面

    <div>
      <button id="iframeBtn" onclick="loadUrl('http://www.baidu.com')">点我加载 iframe.jsp</button>
    </div>
    

    并且我们按 f12 打开开发者工具的时候,会发现页面中,只有 iframe 内部的 #document 在改变,而整个页面并没有刷新。所以 iframe 标签是实现页面局部刷新的,可以看成是一个伪 AJAX

    image-20200515221029851

7.2 原生AJAX

XMLHttpRequest 是 AJAX 的基础。

所有现代浏览器均支持 XMLHttpRequest 对象(IE5 和 IE6 使用 ActiveXObject)

XMLHttpRequest 用于在后台与服务器交换数据。这意味着可以不重新加载整个网页的情况下,对网页的某部分进行更新

7.3 jQuery AJAX

jQuery AJAX 简介

  • jQuery 是一个优秀的js框架,对 js 原生的 Ajax 进行了封装。
  • 在封装后的 AJAX 的操作更加简洁,功能更加强大
  • 使用 jQuery AJAX ,避免重复造轮子

Ajax 的核心是 XMLHttpRequest 对象(XHR),XHR 为向服务器发送请求和解析服务器响应提供了接口,能够以异步方式从服务器获取新数据。

jQuery 提供多个与 AJAX 有关的方法。

通过 jQuery AJAX 方法,我们能够使用 HTTP GET 和 HTTP POST 从远程服务器上请求文本、HTML、XML 或 JSON。同时我们能够把这些外部数据直接载入网页的被选元素中。

jQuery Ajax本质就是 XMLHttpRequest,对他进行了封装,方便调用!

jQuery.ajax(...)
      部分参数:
            url:请求地址
            type:请求方式,GET、POST(1.9.0之后用method)
        headers:请求头
            data:要发送的数据
    contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8")
          async:是否异步
        timeout:设置请求超时时间(毫秒)
      beforeSend:发送请求前执行的函数(全局)
        complete:完成之后执行的回调函数(全局)
        success:成功之后执行的回调函数(全局)
          error:失败之后执行的回调函数(全局)
        accepts:通过请求头发送给服务器,告诉服务器当前客户端可接受的数据类型
        dataType:将服务器端返回的数据转换成指定类型
          "xml": 将服务器端返回的内容转换成xml格式
          "text": 将服务器端返回的内容转换成普通文本格式
          "html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。
        "script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式
          "json": 将服务器端返回的内容转换成相应的JavaScript对象
        "jsonp": JSONP 格式使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数

SpringMVC使用AJAX

  1. 配置 springmvc-servlet.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 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.xp.controller"/>
        <!-- 静态资源过滤 -->
        <mvc:default-servlet-handler/>
        <!-- 开启注解驱动支持 -->
        <mvc:annotation-driven/>
    
        <!-- 视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <!-- 前缀 -->
            <property name="prefix" value="/WEB=INF/html/"/>
            <!-- 后缀 -->
            <property name="suffix" value=".html"/>
        </bean>
    
    </beans>
    
  2. 配置 web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!-- 注册 DispatcherServlet -->
        <servlet>
            <servlet-name>SpringMVC</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc-servlet.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>SpringMVC</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. 编写控制器 AJAXController,假设数据库中账号和密码分别是 admin 和 12345

    package com.xp.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    public class AJAXController {
    	// produces 解决乱码问题
        @RequestMapping(path = "/ajax/userName" ,produces="text/html;charset=UTF-8;")
        @ResponseBody
        public String ajaxUserName(String userName){
            return ("admin".equals(userName))?"正确":"错误";
        }
    
        @RequestMapping(path = "/ajax/password" ,produces="text/html;charset=UTF-8;")
        @ResponseBody
        public String ajaxPassword(String password){
            return ("12345".equals(password))?"正确":"错误";
        }
    
    }
    
  4. 编写前端页面和 AJAX 请求

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>AJAX</title>
        <!-- 引入 jQuery -->
        <script src="../js/jquery-3.4.1.min.js"></script>
        <script type="text/javascript">
            function userNameBlur() {
                $.post({
                    url: "/ajax/userName",
                    data: "userName="+$("#userName").val(),
                    dataType: "text",
                    success: function (data) {
                        var userNameAjax = $("#userNameAjax");
                        if ("正确" === data){
                            userNameAjax.css("color","green");
                        }else {
                            userNameAjax.css("color","red");
                        }
                        userNameAjax.html(data);
                    }
                })
            }
            function passwordBlur() {
                $.post({
                    url: "/ajax/password",
                    data: "password="+$("#password").val(),
                    dataType: "text",
                    success: function (data) {
                        var passwordAjax = $("#passwordAjax");
                        if ("正确" === data){
                            passwordAjax.css("color","green");
                        }else {
                            passwordAjax.css("color","red");
                        }
                        passwordAjax.html(data);
                    }
                });
            }
        </script>
    </head>
    <body>
    <div id="test">
        <label>用户名:<input type="text" id="userName" onblur="userNameBlur()"/></label>
        <span id="userNameAjax"></span>
    </div>
    <div>
        <label>密码:<input type="text" id="password" onblur="passwordBlur()"/></label>
        <span id="passwordAjax"></span>
    </div>
    </body>
    </html>
    
  5. 启动 Tomcat 服务器,测试

    测试时,可以发现,当我们鼠标点击输入框后再点击其它地方失去焦点时,右边的span标签内的内容会发生变化。这样,我们就学会了 SpringMVC 中使用 AJAX。

8. Thymeleaf模板引擎

8.1 简介

Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎,类似 JSP, Velocity, FreeMaker 等。它可以轻易与 Spring MVC 等 Web 框架进行集成作为 Web 应用的模板引擎。是 SpringBoot 官方使用的模板引擎。

官网:https://www.thymeleaf.org/

8.2 特点

  • 动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行。
  • 开箱即用:它提供标准和 Spring 标准两种方言,可以直接套用模板实现 JSTL、OGNL 表达式效果。
  • 多方言支持:Thymeleaf 提供 Spring 标准方言和一个与 SpringMVC 完美集成的可选模板,可以快速地实现表单绑定、属性编辑器、国际化等功能。
  • 与 SpringBoot 完美整合,SpringBoot 提供了 Thymeleaf 的默认配置,并且为 Thymeleaf 设置了视图解析器,可以像以前操作 jsp 一样来操作 Thymeleaf。代码几乎没有任何区别,就是在模板语法上有区别。

8.3 简单使用

  1. 新建一个module:Springmvc-07-Thymeleaf,添加 web 支持

  2. 配置 web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!-- 注册 DispatcherServlet -->
        <servlet>
            <servlet-name>SpringMVC</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:config/springmvc-servlet.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>SpringMVC</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <!-- 解决乱码问题 -->
        <filter>
            <filter-name>encoding</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>encoding</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    </web-app>
    
  3. 导入 Themeleaf 依赖

    <dependencies>
        <!-- Thymeleaf 支持Spring方言 -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.11.RELEASE</version>
        </dependency>
        <!-- Thymeleaf -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.11.RELEASE</version>
        </dependency>
    </dependencies>
    
  4. 配置springmvc-servlet.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 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.xp.controller"/>
        <mvc:default-servlet-handler/>
        <mvc:annotation-driven/>
    
        <!-- 模板解析器 -->
        <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver" id="templateResolver">
            <property name="prefix" value="/WEB-INF/html"/>
            <property name="suffix" value=".html"/>
            <property name="templateMode" value="HTML"/>
            <property name="characterEncoding" value="utf-8"/>
            <property name="cacheable" value="false"/>
        </bean>
    
        <!-- 模板引擎 -->
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine" id="templateEngine">
            <property name="templateResolver" ref="templateResolver"/>
            <property name="enableSpringELCompiler" value="true"/>
        </bean>
    
        <!-- 视图解析器 -->
        <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
            <property name="templateEngine" ref="templateEngine"/>
            <property name="characterEncoding" value="utf-8"/>
        </bean>
    
    </beans>
    
  5. 编写控制器

    @Controller
    public class ThymeleafController {
    
        @RequestMapping("/thymeleaf")
        public String thymeleaf(Model model){
            model.addAttribute("thymeleaf","Hello,Thymeleaf!");
            return "thymeleaf";
        }
    
    }
    
  6. 编写视图层

    注:要使用 Thymeleaf 模板引擎的页面,必须要在 html 标签上加上 xmlns:th="http://www.thymeleaf.org"

    index.xml

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Thymeleaf</title>
    </head>
    <body>
        <h1><a href="/thymeleaf">Hello,Thymeleaf!</a></h1>
    </body>
    </html>
    

    thymeleaf.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <!--/*@thymesVar id="thymeleaf" type="java.lang.String"*/-->
        <div th:text="${thymeleaf}"></div>
    </body>
    </html>
    
  7. 测试

我们可以发现 SpringMVC 整合 Thymeleaf 模板引擎,只是导入依赖后,在 springmvc-servlet.xml 配置文件中加入了模板解析器、模板引擎和视图解析器三个 bean,并不需要修改 web.xml 中的配置。我们就可以在 html 中像 jsp 一样使用 el 表达式了。下面,我们来看下常用的 Thymeleaf 语法以及标签。

8.4 常用语法

Thymeleaf 的主要作用是把 model 中的数据渲染到html 中,因此其语法主要是如何解析 model 中的数据。从以下方面来学习:

  • 变量、方法、条件判断、循环、运算【逻辑运算、布尔运算、比较运算、条件运算】
  • 其它
  1. Thymeleaf 通过 ${...} 来获取 model 中的变量,语法和 el 表达式差不多,但它是 ognl 表达式

    <!--/*@thymesVar id="thymeleaf" type="java.lang.String"*/-->
    <div th:text="${thymeleaf}"></div>
    
  2. Themeleaf 通过 th:object 自定义变量,可以通过 *{...} 取出对应的属性

    <!--/*@thymesVar id="user" type="com.xp.entity.User"*/-->
    <div th:object="${user}">
        <h2 th:text="*{name}"></h2>
        <h2 th:text="*{age}"></h2>
        <!--/*@thymesVar id="friend" type="com.xp.entity.Friend"*/-->
        <h2 th:text="*{friend.name}"></h2>
    </div>
    
  3. ognl 表达式本身就支持方法调用,但需要注意的是必须使用注释指明该变量是哪个类的

    <!--/*@thymesVar id="user" type="com.xp.entity.User"*/-->
    <!--/*@thymesVar id="name" type="java.lang.String"*/-->
    <!--/*@thymesVar id="age" type="java.lang.Integer"*/-->
    <div th:object="${user}">
        <h2 th:text="*{name.hashCode()}"></h2>
        <h2 th:text="*{age.hashCode()}"></h2>
        <!--/*@thymesVar id="friend" type="com.xp.entity.Friend"*/-->
        <h2 th:text="*{friend.name.hashCode()}"></h2>
    </div>
    

    Thymeleaf 中提供了一些内置对象,并且这些对象中提供了一些方法,方便我们调用、获取这些对象,需要使用 #对象名 来调用

    • 一些环境相关的对象

      对象 作用
      #ctx 获取 Thymeleaf 自己的 Context 对象
      #request 如果是 web 程序,可以获取 HttpServletRequest 对象
      #respone 如果是 web 程序,可以获取 HttpServletResponse 对象
      #session 如果是 web 程序,可以获取 HttpSession 对象
      #servletContext 如果是web 程序,可以获取 HttpServletContext 对象
    • Thymeleaf 提供的全局对象

      对象 作用
      #datas 处理 java.util.date 的工具对象
      #calendars 处理 java.util.calendar 的工具对象
      #numbers 用来对数字格式的方法
      #strings 用来处理字符串的方法
      #bools 用来判断布尔值的方法
      #arrays 用来护理数组的方法
      #lists 用来处理 List 集合的方法
      #sets 用来处理 Set 集合的方法
      #maps 用来处理 Map 集合的方法

      例如:

      <div th:text="${#dates.format(data,'yyyy-MM-dd HH:mm:ss')}"></div>
      
      <div th:Object="${#session.getAttribute('user')}">
          <h1 th:text="*{name}"></h1>
          <h1 th:text="*{age}"></h1>
          <h1 th:text="*{friend.name}"></h1>
      </div>
      
  4. 字面值

    • 字符串字面值:使用一对 '' (单引号)引用的内容就是字符串的字面值了

      <div th:text="'字符串字面值'"></div>
      
    • 数字字面值:不需要任何特殊语法,写的是是什么就是什么,可以进行算术运算

      <div th:text="2020"></div>
      <div th:text="2018+2"></div>
      
    • 布尔字面值:只有 true 或 false

      <div th:if="true">布尔值:true</div>
      
  5. 字符串拼接

    • 我们经常使用得普通字符串拼接方法

      <div th:text="'欢迎 '+${user.name}+‘ !’"></div>
      
    • Thymeleaf 使用一对 | 拼接

      <div th:text="|欢迎 +${user.name} !|"></div>
      
  6. 运算

    • 算术运算

      支持的运算符: + - * / %

      <div th:text="${user.age}%2"></div>
      
    • 比较运算运算

      支持的比较运算: >,<,>=,<=,但是 >,< 不能直接使用,因为 html 会解析为标签,要使用别名

      注意 == 和 != 不仅可以比较数值,类似于 equals 的功能

      可以使用的别名:gt(>), lt(<), ge(>=) , le(<=), not(!), eq(==), neq/ne(!=)

    • 条件运算

      • 三元运算

        <div th:text="${user.isAdmin}?'管理员':'普通会员'"></div>
        
      • 默认值

        有的时候,我们取一个值可能为空,这个时候需要做非空判断,可以使用表达式 ?: 默认值简写

        <span th:text="${user.name} ?: '二狗'"></span>
        
  7. Thymeleaf 通过 th:each 实现循环

    <div th:each="list:${lists}">
        <h1 th:text="${list}"></h1>
    </div>
    

    遍历的结合可以是以下类型

    • Iterable,实现了Iterable接口的类
    • Enumeration,枚举
    • Interator,迭代器
    • Map,遍历得到的是Map.Entry
    • Array,数组及其它一切符合数组结果的对象
  8. Thymeleaf 使用 th:if 或者 if:unless 来进行逻辑判断

    <div th:if="${user.age} >= 18">
        <h1>成年人</h1>
    </div>
    

    如果表达式的值为 true,则标签会渲染到页面,否则不进行渲染。

    以下情况会被认为 true

    • 表达式值为 true
    • 表达式值为非0数值
    • 表达式值为非0字符
    • 表达式值为字符串,但不是“false”、“no”,“off”
    • 表达式不是布尔、字符串、数字、字符中的任何一种

    其它情况包括 null 都被认定为 false

  9. Thymeleaf 使用 th:switchth:case 来进行分支控制

    <div th:switch="${user.role}">
      <p th:case="'admin'">用户是管理员</p>
      <p th:case="'manager'">用户是经理</p>
      <p th:case="*">用户是别的玩意</p>
    </div>
    

    需要注意的是,一旦有一个 th:case 成立,其它的则不再判断。与 java 中的 switch 是一样的

    另外 th:case="*" 表示默认,放在最后

  10. Thymeleaf 使用 th:inline="javascript" 来声明该 script 标签的脚本是需要特殊处理的 js 脚本

    <script th:inline="javascript">
        var user = /*[[${user}]]*/ {};
        var age = /*[[${user.age}]]*/ 20;
        console.log(user);
        console.log(age)
    </script>
    
    var user = /*[[Thymeleaf表达式]]*/
    

    因为 Thymeleaf 被注释起来,因此即便是静态环境下,js 代码也不会报错,而是采用表达式后面跟着的默认值。且 User 对象会直接处理为 json 格式

9. 拦截器

9.1概述

SpringMVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现待定的功能。

过滤器与拦截器的区别:拦截器时 AOP 思想的具体应用。

过滤器

  • Servlet 规范中的一部分,任何 JavaWeb 工程都可以使用
  • 再 url-pattern 中配置了 /* 之后,可以对所有要访问的资源进行拦截

拦截器

  • 拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能使用
  • 拦截器只会拦截访问的控制器方法,如果访问得事 jsp/html/css/image/js 是不会进行拦截的

9.2 自定义拦截器

那如何实现拦截器呢?

想要自定义拦截器,必须实现 HandlerInterceptor 接口。

  1. 新建一个 Moudule:Spring-08-Interceptor ,添加 web 支持

  2. 配置 springmvc-servlet.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 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.xp.controller"/>
        <mvc:default-servlet-handler/>
        <mvc:annotation-driven/>
    
        <!-- 拦截器 -->
        <mvc:interceptors>
            <mvc:interceptor>
                <mvc:mapping path="/**"/>
                <bean class="com.xp.config.MyInterceptor"/>
            </mvc:interceptor>
        </mvc:interceptors>
        
        <!-- 模板解析器 -->
        <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
            <property name="prefix" value="/WEB-INF/html/"/>
            <property name="suffix" value=".html"/>
            <property name="cacheable" value="false"/>
            <property name="characterEncoding" value="utf-8"/>
            <property name="templateMode" value="HTML"/>
        </bean>
    
        <!-- 模板引擎 -->
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine" id="templateEngine">
            <property name="templateResolver" ref="templateResolver"/>
            <property name="enableSpringELCompiler" value="true"/>
        </bean>
    
        <!-- 视图解析器 -->
        <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
            <property name="templateEngine" ref="templateEngine"/>
            <property name="characterEncoding" value="utf-8"/>
        </bean>
    
    </beans>
    
  3. 配置 web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!-- 注册DispatcherServlet -->
        <servlet>
            <servlet-name>SpringMVC</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:config/springmvc-servlet.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>SpringMVC</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <!-- 解决乱码问题 -->
        <filter>
            <filter-name>encoding</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>encoding</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    </web-app>
    
  4. 编写实体类 User

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private String account;
        private String password;
    }
    
  5. 创建控制器 InterceptorController

    @Controller
    @RequestMapping("/user")
    public class InperceptorController {
    
        @RequestMapping("/toMain")
        public String toMain(){
            return "main";
        }
    
        @RequestMapping("/toLogin")
        public String toLogin(){
            return "login";
        }
    
        @RequestMapping("/login")
        public String login(User user, HttpSession session){
            // 模拟从数据库中查询数据后判断账号密码是否正确,正确则设置session,否则返回登录页面
            if ("admin".equals(user.getAccount()) && "12345".equals(user.getPassword())){
                session.setAttribute("user",user);
                return "main";
            }else {
                return toLogin();
            }
        }
    
        @RequestMapping("/logout")
        public String logout(HttpSession session){
            session.removeAttribute("user");
            return toLogin();
        }
    
    }
    
  6. 编写视图层

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h2><a href="/user/toMain">首页</a></h2>
    <h2><a href="/user/toLogin">登录</a></h2>
    </body>
    </html>
    

    login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
    </head>
    <body>
    <form action="/user/login">
        <label>用户名:<input type="text" name="account"/></label>
        <label>密码:<input type="text" name="password"/></label>
        <input type="submit" value="登录"/>
    </form>
    </body>
    </html>
    

    main.html

    <!DOCTYPE html>
    <html lang="en"  xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>首页</h1>
    <!-- 有session则显示该div -->
    <div th:object="${#session.getAttribute('user')}"  th:if="Object">
        <span th:text="*{account}"></span>
        <a href="/user/logout">退出</a>
    </div>
    </body>
    </html>
    
  7. 创建拦截器 MyInterceptor ,实现 HandlerInterceptor 接口,并重写 preHandle() 方法

    public class MyInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String url = request.getRequestURI();
            System.out.println(url);
            // 导航页放行
            if ("/".equals(url)){
                return true;
            }
            // 导航页放行
            if (url.contains("index")){
                return true;
            }
            // 登录请求放行
            if (url.contains("login")){
                return true;
            }
            // 跳转登录页面请求放行
            if (url.contains("toLogin")){
                return true;
            }
            // 登录后有session的放行
            if (request.getSession().getAttribute("user") != null){
                return true;
            }
            // 拦截后跳转到登录页面
            response.sendRedirect("/user/toLogin");
            // 拦截
            return false;
        }
    }
    
  8. 启动 Tomcat 测试

    测试没登录前能不能进入首页、测试登录后从导航页是否能进入首页、测试登录后退出后能否再进入首页。

    测试成功则代表拦截器配置成功

10.文件的上传下载

10.1 准备工作

文件上传是项目开发中最常见的功能之一 ,SpringMVC 可以很好的支持文件上传,但是 SpringMVC 上下文中默认没有装配 MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置 MultipartResolver。

前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。

对表单中的 enctype 属性做个详细的说明:

  • application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
  • multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
  • text/plain:除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
<form action="" enctype="multipart/form-data" method="post">
   <input type="file" name="file"/>
   <input type="submit">
</form>

一旦设置了 enctype 为 multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation 发布了开源的 Commons FileUpload 组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。

  • Servlet3.0 规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。
  • 而Spring MVC 则提供了更简单的封装。
  • Spring MVC 为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。
  • Spring MVC 使用 Apache Commons FileUpload 技术实现了一个 MultipartResolver 实现类:
  • CommonsMultipartResolver。因此,SpringMVC 的文件上传还需要依赖 Apache Commons FileUpload 的组件。

10.2 文件上传

  1. 导入文件上传的jar包,commons-fileupload , Maven会自动帮我们导入他的依赖包 commons-io包;

    <!--文件上传-->
    <dependency>
       <groupId>commons-fileupload</groupId>
       <artifactId>commons-fileupload</artifactId>
       <version>1.3.3</version>
    </dependency>
    <!--servlet-api导入高版本的-->
    <dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>javax.servlet-api</artifactId>
       <version>4.0.1</version>
    </dependency>
    
  2. 配置bean:multipartResolver

    注意!!!这个bena的id必须为:multipartResolver , 否则上传文件会报400的错误!在这里栽过坑,教训!

    <!--文件上传配置-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
       <!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
       <property name="defaultEncoding" value="utf-8"/>
       <!-- 上传文件大小上限,单位为字节(10485760=10M) -->
       <property name="maxUploadSize" value="10485760"/>
       <property name="maxInMemorySize" value="40960"/>
    </bean>
    

    CommonsMultipartFile 的 常用方法:

    • String getOriginalFilename():获取上传文件的原名
    • InputStream getInputStream():获取文件流
    • void transferTo(File dest):将上传文件保存到一个目录文件中

    我们去实际测试一下

  3. 编写前端页面

    <form action="/upload" enctype="multipart/form-data" method="post">
     <input type="file" name="file"/>
     <input type="submit" value="upload">
    </form>
    
  4. 编写控制器

    @Controller
    public class FileController {
       //@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
       //批量上传CommonsMultipartFile则为数组即可
       @RequestMapping("/upload")
       public String fileUpload(@RequestParam("file") CommonsMultipartFile file ,HttpServletRequest request) throws IOException {
    
           //获取文件名 : file.getOriginalFilename();
           String uploadFileName = file.getOriginalFilename();
    
           //如果文件名为空,直接回到首页!
           if ("".equals(uploadFileName)){
               return "redirect:/index.jsp";
          }
           System.out.println("上传文件名 : "+uploadFileName);
    
           //上传路径保存设置
           String path = request.getServletContext().getRealPath("/upload");
           //如果路径不存在,创建一个
           File realPath = new File(path);
           if (!realPath.exists()){
               realPath.mkdir();
          }
           System.out.println("上传文件保存地址:"+realPath);
    
           InputStream is = file.getInputStream(); //文件输入流
           OutputStream os = new FileOutputStream(new File(realPath,uploadFileName));//文件输出流
    
           //读取写出
           int len=0;
           byte[] buffer = new byte[1024];
           while ((len=is.read(buffer))!=-1){
               os.write(buffer,0,len);
               os.flush();
          }
           os.close();
           is.close();
           return "redirect:/index.jsp";
      }
    }
    
  5. 测试上传文件,OK!

采用file.Transto 来保存上传的文件

  1. 编写Controller

    @RequestMapping("/upload2")
    public String  fileUpload2(@RequestParam("file") CommonsMultipartFile file,HttpServletRequest request) throws IOException {
    
       //上传路径保存设置
       String path = request.getServletContext().getRealPath("/upload");
       File realPath = new File(path);
       if (!realPath.exists()){
           realPath.mkdir();
      }
       //上传文件地址
       System.out.println("上传文件保存地址:"+realPath);
    
       //通过CommonsMultipartFile的方法直接写文件(注意这个时候)
       file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
    
       return "redirect:/index.jsp";
    }
    
  2. 前端表单提交地址修改

  3. 访问提交测试,OK!

10.3 文件下载

文件下载步骤:

  1. 设置 response 响应头

  2. 读取文件 -- InputStream

  3. 写出文件 -- OutputStream

  4. 执行操作

  5. 关闭流 (先开后关)

代码实现

@RequestMapping(value="/download")
public String downloads(HttpServletResponse response ,HttpServletRequest request)throws Exception{
   //要下载的图片地址
   String  path = request.getServletContext().getRealPath("/upload");
   String  fileName = "基础语法.jpg";

   //1、设置response 响应头
   response.reset(); //设置页面不缓存,清空buffer
   response.setCharacterEncoding("UTF-8"); //字符编码
   response.setContentType("multipart/form-data"); //二进制传输数据
   //设置响应头
   response.setHeader("Content-Disposition",
           "attachment;fileName="+URLEncoder.encode(fileName, "UTF-8"));

   File file = new File(path,fileName);
   //2、 读取文件--输入流
   InputStream input=new FileInputStream(file);
   //3、 写出文件--输出流
   OutputStream out = response.getOutputStream();

   byte[] buff =new byte[1024];
   int index=0;
   //4、执行 写出操作
   while((index= input.read(buff))!= -1){
       out.write(buff, 0, index);
       out.flush();
  }
   out.close();
   input.close();
   return null;
}

前端

<a href="/download">点击下载</a>

测试,文件下载OK

11. SSM框架整合

环境配置

  1. 创建数据库并创建对应的表,并插入数据

    # 创建数据库 ssmproject
    CREATE DATABASE IF NOT EXISTS ssmproject;
    
    # 创建表
    DROP TABLE IF EXISTS book;
    CREATE TABLE IF NOT EXISTS book(
    	book_id INT(10) PRIMARY KEY	auto_increment COMMENT '书id',
    	book_name VARCHAR(100) NOT NULL COMMENT '书名',
    	book_counts INT(11) NOT NULL COMMENT '数量',
    	detail VARCHAR(200) COMMENT '描述'
    );
    
    # 插入数据
    INSERT INTO book (book_name,book_counts,detail) 
    VALUES
    ('Java',1,'从入门到放弃'),
    ('MySQL',10,'从删库到排路'),
    ('Linux',5,'从入门到进牢');
    
    
  2. 在 pom.xml 中导入依赖并配置静态资源过滤

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.xp</groupId>
        <artifactId>SSMProject</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <!-- 依赖 -->
        <dependencies>
            <!-- junit 测试 -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
            <!-- SpringMVC -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.3.RELEASE</version>
            </dependency>
            <!-- 织入包 -->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.5</version>
            </dependency>
            <!-- servlet支持 -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
            </dependency>
            <!-- jackson 解析json -->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.10.2</version>
            </dependency>
            <!-- spring-jdbc JDBC驱动支持 -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.2.3.RELEASE</version>
            </dependency>
            <!-- mysql驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.46</version>
            </dependency>
            <!-- mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.2</version>
            </dependency>
            <!-- c3p0 数据库连接池 -->
            <dependency>
                <groupId>com.mchange</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.5.5</version>
            </dependency>
            <!-- mybatis-Spring -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.3</version>
            </dependency>
            <!-- lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.10</version>
            </dependency>
            <!-- thymeleaf -->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf</artifactId>
                <version>LATEST</version>
            </dependency>
            <!-- Spring 整合 thymeleaf -->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>LATEST</version>
            </dependency>
            <!-- log4j 日志 -->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>2.12.1</version>
            </dependency>
        </dependencies>
    
        <!-- 静态资源过滤问题 -->
        <build>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
            </resources>
        </build>
    
    </project>
    
  3. 添加 web 支持,并把包目录结构先建立起来

    image-20200514173241419

  4. 编写实体类 Book

    /**
     * 书 实体类
     * @author xp
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Book {
        /**
         * 书id
         */
        private Integer bookId;
        /**
         * 书名
         */
        private String bookName;
        /**
         * 书数量
         */
        private Integer bookCounts;
        /**
         * 描述
         */
        private String detail;
    }
    
  5. 编写 mapper 接口

    /**
     * BookMapper
     *
     * @author xp
     */
    public interface BookMapper {
        /**
         * 增加一本书
         *
         * @param book 待增加的书籍信息
         * @return 影响行数
         */
        int addBook(Book book);
    
        /**
         * 根据id删除一本数
         *
         * @param id 被删除的书的id
         * @return 影响行数
         */
        int deleteBookById(@Param("bookId") int id);
    
        /**
         * 修改一本数
         *
         * @param book 修改后的书籍信息
         * @return 影响行数
         */
        int updateBook(Book book);
    
        /**
         * 根据id查询书
         *
         * @param id 书的id
         * @return 查询后的结果
         */
        Book queryBookById(@Param("bookId") int id);
    
        /**
         * 查询所有书
         *
         * @return 存储书籍信息的集合
         */
        List<Book> queryAllBook();
        
        /**
         * 根据书籍名字查询书籍
         *
         * @param name 书籍名字
         * @return 存储书籍信息的集合
         */
        List<Book> queryBookByName(@Param("bookName") String name);
    }
    
  6. 编写 Service 接口和其实现类

    Service 接口

    /**
     * 书本业务层
     *
     * @author xp
     */
    public interface BookService {
        /**
         * 增加一本书
         *
         * @param book 待增加的书籍信息
         * @return 影响行数
         */
        int addBook(Book book);
    
        /**
         * 根据id删除一本数
         *
         * @param id 被删除的书的id
         * @return 影响行数
         */
        int deleteBookById(int id);
    
        /**
         * 修改一本数
         *
         * @param book 修改后的书籍信息
         * @return 影响行数
         */
        int updateBook(Book book);
    
        /**
         * 根据id查询书
         *
         * @param id 书的id
         * @return 查询后的结果
         */
        Book queryBookById(int id);
    
        /**
         * 查询所有书
         *
         * @return 存储书籍信息的集合
         */
        List<Book> queryAllBook();
        
        /**
         * 根据书籍名字查询书籍
         *
         * @param name 书籍名字
         * @return 存储书籍信息的集合
         */
        List<Book> queryBookByName(String name);
    }
    

    Service 实现类

    /**
     * Service 实现类
     * @author xp
     */
    public class BookServiceImpl implements BookService {
    
        private BookMapper bookMapper;
    
        public void setBookMapper(BookMapper bookMapper) {
            this.bookMapper = bookMapper;
        }
    
        @Override
        public int addBook(Book book) {
            return bookMapper.addBook(book);
        }
    
        @Override
        public int deleteBookById(int id) {
            return bookMapper.deleteBookById(id);
        }
    
        @Override
        public int updateBook(Book book) {
            return bookMapper.updateBook(book);
        }
    
        @Override
        public Book queryBookById(int id) {
            return bookMapper.queryBookById(id);
        }
    
        @Override
        public List<Book> queryAllBook() {
            return bookMapper.queryAllBook();
        }
        
        @Override
        public List<Book> queryBookByName(String name) {
            return bookMapper.queryBookByName(name);
        }
    }
    
  7. 创建数据库文件 db.properties

    jdbc.drive=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/ssmproject?useTimezone=true&serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    jdbc.username=root
    jdbc.password=root
    
  8. 编写 MyBatis 核心配置文件 mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <!-- 开启驼峰映射 -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
            <!-- 开启log4j日志 -->
            <setting name="logImpl" value="LOG4J"/>
        </settings>
        <!-- 别名 -->
        <typeAliases>
            <package name="com.xp"/>
        </typeAliases>
        <!-- 注册 mapper -->
        <mappers>
            <mapper resource="mapper/bookMapper.xml"/>
        </mappers>
    </configuration>
    
  9. 配置 log4j.properties

    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger=DEBUG,console,file
    
    #控制台输出的相关设置
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=【%c】-%m%n
    
    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/xp.log
    log4j.appender.file.MaxFileSize=10mb
    log4j.appender.file.Threshold=DEBUG
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=【%p】【%d{yy-MM-dd}】【%c】%m%n
    
    #日志输出级别
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
    
  10. 创建 spring-dao.xml,配置dao层,并交由 Spring 托管

<?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">

    <!-- 关联数据库文件 -->
    <context:property-placeholder location="classpath:properties/db.properties"/>
    <!-- 连接池
        dbcp:半自动化操作,不能自动连接
        c3p0:自动化操作(自动化地加载配置文件,并且可以自动设置到对象中)
        druid、hikari
     -->
    <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
        <property name="driverClass" value="${jdbc.drive}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- SqlSessionFactory -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:config/mybatis-config.xml"/>
    </bean>


    <!-- 配置dao接口扫描包,动态实现了Dao接口可以注入Spring容器中 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 要扫描的包 -->
        <property name="basePackage" value="com.xp.mapper"/>
        <!-- 注入SqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

</beans>
  1. 在 mapper 包下创建 bookMapper.xml,编写映射器映射的sql语句

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.xp.mapper.BookMapper">
        <insert id="addBook" parameterType="book">
            insert into book (book_name, book_counts, detail)
            values (#{bookName},#{bookCounts},#{detail});
        </insert>
        <delete id="deleteBookById" parameterType="_int">
            delete from book where book_id = #{bookId};
        </delete>
        <update id="updateBook" parameterType="book">
            update book
            set book_name = #{bookName},book_counts=#{bookCounts},detail=#{detail}
            where book_id=#{bookId};
        </update>
        <select id="queryBookById" parameterType="_int">
            select * from book where book_id = #{bookId};
        </select>
        <select id="queryAllBook">
            select * from book;
        </select>
        <select id="queryBookByName" parameterType="String" resultType="book">
            select * from book where book_name = #{bookName};
        </select>
    </mapper>
    
  2. 编写 spring-service.xml,将 Service 层注入到 Spring 容器中,并配置相对应的事务

    <?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">
    
        <!-- 扫描service包下的类 -->
        <context:component-scan base-package="com.xp.service"/>
    
        <!-- 将我们所有的业务类,注入到Spring,可以通过配置或注解实现,这里使用配置实现 -->
        <bean id="bookService" class="com.xp.service.impl.BookServiceImpl">
            <property name="bookMapper" ref="bookMapper"/>
        </bean>
    
        <!-- 配置声明式事务 -->
        <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
            <!-- 注入数据源 -->
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!-- AOP 事务支持 -->
    
    </beans>
    
  3. 创建 springmvc-servlet.xml ,将 Controller 层注入到 Spring 中

    <?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">
    
        <!-- 扫描controller下的包 -->
        <context:component-scan base-package="com.xp.controller"/>
        <!-- 静态资源处理 -->
        <mvc:default-servlet-handler/>
        <!-- 注解驱动支持 -->
        <mvc:annotation-driven>
            <!-- 解决Jackson乱码问题 -->
            <mvc:message-converters register-defaults="true">
                <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                    <constructor-arg value="utf-8"/>
                </bean>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                    <property name="objectMapper">
                        <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                            <property name="failOnEmptyBeans" value="false"/>
                        </bean>
                    </property>
                </bean>
            </mvc:message-converters>
        </mvc:annotation-driven>
    
        <!-- 模板解析器 -->
        <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
            <property name="prefix" value="/WEB-INF/html/" />
            <property name="suffix" value=".html" />
            <property name="templateMode" value="HTML5" />
            <property name="cacheable" value="false" />
            <property name="characterEncoding" value="UTF-8"/>
        </bean>
    
        <!-- 模板引擎 -->
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine" id="templateEngine">
            <property name="templateResolver" ref="templateResolver"/>
            <property name="enableSpringELCompiler" value="true"/>
        </bean>
    
        <!-- 视图解析器 -->
        <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
            <property name="characterEncoding" value="utf-8"/>
            <property name="templateEngine" ref="templateEngine"/>
        </bean>
    
    </beans>
    
  4. 创建 applicationContext.xml ,使用 import 标签将刚刚配置的文件导入到这个主配置中

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 整合配置文件 -->
        <import resource="springmvc-servlet.xml"/>
        <import resource="spring-dao.xml"/>
        <import resource="spring-service.xml"/>
    
    </beans>
    
  5. 配置 web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!-- 配置 DispatcherServlet -->
        <servlet>
            <servlet-name>SpringMVC</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:config/applicationContext.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>SpringMVC</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <!-- 解决乱码问题 -->
        <filter>
            <filter-name>encoding</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>encoding</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    </web-app>
    
  6. 编写控制器 BookController

    @Controller
    public class BookController {
    
        private static final String TO_ALL_BOOK = "/toAllBook";
        // Controller 层调用 Service 层
        @Autowired
        private BookService bookService;
    
        public void setBookService(BookService bookService) {
            this.bookService = bookService;
        }
    
        @RequestMapping(TO_ALL_BOOK)
        public String toAllBook() {
            return "allBook";
        }
        
        @RequestMapping("/allBook")
        @ResponseBody
        public String allBook() throws JsonProcessingException {
            List<Book> books = bookService.queryAllBook();
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(books);
        }
    
    }
    
  7. 编写主页 index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>$Title$</title>
        <style>
          a{
            text-decoration: none;
            color: black;
            font-size: 38px;
          }
          h3{
            width: 300px;
            height: 80px;
            margin: 200px auto;
            text-align: center;
            line-height: 80px;
            background-color: #7afdf1;
            border-radius: 5px;
          }
        </style>
      </head>
      <body>
      <h3><a href="${pageContext.request.contextPath}/book/toAllBook">进入书籍页面</a></h3>
      </body>
    </html>
    

到这里,我们的三层架构就已经搭建完成了。

启动Tomcat,简单测试一下配置是否有误。

配置无误后,就开始编写前端页面和控制器。

  • allBook.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>书籍展示</title>
    
        <script src="../../static/js/jquery-3.4.1.min.js"></script>
        <script src="../../static/js/bootstrap.min.js"></script>
        <link type="text/css" rel="stylesheet" href="../../static/css/bootstrap.min.css"/>
        <script type="text/javascript">
            // init 初始化页面,加载后端的数据
            $.get({
                url: "/book/allBook",
                dataType: "json",
                success: function (data) {
                    booksJsonToString(data);
                }
            });
        </script>
    </head>
    <body>
    
    <div class="container">
    
        <!-- 标题 -->
        <div class="row clearfix">
            <div class="col-md-12 column">
                <div class="page-header">
                    <h1>
                        <small>书籍展示</small>
                    </h1>
                </div>
            </div>
        </div>
    
        <div class="row clearfix">
            <!-- 增加按钮 -->
            <div class="col-md-4 column">
                <button class="btn btn-primary" onclick="clickToHref('/book/toAddBook')">增加</button>
                <!-- 显示所有书籍按钮 -->
                <button class="btn btn-primary" onclick="clickToHref('/book/toAllBook')">显示所有书籍</button>
            </div>
            <!-- 搜索框 -->
            <div class="col-md-8 column" >
                <form class="form-inline" id="searchBookForm" style="float: right">
                    <span id="searchError" style="color:red"></span>&nbsp;&nbsp;
                    <input type="text" class="form-control" name="name" placeholder="请输入查询的书籍名称"/>&nbsp;
                    <input id="searchBtn" class="btn btn-secondary" type="button" value="搜索" onclick="searchBtnClick()"/>
                </form>
            </div>
        </div>
    
        <!-- 表格 -->
        <div class="row clearfix">
            <div class="col-md-12 column">
                <table class="table table-hover table-striped table-responsive-sm">
                    <thead>
                    <tr>
                        <td>书籍编号</td>
                        <td>书籍名称</td>
                        <td>书籍数量</td>
                        <td>书籍描述</td>
                        <td>操作</td>
                    </tr>
                    </thead>
                    <tbody id="tbody">
    
                    </tbody>
                </table>
            </div>
        </div>
    
    </div>
    
    <script>
        // 点击后跳转链接
        function clickToHref(href) {
            window.location.href=href;
        }
        function deleteClick(id) {
            var msg = "确认删除吗?";
            if (confirm(msg) === true){
                clickToHref('/book/deleteBook?id='+id)
            } else {
                return false;
            }
        }
        // 更新按钮点击跳转
        function updateClick(href,id) {
            clickToHref(href+"?id="+id);
        }
        // 搜索按钮点击跳转
        function searchBtnClick() {
            $.get({
                url: "/book/searchBook",
                data: $("#searchBookForm").serialize(),
                dataType: "json",
                success: function (data) {
                    var json = JSON.stringify(data);
                    var searchError = $("#searchError");
                    searchError.empty();
                    // 判断数据是否为空
                    if (json==="[]"){
                        searchError.html("搜索不到该书籍");
                        refreshTBody("");
                    }else {
                        booksJsonToString(data);
                    }
                }
            })
        }
    </script>
    <script>
        // 将book集合的json格式转换成字符串
        function booksJsonToString(data) {
            var html = '';
            for (var i=0;i<data.length;i++){
                html += bookJsonToString(data[i]);
            }
            refreshTBody(html);
        }
        // 将book的json格式转换成字符串
        function bookJsonToString(book) {
            var bookId = book.bookId;
            var html = "";
            html += "<tr><td>" + bookId + "</td><td>" + book.bookName + "</td><td>" + book.bookCounts + "</td><td>"
                + book.detail + "</td><td><button class='btn btn-sm btn-info' onclick=" + "updateClick('/book/toUpdateBook',"
                + bookId + ")" + " >修改</button>" + "&nbsp;<button class='btn btn-danger btn-sm' onclick=deleteClick(" + bookId + ")"
                + " >删除</button></td></tr>";
            return html;
        }
        // 刷新表格中的内容
        function refreshTBody(html) {
            var tbody = $("#tbody");
            tbody.empty();
            tbody.html(html);
        }
    </script>
    </body>
    </html>
    
  • addBook.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>增加书籍</title>
    
        <script src="../../static/js/jquery-3.4.1.min.js"></script>
        <script src="../../static/js/bootstrap.min.js"></script>
        <link type="text/css" rel="stylesheet" href="../../static/css/bootstrap.min.css"/>
    </head>
    <body>
    <div class="container">
    
        <!-- 标题 -->
        <div class="row clearfix">
            <div class="col-md-12 column">
                <h1>
                    <small>增加书籍</small>
                </h1>
            </div>
        </div>
    
        <!-- 增加书籍的表单 -->
        <div class="row clearfix">
            <div class="col-md-8 column">
                <form action="/book/addBook">
                    <div class="form-row">
                        <label>书籍名称<input type="text" name="bookName" class="form-control input-group mb-3" required/></label>
                .    </div>
                    <div class="form-row">
                        <label>书籍数量<input type="text" name="bookCounts" class="form-control input-group mb-3" required/>
                        </label>
                    </div>
                    <div class="form-row">
                        <label>书籍描述<input type="text" name="detail" class="form-control input-group mb-3" required/></label>
                    </div>
                    <div class="form-row">
                        <input type="submit" class="btn btn-primary form-control" onclick="return addClick()" value="添加" style="width: 200px"/>
                    </div>
                </form>
            </div>
        </div>
    
    </div>
    
    <script>
        // 防止误点操作
        function addClick(){
            var msg = "确认增加?";
            return confirm(msg);
        }
    </script>
    
    </body>
    </html>
    
  • update.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>修改书籍</title>
    
        <script src="../../static/js/jquery-3.4.1.min.js"></script>
        <script src="../../static/js/bootstrap.min.js"></script>
        <link type="text/css" rel="stylesheet" href="../../static/css/bootstrap.min.css"/>
    </head>
    <body>
    <div class="container">
    
        <!-- 标题 -->
        <div class="row clearfix">
            <div class="col-md-12 column">
                <h1>
                    <small>修改书籍</small>
                </h1>
            </div>
        </div>
    
        <!--/*@thymesVar id="book" type="com.xp.entity.Book"*/-->
        <!-- 更新的表单 -->
        <div class="row clearfix">
            <div class="col-md-6">
                <form action="/book/updateBook">
                    <input type="hidden" name="bookId" th:value="${book.bookId}">
                    <div class="form-row input-group mb-3">
                        <label>书籍名称<input type="text" name="bookName" class="form-control" th:value="${book.bookName} "/></label>
                    </div>
                    <div class="form-row input-group mb-3">
                        <label>书籍数量<input type="text" name="bookCounts" class="form-control" th:value="${book.bookCounts}"/></label>
                    </div>
                    <div class="form-row input-group mb-3">
                        <label>书籍描述<input type="text" name="detail" class="form-control"  th:value="${book.detail}"/></label>
                    </div>
                    <div class="form-row">
                        <input type="submit" class="btn btn-primary form-control" onclick="return updateClick()" value="修改" style="width: 200px"/>
                    </div>
                </form>
            </div>
        </div>
    
    </div>
    
    <script>
        // 防止误点
        function updateClick(){
            var msg = "确认修改?";
            return confirm(msg) === true;
        }
    </script>
    
    </body>
    </html>
    
  • BookController

    package com.xp.controller;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.xp.entity.Book;
    import com.xp.service.BookService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import java.util.List;
    
    @Controller
    @RequestMapping("/book")
    @SuppressWarnings("all")
    public class BookController {
    
        private static final String TO_ALL_BOOK = "/toAllBook";
        // Controller 层调用 Service 层
        @Autowired
        private BookService bookService;
    
        public void setBookService(BookService bookService) {
            this.bookService = bookService;
        }
    
        @RequestMapping(TO_ALL_BOOK)
        public String toAllBook() {
            return "allBook";
        }
    
        @RequestMapping("/allBook")
        @ResponseBody
        public String allBook() throws JsonProcessingException {
            List<Book> books = bookService.queryAllBook();
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(books);
        }
    
        @RequestMapping("/toAddBook")
        public String toAddBook() {
            return "addBook";
        }
    
        @RequestMapping("/addBook")
        public String addBook(Book book) {
            return "redirect:/book" + TO_ALL_BOOK;
        }
    
        @RequestMapping("/deleteBook")
        public String deleteBook(int id) {
            bookService.deleteBookById(id);
            return "redirect:/book" + TO_ALL_BOOK;
        }
    
        @RequestMapping("/updateBook")
        public String updateBook(Book book) {
            bookService.updateBook(book);
            return "redirect:/book" + TO_ALL_BOOK;
        }
    
        @RequestMapping("/toUpdateBook")
        public String toUpdateBook(int id, Model model) {
            Book book = bookService.queryBookById(id);
            model.addAttribute("book", book);
            return "updateBook";
        }
    
        @RequestMapping("/searchBook")
        @ResponseBody
        public String searchBook(String name) throws JsonProcessingException {
            List<Book> book = bookService.queryBookByName(name);
            return new ObjectMapper().writeValueAsString(book);
        }
    
    
    }
    

到这里,我们的 SSM 框架才算完全整合。在以后再次写 SSM 项目时,可以将这个作为模板来进行编写。

posted @ 2020-05-17 17:25  Windows_XP  阅读(193)  评论(0编辑  收藏  举报