SSM系列第4篇MVC

基本概念

mvc(model,view,controller),其中m是封装数据传递的实体类,v是前端页面,c相当于servlet的基本功能,处理请求,返回响应。在springmvc之前ssm中的第二个s是指的structs,structs的页面配置比较的繁琐,对原生的servlet依赖较强,另外还有严重的安全漏洞,所以现在的s指的是springmvc

基础案例

操作步骤
1.首先导入相关的依赖,springmvc的和spring核心的,另外还需要还需要引入servlet的相关依赖
2.创建控制类,控制类用@Controller来进行声明,其中@RequestMapping注解用来声明请求路径,@ResponseBody用来使路径不跳转而是直接写入到HTTP response body中去。同时@ResponseBody可以直接将pojo的json格式返回给页面(这个过程是jackson来实现的)
3.初始化springmvc的环境,需要设置一个配置类,使用方法和之前的spring ioc相同
4.初始化servlet容器,加载mvc的环境,并设置mvc的处理请求。需要创建类继承自其AbstractDispatcherServletInitializer,其中的createServletApplicationContext方法用来指定context类型。getServletMappings方法用来设置需要处理那些请求

实现代码

pom.xml

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
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.eldpepar</groupId>
<artifactId>springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<name>springmvc</name>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring.version>5.2.10.RELEASE</spring.version>
</properties>

<dependencies>
<!--mvc相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>

<!--spring核心-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>

<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 设置编码格式 -->
<uriEncoding>UTF-8</uriEncoding>
<!-- 控制 tomcat 端口号 -->
<port>8099</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>

UserControl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.eldpepar.base.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserControl {

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

MvcConfig.java

1
2
3
4
5
6
7
8
9
10
package com.eldpepar.base;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.eldpepar.base")
public class MvcConfig {

}

ServletInit.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
package com.eldpepar.base;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;

//定义一个servlet容器启动的配置类,在里面加上spring的配置
public class ServletInit extends AbstractDispatcherServletInitializer {

//加载springmvc容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(MvcConfig.class);
return ctx;
}

//设置那些请求归springmvc处理
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

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

工作流程

| 启动服务初始化过程

1.服务器启动,执行ServletInit类,初始化web容器
2.执行createServletApplicationContext方法,创建了WebApplicationContext对象
3.加载MvcConfig
4.执行@ComponScan加载对应的bean
5.加载UserController,没个@RequestMapping的名称对应一个具体方法。如果需要整个类访问相同的路径,可以在类中加入@RequestMapping
6.执行getServletMappings方法,定义所有请求通过springmvc

| 单次请求过程
1.发送相应的请求
2.web容器发现所有请求都要经过springmvc,将请求控制交给springmvc处理
3.解析请求路径
4.由请求路径匹配到相应的方法
5.执行请求的方法
6.检测到由@ResponseBody直接将方法的返回值作为响应体返回给请求方

bean加载控制

在spring加载bean的时候,之前是通过扫描整个包的方式进行加载。但在springmvc中,mvc相关的bean需要mvc进行加载,这个时候就需要进行排除。通常情况下由以下方式来进行排除

1.spring加载bean的时候仍然设定扫描整个包,人工的排除controller包内的bean。需要特别注意的是如果在同一个包的情况下,controller中的扫描和spring的扫描只能存在一个,否则过滤失效

SpringConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.eldpepar.base;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

@Configuration
@ComponentScan(value = "com.eldpepar.base", excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
))
public class SpringConfig {

}

2.spring加载的bean设置扫描范围为精确范围

SpringConfig.java

1
2
3
4
5
6
7
8
9
10
package com.eldpepar.base;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({"com.eldpepar.base.dao","com.eldpepar.base.domain"})
public class SpringConfig {
//其中dao包最好加上,防止无法扫到
}

加载优化

可以通过继承AbstractDispatcherServletInitializer下的子类来优化ServletInit初始化的代码。子类只需要指定相应的类即可
ServletInit.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.eldpepar.base;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class ServletInit extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

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

请求与响应

如果需要请求携带参数,需要在请求方法中设置形参,后台就可以获取到输入的参数了。需要特别注意的是post请求中,psotman工具使用的是Body下的x-www来进行提交,form-data不仅可以提交表单,还可以提交文件。

post中文乱码处理

需要添加一个过滤方法,在过滤方法中指定编码方式
ServletInit.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
31
package com.eldpepar.base;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.Filter;

public class ServletInit extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

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

@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}

参数传递

请求的参数一般有普通参数、pojo类型参数、嵌套pojo类型参数、数组类型参数、集合类型参数

普通参数

请求参数名如果与形参不同,使用@RequestParam绑定参数关系

1
2
3
4
5
6
7
8
//普通参数
@RequestMapping("/getUser")
@ResponseBody
public String getUser(@RequestParam("name") String userName, int age) {
System.out.println("userName->" + userName);
System.out.println("age->" + age);
return "success";
}
pojo参数

只需要参数名与类的属性名相同,定义pojo类型形参就可以接受参数

1
2
3
4
5
6
7
//pojo参数
@RequestMapping("/getUserPojo")
@ResponseBody
public String getUserPojo(User user) {
System.out.println("user->" + user);
return "success";
}
pojo嵌套

如果pojo中带有其他的pojo参数,传递参数的时候,需要用“对象名.参数”的方法来传递。例如address.city

1
2
3
4
5
6
7
//pojo嵌套
@RequestMapping("/getUserPojoNest")
@ResponseBody
public String getUserPojoNest(User user) {
System.out.println("user嵌套->" + user);
return "success";
}
数组参数

请求是,需要将参数的名称设置为数组的名称,并且定义多个参数名相同的参数,这样就可以接收到数组的形参

1
2
3
4
5
6
7
//数组参数
@RequestMapping("/getArr")
@ResponseBody
public String getArr(String[] likes) {
System.out.println("数组参数->" + Arrays.toString(likes));
return "success";
}
集合参数

如果需要传递集合参数,传递的方法和数组的方法是相同的。但需要在后台加上@RequestParam

1
2
3
4
5
6
7
//集合参数
@RequestMapping("/getList")
@ResponseBody
public String getList(@RequestParam List<String> likes) {
System.out.println("集合参数传递->" +likes);
return "success";
}
json参数

传递json参数首先需要加入jackson-databind依赖,然后开启@EnableWebMvc。postman请求的时候需要设置Body->raw,并且选择json。在相应方法参数里需要开启@RequestBody。详细的参数传递规则可以查询json格式规范

相关依赖

1
2
3
4
5
6
<!--json相关-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>

MvcConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.eldpepar.base;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan("com.eldpepar.base")
@EnableWebMvc
public class MvcConfig {

}

MvcConfig.java

1
2
3
4
5
6
7
//json参数
@RequestMapping("/list")
@ResponseBody
public String list(@RequestBody String[] city) {
System.out.println("city->"+ Arrays.toString(city));
return "success";
}
时间日期

传递指定格式日期时间的时候,同样需要在相应方法的参数中设置日期转化。需要使用@DataTimeFormat实现。转化的本质是Convert接口帮我们实现的,要想使用这个接口需要开启@EnableWebMvc。

1
2
3
4
5
6
7
@RequestMapping("/getData")
@ResponseBody
public String getData(@DateTimeFormat(pattern = "yyyy/MM/dd HH:mm:ss")Date date) {
SimpleDateFormat format = new SimpleDateFormat("今天是 " + "yyyy 年 MM 月 dd 日 HH 点 mm 分 ss 秒");
System.out.println(format.format(date));
return "success";
}

REST风格

REST风格通过请求的类型来区分不同的请求,特点是请求的路径较短,比较直观。如果需要接受变量,则需要在方法参数中使用@PathVariable。此外springmvc还提供了了get、post、delete、put等方法的专属Mapping。为了简化ResponseBody和Controller提供了RestController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RequestMapping(value = "/users", method = RequestMethod.GET)
@ResponseBody
public String save(@RequestBody User user) {
System.out.println("user save successful");
return "user save successful";
}

@RequestMapping(value = "/users", method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody User user) {
System.out.println("user update successful");
return "user update successful";
}

@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id) {
System.out.println("user delete successful");
return "user delete successful";
}

优化代码

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
//相当于@Controller和@ResponseBody
@RestController
@RequestMapping("/books")
public class BookController {
@PostMapping
public String save(@RequestBody Book book) {
System.out.println("Book save successful");
return "Book save successful";
}

@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id) {
System.out.println("Book delete successful");
return "Book delete successful";
}

@PutMapping
public String update(@RequestBody Book book) {
System.out.println("Book update successful");
return "Book update successful";
}

@GetMapping("/{id}")
public String getById(@PathVariable Integer id) {
System.out.println("Book getById successful");
return "Book getById successful";
}

@GetMapping
public String getAll() {
System.out.println("Book getAll successful");
return "Book getAll successful";
}
}

SSM系列第4篇MVC
https://www.eldpepar.com/coding/50566/
作者
EldPepar
发布于
2022年11月13日
许可协议