一、SpringMVC 介绍

0 什么是 MVC

知识回顾:Appli Web课程笔记 - MVC

1 什么是 SpringMVC

在 Spring 中我们重点介绍了数据访问层 dao 与业务逻辑层 service 通过 Spring 框架的实现。SpringMVC 是 Spring 的一个子项目,是表述层 Controller 的一整套完备解决方案。

2022-05-04 11.46.01

在如上的设计中:

  • controller 负责请求和数据的接收,接收后将其转发给 service 进行业务处理
  • service 根据需要会调用 dao 对数据进行增删改查
  • dao 把数据处理完后,将结果交给 serviceservice再交给 controller
  • controller 根据需求将 ModelView 组合起来生成页面,转发给前端浏览器

这样做的好处就是 controller 可以处理多个请求,并对请求进行分发,执行不同的业务操作。

随着互联网的发展,上面的模式因为是同步调用,性能慢慢的跟不需求,所以异步调用 成为了如今流行的一种处理方式,如下图所示

image-20221116190114933
  • 因为是异步调用,所以后端不需要返回 View 视图,将其去除
  • 前端如果通过异步调用的方式进行交互,后端就需要将返回的数据转换成 JSON 格式进行返回

特点

  1. Spring 家族原生产品,与 IOC 容器无缝对接

  2. 基于原生 Servlet,通过了功能强大的前端控制器 DispatcherServlet 来进行请求的统一管理。即,servlet 封装为 DispatcherServlet

    • 我们回顾一下之前学习的 Servlet知识回顾:Servlet

      我们可以看到,每实现一个 HttpServlet 的过程中,需要重写 doGet()doPost() 等方法。而这些方法的实现是大量且重复的。我们便可以使用 DispatcherServlet 来简化开发。

  3. 代码清新简洁;内部组件化程度高、可插拔式组件即插即用,想要什么功能配置相应组件即可。

  4. 性能高,适合大型互联网项目需求。

2 SpringMVC 入门案例

2.1 准备工作

构建 maven 项目

  • 设置打包方式为 war

    1
    <packaging>war</packaging>
  • Project Structure 中配置 web.xml

    1
    ${project_path}/src/main/webapp/WEB-INF/web.xml
  • 导入 maven 坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <dependencies>
    <!-- SpringMVC 依赖了Spring的核心包-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.1</version>
    </dependency>

    <!-- servlet API-->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <!-- 此处为 provider -->
    <scope>provided</scope>
    </dependency>
    </dependencies>

2.2 配置 Spring 核心设置

.xml 配置文件

SpringMVC 的配置文件默认的位置和名称:

  • 位置:${project_path}/src/main/webapp/WEB-INF/ 目录下
  • 名称:<servlet-name>-servlet.xml,在本例子中即为 SpringMVC-servlet.xml
1
2
3
4
<!-- 自动扫描包 -->
<context:component-scan base-package="fr.gdai.springmvc.controller"/>
<!-- 可以配置视图解析器,如Thymeleaf -->
<!-- 可以配置其他可使用的插件 -->

注解方案

1
2
3
4
5
6
package fr.gdai.springmvc.config;

@Configuration
@ComponentScan("fr.gdai.springmvc.controller")
public class SpringMvcConfig {
}

2.3 tomcat 服务器加载配置

我们之前说过,SpringMVC 的本质是 servlet,通过将 servlet 封装成 DispatcherServlet,从而对浏览器的请求统一管理

web.xml 配置文件

我们需要在 web.xml 中配置 DispatcherServlet。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--    配置 SpringMVC 的前端控制器 DispatcherServlet -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<!--
/ : 匹配所有的请求,但是不能包括.jsp结尾的请求。(推荐使用)
在tomcat中 .jsp 会交给 JSP servlet 进行处理。
且需 springmvc 开启静态资源处理, 才能访问静态资源
/* : 匹配到所有的请求(包括 .jsp)
*.do : 后缀匹配
-->
<url-pattern>/</url-pattern>
</servlet-mapping>

在配置 SpringMVC 的前端控制器 DispatcherServlet 时,<url-pattern>//* 的区别:

  • /:匹配浏览器向服务器所有的请求(不包括 .jsp

    优点:

    • 静态资源不会经过 springmvc,不用额外开启静态资源配置

    • 可以实现伪静态的效果,比如 *.html

      作用 1 : 给黑客入侵增加难度.

      作用 2 : 有利于 SEO 的优化(排名更靠前)

  • /*:匹配浏览器向服务器所有的请求(包括 .jsp

注解方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package fr.gdai.springmvc.config;

// 定义一个servlet容器启动的配置类,并在其中加载Spring的配置
public class DispatcherServletUtil extends AbstractDispatcherServletInitializer {

// 加载SpringMVC容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext webCtx =
new AnnotationConfigWebApplicationContext();
// 注册SpringMVC的配置
webCtx.register(SpringMvcConfig.class);
return webCtx;
}

// 设置需要SpringMVC处理的请求,同配置文件中的<url-pattern>
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

// 加载普通Spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}

根据以上的配置,我们给出一种更加简洁的配置方式。其实际上是继承 AbstractDispatcherServletInitializer 的子类 AbstractAnnotationConfigDispatcherServletInitializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package fr.gdai.springmvc.config;

public class DispatcherServletUtil extends
AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}

2.4 完成 controller 及测试

我们继续编写 controller 包下的代码以完成测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package fr.gdai.springmvc.controller;

// 2. 定义controller
// 2.1 使用@Controller注解定义Bean
@Controller
public class UserController {
// 2.2 设置当前操作的访问路径,
// 例如浏览器访问 http://localhost:8080/save, 即可访问该方法
@RequestMapping("/save")
// 2.3 设置当前操作的返回值类型, 将该方法的返回值作为响应体返回给浏览器
@ResponseBody
public String save() {
System.out.println("user saved!");
return "{'module':'springmvc:save()'}";
}

@RequestMapping("/delect")
@ResponseBody
public String delect() {
System.out.println("user delected!");
return "{'module':'springmvc:delect()'}";
}

}

我们可以启动 tomcat 服务器来测试这个save() 方法;也可以在 maven 中设置 tomcat 的插件:

  • maven 的配置文 pom.xml 中设置 tomcat 的插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.1</version>
    <configuration>
    <port>8081</port>
    <path>/springmvc</path>
    </configuration>
    </plugin>
    </plugins>
    </build>
  • 然后在 IDEA 中新建一个 Run/Debug Configuration

    1
    2
    Name : xxx
    Run : tomcat7:run
  • 启动服务器。在浏览器中输入

    1
    http://localhost:8081/springmvc/save

    可以观察到页面显示 {'module':'springmvc:save'} 且控制台输出 user saved!

3 入门案例工作流程

3.1 启动服务器初始化过程

image-20221223220537622
  • 服务器启动,执行 DispatcherServletUtil 类,初始化 web 容器
  • 执行createServletApplicationContext()方法,创建了 webApplicationContext 对象
  • 加载 SpringMvcConfig.class
  • 执行 @ComponentScan 加载对应的 Bean
  • 加载 UserController,每个 @RequestMapping("/xxx") 的名称对应一个具体的方法,由 SpringMVC 统一管理,并不是由每个 Bean 单独管理。
  • 执行 getServletMappings() 方法,定义所有的请求都通过 SpringMVC

3.2 单次请求过程

image-20221223221730959
  • 发送请求 http://localhost:8081/springmvc/save
  • web 容器发现所有请求都经过 SpringMVC,将请求交给 SpringMVC` 处理
  • 解析请求路径 /save
  • /save 匹配执行对应的方法 save()
  • 执行 save()
  • 检测到有 @ResponseBody 直接将 save() 方法的返回值作为响应体返回给请求方

4 指定包扫描范围

在一个完整的项目中,我们考虑如下的项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|-- fr.gdai
|-- config
|-- SpringConfig.java
|-- JDBCDataSource.java
|-- MyBatisConfig.java
|-- SpringMvcConfig.java
|-- DispatcherServletUtil.java
|-- domain
|-- User.java
|-- Book.java
|-- dao
|-- UserDao.java
|-- BookDao.java
|-- service
|-- UserService.java
|-- BookService.java
|-- controller
|-- UserController.java
|-- BookController.java

我们可以很容易的发现,在我们的项目中有几种不同种类的 Bean

  • Spring 维护的 Bean 对象:
    • 业务 Beanservice
    • 功能 BeanJDBCDataSource
  • SpringMVC 维护的 Bean 对象:
    • 表现层 Beancontroller

我们在包扫描 @ComponentScan() 时需要区分不同的作用域:

SpringConfig.java

1
2
3
4
5
6
7
8
9
@Configuration
@ComponentScan({"fr.gdai.springmvc.dao", "fr.gdai.springmvc.service"})
// 可以使用如上分别扫描的方式,也可以采用如下过滤的方式
@ComponentScan(value = "fr.gdai.springmvc",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class))
public class SpringConfig {
}

SpringMvcConfig.java

1
2
3
4
@Configuration
@ComponentScan("fr.gdai.springmvc.controller")
public class SpringMvcConfig {
}

5 静态资源的“放行”

在之前的 SpringMVCDispatcherServlet 的设置中,我们定义了需要 SpringMVC 处理的请求路径:

1
2
3
4
5
// 设置需要SpringMVC处理的请求,同配置文件中的<url-pattern>
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

那么在这个路径下的所有请求都会被 SpringMVC 所拦截。当用户想访问一个静态资源,如 html 页面、图片等,该请求也会被 SpringMVC 拦截,并尝试将其处理,而此时就会出现问题。

例如,我们使用 GET 方式请求静态资源 index.html

1
http://localhost:8081/pages/index.html

SpringMVC 会拦截该请求,并匹配 DispatcherServlet 所管理的 Controller 中是否有满足 /pages/index.htmlGET 请求的处理。从而与我们的要求不符,出现错误。此时就需要 SpringMVC “放行”该请求,使其交给 tomcat 服务器直接提供该静态资源。

SpringMVC 框架提供了一个 WebMvcConfigurationSupport 的类来解决该问题,我们需要创建一个类来继承它,通过重写 addResourceHandlers() 方法来实现:

1
2
3
4
5
6
7
8
9
10
11
12
package fr.gdai.springmvc.config;

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
}
}

然后将这个 @Configuration 添加到 SpringMvcConfig.java 的扫描范围内

1
2
3
4
5
6
7
package fr.gdai.springmvc.config;

@Configuration
@ComponentScan({"fr.gdai.springmvc.controller", "fr.gdai.springmvc.config"})
@EnableWebMvc
public class SpringMvcConfig {
}

二、请求与响应

1 请求

请求的映射路径

我们考虑如下的项目结构:

1
2
3
4
5
|-- fr.gdai
|-- ...
|-- controller
|-- UserController.java
|-- BookController.java

UserController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class UserController {

@Autowired
private UserServiceImpl userService;

@RequestMapping("/save")
@ResponseBody
public String userSave() {
System.out.println("user saved!");
return "{'module':'springmvc:userSave()'}";
}
}

BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class BookController {

@Autowired
private BookServiceImpl bookService;

@RequestMapping("/save")
@ResponseBody
public String bookSave() {
System.out.println("book saved!");
return "{'module':'springmvc:bookSave()'}";
}
}

在两个 @Controller 中,我们设置了两个相同的请求路径映射 @RequestMapping("/save")。这种情况是无法成功初始化 servlet 的,错误信息:

1
Initialization of bean failed; nested exception is java.lang.IllegalStateException: Cannot map handler 'userController' to URL path [/save]: There is already handler of type [class fr.gdai.springmvc.controller.BookController] mapped.

我们可以完善请求路径映射来解决这个问题:

1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {

@RequestMapping("/user/save")
@ResponseBody
public String userSave() {
System.out.println("user saved!");
return "{'module':'springmvc:userSave()'}";
}
}
1
2
3
4
5
6
7
8
9
10
@Controller
public class BookController {

@RequestMapping("/book/save")
@ResponseBody
public String bookSave() {
System.out.println("book saved!");
return "{'module':'springmvc:bookSave()'}";
}
}

或者使用请求路径的前缀:

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/user")
public class UserController {

@RequestMapping("/save")
@ResponseBody
public String userSave() {
System.out.println("user saved!");
return "{'module':'springmvc:userSave()'}";
}
}

请求映射路径

  • 名称:@RequestMapping(value = "PATH/PREFIX")
  • 即可用于 Controller方法的注解,设置请求的访问路径
  • 也可用于 Controller 类的注解,为这个类设置统一的请求访问路径前缀

请求方式

Get 请求

我们知道,使用 Get 请求的参数 param1param2 会在 url 中体现出来。如下所示

1
http://localhost:8081/xxx/xxx?param1=value1&param2=value2

image-20221225170434095

所以普通基本类型的参数 param1param2 都可以按照如下形式,被服务器端的方法接收:

1
2
3
@RequestMapping("/xxx/xxx")
@ResponseBody
public void xxx(Type param1, Type param2) { /* TODO */ }

Post 请求

对于 Post 请求,我们需要注意:与 servletdoGet()doPost() 方法不同,SpringMVC不区分这两种方法,即对于程序员来讲后端代码是没有区别的

Post 请求中,所有的请求参数都在请求体RequestBody)中,并不会表现在 url 中。在 PostMan 中可以通过如下方式模拟发送 Post 请求:

image-2022-12-25 17.08.17

服务器端的方法依然是

1
2
3
@RequestMapping("/xxx/xxx")
@ResponseBody
public void xxx(Type param1, Type param2) { /* TODO */ }

关于中文乱码问题

p.s.:根据网友反馈,Tomcat ver.8 以上的版本中,Get 请求不存在中文乱码问题,Post 请求中的中文乱码。

  • 对于 Get 请求的中文乱码问题:因为 Get 请求参数是通过 url 传递的,所以我们需要设置 tomcat 服务器的 urlEncoding 属性为 utf-8。在 Maven 集成的 tomcat 插件中,我们需要在 pom.xml 文件中关于 tomcat 的设置中添加如下内容:

    1
    2
    3
    <configuration>
    <uriEncoding>utf-8</uriEncoding>
    </configuration>
  • 对于 Post 请求的中文乱码问题:我们可以在 servlet 中的 Filter 过滤器中设置编码类型。在 SpringMVC 中,我们依然可以在 DispatcherServlet 的配置中通过设置 Filter 的方法解决:

1
2
3
4
5
6
7
8
9
// 乱码处理
@Override
protected Filter[] getServletFilters() {
// 使用 SpringMVC 提供的字符过滤器
CharacterEncodingFilter filter = new CharacterEncodingFilter();
// 设置字符过滤器的编码类型为 utf-8
filter.setEncoding("utf-8");
return new Filter[]{filter};
}

请求的参数

基本数据参数

  • url 地址传递参数,url 地址参数名与形参变量名相同,定义形参即可接收参数。
1
2
3
4
5
6
7
@RequestMapping("/addUser")
@ResponseBody
public String addUser(String name, int age) {
System.out.println("普通参数传递 : addUser ---> name = " + name);
System.out.println("普通参数传递 : addUser ---> age = " + age);
return "{'module':'springmvc:addUser()'}";
}

image-20221225162747864

1
2
普通参数传递 : addUser ---> name = gdai
普通参数传递 : addUser ---> age = 24
  • url 地址传递参数。若 url 地址参数名与形参变量名不相同,可使用 @RequestParam() 注解定义形参即可接收参数。
1
2
3
4
5
6
7
8
@RequestMapping("/addUserDiffParamName")
@ResponseBody
public String addUserDiffParamName(@RequestParam("name") String userName,
@RequestParam("age") int userAge) {
System.out.println("普通参数(不同参数名)传递 : addUser ---> name = " + userName);
System.out.println("普通参数(不同参数名)传递 : addUser ---> age = " + userAge);
return "{'module':'springmvc:addUser()'}";
}

image-20221225163722242

1
2
普通参数(不同参数名)传递 : addUser ---> name = gdai
普通参数(不同参数名)传递 : addUser ---> age = 24
  • @RequestParam 作为形参注解,用于绑定请求参数与方法内形参间的关系
    • 其中,required 参数:是否为必传参数
    • defaultValue 参数:参数默认值

日期类型参数

在日常编程中,不同的系统中的日期类型数据也不尽相同:

  • 1998-01-31
  • 1998/01/31
  • 31/01/1998,等

SpringMVC 中,默认的日期格式为 yyyy/MM/dd,可以自动的将字符串 yyyy/MM/dd 转化成 Date 类型的数据。如下所示

1
2
3
4
5
6
7
    @RequestMapping("/dateParam")
@ResponseBody
public String dateParam(Date date) {
System.out.println("日期类型集合参数传递 ---> " + date);
return "{'module':'springmvc:dateParam()'}";
}
}

image-20221226151009465

1
日期类型集合参数传递 ---> Sat Jan 31 00:00:00 CET 1998

我们也可以更改为其他的格式,则需要在 Date 形参前使用 @DateTimeFormat(pattern="") 注解指定日期格式。如下所示:

1
2
3
4
5
6
@RequestMapping("/dateParam")
@ResponseBody
public String dateParam(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date) {
System.out.println("日期类型集合参数传递 ---> " + date);
return "{'module':'springmvc:dateParam()'}";
}

image-20221226151718448

1
日期类型集合参数传递 ---> Sat Jan 31 01:59:59 CET 1998

实体类参数类型

更多情况下,我们使用实体类的参数作为形参传入方法中。在 SpringMVC 框架中,可以自动地将请求内容中的类的属性创建成一个实体类。如下所示:

1
2
3
4
5
6
@RequestMapping("/addBookEntity")
@ResponseBody
public String addBookEntity(Book book) {
System.out.println("实体类型参数传递 ---> " + book);
return "{'module':'springmvc:addBookEntity()'}";
}

image-20221226102959351

1
实体类型参数传递 ---> Book{bookId=null, bookName='test', price=100, stock=100}

如果在实体类中引用了其他类,在请求参数中可以使用 Xxx.xxx 对这个引用类进行创建并赋值,如下

image-20221226104206875

1
实体类型参数传递 ---> Book{bookId=null, bookName='test', author=Person{name='gdai', birthday='1998-01-01'}}

数组类型参数

对于数组类型的接收,我们可以使用相同的参数名来实现。如下所示

1
2
3
4
5
6
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] args){
System.out.println("数组类型参数传递 ---> " + Arrays.toString(args));
return "{'module':'springmvc:arrayParam()'}";
}

image-20221226105223069

1
数组类型参数传递 ---> [val1, val2, val3]

集合类型参数

在集合类型(SetListMap 等类型)中,不能直接像数组类型那样直接使用相同的参数名来实现。因为我们如果不使用 @RequestParam 指定参数,SpringMVC 会将 List 视为一个没有实现类的接口,从而报错 Failed to instantiate [java.util.List]: Specified class is an interface

所以在使用集合类型参数时,我们需要使用 @RequestParam 指定参数

1
2
3
4
5
6
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> args){
System.out.println("集合类型参数传递 ---> " + args);
return "{'module':'springmvc:listParam()'}";
}

image-20221226110724480

1
集合类型参数传递 ---> [val1, val2, val3]

JSON 集合类型参数

JSON 是一种轻量级的数据交换格式,易于人阅读和编写,可以在多种语言之间进行数据交换。JSON 遵循一定的语法词法结构来格式化数据,在数据的接收段也应该有对应的解释器来将这种格式化的数据转化成程序内的属性。所以

(1)我们首先需要在 Maven 中导入一个反序列化 JSON 的工具 jackson-databind

1
2
3
4
5
6
<!--        JSON 系列化/反序列化工具-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.2</version>
</dependency>

(2)在 SpringMVC 中开启自动地将 JSON 转化成 Java 的类属性:在 SpringMVCConfig.java添加 @EnableWebMvc 注解

注意 ⚠️:@EnableWebMvc 的功能有许多,将在后面的内容中介绍。

1
2
3
4
5
6
7
package fr.gdai.springmvc.config;

@Configuration
@ComponentScan("fr.gdai.springmvc.controller")
@EnableWebMvc
public class SpringMvcConfig {
}

(3)在后端相关代码的形参前添加 @RequestBody 注解来表明请求的参数是由请求体携带的

1
2
3
4
5
6
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> args){
System.out.println("JSON类型参数传递 ---> " + args);
return "{'module':'listParamForJson()'}";
}

(4)在 PostMan 中使用 JSON 格式传输请求数据:POST -> Body -> raw -> JSON

image-20221226142819972

(5)测试结果

1
JSON类型参数传递 ---> [test1, test2, test3]

JSON 实体类型参数

对于 JSON 的实体类型参数,后端的相关代码与表单的形式类似:

1
2
3
4
5
6
@RequestMapping("/entityParamForJson")
@ResponseBody
public String entityParamForJson(@RequestBody Book book) {
System.out.println("JSON实体类型参数传递 ---> " + book);
return "{'module':'springmvc:entityParamForJson()'}";
}

image-20221226143856662

1
JSON实体类型参数传递 ---> Book{bookId=null, bookName='testForJSON', price=200, stock=200}

相同的,如果在实体类中引用了其他类,也可以在 JSON 格式中使用嵌套的形式赋值:

image-20221226144222485

1
JSON实体类型参数传递 ---> Book{bookId=null, bookName='test', author=Person{name='gdai', birthday='1998-01-01'}}

JSON 实体集合类型参数

对于 JSON 的实体集合类型参数(List<Book> books),后端的相关代码与表单的形式类似:

1
2
3
4
5
6
@RequestMapping("/entityListParamForJson")
@ResponseBody
public String entityListParamForJson(@RequestBody List<Book> books) {
System.out.println("JSON实体类型集合参数传递 ---> " + books);
return "{'module':'springmvc:entityListParamForJson()'}";
}

image-20221226145146611

1
2
3
4
JSON实体类型集合参数传递 ---> 
[Book{bookId=null, bookName='test01', price=100, stock=100},
Book{bookId=null, bookName='test02', price=200, stock=200},
Book{bookId=null, bookName='test03', price=300, stock=300}]

2 响应

所谓“响应”,就是将用户的请求经过处理后的结构反馈给用户。响应大题分为两种类型:响应页面响应数据

响应页面

我们在之前的请求部分中使用了 @ResponseBody 来将函数返回的内容作为响应体给用户浏览器。当我们想响应某个页面时,我们不需要使用该注解,而是将响应页面的相对 webapp 的地址作为字符串返回。如下所示:

文件系统结构

1
2
3
4
5
6
7
8
9
10
|-- ...
|-- java
|-- fr.gdai
|-- ...
|-- controller
|-- UserController.java
|-- BookController.java
|-- webapp
|-- WEB-INF
|-- index.jsp
1
2
3
4
5
6
7
8
9
10
@Controller
@RequestMapping("/book")
public class BookController {

@RequestMapping("/jumpToIndex")
public String jumpToIndex() {
System.out.println("跳转到index");
return "/index.jsp";
}
}

此时我们在浏览器中输入

1
http://localhost:8081/springmvc/book/jumpToIndex

即可跳转到 index.jsp 页面

Title Hello SpringMVC

响应数据

响应基本数据

对于响应数据来讲,我们需要将返回的数据最为响应题反馈给用户浏览器,所以我们需要 @ResponseBody 注解。下面以字符串为例,其他基本数据类型同理:

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/book")
public class BookController {

@RequestMapping("/toText")
@ResponseBody
public String toText() {
System.out.println("返回纯文本信息");
return "response text";
}
}

此时我们在浏览器中输入

1
http://localhost:8081/springmvc/book/toText

即可得到响应的数据:

Title response text

响应数组/集合类型

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/book")
public class BookController {

@RequestMapping("/toArray")
@ResponseBody
public int[] toText() {
System.out.println("返回数组信息");
return new int[]{1,2,3};
}
}

此时我们在浏览器中输入

1
http://localhost:8081/springmvc/book/toArray

即可得到响应的数据:

Title [1,2,3]

响应实体类型数据

如果我们想响应一个实体类对象,只需要返回该实体类对象,并将其作为响应体 @ResponseBody 即可。SpringMVC 框架与 jackson-databind 可以自动将实体类对象的属性数据转化为 JSON 格式,如下所示:

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/book")
public class BookController {

@RequestMapping("/toEntity")
@ResponseBody
public Book toEntity() {
System.out.println("返回实体类信息");
return new Book(null, "responseJSON", 100, 100);
}
}

此时我们在 PostManGet 请求中输入

1
http://localhost:8081/springmvc/book/toEntity

即可得到响应的数据:

1
2
3
4
5
6
{
"bookId": null,
"bookName": "responseJSON",
"price": 100,
"stock": 100
}

响应实体组数/集合类型数据

该例子我们结合了 MySQLMyBatisSpringSpringMVC 框架对数据库进行查询,并响应查询结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
@RequestMapping("/book")
public class BookController {

@Autowired
private BookServiceImpl bookService;

@RequestMapping("/toEntityList")
@ResponseBody
public List<Book> showAllBook() {
List<Book> books = bookService.showAllBooks();
System.out.println("返回实体集合信息");
return books;
}
}

此时我们在 PostManGet 请求中输入

1
http://localhost:8081/springmvc/book/toEntityList

即可得到响应的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"bookId": 1,
"bookName": "L_etranger",
"price": 100,
"stock": 100
},
{
"bookId": 2,
"bookName": "Nausea",
"price": 100,
"stock": 200
}
]

3 数据的封装

通过前面的学习,我们可以看到:在响应请求时,响应数据分为很多类型:基本数据类型、数组、JSON 等,前端人员在拿到这些信息时需要对于这些信息的类型再做处理,显然这样又增加了前后端的耦合度。此时我们希望通过封装响应体中的响应数据,使得数据结构具有通用性。

我们创建一个结果类 Result,其中声明若干属性:(该类的属性并不是固定的,根据需要增减)

  • <T> data:用来存放请求处理后的结果
  • Integer code:用来描述操作的种类
  • String msg:用来存放返回到前端的消息
1
2
3
4
5
6
7
8
9
package fr.gdai.springmvc.controller;

public class Result<T> {
private Integer code;
private T data;
private String msg;

// 省略setter, getter和constructor
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package fr.gdai.springmvc.controller;

public class ResultCode {
public static final Integer INSERT_SUCC = 20011;
public static final Integer DELETE_SUCC = 20021;
public static final Integer UPDATE_SUCC = 20031;
public static final Integer SELECT_SUCC = 20041;

public static final Integer INSERT_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer SELECT_ERR = 20040;
}

最后,将我们所有的响应数据都通过 Result 类反馈给前端浏览器。我们以一个“结合了 MySQLMyBatisSpringSpringMVC 框架对数据库进行查询,并响应查询结果”的例子作为演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Controller
@RequestMapping("/book")
public class BookController {

@Autowired
private BookServiceImpl bookService;

@RequestMapping("/toEntityList")
@ResponseBody
public Result showAllBook() {
Integer code;
String message;

List<Book> books = bookService.showAllBooks();
if (books == null){
code = ResultCode.SELECT_ERR;
message = "查询结果为空,请重试";
} else {
code = ResultCode.SELECT_SUCC;
message = "查询成功";
}
System.out.println("返回实体集合信息");
return new Result(code, books, message);
}
}

此时我们在 PostManGet 请求中输入

1
http://localhost:8081/springmvc/book/toEntityList

即可得到响应的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"code": 20041,
"data": [
{
"bookId": 1,
"bookName": "L_etranger",
"price": 100,
"stock": 100
},
{
"bookId": 2,
"bookName": "Nausea",
"price": 100,
"stock": 200
}
],
"msg": "查询成功"
}

三、REST

1 REST 简介

REST (Representation State Transfer),表现形式状态转换。通俗来讲就是:资源在网络中以某种表现形式进行状态转移。分解开来:

  • Resource:资源,即数据(前面说过网络的核心)。比如 newsfeed,friends等;
  • Representational:某种表现形式,比如用 JSON,XML,JPEG等;
  • State Transfer:状态变化。通过HTTP动词实现。

传统风格的资源描述形式:

1
2
http://localhost:8081/user/getById?id=1
http://localhost:8081/user/saveUser

REST 风格的资源描述形式(RESTful):

1
2
http://localhost:8081/user/1
http://localhost:8081/user

优点:

  • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
  • 书写简化

我们通过 REST 风格来描述资源,使得无法通过地址得知对资源是何种操作,那么我们又该如何确定对于资源的操作呢?

一般来说,几乎所有的主流网络协议都有两个部分,一个是协议头,一个是协议体。协议头中是协议自己要用的数据,协议体才是用户的数据。所以,协议头主要是用于协议的控制逻辑,而协议体则是业务逻辑。HTTP 的动词(或是 Method)是在协议头中,所以,其主要用于控制逻辑。

下面是 HTTP 的动词规范:常用的为 GETPUTDELETEPOST

方法 描述 幂等
GET 用于查询操作,对应于数据库的 select 操作 ✔︎
PUT 用于所有的信息更新,对应于数据库的 update操作 ✔︎︎
DELETE 用于更新操作,对应于数据库的 delete 操作 ✔︎︎
POST 用于新增操作,对应于数据库的 insert 操作
HEAD 用于返回一个资源对象的“元数据”,或是用于探测 API 是否健康 ✔︎
PATCH 用于局部信息的更新,对应于数据库的 update 操作
OPTIONS 获取 API 的相关的信息。 ✔︎

注意 ⚠️:

  • 描述模块的名称通常使用复数,也就是使用复数的形式表示此类资源,而非单个资源。例如 usersbooks

2 RESTful 风格的改写

我们按照之前介绍的传统风格的资源描述形式重新写一个 Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package fr.gdai.springmvc.controller;

@Controller
public class TestController {

@RequestMapping("/test/select")
@ResponseBody
public String select(int id){
System.out.println("test:select id=" + id);
return "{'module':'test_select()'}";
}

@RequestMapping("/test/update")
@ResponseBody
public String update(@RequestBody User user){
System.out.println("test:update " + user);
return "{'module':'test_update()'}";
}

@RequestMapping("/test/delete")
@ResponseBody
public String delete(int id){
System.out.println("test:delete id=" + id);
return "{'module':'test_delete()'}";
}

@RequestMapping("/test/insert")
@ResponseBody
public String insert(@RequestBody User user){
System.out.println("test:insert " + user);
return "{'module':'test_insert()'}";
}
}

现在我们就开始着手在我们已经掌握的传统风格的基础上,将其改写成 REST 风格:

  1. 关于 select(int id) 方法的改写

    • 首先我们@RequestMapping() 内的 value改为 "/users"

    • 然后需要指定 @RequestMapping() 内的 method,从而设置它的请求行为(HTTP 动作)

    • 对于请求体内传递的参数,与之前一样,我们任然使用 @RequestBody 来指定相关的参数

    • 对于非请求体内传递的参数,如 select() 方法中的参数 id,与之前不同的是,参数的传递使用的是路径的方式,即

      1
      xxx/tests/${id}

      所以,我们需要在方法的形参前使用 @PathVariable 注解,然后在请求路径中使用 {id} 与接收数据的形参

    1
    2
    3
    4
    5
    6
    @RequestMapping(value="/tests", method=RequestMethod.POST)
    @ResponseBody
    public String select(int id){
    System.out.println("test:select id=" + id);
    return "{'module':'test_select()'}";
    }
  2. 将其余方法都按照上述步骤更改为其对应的请求行为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package fr.gdai.springmvc.controller;

    @Controller
    public class TestController {

    @RequestMapping(value = "/tests/{id}", method = RequestMethod.GET)
    @ResponseBody
    public String select(@PathVariable int id){
    System.out.println("test:select id=" + id);
    return "{'module':'test_select()'}";
    }

    @RequestMapping(value = "/tests", method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user){
    System.out.println("test:update " + user);
    return "{'module':'test_update()'}";
    }

    @RequestMapping(value = "/tests/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable int id){
    System.out.println("test:delete id=" + id);
    return "{'module':'test_delete()'}";
    }

    @RequestMapping(value = "/tests", method = RequestMethod.POST)
    @ResponseBody
    public String insert(@RequestBody User user){
    System.out.println("test:insert " + user);
    return "{'module':'test_insert()'}";
    }
    }

@RequestBody@RequestParam@PathVariable

区别

  1. @RequestBody 用于接收请求体内的数据,通常是 JSON 数据;
  2. @RequestParam 用于接收 url 地址传参表单传参
  3. @PathVariable 用于接收路径参数使用 {参数名称} 描述路径参数

应用

  • 后期开发中,发送请求参数超过一个时,以 JSON 格式为主,@RequestBody 应用较广;
  • 如果发送JSON 格式数据,选用 @RequestParam 接收请求参数;
  • 采用 RESTful 进行开发,当发送请求参数较少时(例如一个),可以采用 @PathVariable 接收请求路径参数,常用于传递 id 值。当发送请求参数超过一个时,以 JSON 格式为主。

3 简化 RESTful 风格

我们之前已经成功的将传统风格的请求方式改写成了 RESTful 风格,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package fr.gdai.springmvc.controller;

@Controller
public class TestController {

@RequestMapping(value = "/tests/{id}", method = RequestMethod.GET)
@ResponseBody
public String select(@PathVariable int id){
System.out.println("test:select id=" + id);
return "{'module':'test_select()'}";
}

@RequestMapping(value = "/tests", method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody User user){
System.out.println("test:update " + user);
return "{'module':'test_update()'}";
}

@RequestMapping(value = "/tests/{id}", method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable int id){
System.out.println("test:delete id=" + id);
return "{'module':'test_delete()'}";
}

@RequestMapping(value = "/tests", method = RequestMethod.POST)
@ResponseBody
public String insert(@RequestBody User user){
System.out.println("test:insert " + user);
return "{'module':'test_insert()'}";
}
}

但是我们发现,

  1. 对于每个方法,都需要分别配置 @RequestMapping 的属性,且 value 的前缀都是 /tests,甚是麻烦。所以我们可以将其简化,在整个 Controller 类前,使用 @RequestMapping("/tests") 简化。

  2. 对于每个方法,都使用了响应体 @ResponseBody 来反馈用户浏览器,所以我们也将其置于整个 Controller 类前。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package fr.gdai.springmvc.controller;

    @Controller
    @RequestMapping("/tests")
    @ResponseBody
    public class TestController {

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String select(@PathVariable int id){...}

    @RequestMapping(method = RequestMethod.PUT)
    public String update(@RequestBody User user){...}

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable int id){...}

    @RequestMapping(method = RequestMethod.POST)
    public String insert(@RequestBody User user){...}
    }

    可也以使用 @RestController 注解进一步简化 @Controller + @ResponseBody

  3. 至此,我们的业务代码已经简化到只剩 @RequestMapping(..., method) 注解了。我们任然可以将 @RequestMapping(..., method) 简化成 @MethodMapping,即 @GetMapping@PutMapping@DeleteMapping@PostMapping

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package fr.gdai.springmvc.controller;

    @RestController
    @RequestMapping("/tests")
    public class TestController {

    @GetMapping("/{id}")
    public String select(@PathVariable int id){...}

    @PutMapping
    public String update(@RequestBody User user){...}

    @DeleteMapping("/{id}")
    public String delete(@PathVariable int id){...}

    @PostMapping
    public String insert(@RequestBody User user){...}
    }

四、拦截器

image-20221227223907933

TODO

五、SSM 整合

1 文件体系

在本章节,我们不以实际的项目代码作为展示,只是将配置过程显示出来。首先我们先给出一个项目的基本文件体系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|-- pom.xml
>-- target
v-- src
>-- test
v-- main
v-- java
v-- fr.gdai.PORJECT_NAME
v-- config
|-- SpringConfig.java
|-- SpringJdbcConfig.java
|-- SpringMvcConfig.java
|-- SpringMvcSupport.java
|-- ServletConfig.java
|-- MyBatisConfig.java
|-- DataSourceConfig.java
>-- domain
>-- dao (接口, 关联MyBatis的Mapping文件)
v-- service
|-- XxxService.java
>-- implement
>-- controller
>-- interceptor
v-- resources
|-- jdbc.properties
v-- fr/gdai/springmvc/dao
|-- XxxDao.xml (MyBatis的Mapping文件)
v-- webapp
>-- WEB-INF
>-- pages
>-- images
>-- css
>-- js
|-- index.jsp

2 Maven 构建项目

我们使用 Maven 构建项目,在 pom.xml 文件中将打包方式更改为 war

1
<packaging>war</packaging>

添加依赖坐标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<dependencies>

<!-- ****************** Spring 与 SpringMVC 相关 ****************** -->
<!-- SpringMVC: 依赖了 Spring 的核心依赖包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>

<!-- SpringMVC: 需要使用到 servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

<!-- JSON 系列化/反序列化工具-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.2</version>
</dependency>

<!-- ****************** MyBatis 与 数据库连接相关 ****************** -->
<!-- Mybatis 核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

<!-- Spring 整合 MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>

<!-- C3P0 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>

<!-- Spring 整合 JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>

<!-- ****************** JUnit 与测试相关 ****************** -->
<!-- Junit 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<!-- Spring 整合 Junit -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>

</dependencies>

我们既可以使用单独的 tomcat 服务器,也可以使用 Maven 整合的 tomcat 服务器插件,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
<plugins>
<!-- tomcat 插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>端口号</port>
<path>/项目根目录路径</path>
<uriEncoding>utf-8</uriEncoding>
</configuration>
</plugin>
</plugins>
</build>

3 MyBatis 与数据库连接

因为本章节不考虑实际项目的数据,所以在此我们不提供数据库建表语句。但是我在下方给出一个数据库快速建表的在线工具,作者:程序员鱼皮

关于 MyBatis 的相关知识,参考以下链接:

下面我们开始设置数据库的连接:

jdbc.properties

我们使用 .properties 文件来配置 JDBC 连接信息:

${PROJECT_ROOT}/src/main/resources/jdbc.properties

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/数据库名
jdbc.username=root
jdbc.password=root

DataSourceConfig.java

fr.gdai.PROJECT_NAME.config.DataSourceConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package fr.gdai.ssm.config;

@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {

@Value("${jdbc.driver}")
private String driver;

@Value("${jdbc.url}")
private String url;

@Value("${jdbc.username}")
private String userName;

@Value("${jdbc.password}")
private String password;

@Bean
public DataSource getDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(userName);
dataSource.setPassword(password);
return dataSource;
}
}

MyBatisConfig.java

fr.gdai.PROJECT_NAME.config.MyBatisConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package fr.gdai.ssm.config;

public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean =
new SqlSessionFactoryBean();
// 设置使用别名的包
sqlSessionFactoryBean.setTypeAliasesPackage("fr.gdai.springmvc.domain");
// 为SqlSessionFactory 设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}

@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer =
new MapperScannerConfigurer();
// 设置自动扫描的包
mapperScannerConfigurer.setBasePackage("fr.gdai.springmvc.dao");
return mapperScannerConfigurer;
}

}

XxxDao.java

``fr.gdai.PROJECT_NAME.dao.XxxDao.java`

1
2
3
4
package fr.gdai.springmvc.dao;

@Repository
public interface XxxDao {...}

XxxDao.xml

注意 ⚠️:

要与 XxxDao.java 接口处于同一包下(即 XxxDao 接口的全类名和映射文件的命名空间 namespace 保持一致)

${PROJECT_ROOT}/src/main/resources/fr/gdai/PORJECT_NAME/dao/XxxDao.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fr.gdai.PORJECT_NAME.dao.XxxDao">
<select id="methodOfInterface" resultType="TYPE">
selece * from xxx
</select>
...
</mapper>

4 SpringSpringMVC 配置

SpringConfig.java

我们统一将所有的配置都放在 config 包下:

fr.gdai.PORJECT_NAME.config.SpringConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
package fr.gdai.ssm.config;

@Configuration
@Import({DataSourceConfig.class, SpringJdbcConfig.class, MyBatisConfig.class})
// @ComponentScan({"fr.gdai.ssm.dao", "fr.gdai.ssm.service"})
// 可以使用如上分别扫描的方式,也可以采用如下过滤的方式
@ComponentScan(value = "fr.gdai.ssm",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class))
public class SpringConfig {
}

SpringJdbcConfig.java

fr.gdai.PORJECT_NAME.config.SpringJdbcConfig.java

1
2
3
4
5
6
7
8
9
10
import org.springframework.jdbc.core.JdbcTemplate;

public class SpringJdbcConfig {
@Bean
public static JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}

SpringMvcConfig.java

fr.gdai.PORJECT_NAME.config.SpringMvcConfig.java

1
2
3
4
5
6
7
8
package fr.gdai.ssm.config;

@Configuration
@ComponentScan({"fr.gdai.springmvc.controller", "fr.gdai.springmvc.config"})
@EnableWebMvc
public class SpringMvcConfig {
}

SpringMvcSupport.java

fr.gdai.PORJECT_NAME.config.SpringMvcSupport.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package fr.gdai.ssm.config;

/**
* 关于静态页面的"放行"
*/
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
}
}

ServletConfig.java

fr.gdai.PORJECT_NAME.config.ServletConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package fr.gdai.ssm.config;

public class ServletConfig extends
AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

// 乱码处理
@Override
protected Filter[] getServletFilters() {
// 使用 SpringMVC 提供的字符过滤器
CharacterEncodingFilter filter = new CharacterEncodingFilter();
// 设置字符过滤器的编码类型为 utf-8
filter.setEncoding("utf-8");
return new Filter[]{filter};
}
}