25:SpringMVC || 这是我的一小步也从此纵情江湖

SpringMVC


ssm:mybatis + Spring + SpringMVC

MVC三层架构

JavaSE:认真学习,入门Java

JavaWeb:认真学习,了解开发

框架:研究官方文档,锻炼自学能力,锻炼笔记能力,锻炼项目能力

SpringMVC + Vue + SpringBoot + SpringCloud + Linux

SSM框架整合 JavaWeb项目

Spring:IOC 和 AOP

SpringMVC:SpringMVC的执行流程

官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc


一、MVC

1. 什么是MVC

MVC:模型(Model)、 视图(View) 、控制器(Controller),是一种软件设计规范。

将业务逻辑、数据、显示分离的方法来组织代码

MVC主要作用是降低了视图与业务逻辑间的双向耦合

MVC不是一种设计模式,MVC是一种架构模式,不同的MVC存在差异

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

  • 业务处理:业务逻辑(Service)
  • 数据持久层:CRUD(Dao)

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

  • 展示数据
  • 提供链接发起Servlet请求

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

  • 接受用户的请求:request,请求参数,session信息
  • 交给业务层处理对应的代码
  • 控制视图的跳转

这里我们可以补充个知识:加入一个用户User类,有十几个属性,而前端只需要用户名和密码,这时我们要是用整个User对象,有些浪费了,就可以选择将User对象中的用户名和密码拆出来叫UserVo类,里面只有这两个属性,vo即ViewObject,视图层需要的对象。

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

image-20220515164559189

2. Model 1 时代

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

  • Model 1 中,主要分为两层,视图层和模型层。

    image-20220515164746480

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

Model 1 的缺点:JSP职责不单一,职责过重,不便于维护,JSP本质上就是一个Servlet

假设有一道面试题:你的项目的架构,是设计好的,还是演进的?

  • 肯定是演进的
  • Alibaba 买了一个PHP的小型网站进行改动 PHP一般用来做个人网站
  • 随着用户量越来越大,PHP开始承受不了了,要转向Java,后台架构要全部变化
  • 但是数据量太大了,用的IOE这些国外的数据库,成本太高,所以回到MySQL了
  • MySQL,但是MySQL还是太慢了:MySQL ---> AliSQL、AliRedis
  • ALL IN ONE ,所有的项目都是从小开始,---> 然后越来越大 微服务

3. Model 2 时代

Model 2 把一个项目分为三部分,包括视图、控制、模型

image-20220515165149214

  1. 用户发请求
  2. Servlet接收请求数据,并调用相应的业务逻辑方法
  3. 业务处理完毕,返回更新后的数据给Servlet
  4. servlet转向到JSP,由JSP来渲染页面

职责分析

Controller:控制器

  1. 获取表单数据
  2. 调用业务逻辑
  3. 转向指定的页面

Model:模型

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

View:视图

  1. 显示页面

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


二、回顾Servlet

新建一个空的Maven项目,删除src,当做父项目,在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.laxsilence</groupId>
    <artifactId>SpringMVC</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.18</version>
        </dependency>
        <dependencies>
<!--        Servlet的依赖-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
<!--        JSP的依赖-->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.3</version>
        </dependency>
<!--        jstl表达式的依赖-->
        <dependency>
            <groupId>javax.servlet.jsp.jstl</groupId>
            <artifactId>jstl-api</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
<!--        standard表达式-->
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
    </dependencies>
</project>

新建Module项目,当做子工程,依旧创建普通的maven项目,不需要创建webapp的maven项目

在子项目上右键,添加框架支持,添加web框架支持。

良好习惯:不管有没有继承父项目的依赖,子项目中都再导入一次。

接下来我们正式开始:

  1. 编写一个Servlet类,用来处理用户的请求

    package com.laxsilence.servlet;
    
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    public class HelloServlert extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //1. 获取前端参数
            String method = req.getParameter("method");
            if (method.equals("add")){
                req.getSession().setAttribute("msg","执行了add方法");
            }
            if (method.equals("delete")){
                req.getSession().setAttribute("msg","执行了delete方法");
            }
            //2. 调用业务层
    
            //3. 视图转发或者重定向
    //        req.getRequestDispatcher()
    //        resp.sendRedirect();
            req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doGet(req, resp);
        }
    }
    
  2. 编写test.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    
    ${msg}
    
    </body>
    </html>
    
  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">
        
        <servlet>
            <servlet-name>hello</servlet-name>
            <servlet-class>com.laxsilence.servlet.HelloServlert</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>hello</servlet-name>
            <url-pattern>/hello</url-pattern>
        </servlet-mapping>
    
    <!--    <session-config>-->
    <!--        <session-timeout>15</session-timeout>-->
    <!--    </session-config>-->
    <!--    -->
    <!--    <welcome-file-list>-->
    <!--        <welcome-file>index.jsp</welcome-file>-->
    <!--    </welcome-file-list>-->
        
    </web-app>
    
  4. 配置Tomcat

  5. 启动tomcat

  6. 测试

    image-20220515173500489

    image-20220515173516347

MVC框架要做哪些事情?

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

说明:

  • 常见的服务器端MVC框架有:Struts、SpringMVC、ASP.NET MVC、Zend FrameWork、JSF;
  • 常见前端MVC框架:vue、angularjs、react、backbone
  • 由MVC演化出了另外一些模式:MVP、MVVM等等
  • MVVM:
    • M(Model)
    • V(View)
    • VM(ViewModel):双向绑定,前后端分离的核心

三、什么是SpringMVC

1. 概述

image-20220515174632731

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

官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc

我们为什么要学习SpringMVC呢?

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

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

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

正因为SpringMVC好,简单,便捷,易学,天生和Spring无缝集成,使用SpringIOC和AOP,使用约定大于配置,能够进行简单的junit测试,支持Restful风格、异常处理、本地化、国际化、数据验证、类型转换、拦截器等,所以我们需要学习。最重要的是,使用的人和使用的公司多。

2.中心控制器

Spring的web框架围绕DispatcherServlet设计。DispatcherServlet的作用是将请求分发给不同的处理器。从Spring2.5开始,使用Java5或者以上版本的用户可以采用基于注解的controller声明方式。

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

image-20220515181155558

DispatcherServlet的核心方法:

image-20220515181404185

在了解SpringMVC的原理之前,我们先写一个简单的程序。

3. Hello,SpringMVC

新建一个Module,添加web的支持框架

确定导入了SpringMVC的依赖

  1. 配置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">
    
        <!--1. 注册DispatcherServlet-->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--关联一个springMVC的配置文件:【Servlet-name】 -servlet.xml-->
            <!--通过初始化参数指定SpringMVC配置文件的位置,进行关联-->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc-servlet.xml</param-value>
            </init-param>
            <!--启动级别:1  数字越小,启动越早-->
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <!-- / 匹配所有的请求:(不包括.jsp) -->
        <!-- /* 匹配所有的请求:(包括.jsp) -->
        <!--所有的请求都会被SpringMVC拦截-->
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
    </web-app>
    
  2. 编写SpringMVC的配置文件!名称:springmvc-servlet.xml(就是Spring的配置文件框架):【servletname】-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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    
    
    </beans>
    
  3. 添加处理映射器

    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    
  4. 添加处理器适配器

    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
  5. 添加视图解析器

    <!--添加视图解析器:DispatcherServlet给他的ModelandView-->
    <!--
    	1. 获取了ModelAndView的数据
    	2. 解析它的视图名字
    	3. 拼接视图名字,前后缀
    	4. 将数据渲染到视图上
    -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
    
  6. 编写我们要操作业务Controller,要么实现Controller接口,要么增加注解;需要返回一个Model and View,装数据,封视图。

    package com.laxsilence.controller;
    
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.Controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class HelloController implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            //ModelAndView 模型和视图
            ModelAndView mv = new ModelAndView();
            
            //调用业务层,暂时我们不需要
            
            //封装对象,放在ModelAndView中,
            mv.addObject("msg","HelloSpringMVC");
            //封装要跳转的视图,放在ModelAndView中
            mv.setViewName("hello");//:/WEB-INF/jsp/hello.jsp
            return mv;
        }
    }
    
  7. 将这个类交给Spring 容器,注册Bean

    <bean id="/hello" class="com.laxsilence.controller.HelloController"/>
    
  8. 写要跳转的jsp界面,显示ModelAndView存放的数据,以及我们的正常页面

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

    在Deployment中点-,删除子项目1,点击+,添加子项目2

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

  1. 查看控制台输出,看是不是少了什么jar包
  2. 如果jar包存在,显示无法输出,就在IDEA项目发布中,添加lib依赖
  3. 重启Tomcat解决

建一个新的Webapp项目的maven,可以发现,Maven默认的web目录是/src/main/webapp,而我们通过添加项目框架添加的web,是跟src同级别的,所以会显示没有导入包。

我们需要再项目结构的artfact中,手动添加lib目录,并导入依赖。

image-20220515185719924

点击lib文件夹,点击加号,添加library

image-20220515185803988

全选,ok

image-20220515185845521

最后应用,ok,然后重启Tomcat。

建议使用maven自带的webapp,基本不会有这些问题。

可以发现,到这里,我们彻底摆脱了servlet,改为了写controller类。

4. SpringMVC执行原理

SpringMVC的原理如下图所示:

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

image-20220515181830006

前端控制器,我们就可以理解为:web.xml,注册的DispatcherServlet,可以接收所有的url请求,即我们的/hello也会经过它。

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

    <!--1. 注册DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--关联一个springMVC的配置文件:【Servlet-name】 -servlet.xml-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath: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>

而页面控制器/处理器,我们就可以理解为实现类Controller接口的类。

当返回一个ModelAndView时,就是返回到封装好的view的路径,返回了封装model的数据,:

package com.laxsilence.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //ModelAndView 模型和视图
        ModelAndView mv = new ModelAndView();
        //封装对象,放在ModelAndView中,
        mv.addObject("msg","HelloSpringMVC");
        //封装要跳转的视图,放在ModelAndView中
        mv.setViewName("hello");//:/WEB-INF/jsp/hello.jsp
        return mv;
    }
}

我们再来换一种方式理解:

image-20220515193838026

在上图中,实现为SpringMVC帮我们做的,我们只需要做虚线的地方。

我们完整的分析一遍执行流程:

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

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

    • 以上的url拆分为三部分:

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

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

      hello表示控制器

    • 通过分析,这个url表示,请求位于localhost:8080上的SpringMVC站点的hello控制器

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

    DispatcherServlet会问HandlerMapping,这个请求哪个处理器能执行。即hello这个处理器

  3. HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为hello,已经找到了这个控制器hello

  4. HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射,说我找到了hello控制器,即bean的id为hello

  5. HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler,开始跳转到Controller类。

  6. Handler让具体的Controller执行。

  7. Controller将具体的执行信息返回给HandlerAdapter,如返回一个ModelAndView对象

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

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

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

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

  12. 最终视图呈现给用户

到此,一个完整的流程执行成功,记得对应这个例子来看。1-4其实就是原来的servlet-mapping。

5. 完整测试

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>

  <parent>
    <artifactId>SpringMVC</artifactId>
    <groupId>com.laxsilence</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <groupId>com.laxsilence</groupId>
  <artifactId>springmvc-02-hellomvc2</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>springmvc-02-hellomvc2 Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.18</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.3</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>springmvc-02-hellomvc2</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

HelloController

image-20220624183100338

package com.laxsilence.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @ClassName HelloController
 * @Description TODO
 * @Author 刘英俊
 * @Date 2022/6/24 18:05
 * @Version 1.0
 **/
public class HelloController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = new ModelAndView();
        //业务代码
        String result = "HelloSpringMVC";
        mv.addObject("msg",result);
        //视图跳转
        mv.setViewName("test");

        return mv;
    }
}

配置文件:

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

    <!--处理器映射器-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    <!--处理器适配器-->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    <!--视图解析器:模板引擎 Thymeleaf Freemarker 。。。 这个是可以变的 甚至可以自定义-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--BeanNameUrlHandlerMapping:根据id寻找bean-->
    <bean id="/hello" class="com.laxsilence.controller.HelloController"/>
</beans>

web.xml:

image-20220624183249709

<?xml version="1.0" encoding="UTF8"?>
<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">

    <!--配置DispatchServlet:这个是SpringMVC的核心,也叫请求分发器,也叫前端控制器-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--DispatchServlet要绑定Spring的配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--启动级别:1-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!--  在SpringMVC中 / 和 /*   -->
    <!-- 1. /: 只匹配所有的请求,不会去匹配jsp页面   -->
    <!-- 2. /*:匹配所有的请求,包括jsp页面 -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
        <!--url-pattern的匹配优先级:-->
        <!--精准匹配 > 最长路径匹配(/*)> 后缀匹配(Tomcat中的web.xml默认定义了jsp的servlet)> 缺省匹配(/)-->
    </servlet-mapping>
</web-app>

test.jsp:

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

6. 补充

到这里我们可以理解MVC的原理了,但是在实际开发中我们不会这样来用,每次都创建bean是很麻烦的一件事,使用注解来开发,才是MVC的精髓。

接下来我们将使用注解来开发SPringMVC


四、使用注解开发SpringMVC

1. 使用注解开发

  1. 新建一个module springmvc-03-annotation,配置maven

    <?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">
        <parent>
            <artifactId>SpringMVC</artifactId>
            <groupId>com.laxsilence</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>springmvc-03-annotation</artifactId>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.3.18</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>javax.servlet.jsp-api</artifactId>
                <version>2.3.3</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
        </dependencies>
    
        <build>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
            </resources>
            <finalName>springmvc-02-hellomvc2</finalName>
            <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
                <plugins>
                    <plugin>
                        <artifactId>maven-clean-plugin</artifactId>
                        <version>3.1.0</version>
                    </plugin>
                    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                    <plugin>
                        <artifactId>maven-resources-plugin</artifactId>
                        <version>3.0.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.8.0</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.22.1</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-war-plugin</artifactId>
                        <version>3.2.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-install-plugin</artifactId>
                        <version>2.5.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-deploy-plugin</artifactId>
                        <version>2.8.2</version>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
        
    </project>
    
  2. 配置web.xml,右键Module,添加框架,选择web.4.0版本,创建后,记得打开项目结构,在ArtFacts中添加lib目录

    注意:

    • 注意web.xml版本问题
    • 注册DispatchServlet
    • 关联SpringMVC的配置文件
    • 启动级别为1
    • 映射路径用 /

    image-20220624185117187

    <?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">
    
        <!--1. 注册DispatcherServlet-->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--关联一个springMVC的配置文件:【Servlet-name】 -servlet.xml-->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath: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>
    
  3. 添加SpringMVC配置(注解版):

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--自动扫描包,让指定包下的注解生效 由IOC容器统一管理-->
        <context:component-scan base-package="com.laxsilence.controller"/>
        <!--让SpringMVC不处理静态资源-->
        <mvc:default-servlet-handler/>
    
        <!--
        支持mvc注解驱动
            在Spring中一般采用@RequestMapping注解来完成映射关系
            要想使@RequestMapping注解生效
            必须向上下文中注册DefaultAnnotationHandlerMapping
            和一个AnnotationMethodHandlerAdapter实例
            这两个实例分别在类级别和方法级别处理
            而annotation-driven配置帮助我们自动完成上述两个实例的注入
        -->
        <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. 新建java包和web中的jsp包,新建hello.jsp页面

    image-20220624192129818

    image-20220624192147248

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

    编写一个Java控制类

    package com.laxsilence.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    //@RequestMapping("/hello")
    public class HelloController {
        //真实访问地址:localhost:8080//hello/h1
        //真实访问地址:localhost:8080/h1
        @RequestMapping("/h1")
        public String hello(Model model){
            //封装数据
            model.addAttribute("msg","Hello,SpringMVCAnnotation!");
            //web-inf/jsp/hello.jsp
            return "hello";  //会被视图解析器处理  返回后在视图解析器中进行拼接
        }
    }
    
  6. 配置tomcat进行访问,最好用Tomcat9 跟web 4 比较匹配

    发现运行成功

    image-20220624201416456

注意:

  1. 使用SpringMVC必须配置的三大件
    • 处理器映射器
    • 处理器适配器
    • 视图解析器
  2. 通常我们需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可,省去了大段的xml配置

2. Controller配置总结

控制器Controller

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

(1)实现Controller接口

Controller是一个接口,在org.springframework.web.servlet.mvc包下,接口中只有一个方法:

//实现该接口的类获得控制器功能
 public interface Controller {
   @Nullable
   //处理请求并且返回一个模型与视图对象
   ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

}

测试:

第一步:跟以前一样,弄好所有环境

第二步:创建编写一个Controller类,ControllerTest1

package com.laxsilence.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//只要实现了Controller接口的类,说明这就是一个控制器,返回一个Model and View
//不要导错包
public class ControllerTest1 implements Controller {

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

第三步:编写完毕后,去Spring配置文件中注册请求的bean,name对应请求路径,class对应处理请求的类

<bean name="/t1" class="com.laxsilence.controller.ControllerTest1"/>

第四步:编写test.jsp,对应视图解析器

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

第五步:运行

说明:

  • 实现Controller接口编写控制器是很老的方法
  • 缺点:一个控制器只有一个方法,如果要多个方法则需要定义多个Controller,比较麻烦

(2)使用注解@Controller

  • @Controller注解类型用于声明Spring类的实例是一个控制器(在将IOC的时候还提到了另外3个注解);

    @Component   组件
    @Service     Service
    @Controller   Controller
    @Repository   Dao
    
  • Spring可以使用扫描机制来找到应用程序中所有基于注解的控制器类,为了保证Spring能找到你的控制器,需要再配置文件中声明组件扫描。

    <!--自动扫描指定的包,下面所有的注解类交给IOC容器管理-->
    <context:component-scan base-package="com.laxsilence.controller"/>
    
  • 增加一个ControllerTest2类,使用注解实现;

    package com.laxsilence.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * @ClassName ControllerTest2
     * @Description TODO
     * @Author 刘英俊
     * @Date 2022/6/30 13:35
     * @Version 1.0
     **/
    
    @Controller //代表这个类会被Spring接管,被这个注解的类中的所有方法,如果返回值是String,并且有具体页面可以跳转,那么就会被视图解析器解析
    public class ControllerTest2 {
        @RequestMapping("/t2")
        public String test1(Model model){
            model.addAttribute("msg","ControllerTest2");
            return "test";
        }
    }
    
  • 配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--自动扫描包,让指定包下的注解生效 由IOC容器统一管理-->
        <context:component-scan base-package="com.laxsilence.controller"/>
        <!--让SpringMVC不处理静态资源-->
        <mvc:default-servlet-handler/>
    
        <!--
        支持mvc注解驱动
            在Spring中一般采用@RequestMapping注解来完成映射关系
            要想使@RequestMapping注解生效
            必须向上下文中注册DefaultAnnotationHandlerMapping
            和一个AnnotationMethodHandlerAdapter实例
            这两个实例分别在类级别和方法级别处理
            而annotation-driven配置帮助我们自动完成上述两个实例的注入
        -->
        <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>
    
    <!--    <bean name="/t1" class="com.laxsilence.controller.ControllerTest1"/>-->
    
    </beans>
    
  • 运行发布测试

可以发现,我们的两个请求都是指向同一个视图,但是页面显示的结果是不一样的,从这里可以看出视图是可以复用的,而控制器与视图之间是弱耦合关系。

注解是平时使用最多的方式,除了这两种之外其实也还有其他的方式,可以自行研究。

3. RequestMapping说明

@RequestMapping

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

  • 为了测试结论更加准确,我们新建一个类 ControllerTest3

    package com.laxsilence.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * @ClassName ControllerTest3
     * @Description TODO
     * @Author 刘英俊
     * @Date 2022/6/30 13:52
     * @Version 1.0
     **/
    
    @Controller
    @RequestMapping("/c3")
    public class ControllerTest3 {
        @RequestMapping("/t1")
        public String test1(Model model){
            model.addAttribute("msg","test3");
            return "test";
        }
    }
    
  • 这时,我们进行/t1测试:

    1. 直接进/t1:

      image-20220630135948044

      可以发现访问的是ControllerTest1的内容

    2. 通过/c3/t1进

      image-20220630140026410

      确实是ControllerTest3的内容

  • 也就是说

4. RestFul风格

概念

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

功能

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

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

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

传统方式操作资源:

通过不同的参数来实现不同的效果!方法单一,post和get

使用RestFul操作资源:

可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!

测试:

  1. 新建一个RestFulController

  2. 在SpringMVC中可以使用@PathVariable注解,让方法参数的值对应绑定到一个URl模板变量上。

    package com.laxsilence.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * @ClassName RestFulController
     * @Description TODO
     * @Author 刘英俊
     * @Date 2022/6/30 14:15
     * @Version 1.0
     **/
    @Controller
    public class RestFulController {
    
    
        //以前的实现方式   http://localhost:8080/add?a=1&b=2
        @RequestMapping("/add")
        public String test1(int a, int b, Model model){
            int res = a+b;
            model.addAttribute("msg","结果为:"+res);
            return "test";
        }
    
        //使用RestFul风格 http://localhost:8080/add/a/b
        @RequestMapping("/add/{a}/{b}")
        public String test2(@PathVariable int a, @PathVariable int b, Model model){
            int res = a+b;
            model.addAttribute("msg","结果为:"+res);
            return "test";
        }
    
    }
    
  3. 测试

    • 以前的方式

      image-20220630142304398

    • RestFul

      image-20220630142239539

如何实现同一个URL,不同的方法呢?

点开RequestMapping,可以发现:

image-20220630142553304

我们将方法这个注解参数加上:

@RequestMapping(name = "/add/{a}/{b}",method = RequestMethod.DELETE) 
//但是有可能会报错,不支持,出现a不是int类型这种问题
//解决方法,将name换成value或者path就可以解决,当然有更好的方式@DeleteMapping ("/add/{a}/{b}")
public String test3(@PathVariable int a, @PathVariable String b, Model model){
    int res = a+b;
    model.addAttribute("msg","结果为:"+res);
    return "test";
}

执行,会报错,因为我们直接请求URL是get方法,而代码中我们限定了DELETE方法

image-20220630142758653

还有一种替代的方法:

//    @RequestMapping(name = "/add/{a}/{b}",method = RequestMethod.DELETE)
    @DeleteMapping ("/add/{a}/{b}")  //可以用它来替代上面的注解
    public String test3(@PathVariable int a, @PathVariable int b, Model model){
        int res = a+b;
        model.addAttribute("msg","结果为:"+res);
        return "test";
    }

也就是说我们可以使用method属性指定请求类型

用于约束请求的类型,可以收窄请求范围,指定请求谓词的类型如GET、POST、HEAD、OPTIONS、PUT、DELETE、PATCH、TRACE等

这样就可以实现同一个URL,通过不同的请求类型,实现不同的方法

小结:

SpringMVC的@RequestMapping注解能够处理HTTP请求的方法,如GET,PUT,POST等

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

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

@GetMapping
@PostMapping
@DeleteMapping
@PatchMapping
@PutMapping

这些都是组合注解,比如@GetMapping

它所扮演的是@RequestMapping(method=RequestMethod.GET)的一个快捷方法,平时使用也比较多

可以根据表单提交的方式来执行不同的方法,得到不同的结果。

思考:

使用RestFul的优点:

  • 使得路径更加简洁
  • 高效,支持缓存
  • 获取参数更加方便,框架会自动进行类型转换
  • 可以通过路径变量的类型约束访问参数,如果类型不一样,则访问不到对应的请求方法
  • 安全,看不到参数到底传的是什么,不会暴露变量名

五、SpringMVC 结果跳转方式

1. Model And View

设置Model And View 对象,根据View的名称,和视图解析器跳到指定的页面

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

这些前面我们已经写过了

除了这种方式,我们还可以用一种其他方式

2. ServletAPI

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

  1. 通过HttpServletResponse进行输出
  2. 通过HttpServletResponse实现重定向
  3. 通过HttpServletResponse实现转发

其实就是最原始的Servlet

image-20220630151404803

就是在Controller中依旧可以使用HttpServletResponse和HttpServletRequest

3. SpringMVC

通过SpringMVC来实现转发和重定向 - 无需视图解析器

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

package com.laxsilence.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * @ClassName ModelTest1
 * @Description TODO
 * @Author 刘英俊
 * @Date 2022/6/30 15:10
 * @Version 1.0
 **/
@Controller
public class ModelTest1 {

    @RequestMapping("/m1/t1")
    public String test(Model model){
        model.addAttribute("msg","modelTest1");
        //转发 URL没有变化
//        return "/WEB-INF/jsp/test.jsp";
        //转发 可以直接看出是转发
//        return "forward:/WEB-INF/jsp/test.jsp";
        //重定向  URL会变
        return "redirect:/index.jsp";
    }
}

通过SpringMVC来实现转发和重定向 - 配置了视图解析器

重定向,不需要视图解析器,本质就是重新请求到一个新地方,所以注意路径问题

可以重定向到另外一个请求实现

加了redirect和forward不会走视图解析器,不显示写forward的请求转发会走视图解析器,并且重定向不能访问WEB-INF目录下的内容,想要通过重定向访问WEB-INF目录下的东西,可以通过另外一个方法请求转发过去

package com.laxsilence.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * @ClassName ModelTest1
 * @Description TODO
 * @Author 刘英俊
 * @Date 2022/6/30 15:10
 * @Version 1.0
 **/
@Controller
public class ModelTest1 {

    @RequestMapping("/m1/t1")
    public String test(Model model){
        model.addAttribute("msg","modelTest1");
        //转发 URL没有变化
//        return "test";
        //重定向  URL会变
        return "redirect:/index.jsp";
    }
}

六、SpringMVC 数据处理

在以前我们使用request.getParameter();获取前端表单的一些参数

1. 处理提交数据

(1)提交的域名称和处理方法的参数名一致

提交数据:http://localhost:8080/hello?name=liu

处理方法:

@RequestMapping("/hello")
public String hello(String name){
    System.out.pringln(name);
    return "hello";
}

后台输出:liu

(2)提交的域名称和处理方法的参数名不一致

提交数据:http://localhost:8080/hello?username=liu

处理方法:

//@RequestParam("username") :username提交的域名称
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name){
    System.out.pringln(name);
    return "hello";
}

后台输出:liu

建议:其实不管是不是一样的,都加上这个注释,就可以清楚的知道这个参数是从前端接收的

(3)提交的是一个对象

要求提交的表单域和对象的属性名一致,参数使用对象即可

  1. 实体类

    public class User{
        private int id;
        private String name;
        private int age;
        
        //构造
        //get set toString
    }
    
  2. 提交数据:http://localhost:8080/mvc/user?name=liu&id=1&age=18

  3. 处理方法

      /*
         * 1. 接收前端用户传递的参数,判断参数的名字,假设名字直接在方法上,可以直接使用
         * 2. 假设传递的是一个对象User,匹配User对象中的属性名,如果名字一致,则ok,否则匹配不到
        **/
    @RequestMapping("/user")
    public String user(User user){
        System.out.println(user);
        return "hello";
    }
    
  4. 后台输出:User

如果使用对象的话,前端传递的参数名和对象名必须一致,否则就是null。

2. 数据显示到前端

(1)第一种:通过ModelAndView

我们前面一直都是用这个的,就不多说了

public class ControllerTest1 implements Controller {

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

(2)第二种:通过Model

@Controller 
public class ControllerTest2 {
    @RequestMapping("/t2")
    public String test1(@RequestParam("username") String name,Model model){
        //相当于req.setAttribute("msg",name)
        model.addAttribute("msg",name);
        return "test";
    }
}

(3)第三种:通过ModelMap

@Controller 
public class ControllerTest2 {
    @RequestMapping("/t2")
    public String test1(@RequestParam("username") String name,ModelMap map){
        map.addAttribute("msg",name);
        return "test";
    }
}

(4)对比

LinkedHashMap

ModelMap:继承了LinkedHashMap,所以它拥有LinkedHashMap的全部功能

Model:继承了ModelMap,对于新手而言大部分使用这个

后面考虑到性能和优化,就不能仅限于此


七、乱码问题解决

测试:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/e/t1" method="post">
    <input type="text" name="name">
    <input type="submit">
</form>
</body>
</html>
package com.laxsilence.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class EncodingController {
    @PostMapping("/e/t1")
    public String test1(String name, Model model){
        model.addAttribute("msg",name);
        return "test";
    }
}

image-20220630164251621

其实使用get方式接收请求也不会乱码

1. 自己写过滤器

使用过滤器:

package com.laxsilence.filter;

import javax.servlet.*;
import java.io.IOException;

public class EncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {

    }
}

注册过滤器:

<filter>
    <filter-name>encoding</filter-name>
    <filter-class>com.laxsilence.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2. SpringMVC写好的过滤器

在web.xml中进行配置

<!--配置SpringMVC的乱码过滤-->
<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>

注意:

/不会拦截页面,只会拦截路径。

/* 会路径和页面

/* 是拦截所有的文件夹,不包含子文件夹

/** 是拦截所有的文件夹及里面的子文件夹


八、JSON

前后端分离时代:

  • 后端 --> 部署后端,提供接口(写一个方法,让别人调用),提供数据
  • 前端 --> 前端独立部署,负责渲染后端的数据,并且展示给用户

前后端建立连接:

  • 本来可以提供对象过去,但前端不方便使用,没对象
  • 所以前后端约定:使用json

1. 什么是JSON

Json:

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

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

JSON的要求和语法格式:

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

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

{"name":"Liu"}
{"age":"3"}
{"sex":"男"}

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

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

    var obj = {a:'hello',b:'world'}; //这是一个对象,注意键名也是可以使用引号包裹的
    var json = '{"a":"hello","b":"world"}'; //这是一个JSON字符串,本质是一个字符串
    

2. JSON和JS对象互转

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

    var obj = JSON.parse('{"a":"Hello","b":"World"}');
    //结果为:{a:'Hello',b:'World'}
    
  2. 要从JS对象转换为JSON对象,使用JSON.Stringify()方法

    var json = JSON.stringify({a:"Hello",b:"World"});
    //结果为:'{"a":"Hello","b":"World"}'
    
  3. 测试

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script type="text/javascript">
            //编写一个js对象
            var user = {
                name:"liu",
                age:18,
                sex:"男"
            };
    
            //将js对象转为json对象
            var json = JSON.stringify(user);
            console.log(json);
    
            //将json转为js对象  IDEA可以通过.var快捷生成变量
            var obj = JSON.parse(json);
            console.log(obj); 
        </script>
    </head>
    <body>
    
    </body>
    </html>
    

    image-20220630195600223

3. Controller返回JSON数据(Jackson)

  • Jackson应该是目前比较好的json解析工具了
  • 当然工具不止这一个,比如还有阿里巴巴的fastjson等等
  1. 我们先使用Jackson,使用它需要导入它的jar包
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>
  1. 新建Module,导入lib,配置web.xml和springmvc-servlet.xml,前面已经配置过,并且已经整理到常用配置中

  2. 编写一个User实体类

    package com.laxsilence.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private String name;
        private int age;
        private String sex;
    }
    
  3. 编写一个Controller

    package com.laxsilence.controller;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.laxsilence.pojo.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    public class JsonController {
    
        @RequestMapping("/j1")
        @ResponseBody //加了这个注释,就不会走视图解析器,直接返回一个字符串
        public String json() throws JsonProcessingException {
            User user = new User("Lax",18,"男");
    //        String str = user.toString();
    //        return str;  //可以发现返回的是:  User(name=Lax, age=18, sex=?)
    
            //jackson
            ObjectMapper mapper = new ObjectMapper();
            String str = mapper.writeValueAsString(user);
            return str;  //返回的为:{"name":"Lax","age":18,"sex":"?"}
        }
    }
    
  4. 执行发现:

    image-20220630205231710

    确实被解析为Json了,但是有问题的是出现了乱码

  5. 乱码解决

    @RequestMapping("/j1")替换:@RequestMapping(value = "/j1",produces = "application/json;charset=utf-8")

    image-20220701141917287

  6. 但是这样并不能让我们满意,因为每一个请求都要写很麻烦,SpringMVC有更简便的方式,即代码优化

    乱码统一解决:

    我们可以在SpringMVC的配置文件上添加一段消息StringHttpMessageConverter转换配置!

    <mvc:annotation-driven>
        <!--JSON 乱码问题配置-->
        <mvc:message-converters>
            <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>
    

    这样我们的第6步就不需要了,乱码会自动解决掉。

    image-20220701143041241

  7. 补充:可以将类上的@Controller替换为@RestController,那么这个类也不会走视图解析器,其类下的所有方法只会返回字符串

    也就是说:

    • @Controller和@ResponseBody搭配
    • 使用了@RestController就不需要@ResponseBody
    • @RestController会自动将Java对象与前端交互的时候转为json
  8. 测试集合输出,多个对象的JSON

    @RequestMapping("/j2")
    @ResponseBody
    public String json2() throws JsonProcessingException {
        List<User> list = new ArrayList<>();
        User user1 = new User("刘英俊",18,"男");
        User user2 = new User("刘英俊2",18,"男");
        User user3 = new User("刘英俊3",18,"男");
        User user4 = new User("刘英俊4",18,"男");
        list.add(user1);
        list.add(user2);
        list.add(user3);
        list.add(user4);
        ObjectMapper mapper = new ObjectMapper();
        String str = mapper.writeValueAsString(list);
        return str;
    }
    

    image-20220701145624286

  9. 测试时间转为Json

    @RequestMapping("/j3")
    @ResponseBody
    public String json3() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        Date date = new Date();
        String str = mapper.writeValueAsString(date);
        return str;
    }
    

    image-20220701145841057

    默认解析后是时间戳,解决方法:

    • 将其转换为可以看懂的时间,date.toLocaleString();

      image-20220701150249115

    • 使用SimpleDateFormat类

      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      String format = simpleDateFormat.format(date);
      

      image-20220701151007799

    • 使用ObjectMapper解决

      @RequestMapping("/j3")
          @ResponseBody
          public String json3() throws JsonProcessingException {
              ObjectMapper mapper = new ObjectMapper();
              Date date = new Date(); //时间戳
              SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
              ObjectMapper configure = mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
              mapper.setDateFormat(simpleDateFormat);
              String str = configure.writeValueAsString(date); 
              return str;
          }
      

      image-20220701151459930

  10. 到这里,我们发现,每一个方法都有一些公共的东西,按照Java的思想,我们需要把这些东西提取出来,新建utils包

    package com.laxsilence.utils;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    
    import java.text.SimpleDateFormat;
    
    /**
     * @ClassName JsonUtils
     * @Description TODO
     * @Author 刘英俊
     * @Date 2022/7/1 15:16
     * @Version 1.0
     **/
    public class JsonUtils {
        public static String getJson(Object obj,String dataFormat){
            ObjectMapper mapper = new ObjectMapper();
            SimpleDateFormat sdf = new SimpleDateFormat(dataFormat);
            mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            mapper.setDateFormat(sdf);
    
            try {
                return mapper.writeValueAsString(obj);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    这个时候,我们再来演示时间那个例子:

    @RequestMapping("/j4")
    @ResponseBody
    public String json4() throws JsonProcessingException {
        Date date = new Date(); //时间戳
        return JsonUtils.getJson(date,"yyyy-MM-dd HH-mm-ss");
    }
    
  11. 但是这样我们还不满足,因为这需要我们手动传一个时间格式化参数,如果我们想要解析为JSON的不是时间呢,或者时间不想写格式化参数呢,于是,我们把工具类再完善下:

    package com.laxsilence.utils;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    
    import java.text.SimpleDateFormat;
    
    public class JsonUtils {
    
        public static String getJson(Object obj){
            //这里注意思路,不要重新复制,方法重载的时候,可以直接调用其他方法,只需要把多余的变量随便写一个值即可
            return getJson(obj,"yyyy-MM-dd HH-mm-ss");
        }
    
        public static String getJson(Object obj,String dataFormat){
            ObjectMapper mapper = new ObjectMapper();
            SimpleDateFormat sdf = new SimpleDateFormat(dataFormat);
            mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            mapper.setDateFormat(sdf);
    
            try {
                return mapper.writeValueAsString(obj);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    这个时候,我们再来写时间的例子:

    @RequestMapping("/j4")
    @ResponseBody
    public String json4() throws JsonProcessingException {
        Date date = new Date(); //时间戳
        return JsonUtils.getJson(date);
    }
    

    就这么简单。

4. JackSon解析Json

点击进入JackSon官网

Json字符串:

String data = {  
            "type":2,"range":1,"start":1368417600,"end":1368547140,"cityName":"天津",  
            "companyIds":["12000001"],  
            "companyNames":["天津"],  
            "12000001":{  
                "data":[47947,48328,48573,48520],  
                "timestamps":[1368417600,1368417900,1368418200,1368418500]  
            }  
    }  

解析字符串:

package com.cennavi.dqe.test;  
  
import java.io.IOException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.TimeZone;  
  
import com.fasterxml.jackson.core.JsonProcessingException;  
import com.fasterxml.jackson.databind.JsonNode;  
import com.fasterxml.jackson.databind.ObjectMapper;  
  
public class ParseJsonTest {  
  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        String data = "{\"type\":2,\"range\":1,\"start\":1368417600,\"end\":1368547140,"  
                + "\"cityName\":\"天津\",\"companyIds\":[\"12000001\"],\"companyNames\":[\"天津\"],"  
                + "\"12000001\":{\"data\":[47947,48328,48573,48520],"  
                + "\"timestamps\":[1368417600,1368417900,1368418200,1368418500]}}";  
        String data2 = parseJson(data);  
        System.out.println(data2);  
    }  
  
    public static String parseJson(String data) {  
        // 用来展现解析Json得到的值  
        StringBuffer buf = new StringBuffer();  
        try {  
            ObjectMapper mapper = new ObjectMapper();  
            JsonNode rootNode = mapper.readTree(data); // 读取Json  
            // rootNode.path("xx")返回的还是一个JsonNode对象,调用该JsonNode的相应方法,得到键对应的值  
            int type = rootNode.path("type").asInt();  
            int range = rootNode.path("range").asInt();  
            long start = rootNode.path("start").asLong();  
            long end = rootNode.path("end").asLong();  
            String cityName = rootNode.path("cityName").asText();  
  
            // 转换时间格式  
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");  
            sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));  
  
            String str = "类型(type):" + type + "\r\n" + "范围(range):" + range  
                    + "\r\n" + "开始时间(start):"  
                    + sdf.format(new Date(start * 1000)) + "\r\n"  
                    + "结束时间(end):" + sdf.format(new Date(end * 1000)) + "\r\n"  
                    + "城市名称(cityName):" + cityName;  
            buf.append(str);  
            // 得到companyIds的JsonNode对象  
            JsonNode companyIds = rootNode.path("companyIds");  
            JsonNode companyNames = rootNode.path("companyNames");  
  
            // 遍历companyIds中的内容  
            for (int i = 0; i < companyIds.size(); i++) {  
                String companyId = companyIds.get(i).asText();  
                // 本例解析的Json字符串中companyIds与companyNames的长度是相同的,所有直接遍历companyNames  
                String companyName = companyNames.get(i).asText();  
                // companyId的值:12000001,对应Json串中的  
                // "12000001":{"data":[...],"timestamps":[....]}  
                JsonNode infoNode = rootNode.path(companyId);  
                // 得到"12000001":{"data":[...],"timestamps":[....]}中的data和timestamps的JsonNode对象  
                JsonNode dataNode = infoNode.path("data");  
                JsonNode timestampsNode = infoNode.path("timestamps");  
                // 遍历data和timestamps 本例中data.size与timestamps.size是相等的  
  
                buf.append("\r\n{\r\n  公司ID(companyId):" + companyId  
                        + "\r\n  公司名称(companyName):" + companyName + "\r\n"  
                        + " data:");  
                for (int j = 0; j < dataNode.size(); j++) {  
                    long dataValue = dataNode.get(j).asLong();  
                    buf.append(dataValue + ",");  
                }  
                buf.append("\r\n time:");  
                for (int k = 0; k < timestampsNode.size(); k++) {  
                    long timeValue = timestampsNode.get(k).asLong();  
                    buf.append(sdf.format(new Date(timeValue * 1000)) + ",");  
                }  
                buf.append("\r\n}\r\n");  
            }  
        } catch (JsonProcessingException e) {  
            e.printStackTrace();  
        } catch (IOException e) {  
            // TODO 自动生成的 catch 块  
            e.printStackTrace();  
        }  
        return buf.toString();  
    }  
  
}  

结果:

image-20220702170455083

5. FastJson

Fastjson.jar是阿里开发的一款专门用于Java开发的包,可以方便的实现json对象与JavaBean对象的转换,实现JavaBean对象和json字符串的转换,实现json对象和json字符串的转换。实现json转换的方法有很多,最后的实现结果都是一样的。

fastjson同样需要先添加pom依赖,记得导到lib中

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.8</version>
</dependency>

fastjson有三个主要的类:

  • 【JSONObject代表json对象】
    • JSONObject实现了Map接口,猜想JSONObject底层操作时由Map实现的
    • JSONObject对应JSON对象,通过各种形式的get()方法获取json对象中的数据,也可以利用size(),isEmpty()等方法获取“键:值”对的个数和判断是否为空,其本质是通过实现Map接口并调用接口中的方法完成的
  • 【JSONArray代表json对象数组】
    • 内部是有List接口中的方法来完成操作的
  • 【JSON代表JSONObject和JSONArray的转化】
    • JSON类源码分析和使用
    • 仔细观察这些方法,主要是实现json对象,json对象数组,javabean对象,json字符串之间的相互转化

实际测试:

@RequestMapping("/j5")
    @ResponseBody
    public String json5() {
        List<User> userlist = new ArrayList<>();
        User user1 = new User("刘英俊",18,"男");
        User user2 = new User("刘英俊2",18,"男");
        User user3 = new User("刘英俊3",18,"男");
        User user4 = new User("刘英俊4",18,"男");
        userlist.add(user1);
        userlist.add(user2);
        userlist.add(user3);
        userlist.add(user4);

        String string = JSON.toJSONString(userlist); //Java对象转JSON字符串
        String string2 = JSON.toJSONString(user1);
        System.out.println(string);
        User u2 = JSON.parseObject(string2,User.class);//JSON字符串解析为java对象
        System.out.println(u2);
        JSONObject object1 = (JSONObject)JSON.toJSON(user1); //java对象转换为json对象
        System.out.println(object1);
        User jsonToUser = JSON.toJavaObject(object1,User.class);//json对象转换为java对象
        System.out.println(jsonToUser.toString());
        return string;
    }

image-20220701155728725

image-20220701160428378


九、SSM整合

1. 环境要求

环境:

  • IDEA
  • MySQL 8.0.13
  • Tomcat 9.0.64
  • Maven 3.5.3

要求:熟练掌握MySQL数据库,了解SSM框架和简单的前端知识

2. 流程分析

image-20220701161654273

3. 数据库环境

创建一个存放书籍数据的数据库表

create database `ssmbuild`;
use `ssmbuild`;
drop table if exists `books`;

create table `books`(
    `bookID` int(10) not null auto_increment comment '书id',
    `bookName` varchar(100) not null comment '书名',
    `bookCounts` int(11) not null comment '数量',
    `detail` varchar(200) not null comment '描述',
    key `bookID` (`bookID`)
)engine=innodb default charset=utf8;

insert into `books`(`bookID`,`bookName`,`bookCounts`,`detail`)values
(1,'Java',1,'从入门到放弃'),(2,'MySQL','10','从删库到跑路'),(3,'Linux',5,'从进门到进牢');

4. 基本环境搭建

创建一个新的普通Maven项目:

image-20220701163113982

(1)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.laxsilence</groupId>
    <artifactId>ssmbuild</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <!--依赖:junit、数据库驱动、连接池、servlet、jsp、mybatis、mybatis-spring、spring等-->
    <dependencies>
        <!--Junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
        </dependency>
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>
        <!--数据库连接池  c3p0-->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</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>javax.servlet.jsp-api</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
        </dependency>
        <!--jstl表达式的依赖-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp.jstl</groupId>
            <artifactId>jstl-api</artifactId>
            <version>1.2</version>
        </dependency>
        <!--standard表达式-->
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <!--Mybatis 和 Spring 整合-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
        <!--Spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.18</version>
        </dependency>
        <!--Spring  操作数据库,需要spring-jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.12</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
        <!--使用AOP织入-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
        <!-- log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

    <!--静态资源导出-->
    <!--在build中配置resources,来防止我们资源导出失败的问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>

(2)IDEA连接数据库

image-20220701165117247

5. MyBatis层

(1)搭建包结构和资源结构

image-20220701170917404

applicationContext.xml:

<?xml version="1.0" encoding="UTF8"?>
<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">

</beans>

mybatis-config.xml:

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration 核心配置文件-->
<configuration>
        <!--配置数据源,交给Spring做-->

        <settings>
                <!--日志实现-->
                <setting name="logImpl" value="LOG4J"/>
        </settings>

        <!--起别名-->
        <typeAliases>
                <package name="com.laxsilence.pojo"/>
        </typeAliases>
        

        <mappers>
                <mapper class="com.laxsilence.dao.BookMapper"/>
        </mappers>

</configuration>

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/laxsilence.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

database.properties:

#mysql8.0以下,不用.cj
driver=com.mysql.cj.jdbc.Driver
#如果是mysql8.0以上,需要加时区的配置
url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
username=root
password=123456

(2)编写实体类

Books.java:

package com.laxsilence.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @ClassName Books
 * @Description TODO
 * @Author 刘英俊
 * @Date 2022/7/1 17:13
 * @Version 1.0
 **/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Books {
    private int bookID;
    private String bookName;
    private int bookCounts;
    private String detail;
}

(3)编写dao层下的mapper及mapper.xml

BookMapper.java:

package com.laxsilence.dao;

import com.laxsilence.pojo.Books;

import java.util.List;

public interface BookMapper {
    //增加一本书
    int addBook(Books books);
    //删除一本书
    int deleteBookById(int id);
    //更新一本书
    int updateBook(Books books);
    //查询一本书
    Books queryBookById(int id);
    //查询全部的书
    List<Books> queryAllBook();
}

BookMapper.xml:

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.laxsilence.dao.BookMapper">

    <insert id="addBook" parameterType="Books">
        insert into ssmbuild.books(bookName, bookCounts, detail) 
        VALUES (#{bookName},#{bookCounts},#{detail});
    </insert>

    <delete id="deleteBookById" parameterType="int">
        delete from ssmbuild.books where bookID=#{bookID};
    </delete>

    <update id="updateBook" parameterType="Books">
        update ssmbuild.books
        set bookName=#{bookName},bookCounts=#{bookCounts},detail=#{detail}
        where bookID=#{bookID};
    </update>

    <select id="queryBookById" resultType="Books">
        select * from ssmbuild.books where bookID=#{bookID};
    </select>

    <select id="queryAllBook" resultType="Books">
        select * from ssmbuild.books;
    </select>
</mapper>

(4)将mapper.xml绑定到核心配置文件中

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 核心配置文件-->
<configuration>
        <!--配置数据源,交给Spring做-->

        <!--起别名-->
        <typeAliases>
                <package name="com.laxsilence.pojo"/>
        </typeAliases>

        <mappers>
                <mapper class="com.laxsilence.dao.BookMapper"/>
        </mappers>

</configuration>

(5)编写service层

BookService.java:

package com.laxsilence.service;

import com.laxsilence.pojo.Books;

import java.util.List;

public interface BookService {
    //增加一本书
    int addBook(Books books);
    //删除一本书
    int deleteBookById(int id);
    //更新一本书
    int updateBook(Books books);
    //查询一本书
    Books queryBookById( int id);
    //查询全部的书
    List<Books> queryAllBook();
}

BookServiceImpl.java:

package com.laxsilence.service;

import com.laxsilence.dao.BookMapper;
import com.laxsilence.pojo.Books;

import java.util.List;

/**
 * @ClassName BookServiceImpl
 * @Description TODO
 * @Author 刘英俊
 * @Date 2022/7/1 17:52
 * @Version 1.0
 **/
public class BookServiceImpl implements BookService {

    //业务层调用Dao层,加一些逻辑运算
    private BookMapper bookMapper;
    public void setBookMapper(BookMapper bookMapper) {
        this.bookMapper = bookMapper;
    }

    @Override
    public int addBook(Books books) {
        return bookMapper.addBook(books);
    }

    @Override
    public int deleteBookById(int id) {
        return bookMapper.deleteBookById(id);
    }

    @Override
    public int updateBook(Books books) {
        return bookMapper.updateBook(books);
    }

    @Override
    public Books queryBookById(int id) {
        return bookMapper.queryBookById(id);
    }

    @Override
    public List<Books> queryAllBook() {
        return bookMapper.queryAllBook();
    }
}

6. Spring层

(1)Spring整合Dao层 --> 基本替代MyBatis核心配置文件

新建spring-dao.xml,在applicationContext.xml中进行关联

<?xml version="1.0" encoding="UTF8"?>
<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">

    <!--Spring 的总配置文件-->

    <import resource="classpath:spring-dao.xml"/>
  
</beans>

spring-dao.xml:

<?xml version="1.0" encoding="UTF8"?>
<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">

    <!-- 1. 关联数据库配置文件 -->
    <context:property-placeholder location="classpath:database.properties"/>

    <!-- 2. 连接池
        - dbcp  半自动化操作,不能自动连接
        - c3p0   自动化操作(自动化的加载配置文件,可以自动设置到对象中)
        - druid
        - hikari
    -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>

        <!--c3p0的私有属性-->
        <property name="initialPoolSize" value="10"/>
        <property name="maxPoolSize" value="30"/>
        <property name="minPoolSize" value="10"/>
        <!--关闭连接后不自动commit-->
        <property name="autoCommitOnClose" value="false"/>
        <!--获取连接超时时间-->
        <property name="checkoutTimeout" value="10000"/>
        <!--当连接失败后重试次数-->
        <property name="acquireRetryAttempts" value="2"/>
    </bean>

    <!--3. sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--绑定MyBatis的配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

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

</beans>

(2)Spring整合Service层 --> 事务

新建spring-service.xml,在applicationContext进行关联:

<?xml version="1.0" encoding="UTF8"?>
<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">

    <!--Spring 的总配置文件-->

    <import resource="classpath:spring-dao.xml"/>
    <import resource="classpath:spring-service.xml"/>

</beans>

也可以在IDEA的项目结构中查看:

image-20220701200356393

spring-service.xml:

<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx
       https://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--扫描service下的包-->
    <context:component-scan base-package="com.laxsilence.service"/>

    <!--将我们的所有业务类注入到Spring,可以通过配置或者注解实现-->
    <bean id="BookServiceImpl" class="com.laxsilence.service.BookServiceImpl">
        <property name="bookMapper" ref="bookMapper"/>
    </bean>

    <!--声明式事务配置-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--结合AOP实现事务的织入-->
    <!--配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--给哪些方法配置事务-->
        <!--配置事务的传播特性-->
        <tx:attributes>
            <!--            <tx:method name="add" propagation="REQUIRED"/>-->
            <!--            <tx:method name="delete" propagation="REQUIRED"/>-->
            <!--            <tx:method name="update" propagation="REQUIRED"/>-->
            <!--            <tx:method name="query" read-only="true"/>-->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!--配置事务切入-->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.laxsilence.dao.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
</beans>

7. SpringMVC层

(1)将项目变为Web项目

右键项目,添加框架支持,选择web项目

在项目结构中,添加lib目录,加入maven的依赖库。

截止目前,项目框架如下:

image-20220701200247496

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

    <!--DispatchServlet-->
    <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: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>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>

    <!--默认的Session过期时间-->
    <session-config>
        <session-timeout>15</session-timeout>
    </session-config>
</web-app>

(3)编写spring-mvc.xml

<?xml version="1.0" encoding="UTF8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--自动扫描包,让指定包下的注解生效 由IOC容器统一管理-->
    <context:component-scan base-package="com.laxsilence.controller"/>
    <!--让SpringMVC不处理静态资源-->
    <mvc:default-servlet-handler/>

    <!--
    支持mvc注解驱动
        在Spring中一般采用@RequestMapping注解来完成映射关系
        要想使@RequestMapping注解生效
        必须向上下文中注册DefaultAnnotationHandlerMapping
        和一个AnnotationMethodHandlerAdapter实例
        这两个实例分别在类级别和方法级别处理
        而annotation-driven配置帮助我们自动完成上述两个实例的注入
    -->
    <mvc:annotation-driven/>
<!--    <mvc:annotation-driven>-->
<!--        &lt;!&ndash;JSON 乱码问题配置&ndash;&gt;-->
<!--        <mvc:message-converters>-->
<!--            <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 class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>


(4)部署Tomcat

image-20220701201754004

image-20220701201813644

8. 编写查询全部书籍业务

(1)编写Controller

package com.laxsilence.controller;

import com.laxsilence.pojo.Books;
import com.laxsilence.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

/**
 * @ClassName BookController
 * @Description TODO
 * @Author 刘英俊
 * @Date 2022/7/1 20:19
 * @Version 1.0
 **/

@Controller
@RequestMapping("/book")
public class BookController {
    //Controller调用Service层
    @Autowired
    @Qualifier("BookServiceImpl")
    private BookService bookService;

    //查询全部的书籍并且返回到一个书籍展示页面
    @RequestMapping("/allBook")
    public String list(Model model){
        List<Books> books = bookService.queryAllBook();
        model.addAttribute("list",books);
        return "allBook";
    }
}

(2)编写allBook.jsp以及index.jsp

allBook.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>书籍展示</title>
</head>
<body>
<h2>书籍展示</h2>
</body>
</html>

index.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <h3>
    <a href="${pageContext.request.contextPath}/book/allBook">进入书籍展示页面</a>
  </h3>
  </body>
</html>

(3)执行

报错:Bean不存在

Error creating bean with name 'bookController':

步骤:

  1. 查看这个Bean是否注入成功

  2. Junit单元测试

    import com.laxsilence.pojo.Books;
    import com.laxsilence.service.BookService;
    import org.junit.Test;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import java.util.List;
    
    /**
     * @ClassName MyTest
     * @Description TODO
     * @Author 刘英俊
     * @Date 2022/7/1 20:35
     * @Version 1.0
     **/
    public class MyTest {
        @Test
        public void test(){
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            BookService bookServiceImpl = context.getBean("BookServiceImpl", BookService.class);
            List<Books> books = bookServiceImpl.queryAllBook();
            for (Books book : books) {
                System.out.println(book);
            }
        }
    }
    

image-20220701204508876

  1. 所以问题不在底层,是Spring上出了问题

    SpringMVC整合的时候没有调用到service层的bean

    • applicationContext.xml中没有注册spring-mvc.xml ,我们是整合的,排除这个问题

    • web.xml中,我们也绑定了配置文件,但是我们绑定的是spring-mvc.xml ,而不是applicationContext.xml

      所以在这里面我们进行修改即可

重新执行,没有问题:

image-20220701205125513

image-20220701205214689

(4)优化界面

优化主页:

修改完前端之后不用重新发布:

image-20220701205558520

甚至可以修改为自动刷新:

image-20220701205946819

index.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
    <style>
      a{
        text-decoration: none;
        font-size: 40px;
        color: black;
      }
      h3{
        width: 400px;
        height: 100px;
        margin: 100px auto;
        text-align: center;
        line-height: 100px;
        background-color:lightblue;
        border-radius: 5px;
      }
    </style>
  </head>
  <body>
  <h3>
    <a href="${pageContext.request.contextPath}/book/allBook">进入书籍展示页面</a>
  </h3>
  </body>
</html>

allBook.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>书籍展示</title>
    <%--引入bootstrap--%>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></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-12 column">
                <table class="table table-hover table-striped">
                    <thead>
                    <tr>
                        <th>书籍编号</th>
                        <th>书籍名称</th>
                        <th>书籍数量</th>
                        <th>书籍详情</th>
                    </tr>
                    </thead>
                    <%--书籍从数据库中查询出来,从list中遍历出来:foreach--%>
                    <tbody>
                    <c:forEach var="book" items="${list}">
                        <tr>
                            <td>${book.bookID}</td>
                            <td>${book.bookName}</td>
                            <td>${book.bookCounts}</td>
                            <td>${book.detail}</td>
                        </tr>
                    </c:forEach>
                    </tbody>
                </table>
            </div>
        </div>
    </div>

</body>
</html>

效果如下:

image-20220701212416759

image-20220701212407093

9. 编写添加书籍业务

在书籍列表下面,补充添加书籍按钮:

<div class="col-md-12 column">
    <div class="page-header">
        <h1>
            <small>书籍列表 -------- 显示所有书籍</small>
        </h1>
    </div>
</div>

<div class="row">
    <div class="col-md-4 cloumn">
        <%--toAddBook--%>
        <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增书籍</a>
    </div>
</div>

在Controller中创建跳转方法:

//跳转到增加书籍页面
@RequestMapping("/toAddBook")
public String toAddPaper(){
    return "addBook";
}

创建addBook.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>增加书籍</title>
    <%--引入bootstrap--%>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></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>

        <form action="${pageContext.request.contextPath}/book/addBook" method="post">
            <div class="form-group">
                <label for="bookName">书籍名称:</label>
                <input type="text" class="form-control" id="bookName" name="bookName" required>
            </div>

            <div class="form-group">
                <label for="bookCounts">书籍数量:</label>
                <input type="text" class="form-control" id="bookCounts" name="bookCounts" required>
            </div>

            <div class="form-group">
                <label for="detail">书籍描述:</label>
                <input type="text" class="form-control" id="detail" name="detail" required>
            </div>

            <div class="form-group">
                <input type="submit" class="form-control btn btn-primary" value="添加">
            </div>
        </form>

    </div>
</body>
</html>

效果演示:
image-20220701214915543

image-20220701214946837

image-20220701214955055

10. 编写修改、删除书籍业务

(1)修改书籍

在allBook.jsp,新增两个按钮:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>书籍展示</title>
    <%--引入bootstrap--%>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></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 class="row">
                <div class="col-md-4 cloumn">
                    <%--toAddBook--%>
                    <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增书籍</a>
                </div>
            </div>
        </div>

        <div class="row clearfix">
            <div class="col-md-12 column">
                <table class="table table-hover table-striped">
                    <thead>
                    <tr>
                        <th>书籍编号</th>
                        <th>书籍名称</th>
                        <th>书籍数量</th>
                        <th>书籍详情</th>
                        <th>操作</th>
                    </tr>
                    </thead>
                    <%--书籍从数据库中查询出来,从list中遍历出来:foreach--%>
                    <tbody>
                    <c:forEach var="book" items="${list}">
                        <tr>
                            <td>${book.bookID}</td>
                            <td>${book.bookName}</td>
                            <td>${book.bookCounts}</td>
                            <td>${book.detail}</td>
                            <td>
                                <a href="${pageContext.request.contextPath}/book/toUpdateBook?id=${book.bookID}">修改</a>
                                &nbsp; | &nbsp;
                                <a href="">删除</a>
                            </td>
                        </tr>
                    </c:forEach>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>

image-20220701235043848

在Controller中创建跳转到修改界面的方法:

//跳转到修改页面
@RequestMapping("/toUpdateBook")
public String toUpdatePaper(int id,Model model){
    //需要知道修改的那本书的id
    Books books = bookService.queryBookById(id);
    model.addAttribute("Qbook",books);
    return "updateBook";
}

在Controller编写修改方法:

//书籍修改
@RequestMapping("updateBook")
public String updateBook(Books books){
    System.out.println("Books=>"+books.toString());
    bookService.updateBook(books);
    return "redirect:/book/allBook";
}

编写修改界面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>修改书籍</title>
    <%--引入bootstrap--%>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></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>

    <form action="${pageContext.request.contextPath}/book/updateBook" method="post">

        <%--出现问题,提交修改请求,修改失败,初次考虑是事务问题,配置完依旧失败,是查询语句问题,没有传递id--%>
        <%--使用隐藏域,传递id--%>
        <input type="hidden" name="bookID" value="${Qbook.bookID}">

        <div class="form-group">
            <label for="bookName">书籍名称:</label>
            <input type="text" class="form-control" id="bookName" name="bookName" required value="${Qbook.bookName}">
        </div>

        <div class="form-group">
            <label for="bookCounts">书籍数量:</label>
            <input type="text" class="form-control" id="bookCounts" name="bookCounts" required value="${Qbook.bookCounts}">
        </div>

        <div class="form-group">
            <label for="detail">书籍描述:</label>
            <input type="text" class="form-control" id="detail" name="detail" required value="${Qbook.detail}">
        </div>

        <div class="form-group">
            <input type="submit" class="form-control btn btn-primary" value="修改">
        </div>
    </form>

</div>
</body>
</html>

image-20220701235323492

image-20220701235349640

image-20220701235402942

(2)删除书籍

在allBook.jsp中添加内容:这一次我们使用restFul风格

<a href="${pageContext.request.contextPath}/book/deleteBook/${book.bookID}">删除</a>

在Controller中编写deleteBook方法:

//删除书籍
@RequestMapping("deleteBook/{bookID}")
public String deleteBook(@PathVariable("bookID") int id){
    bookService.deleteBookById(id);
    return "redirect:/book/allBook";
}

image-20220702000121101

image-20220702000137726

11. 新增搜索功能

修改allBook.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>书籍展示</title>
    <%--引入bootstrap--%>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></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 class="row">
                <div class="col-md-4 cloumn">
                    <%--toAddBook--%>
                    <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增书籍</a>
                    <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/allBook">显示全部书籍</a>
                </div>
                <div class="col-md-8 cloumn" >
                    <%--查询书籍--%>
                    <form action="${pageContext.request.contextPath}/book/queryBookName" method="post" class="form-inline">
                        <div class="form-group">
                            <div class="input-group" >
                                ${error}
                                <input type="text" class="form-control" name="queryBookName" id="queryBookName" placeholder="请输入要查询的书籍名称">
                            </div>
                        </div>
                        <button type="submit" class="btn btn-primary">查询</button>
                    </form>
                </div>
            </div>
        </div>

        <div class="row clearfix">
            <div class="col-md-12 column">
                <table class="table table-hover table-striped">
                    <thead>
                    <tr>
                        <th>书籍编号</th>
                        <th>书籍名称</th>
                        <th>书籍数量</th>
                        <th>书籍详情</th>
                        <th>操作</th>
                    </tr>
                    </thead>
                    <%--书籍从数据库中查询出来,从list中遍历出来:foreach--%>
                    <tbody>
                    <c:forEach var="book" items="${list}">
                        <tr>
                            <td>${book.bookID}</td>
                            <td>${book.bookName}</td>
                            <td>${book.bookCounts}</td>
                            <td>${book.detail}</td>
                            <td>
                                <a href="${pageContext.request.contextPath}/book/toUpdateBook?id=${book.bookID}">修改</a>
                                &nbsp; | &nbsp;
                                <a href="${pageContext.request.contextPath}/book/deleteBook/${book.bookID}">删除</a>
                            </td>
                        </tr>
                    </c:forEach>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>

BookMapper.java 新增接口方法:

//通过书名查书籍
List<Books> queryBookByName(@Param("bookName") String bookName);

BookMapper.xml新增接口实现:

<select id="queryBookByName" resultType="Books">
    select * from ssmbuild.books where bookName like concat('%',#{bookName},'%');
</select>

业务类接口新增方法:

//通过书名查书籍
List<Books> queryBookByName(String bookName);

业务接口实现类实现方法:

@Override
public List<Books> queryBookByName(String bookName) {
    return bookMapper.queryBookByName(bookName);
}

Controller新增方法:

//查询书籍
@RequestMapping("queryBookName")
public String queryBookName(String queryBookName,Model model){
    List<Books> list = bookService.queryBookByName(queryBookName);
    if (list == null){
        model.addAttribute("error","没有该本书");
    }
    model.addAttribute("list",list);
    return "allBook";
}

测试:

image-20220702163632586

image-20220702163643312

image-20220702163651457


十、Ajax

Web1.0时代:

  • 登陆,如果失败,需要重写刷新页面,才能重新登陆;不点击提交按钮,就不知道自己密码输错了

Web2.0时代:

  • 现在,大多数网站都是局部刷新,不刷新整个页面的情况下,实现页面更新;注册的时候,发现手机已经注册过了,但是你只是输入了,还没提交,就已经提示输入正确还是错误了
  • 这就是Ajax

增强B/S的体验性质

B/S:未来的主流,并且会爆发式的持续增长

产业链:H5+网页+客户端+手机端(Android + IOS)+ 小程序

1. 简介

  • Ajax = Asynchronous JavaScript and xml(异步的JavaScript和xml)

  • Ajax是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。

  • Ajax不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。

  • 在2005年,Google通过其Google Suggest使得Ajax流行起来,它能自动帮你完成搜索单词

  • Google Suggest使用Ajax创造出动态性极强的web界面:当您在谷歌的搜索框输入关键字时,JavaScript会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表,就跟国内百度的搜索框一样。

  • 如果我们查看部分网站的请求,如果type是xhr,说明是ajax异步的。

    image-20220702171904076

  • 传统的网页,即不用ajax的技术的网页,想要更新内容或者提交一个表单,都需要重新加载整个网页。

  • 使用ajax技术的网页,通过在后台服务器进行少量的数据交换,就可以实现异步局部更新。

  • 使用Ajax,用户可以创建接近本地桌面应用的直接、高可用、更丰富、更动态的web用户界面

2. 伪造Ajax

我们可以使用前端的一个标签来伪造一个Ajax的样子,即iframe标签

在IDEA中搭建好所有环境

测试环境搭建:

package com.laxsilence.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class AjaxController {
    @RequestMapping("/t1")
    public String test(){
        return "hello";
    }
}

image-20220702180130560

使用Iframe:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>iFrame测试体验页面无刷新</title>
    <script>
        function go(){
            /*所有的值都提前获取*/
            let url = document.getElementById("url").value;
            document.getElementById("iframe1").src=url;
        }
    </script>
</head>
<body>
<div>
    <p>请输入地址:</p>
    <p>
        <input type="text" id="url" value="https://www.baidu.com">
        <input type="button" value="提交" onclick="go()">
    </p>
</div>

<div>
    <iframe id="iframe1" src="https://www.cnblogs.com/Laxsilence" frameborder="0" style="width: 100%; height: 300px;">

    </iframe>
</div>
</body>
</html>

image-20220702180033621

但其实iframe还是访问了请求的,所以说是伪造的。

3. Ajax初体验

利用Ajax可以做什么呢?

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

JQuery.ajax:

  • 纯JS原生实现Ajax我们不去讲解这里,直接使用jquery提供的,方便学习和使用,避免重复造轮子,有兴趣的同学可以去了解下JS原生的XMLHttpRequest!

    <script type="text/javascript">
    var xmlhttp;
    function loadXMLDoc(url)
    {
    xmlhttp=null;
    if (window.XMLHttpRequest)
    {// code for all new browsers
    xmlhttp=new XMLHttpRequest();
    }
    else if (window.ActiveXObject)
    {// code for IE5 and IE6
    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    if (xmlhttp!=null)
    {
    xmlhttp.onreadystatechange=state_Change;
    xmlhttp.open("GET",url,true);
    xmlhttp.send(null);
    }
    else
    {
    alert("Your browser does not support XMLHTTP.");
    }
    }
    
    function state_Change()
    {
    if (xmlhttp.readyState==4)
    {// 4 = "loaded"
    if (xmlhttp.status==200)
     {// 200 = OK
     // ...our code here...
     }
    else
     {
     alert("Problem retrieving XML data");
     }
    }
    }
    </script>
    
  • Ajax的核心是XMLHttpRequest对象(XHR)。XHR为向服务器发送请求和解析服务器响应提供了接口。能够以异步方式从服务器获取新数据。

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

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

  • jQuery 不是生产者,而是大自然搬运工。

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

  • jQuery的在线CDN:

    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
        <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    
  • image-20220703164522121

  • Ajax需要知道:

    • Html+css+js

    • js:

      函数:闭包()()

      DOM:

      • id、name、tag
      • create、remove

      BOM:

      • window
      • document
    • ES6:import require

实现:点击松开输入框发起请求

  • 必须在spring-mvc.xml中进行配置,默认静态资源加载器<mvc:default-servlet-handler/>

  • index.jsp页面:

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
      <head>
        <title>$Title$</title>
        <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
        <script src="${pageContext.request.contextPath}/static/js/jquery-3.5.1.js"></script>
        <script>
          function a(){
            $.post({
              url:"${pageContext.request.contextPath}/a1",
              data:{"name":$("#username").val()},
              success:function (data){
                  //data封装了从服务器返回的数据
                alert(data);
              }
            })
          }
        </script>
      </head>
      <body>
        <%--失去焦点的时候,发起一个请求(携带信息)到后台--%>
        用户名:<input type="text" id="username" onblur="a()">
    
      </body>
    </html>
    
  • Controller接收请求:

    package com.laxsilence.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    
    @RestController
    public class AjaxController {
        @RequestMapping("/t1")
        public String test(){
            return "hello";
        }
    
        @RequestMapping("/a1")
        public void a1(String name, HttpServletResponse response) throws IOException {
            System.out.println("a1:param=>" + name);
            if ("liu".equals(name)){
                response.getWriter().print("true");
            }else {
                response.getWriter().print("false");
            }
        }
    }
    
  • image-20220703164016950

4. 真实的Ajax

写一个实体类:User

package com.laxsilence.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private int age;
    private String sex;
}

写一个测试页面:Test2.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="${pageContext.request.contextPath}/static/js/jquery-3.5.1.js"></script>
    <script>
        /*要么写入口函数,要么将script放到代码最后*/
        $(function (){
            $("#btn").click(function (){
                console.log("点击事件");
                /*简写,可以直接把url写到参数里,参数和回调函数都可以省略*/
                $.post("${pageContext.request.contextPath}/a2",function (data) {
                    var html="<>";
                    for (let i = 0; i < data.length; i++) {
                        html += "<tr>"+
                            "<td>"+data[i].name+"</td>"+
                            "<td>"+data[i].age+"</td>"+
                            "<td>"+data[i].sex+"</td>"+
                            "</tr>"
                    }
                    $("#content").html(html);
                })
            })
        });
    </script>
</head>
<body>
<input type="button" value="加载数据" id="btn">
<table>
    <tr>
        <td>姓名</td>
        <td>年龄</td>
        <td>性别</td>
    </tr>
    <tbody id="content">
        <%--数据从后台来--%>

    </tbody>
</table>
</body>
</html>

编写Controller:

@RequestMapping("/a2")
/*这里我们需要给前端ajax返回一个List*/
public List<User> a2(){
    ArrayList<User> userList = new ArrayList<>();
    //添加数据
    userList.add(new User("Laxsilence",12,"男"));
    userList.add(new User("Liu",1,"男"));
    userList.add(new User("Yuan",12,"女"));
    return userList;
}

5. 使用Ajax验证用户名

编写Controller:

@RequestMapping("/a3")
public String a3(String name,String password){
    String msg = null;
    if (name!=null){
        if ("admin".equals(name)){
            msg="ok";
        }else {
            msg="用户名有误";
        }
    }
    if (password!=null){
        if ("123456".equals(password)){
            msg="ok";
        }else {
            msg="密码有误";
        }
    }
    return msg;
}

编写登录界面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<script src="${pageContext.request.contextPath}/static/js/jquery-3.5.1.js"></script>
<script>
    function a1() {
        //发起一个请求
        $.post({
            url:"${pageContext.request.contextPath}/a3",
            data:{"name":$("#name").val()},
            success:function (data){
                if (data.toString()==="ok"){
                    $("#userInfo").css("color","green");
                }
                if (data.toString()!="ok"){
                    $("#userInfo").css("color","red");
                }
                $("#userInfo").html(data);
            }
        })
    }
    function a2() {
        $.post({
            url:"${pageContext.request.contextPath}/a3",
            data:{"password":$("#password").val()},
            success:function (data){
                if (data.toString()==="ok"){
                    $("#passInfo").css("color","green");
                }
                if (data.toString()!="ok"){
                    $("#passInfo").css("color","red");
                }
                $("#passInfo").html(data);
            }
        })
    }
</script>
<body>
<p>
    用户名:<input type="text" id="name" onblur="a1()">
    <span id="userInfo"></span>
</p>
<p>
    密码:<input type="password" id="password" onblur="a2()">
    <span id="passInfo"></span>
</p>
</body>
</html>

测试:

image-20220703182324444

image-20220703182335390

6. 总结

  1. 编写对应处理的Controller,返回消息或字符串或者json格式的数据
  2. 编写ajax请求
    • url:Controller请求
    • data:键值对
    • success:回调函数
  3. 给Ajax绑定事件,点击click,失去焦点onblur,键盘弹起keyup

十一、SSM整合补充(ajax登陆)

学完了json和ajax,我们就可以对SSM做补充了,比如新增登陆界面

创建数据库:

use ssmbuild;

create table `user`(
    `userID` int(20) not null auto_increment comment '用户id',
    `userName` varchar(20) not null comment '用户名',
    `userAge` int(10) not null comment '用户年龄',
    `userSex` varchar(10) not null comment '用户性别',
    key `userID` (userID)
)engine=innodb default charset=utf8;

insert into user(userID, userName, userAge, userSex) VALUES
(1,'admin',18,'男');

alter table user add `password` varchar(200) not null comment '用户密码';

使用MD5加密:

UPDATE user SET ssmbuild.user.password=MD5(ssmbuild.user.password) WHERE userID;-- 加密所有密码

新建User类:

package com.laxsilence.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int userID;
    private String userName;
    private int userAge;
    private String userSex;
}

新建UserMapper接口:

package com.laxsilence.dao;

import com.laxsilence.pojo.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {
    User queryUser(@Param("userName") String userName, @Param("password") String password);
    User queryUserName(@Param("userName") String userName);
}

新建UserMapper.xml,并绑定:

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.laxsilence.dao.UserMapper">
    <select id="queryUser" resultType="User">
        select * from ssmbuild.user where userName=#{userName} and password=md5(#{password});
    </select>
    <select id="queryUserName" resultType="User">
        select * from ssmbuild.user where userName=#{userName};
    </select>
</mapper>

新建Service层的接口和实现类

package com.laxsilence.service;

import com.laxsilence.pojo.User;

public interface UserService {
    User login(String userName,String password);
    User userName(String userName);
}
package com.laxsilence.service;

import com.laxsilence.dao.UserMapper;
import com.laxsilence.pojo.User;

public class UserServiceImpl implements UserService{
    private UserMapper userMapper;



    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public User login(String userName, String password) {
        return userMapper.queryUser(userName,password);
    }

    @Override
    public User userName(String userName) {
        return userMapper.queryUserName(userName);
    }
}

在Spring中进行注册:

<bean id="UserServiceImpl" class="com.laxsilence.service.UserServiceImpl">
    <property name="userMapper" ref="userMapper"/>
</bean>

新建UserController:

package com.laxsilence.controller;

import com.laxsilence.pojo.User;
import com.laxsilence.service.BookService;
import com.laxsilence.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.util.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;


@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    @Qualifier("UserServiceImpl")
    private UserService userService;

    @RequestMapping("/a3")
    @ResponseBody
    public String a3(String name){
        User loginUser = userService.userName(name);
        String msg = "用户名有误";
        if (loginUser!=null){
            if (loginUser.getUserName().equals(name)){
                msg="用户名正确";
            }else {
                msg="用户名有误";
            }
        }
        return msg;
    }

    @RequestMapping("/login")
    public String login2(String name, String password, Model model){
        String pass = DigestUtils.md5DigestAsHex(password.getBytes());
        System.out.println(pass);
        User loginUser = userService.login(name,password);
        System.out.println(loginUser);
        if (loginUser!=null){
            if (loginUser.getUserName().equals(name)){
                if (loginUser.getPassword().equals(pass)){
                    return "allBook";
                }
            }
        }
        model.addAttribute("error","密码输入错误,请重新登陆");
        return "redirect:/index.jsp";
    }
}

编写首页:即登录页

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>登陆</title>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <script src="${pageContext.request.contextPath}/static/js/jquery-3.5.1.js"></script>
    <script>
      function a1() {
        console.log($("#name").val());
        //发起一个请求
        $.post({
          url:"${pageContext.request.contextPath}/user/a3",
          data:{"name":$("#name").val()},
          success:function (data){
            if (data.toString()==="用户名正确"){
              $("#userInfo").css("color","green");
            }
            if (data.toString()!="用户名正确"){
              $("#userInfo").css("color","red");
            }
            $("#userInfo").html(data);
          }
        })
      };

    </script>
    <style>

    </style>
  </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>
      <form class="form-inline" method="post" action="${pageContext.request.contextPath}/user/login">
        <div class="form-group">
          <input type="text" id="name" name="name" required onblur="a1()" placeholder="请输入用户名">
          &nbsp;&nbsp;<span id="userInfo"></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        </div>
        <div class="form-group">
          <input type="password" id="password" name="password" placeholder="请输入密码">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        </div>
        <button type="submit" class="btn btn-primary">登陆</button>
      </form>

    </div>
  </div>
  </body>
</html>

将字符串乱码解决,记得导入jackson的包,并加到lib中:

<mvc:annotation-driven>
    <!--JSON 乱码问题配置-->
    <mvc:message-converters>
        <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>

测试:

image-20220703221122904

image-20220703221139929

image-20220703221154169

image-20220703221203382

image-20220703221218633


十二、拦截器

1. 概述

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

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

过滤器:

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

拦截器:

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

2. 自定义拦截器

那如何实现拦截器呢?

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

  1. 新建一个Module,springmvc-07-Interceptor,添加web支持

  2. 配置web.xml和springmvc-servlet.xml文件

  3. 编写一个拦截器

    package com.laxsilence.config;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @ClassName MyInterceptor
     * @Description TODO
     * @Author 刘英俊
     * @Date 2022/7/4 16:10
     * @Version 1.0
     **/
    public class MyInterceptor implements HandlerInterceptor {
        //return true:执行下一个拦截器,放心
        //return false:卡死,不执行下一个拦截器
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("==============处理前==================");
            return true;
        }
    
        //后面两个方法不需要返回值,主要用来写日志
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("====处理后=========");;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("==========清理==========");;
        }
    }
    
  4. 配置拦截器在SPringMVC中:

    <!--拦截器配置-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--包括这个请求下面的所有请求-->
            <mvc:mapping path="/**"/>
            <bean class="com.laxsilence.config.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    
  5. 编写Controller

    package com.laxsilence.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @ClassName TestController
     * @Description TODO
     * @Author 刘英俊
     * @Date 2022/7/4 16:07
     * @Version 1.0
     **/
    
    @RestController
    public class TestController {
        @RequestMapping("/t1")
        public String test(){
            System.out.println("TestControoler ==> test()执行了!");
            return "ok";
    
        }
    
    }
    
  6. 测试

    image-20220704162323347

  7. 假设:

    如果将拦截器的请求该为false的话,那么就只会出现==========处理前=======,不会执行接下来的方法。

3. 登陆判断验证

编写登录界面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登陆</title>
</head>
<body>
<h1>登陆页面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
    用户名:<input type="text" name="username"/>
    密码:<input type="password" name="password"/>
    <input type="submit" value="提交">
</form>
</body>
</html>

编写首页:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <h1>
    <a href="${pageContext.request.contextPath}/user/goLogin">登陆页面</a>
    <a href="${pageContext.request.contextPath}/user/main">首页</a>
  </h1>

  </body>
</html>

编写主页:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>首页</title>
</head>
<body>
<h1>首页</h1>
<span>${username}</span>
<p>
    <a href="${pageContext.request.contextPath}/user/goOut">注销</a>
</p>
</body>
</html>

需求:第一次访问主页,被拦截,跳转到登陆界面,登陆之后,存入session,之后访问主页不被拦截

编写Controller:

package com.laxsilence.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
@RequestMapping("/user")
public class LoginController {


    @RequestMapping("/goLogin")
    public String goLogin(){
        return "login";
    }

    @RequestMapping("/main")
    public String main(){
        return "main";
    }

    @RequestMapping("/login")
    public String login(HttpSession session, String username, String password, Model model){
        //把用户的信息存在session中
        session.setAttribute("userLoginInfo",username);
        model.addAttribute("username",username);
        return "main";
    }

    @RequestMapping("/goOut")
    public String goOut(HttpSession session, String username, String password, Model model){
        //把用户的信息存在session中
        session.removeAttribute("userLoginInfo");
        return "main";
    }


}

编写拦截器:

package com.laxsilence.config;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LogiinInterceptor implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();

        //放行判断。登录页面
        if (request.getRequestURI().contains("goLogin")){
            return true;
        }
        if (request.getRequestURI().contains("login")){
            return true;
        }
        //放行判断,判断什么情况下是登陆的
        if (session.getAttribute("userLoginInfo")!=null){
            return true;
        }
        //判断什么情况下没有登陆
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
        return false;
    }
}

配置拦截器:

<mvc:interceptor>
    <!--包括这个请求下面的所有请求-->
    <mvc:mapping path="/user/**"/>
    <bean class="com.laxsilence.config.LogiinInterceptor"/>
</mvc:interceptor>

测试:

image-20220704170440445image-20220704170449930image-20220704170500348

image-20220704170515421


十三、SSM整合再补充(拦截器)

编写拦截器:

package com.laxsilence.config;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserConfig implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))){
//            response.setHeader("REDIRECT","REDIRECT");
//            return true;
//        }
        if (request.getRequestURI().contains("book")){
            if (request.getSession().getAttribute("userLoginInfo")!=null){
                return true;
            }
            request.getRequestDispatcher("/index.jsp").forward(request,response);
            return false;
        }
        return true;
    }
}

修改Controller

@RequestMapping("/login")
public String login2(String name, String password, Model model, HttpSession session){
    if (name==null&password==null){
        return "redirect:/index.jsp";
    }
    String pass = DigestUtils.md5DigestAsHex(password.getBytes());
    System.out.println(pass);
    User loginUser = userService.login(name,password);
    System.out.println(loginUser);
    if (loginUser!=null){
        if (loginUser.getUserName().equals(name)){
            if (loginUser.getPassword().equals(pass)){
                session.setAttribute("userLoginInfo",name);
                return "allBook";
            }
        }
    }
    model.addAttribute("error","密码输入错误,请重新登陆");
    return "redirect:/index.jsp";
}

注册拦截器:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.laxsilence.config.UserConfig"/>
    </mvc:interceptor>
</mvc:interceptors>

测试:

直接访问全部书籍,在以前是可以直接访问到的,跳过登陆,现在不行了:

image-20220704180503808

登陆成功之后:

image-20220704180531197

再次检查,直接访问全部书籍:

发现不需要登陆了,因为session已经存在!

image-20220704180556573


十四、文件上传和下载

文件上传是项目开发中最常见的功能之—,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 SoftwareFoundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。

  • Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。

  • 而Spring MVC则提供了更简单的封装。

  • Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver 的。

  • Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类: CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。

1. 文件上传

导入依赖:

记得添加到lib

<!--servlet-api导入高版本的-->        
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

<!--文件上传-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

编写表单:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
    <input type = "file" name="file"/>
    <input type = "submit" value="upload" />
  </form>
  </body>
</html>

编写Controller:

package com.laxsilence.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;

@RestController
public class FileController {

    /*方法1*/
    //@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";
    }

    /*
     * 采用file.Transto 来保存上传的文件
     */
    //方法2
    @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";
    }

}

测试:

image-20220704184026187

image-20220704184410074

2. 文件下载

实现步骤

  1. 设置 response 响应头
  2. 读取 InputStream
  3. 写入 OutputStream
  4. 执行
  5. 关闭流(先开后关)

Controller:

@RequestMapping(value="/download")
public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception{
    //要下载的图片地址
    String  path = request.getServletContext().getRealPath("/upload");
    String  fileName = "学习计划.png";

    //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();
    }
    //5、关闭流(先开后关)
    out.close();
    input.close();
    return null;
}

注意:在web根目录下新建upload文件夹,并且里面放上你想要被下载的东西

下载链接:

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

image-20220704185108401

方法二:

直接a标签:

<a href="/static/学习计划.png">点击下载</a>
posted @ 2022-07-04 19:08  Laxsilence  阅读(74)  评论(1)    收藏  举报