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的模式。

2. Model 1 时代
-
在web早期的开发中,通常采用的都是Model 1。
-
Model 1 中,主要分为两层,视图层和模型层。

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 把一个项目分为三部分,包括视图、控制、模型

- 用户发请求
- Servlet接收请求数据,并调用相应的业务逻辑方法
- 业务处理完毕,返回更新后的数据给Servlet
- servlet转向到JSP,由JSP来渲染页面
职责分析
Controller:控制器
- 获取表单数据
- 调用业务逻辑
- 转向指定的页面
Model:模型
- 业务逻辑
- 保存数据的状态
View:视图
- 显示页面
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框架支持。
良好习惯:不管有没有继承父项目的依赖,子项目中都再导入一次。
接下来我们正式开始:
-
编写一个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); } } -
编写test.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> ${msg} </body> </html> -
配置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> -
配置Tomcat
-
启动tomcat
-
测试


MVC框架要做哪些事情?
- 将url映射到java类或java类的方法
- 封装用户提交的数据
- 处理请求--调用相关的业务处理--封装响应数据
- 将响应的数据进行渲染 .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. 概述

SpringMVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。
官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc
我们为什么要学习SpringMVC呢?
- 轻量级,简单易学
- 高效,基于请求响应的MVC架构
- 与Spring兼容性好,无缝结合
- 约定大于配置
- 功能强大:RESTful、数据验证、格式化、本地化、主题等
- 简洁灵活
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基类)。

DispatcherServlet的核心方法:

在了解SpringMVC的原理之前,我们先写一个简单的程序。
3. Hello,SpringMVC
新建一个Module,添加web的支持框架
确定导入了SpringMVC的依赖
-
配置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> -
编写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> -
添加处理映射器
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> -
添加处理器适配器
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> -
添加视图解析器
<!--添加视图解析器: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> -
编写我们要操作业务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; } } -
将这个类交给Spring 容器,注册Bean
<bean id="/hello" class="com.laxsilence.controller.HelloController"/> -
写要跳转的jsp界面,显示ModelAndView存放的数据,以及我们的正常页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> ${msg} </body> </html> -
配置Tomcat,启动测试
在Deployment中点
-,删除子项目1,点击+,添加子项目2
可能会遇到的问题:访问出现404,排查步骤:
- 查看控制台输出,看是不是少了什么jar包
- 如果jar包存在,显示无法输出,就在IDEA项目发布中,添加lib依赖
- 重启Tomcat解决
建一个新的Webapp项目的maven,可以发现,Maven默认的web目录是/src/main/webapp,而我们通过添加项目框架添加的web,是跟src同级别的,所以会显示没有导入包。
我们需要再项目结构的artfact中,手动添加lib目录,并导入依赖。
![]()
点击lib文件夹,点击加号,添加library
全选,ok
最后应用,ok,然后重启Tomcat。
建议使用maven自带的webapp,基本不会有这些问题。
可以发现,到这里,我们彻底摆脱了servlet,改为了写controller类。
4. SpringMVC执行原理
SpringMVC的原理如下图所示:
当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理模式,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。

前端控制器,我们就可以理解为: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;
}
}
我们再来换一种方式理解:

在上图中,实现为SpringMVC帮我们做的,我们只需要做虚线的地方。
我们完整的分析一遍执行流程:
-
DispatcherServlet表示前端控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求,并拦截请求。
-
我们假设请求的url为:http://localhost/SpringMVC/hello
-
以上的url拆分为三部分:
http://localhost:8080服务器域名
SpringMVC部署在服务器上的web站点
hello表示控制器
-
通过分析,这个url表示,请求位于localhost:8080上的SpringMVC站点的hello控制器
-
-
HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler
DispatcherServlet会问HandlerMapping,这个请求哪个处理器能执行。即hello这个处理器
-
HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为hello,已经找到了这个控制器hello
-
HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射,说我找到了hello控制器,即bean的id为hello
-
HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler,开始跳转到Controller类。
-
Handler让具体的Controller执行。
-
Controller将具体的执行信息返回给HandlerAdapter,如返回一个ModelAndView对象
-
HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet
-
DispatcherServlet调用视图解析器(ViewResolve)来解析HandlerAdapter传递的逻辑视图名
-
视图解析器将解析的逻辑视图名传给DispatcherServlet
-
DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图
-
最终视图呈现给用户
到此,一个完整的流程执行成功,记得对应这个例子来看。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:

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:

<?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. 使用注解开发
-
新建一个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> -
配置web.xml,右键Module,添加框架,选择web.4.0版本,创建后,记得打开项目结构,在ArtFacts中添加lib目录
注意:
- 注意web.xml版本问题
- 注册DispatchServlet
- 关联SpringMVC的配置文件
- 启动级别为1
- 映射路径用 /

<?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> -
添加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/目录下,这样可以保证视图安全,因为这个目录的文件,客户端不能直接访问
-
新建java包和web中的jsp包,新建hello.jsp页面


<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> ${msg} </body> </html> -
创建一个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"; //会被视图解析器处理 返回后在视图解析器中进行拼接 } } -
配置tomcat进行访问,最好用Tomcat9 跟web 4 比较匹配
发现运行成功

注意:
- 使用SpringMVC必须配置的三大件
- 处理器映射器
- 处理器适配器
- 视图解析器
- 通常我们需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可,省去了大段的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测试:
-
直接进/t1:

可以发现访问的是ControllerTest1的内容
-
通过/c3/t1进

确实是ControllerTest3的内容
-
-
也就是说
-
注解在方法上面访问:
-
注解在类和方法上
-
4. RestFul风格
概念
Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
功能
-
资源:互联网所有的事物都可以被抽象为资源
-
资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。
-
分别对应 添加、删除、修改、查询。
传统方式操作资源:
通过不同的参数来实现不同的效果!方法单一,post和get
使用RestFul操作资源:
可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!
-
http://127.0.0.1/item/1 查询,GET
-
http://127.0.0.1/item 新增,POST
-
http://127.0.0.1/item 更新,POST
-
http://127.0.0.1/item/1 删除,DELETE
测试:
-
新建一个RestFulController
-
在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"; } } -
测试
-
以前的方式

-
RestFul

-
如何实现同一个URL,不同的方法呢?
点开RequestMapping,可以发现:

我们将方法这个注解参数加上:
@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方法

还有一种替代的方法:
// @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,不需要视图解析器
- 通过HttpServletResponse进行输出
- 通过HttpServletResponse实现重定向
- 通过HttpServletResponse实现转发
其实就是最原始的Servlet

就是在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)提交的是一个对象
要求提交的表单域和对象的属性名一致,参数使用对象即可
-
实体类
public class User{ private int id; private String name; private int age; //构造 //get set toString } -
处理方法
/* * 1. 接收前端用户传递的参数,判断参数的名字,假设名字直接在方法上,可以直接使用 * 2. 假设传递的是一个对象User,匹配User对象中的属性名,如果名字一致,则ok,否则匹配不到 **/ @RequestMapping("/user") public String user(User user){ System.out.println(user); return "hello"; } -
后台输出: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";
}
}

其实使用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对象互转
-
要实现从JSON字符串转换为JS对象,使用JSON.parse()方法:
var obj = JSON.parse('{"a":"Hello","b":"World"}'); //结果为:{a:'Hello',b:'World'} -
要从JS对象转换为JSON对象,使用JSON.Stringify()方法
var json = JSON.stringify({a:"Hello",b:"World"}); //结果为:'{"a":"Hello","b":"World"}' -
测试
<!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>
3. Controller返回JSON数据(Jackson)
- Jackson应该是目前比较好的json解析工具了
- 当然工具不止这一个,比如还有阿里巴巴的fastjson等等
- 我们先使用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>
-
新建Module,导入lib,配置web.xml和springmvc-servlet.xml,前面已经配置过,并且已经整理到常用配置中
-
编写一个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; } -
编写一个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":"?"} } } -
执行发现:

确实被解析为Json了,但是有问题的是出现了乱码
-
乱码解决
将
@RequestMapping("/j1")替换:@RequestMapping(value = "/j1",produces = "application/json;charset=utf-8")
-
但是这样并不能让我们满意,因为每一个请求都要写很麻烦,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步就不需要了,乱码会自动解决掉。

-
补充:可以将类上的
@Controller替换为@RestController,那么这个类也不会走视图解析器,其类下的所有方法只会返回字符串也就是说:
- @Controller和@ResponseBody搭配
- 使用了@RestController就不需要@ResponseBody
- @RestController会自动将Java对象与前端交互的时候转为json
-
测试集合输出,多个对象的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; }
-
测试时间转为Json
@RequestMapping("/j3") @ResponseBody public String json3() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); Date date = new Date(); String str = mapper.writeValueAsString(date); return str; }
默认解析后是时间戳,解决方法:
-
将其转换为可以看懂的时间,date.toLocaleString();

-
使用SimpleDateFormat类
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String format = simpleDateFormat.format(date);
-
使用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; }
-
-
到这里,我们发现,每一个方法都有一些公共的东西,按照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"); } -
但是这样我们还不满足,因为这需要我们手动传一个时间格式化参数,如果我们想要解析为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
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();
}
}
结果:

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


九、SSM整合
1. 环境要求
环境:
- IDEA
- MySQL 8.0.13
- Tomcat 9.0.64
- Maven 3.5.3
要求:熟练掌握MySQL数据库,了解SSM框架和简单的前端知识
2. 流程分析

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项目:

(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连接数据库

5. MyBatis层
(1)搭建包结构和资源结构

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的项目结构中查看:

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的依赖库。
截止目前,项目框架如下:

(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>-->
<!-- <!–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>-->
<!--视图解析器-->
<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


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':
步骤:
-
查看这个Bean是否注入成功
-
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); } } }

-
所以问题不在底层,是Spring上出了问题
SpringMVC整合的时候没有调用到service层的bean
-
applicationContext.xml中没有注册spring-mvc.xml ,我们是整合的,排除这个问题
-
web.xml中,我们也绑定了配置文件,但是我们绑定的是spring-mvc.xml ,而不是applicationContext.xml
所以在这里面我们进行修改即可
-
重新执行,没有问题:


(4)优化界面
优化主页:
修改完前端之后不用重新发布:

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

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>
效果如下:


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>
效果演示:



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>
|
<a href="">删除</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

在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>



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


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>
|
<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";
}
测试:



十、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异步的。

-
传统的网页,即不用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";
}
}

使用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>

但其实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> -

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

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>
测试:


6. 总结
- 编写对应处理的Controller,返回消息或字符串或者json格式的数据
- 编写ajax请求
- url:Controller请求
- data:键值对
- success:回调函数
- 给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="请输入用户名">
<span id="userInfo"></span>
</div>
<div class="form-group">
<input type="password" id="password" name="password" placeholder="请输入密码">
</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>
测试:





十二、拦截器
1. 概述
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理,开发者可以自己定义一些拦截器来是实现特定的功能。
过滤器和拦截器的区别:拦截器是AOP思想的具体应用。
过滤器:
- servlet规范中的一部分,任何java web工程都可以使用
- 在url-pattern 中配置/*之后,可以对所有要访问的资源进行拦截
拦截器:
- 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
- 拦截器只会拦截访问的控制器方法,如果访问的是jsp/html/css/image/js是不会进行拦截的
2. 自定义拦截器
那如何实现拦截器呢?
想要自定义拦截器,必须实现HandlerInterceptor接口。
-
新建一个Module,springmvc-07-Interceptor,添加web支持
-
配置web.xml和springmvc-servlet.xml文件
-
编写一个拦截器
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("==========清理==========");; } } -
配置拦截器在SPringMVC中:
<!--拦截器配置--> <mvc:interceptors> <mvc:interceptor> <!--包括这个请求下面的所有请求--> <mvc:mapping path="/**"/> <bean class="com.laxsilence.config.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors> -
编写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"; } } -
测试

-
假设:
如果将拦截器的请求该为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>
测试:




十三、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>
测试:
直接访问全部书籍,在以前是可以直接访问到的,跳过登陆,现在不行了:

登陆成功之后:

再次检查,直接访问全部书籍:
发现不需要登陆了,因为session已经存在!

十四、文件上传和下载
文件上传是项目开发中最常见的功能之—,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";
}
}
测试:


2. 文件下载
实现步骤
- 设置 response 响应头
- 读取 InputStream
- 写入 OutputStream
- 执行
- 关闭流(先开后关)
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>

方法二:
直接a标签:
<a href="/static/学习计划.png">点击下载</a>



浙公网安备 33010602011771号