书成

再这样堕落下去就给我去死啊你这混蛋!!!

0%

SpringMvc基础使用

Spring MVC

Spring MVC 流程

Spring MVC 是围绕 DispatchServlet 工作的,在它的基础上,还包含其它组件一起工作,具体流程图如下:

在 Web 服务器启动 Spring MVC 时, @RequestMapping 代表的请求路径与映射方法的映射关系就被扫描到 HandlerMapping 中存储,之后当用户发起的请i去被 DispatcherServlet 拦截之后,通过 URI 与其它条件,可以在 HandlerMapping 中找到对应的控制器或方法响应,但是 HandlerMapping 返回的是一个 HandlerExecutionChain 对象。

在 HandlerExecutionChain 对象中,包含一个处理器(handler)以及一系列拦截器。处理器其实是对控制器的包装。得到了处理器后,还需要运行它,但是因为有各种各样的请求,比如普通 HTTP 请求,WebSocket 请求等,所以需要一个适配器 HandlerAdapter 去运行处理器。

调用处理器后,会返回一个模型和视图对象 ModelAndView,然后通过视图解析器 ViewResolver 解析视图逻辑名称,当视图解析器定位到试图后,将数据模型渲染出来,返回给客户端。当然这里的步骤并不是必须的,比如在前后端分离的场景中,处理器返回的不是视图名,就不需要经过这些步骤。

静态资源配置

Spring Boot 中,默认有5个地方可以存放静态资源,按优先级排序如下:

  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/
  • /

前面四个目录表示 resources 目录下的不同目录,第五个表示 webapp 目录,使用较少。创建 Spring Boot 项目时,系统默认为我们创建了 classpath:/static/ 目录。

如果想自定义配置,可以在配置文件中修改如下配置:

1
2
3
4
# 表示自定义资源位置
spring.resources.static-locations=classpath:/mystatic/
# 表示定义的URL规则
spring.mvc.static-path-pattern=/**

@ControllerAdvice

@ControllerAdvice 是一个增强的 Controler, 使用增强 Controller 可以实现三个方面的功能:

  1. 全局异常处理
  2. 全局数据绑定
  3. 全局数据预处理

全局异常处理

全局异常处理与 @ExceptionHandler 注解搭配使用,例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
@RestControllerAdvice
public class GlobalExceptionHandler {

// 指定处理哪些异常,RespBean 是自定义的返回结果
@ExceptionHandler(SQLException.class)
public RespBean SQLException(SQLException e){
if(e instanceof SQLIntegrityConstraintViolationException)
return RespBean.error("该数据有外键关联,操作失败!");
e.printStackTrace();
return RespBean.error("数据库操作失败!");
}
}

在该类中,也可以定义多个不同的方法处理不同的异常。

全局数据绑定

全局数据绑定可以做一些数据初始化操作,将公共数据定义下来:

1
2
3
4
5
6
7
@ModelAttribute(name="md")
public Map<String, Object> myData(){
Map<String, Object> map = new HashMap<>();
map.put("name","adam");
map.put("age", 66);
return map;
}

将定义添加在 @ControllerAdvice 注解的类中,这样每个 Controller 接口中都能访问这些数据。

全局数据预处理

通过 @ControllerAdvice 中的 @InitBinder 注解可以绑定一个前缀,在前端传参的时候可以通过前缀获取对应参数。@InitBinder 可以在进入控制器方法前先运行它标注的方法,可以修改 WebDataBinder机制

获取参数

无注解下获取参数

在无注解情况下,Spring MVC 也允许获取参数,且允许参数为空,要求参数名称和 HTTP 请求的参数名称一致。测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class TestController {

@GetMapping("/no/annotation")
public Map<String, Object> noAnnotation(Integer intVal, Long longVal, String stringVal){
Map<String, Object> map = new HashMap<>();
map.put("intVal", intVal);
map.put("longVal", longVal);
map.put("stringVal", stringVal);
return map;
}
}

在浏览器输入 http://localhost:8080/no/annotation?intVal=1&longVal=1&stringVal=String 查看结果

@RequestParam

由于前后端命名规则可能不同,为了将参数对应起来,可以使用 @RequestParam 来确定前后端关系。测试代码如下:

1
2
3
4
5
6
7
8
9
10
@GetMapping("/annotation")
public Map<String, Object> requestParam(@RequestParam("int_val") Integer intVal,
@RequestParam("long_val") Long longVal,
@RequestParam(value = "string_val", required = false) String stringVal){
Map<String, Object> map = new HashMap<>();
map.put("intVal", intVal);
map.put("longVal", longVal);
map.put("stringVal", stringVal);
return map;
}

在浏览器输入 http://localhost:8080/annotation?int_val=1&long_val=1&string_val=String 查看结果,默认情况下 @RequestParam 注解标注的参数不能为空,如果需要配置为空,可配置其属性 required 为 false

传递数组

Spring MVC 内部支持由逗号分隔的数组,测试代码如下:

1
2
3
4
5
6
7
8
9
10
@GetMapping("/array")
public Map<String, Object> requestArray(@RequestParam("int_val") Integer[] intVals,
@RequestParam("long_val") Long[] longVals,
@RequestParam(value = "string_val", required = false) String []stringVals){
Map<String, Object> map = new HashMap<>();
map.put("intVals", Arrays.toString(intVals));
map.put("longVals", Arrays.toString(longVals));
map.put("stringVals", Arrays.toString(stringVals));
return map;
}

在浏览器输入 http://localhost:8080/array?intVals=1,2,3&longVals=4,5,6&stringVals=adam,tom,%E5%AD%97%E7%AC%A6%E4%B8%B2 查看结果即可。

获得 URL 参数

在 REST 风格的网站中,参数有时候会通过 URL 进行传递,此时可以通过 @PathVariable 获取 URL 参数。测试代码如下:

1
2
3
4
5
6
7
8
@GetMapping("/{id}/{name}")
public Map<String, Object> get(@PathVariable("id") int id,
@PathVariable("name") String name){
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("name", name);
return map;
}

在浏览器输入 http://localhost:8080/99/adam 查看结果

传递 JSON

在很多情况下,需要通过请求体传递 JSON 到服务端,服务端可以使用 @RequestBody 来接收请求体的 JSON 参数。

获取格式化参数

在某些情况,需要格式化数据, Spring MVC 对日期类型和数字类型分别提供了 @DateTimeFormat 和 @NumberFormat 来进行处理。对于日期类的数据参数,还可以在配置文件中添加配置项 spring.mvc.date-format=yyyy-MM-dd 类进行配置。

1
2
3
4
5
6
7
8
@GetMapping("/format")
public Map<String, Object> format(@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date date,
@NumberFormat(pattern = "#,###.##") Double number){
Map<String, Object> map = new HashMap<>();
map.put("date", date);
map.put("number", number);
return map;
}

在浏览器输入 http://localhost:8080/format?date=1998-01-03&number=1,32.2 验证结果。

自定义参数转换规则

处理器获取参数逻辑

当一个请求到来,在处理器执行过程中,首先从 HTTP 请求和上下文环境来获取参数,如果是简单的参数会用简单的转换器进行转换,如果是 HTTP 请求体,会调用 HttpMessageConverter 接口的方法对请求体信息进行转换

在 HttpMessageConverter 中,会调用 WebDataBinder 机制获取参数,它主要通过三种接口进行参数类型转换与验证:Converter, Formatter 与 GenericConverter。其中 Converter 是一个普通的转换器,Formatter 是格式化转化器, GenericConverter 是数组转换器。Spring MVC 会自动遍历 IoC 容器注册以上三种类的 Bean。

自定义一对一转换器

首先顶一个 User 类如下:

1
2
3
4
5
public class User {
String name;
int id;
// 省略其它代码...
}

然后定义一个转换器如下,传入第一个泛型参数是原始类型,第二个参数是转换后的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class String2UserConverter implements Converter<String, User> {
@Override
public User convert(String s) {
User user = new User();
String[] attrs = s.split("-");
String name = attrs[0];
int id = Integer.parseInt(attrs[1]);
user.setId(id);
user.setName(name);
return user;
}
}

编写控制器方法如下:

1
2
3
4
@GetMapping("/converter")
public User getUserByConverter(User user){
return user;
}

在浏览器输入 http://localhost:8080/converter?user=adam-22 查看结果即可。

数组转换

数组转换器首先会把字符串用逗号分割为一个个子字符串,然后根据原始泛型,目标泛型找到对应的 Converter 进行转换。

编写控制器方法如下:

1
2
3
4
@GetMapping("/list")
public List<User> getUserListByConverter(List<User> users){
return users;
}

在浏览器输入 http://localhost:8080/list?users=adam-22,tom-13,marry-99 查看结果即可。

自定义验证器

在 WebDataBinder 中除了可以注册转换器之外,还可以注册验证器,验证接口定义如下:

1
2
3
4
5
6
7
public interface Validator {
// 返回 true 时才验证
boolean supports(Class<?> var1);

// var1 表示被验证对象, var2 表示错误对象
void validate(Object var1, Errors var2);
}

绑定验证器需要获得 WebDataBinder 对象,调用它的 setValidator 对象来绑定验证器。

自定义拦截器

自定义拦截器需要实现 HandlerInterceptor 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyInterceptor implements HandlerInterceptor {
@Override
// 这个方法返回 true 才会继续执行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("处理器前方法");
return false;
}

@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("处理器完成后方法");
}
}

之后注册这个拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootApplication
// 实现 WebMvcConfigurer
public class MvcdemoApplication implements WebMvcConfigurer {

public static void main(String[] args) {
SpringApplication.run(MvcdemoApplication.class, args);
}

@Override
public void addInterceptors(InterceptorRegistry registry){
// 注册拦截器
InterceptorRegistration ir = registry.addInterceptor(new MyInterceptor());
// 指定拦截路径
ir.addPathPatterns("/*");
}

}

然后添加控制器方法测试:

1
2
3
4
@GetMapping("/interceptor/")
public void interceptorTest(){
System.out.println("执行处理器逻辑");
}

多个拦截器之间也是按责任链模式的规则调用,执行顺序便是注册顺序

文件上传

Spring MVC 对文件上传提供了良好的支持,首先 DispatherServlet 通过适配器模式将 HttpServletRequest 对象转换为 MultipartHttpServletReques 对象,这个对象定义了许多文件相关方法。

在使用 Spring MVC 上传文件时,还需要配置 MultipartHttpServletRequest,这个是通过 MultipartResolver 接口实现,它有两个实现类:

  • StandardServletMultipartResolver 推荐使用,不需要依赖第三方包,并且也是默认使用。
  • CommonsMultipartResolver 不推荐使用,依赖第三方包。

在 Controller 中,可以使用 Part 接口(Servlet API 提供)或者 MultipartFile 接口(Spring MVC 提供)作为参数

首先在配置文件对上传文件做配置:

1
2
3
4
5
6
7
8
9
10
11
12
# 是否开启 Spring MVC 多分部上传
spring.servlet.multipart.enabled=true
# 文件写入磁盘的阈值
spring.servlet.multipart.file-size-threshold=10MB
# 限制单个文件大小
spring.servlet.multipart.max-file-size=2MB
# 限制所有文件最大大小
spring.servlet.multipart.max-request-size=10MB
# 默认上传文件夹
spring.servlet.multipart.location=G:/upload
# 是否支持延迟解析
spring.servlet.multipart.resolve-lazily=false

添加控制器方法:

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
@PostMapping("/upload/part")
public String upload(Part file){
String fileName = file.getSubmittedFileName();
try{
// 可以直接写入文件
file.write(fileName);
}catch (Exception e){
e.printStackTrace();
return "上传失败";
}
return "上传成功!!!";
}

@PostMapping("/upload/multipart")
public String uploadMultipart(MultipartFile file){
String filename = file.getOriginalFilename();
try{
// 需要创建一个新的 File 对象
file.transferTo(new File(filename));
}catch (Exception e){
e.printStackTrace();
return "上传失败";
}
return "上传成功!!!";
}

重定向

重定向就是将网络请求重新定个方向或者转到其它位置,在 Spring MVC 中,可以使用字符串指定跳转或者使用模型和视图指定跳转,只需要在需要重定向的 url 前加上 redirect: 即可。

1
2
3
4
5
6
// 字符串指定跳转
return "redirect:/home"

使用模型和视图指定跳转
mv.setViewName("redirect:/home")
return mv

转发与重定向的区别

  • 转发的地址栏还是原来的地址,重定向地址栏显示的是新的URL。
  • 转发页面和转发到的页面可以共享 request 里面的数据,重定向不能。
  • 转发是服务器行为,重定向是客户端行为(两次 Request)。
  • 转发效率较高。

RedirectAttributes

为了解决转发的数据共享问题,Spring MVC 提供了 RedirectAttributes,它扩展了 ModelMap 接口,有一个 addFlashAttribute方法,这个方法可以保存需要传递给重定向的数据。

操作会话对象

Spring MVC 提供了两个注解操作会话对象:

  • @SessionAttribute 应用于参数,将 HttpSession 中的属性读出,赋予控制器参数。
  • @SessionAttributes 应用于类,将相关数据模型的属性自动保存到 Session 中,可以指定数据模型名称或者数据模型的类型来保存。例:@SessionAttributes(names = {"user"}, types = Long.class)

跨域问题

跨域问题可以通过 @CrossOrigin 注解解决,例如:@CrossOrigin(value="http://localhost:8081"),该注解可以放在类上或者方法上。

也可以进行全局配置:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**")
.allowedOrigins("http://localhost:8081")
.allowedMethods("*")
.allowedHeaders("*");
// 配置来自8081端口的所有路径,所有请求方法,所有请求头
}
}