本文是读《Spring Boot2精髓-从构件小系统到架构分布式大系统》的读书笔记。
MVC框架 会处理的技术需求
- HTTP URL映射到Controller某个方法
- HTTP参数映射到Controller方法的参数上
- 参数的校验
- MVC错误处理
- MVC中如何调用视图
- MVC中如何序列化对象成JSON
- 拦截器等高级定制
3.1集成MVC框架
引入依赖:
Spring Boot集成了Spring MVC框架并实现自动配置,pom中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Web 应用目录结构:
Web 的模板文件位于 resources/templates 目录下,模板文件使用的静态资源文件,如 JS 、
css 、图片,存放在 resources/static 目录下 。 在 MVC 中, 视图名自动在 templates 目录下找到对
应的模板名称,模板中使用的静态资源将在 static 目录下查找 。 如下 Controller 代码所示,将
“/userdetail.html?id=xxx ”请求映射到 foo 方法:
@RequestMapping("/userdetail.html")
public String foo(String id){
.......
return "/admin/userInfo.btl";
}
Java 包名结构:
从上图可以看到,在 Spring Boot 应用中,通常会创建如下子包名 :
Controll er一一此包下包含了 MVC 的 Controller,如 UserControlle r;
Service一一此包下有业务处理代码 , 如 UserService ;
entity一一包含了业务实体 , 如 U s er 类 :
conf一一包含了一些配置类,比如用于配置数据源的 DataSourceCon旬, 还有本章的
JSON 序列化配置 JacksonConf。
Spring Boot 应用的程序入口 Ch3Application 建立在这些包名上 , 这样 Spring Boot 应用能自
动扫描整个项目的结构。
Ch3Application 同其他 Spring Boot 程序一样 , 仅仅是一个带有@SpringBootApplication 注解
的类 :
package com.bee.sample.ch3;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Ch3Application {
public static void main(String[] args) {
SpringApplication.run(Ch3Application.class, args);
}
}
3.2 使用Controller
建议熟悉curl
命令后再来测试Controller,可以提高开发效率。
3.3 URL映射到方法
@RequestMapping:
你可以使用@RequestMapping 来映射 URL,比如/test 到某个 Controller 类 , 或者是某个具
体的方法。通常类上的注解@RequestMapping 用来标注请求的路径 , 方法上的@RequestMapping
注解进一步映射特定的 URL 到某个具体的处理方法。
URL路径匹配:
Ant 路径表达式
Ant 用符号“*”来表示匹配任意字符,用“**”来表示统配任意路径,用“?”来匹配
单个字符,比如:
- /user/*. html ,匹配/user/1.html、/user/2.html 等 。
- /**/1.html ,匹配/1.html,也匹配/user/1.htm l , 还匹配/user/add/1.html。。
- /user/?.html,匹配/user/1.html,但不匹配/user/11.htm l 。
HTTP method匹配:
@RequestMapping 提供 method 属性来映射对应 HTTP 的请求方法,通常 HTTP 请求方法有
如下内容:
- GET ,用来获取 URL 对应的内容 。
- POST , 用来向服务器提交信息。
- HEAD ,同 GET ,但不返回消息体,通常用于返回 URL 对应的元信息,如过期时间等 。 搜索引擎通常用 HEAD 来获取网页信息。
- PUT , 同 POST ,用来向服务器提交信息,但语义上更像一个更新操作 。同一个数据 ,多次 PUT 操作 ,也不会导致数据发生改变。 而 POST 在语义上更类似新增操作 。
- DELETE,删除对应 的资源信息。
- PATCH 方法 , 类似 PUT 方法 , 表示信息 的局部更新。
Spring 提供了简化后 的 @RequestMapping , 提供了新 的注解来表示 HTTP 方法 :
@GetMapping;
@PostMapping;
@PutMapping;
@DeleteMapping;
@PatchMapping 。
consumes和produces:
属性 consumes 意味着请求的 HTTP 头的 Content-Type 媒体类型与 consumes 的值匹配 , 才
能调用此方法 。
params和header匹配:
3.4 方法参数
Spring 的 Controller 方法可以接受多种类型参数,比如我们看到的 path 变量,还有 MVC 的Model 。 除此之外,方法还能接受以下参数。
- @PathVariable,可以将 URL 中的值映射到方法参数中。
- Model, Spring 中通用的 MVC 模型,也可以使用 Map 和 ModelMap 作为渲染视图的模
型 。 - ModelAndView,包含了模型和视图路径的对象 。
- JavaBean ,将 HTTP 参数映射到 JavaBean 对象 。
- MultipartFile ,用于处理文件上传 。
- @ModeIAttribute,使用该注解的变量将作为 Model 的一个属性 。
- WebRequest 或者 NativeWebRequest,类似 Servlet Request ,但做了 一定封装。
- java.io.InputStream 和 java.io.Reader ,用来获取 Servlet API 中的 lnputStream/Reader 。
- java.io.OutputStream I java.io.Writer,用来获取 Servlet API 中的 OutputStream/Writer 。
- HttpMethod , 枚举类型 , 对应于 HTTP Method,如 POST 、 GET.
- @MatrixVariable,矩阵变量。
- @RequestParam , 对应于 HTTP 请求的参数,自动转化为参数对应的类型。
- @RequestHeader,对应于 HTTP 请求头参数,自动转化为对应的类型。
- @RequestBody , 自动将请求内容转为指定的对象 , 默认使用 HtψMessageConverters - 来转化。
- @RequestPart,用于文件上传,对应于 HTTP 协议的 multipart/form-data 。
- @SessionAttribute , 该方法标注的变量来自于 Session 的属性。
- @RequestAttribute,该标注的变量来自于 request 的属性。
- @InitBinder,用在方法上,说明这个方法会注册多个转化器,用来个性化地将 HTTP请求参数转化成对应的 Java 对象,如转化为 日期类型、浮点类型 、 JavaBean 等,当然,也可以实现 WebBindinglnitializer 接口来用于 Spring Boot 应用所需要的 dataBinder。
- BindingResult 和 Errors , 用来处理绑定过程中的错误。
这里部分含义比较明确,限于篇幅不做详细讲解,下面会对常用 的 PathVariable 、 Model 、
ModelAndView 、 JavaBean 、 文件上传、 ModelAttribute 进行讲解 。
PathVariable:
注解 PathVariable 用于从请求 URL 中获取参数井映射到方法参数中
Model&ModelAndView:
任何 MVC 框架都有一个类似 Map 结构的 Model,可以向 Model 添加视图需要的变量, Spring
MVC 中的 Model 就是完成此功能的 。
JavaBean接受HTTP参数:
@RequsetBody接受JSON:
curl命令在linux、Mac系统里都是内置的,Windows系统 则需要自己下载安装。
MultipartFile:
通过 MultipartFile 来处理文件上传:
@ModelAttribute:
注解 ModelAttribute 通常作用在 Controller 的某个方法上,此方法会首先被调用,井将方法
结果作为 Model 的属性 , 然后再调用对应的 Controller 处理方法。
package com.bee.sample.ch3.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.bee.sample.ch3.controller.form.OrderPostForm;
import com.bee.sample.ch3.service.UserService;
@Controller
@RequestMapping("/modelattribute")
public class ModelAttributeController {
@Autowired UserService userService;
/**
* Controller方法中的公共放啊,调用方法前先调用此方法。
* @param id
* @param model
*/
@ModelAttribute
public void findUserById(@PathVariable Long id,Model model) {
model.addAttribute("user", userService.getUserById(id));
}
@GetMapping(path = "/{id}/get.json")
@ResponseBody
public String getUser(Model model) {
System.out.println(model.containsAttribute("user"));
return "success";
}
}
@InitBinder:
3.5 验证框架
Spring Boot 支持 JSR-303 、 Bean 验证框架 , 默认实现用的是 Hibernate validator。在 Spring
MVC 中,只需要使用@Valid 注解标注在方法参数上, Spring Boot 即可对参数对象进行校验 ,
校验结果放在 BindingResult 对象中。
JSR-303:
MVC中使用@Validated:
在 Controller 中 ,只需要给方法参数加上@Validated 即可触发一次校验 。
自定义校验:
3.6 WebMvcConfigurer
WebMvcConfigurer 是用来全局定制 化 Spring Boot 的 MVC 特性。开发者通过实现WebMvcConfigurer 接口来配置应用的 MVC 全局特性。
拦截器:
通过 addlnterceptors 方法可以设置多个拦截器,比如对特定的 URI 设定拦截器以检查用户是否登录,打印处理用户请求耗费的时间等。
跨域访问:
出于安全的考虑,浏览器会禁止 AJAX 访问不同域的地址 。 W3C 的 CORS 规范( Cross-origin
resource sharing )允许实现跨域访问,并被现在大多数浏览器支持,包括:
Spring Boot 提供了对 CORS 的支持,可 以实现 addCorsMappings 接口来添加特定的配置 :
格式化:
将 HTTP 请求映射到 Controller 方法的参数上后 , Spring 会自动进行类型转化。对于日 期类
型的参数, Spring 默认并没有配置如何将字符串转为日期类型。为了支持可按照指定格式转为
日期类型,需要添加一个 DateFormatter 类 :
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DateFormatter (”yyyy- MM-dd HH:mm:ss ”));
}
DateFormatter 类实现将字符串转为日期类型 java. util.Data 。
注册Controller:
应用有时候没有必要为一个 URL 指定一个 Contro ller 方法,可以直接将 URI 请求转到对模
板的渲染上 。 比如应用中的如下代码:
视图技术
Spring Boot 支持多种视图技术,内置支持如下:
• FreeMarker:
• Groovy:
• Thyneleaf:
• Mustache 。
本章将简要介绍如何在 Spring 中使用模板技术,并会在第 4 章重点介绍笔者开发的 Beet!
模板引擎 。
使用FreeMarker:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
使用Beetl:
使用Jackson:
在 MVC 框架中,Spring Boot 内置了 Jackson 来完成 JSON 的序列化和反序列化 。在 Controller
中,方法注解为@ResponseBody,自动将方法返回的对象序列化成 JSON 。
Redirect和Forward:
通常而言 , Controller 都会返回一个视图名称 , 比如以 btl 结尾的视图会交给 Beetl模板引擎
渲染。有些情况下 , Controller 会返回客户端一个 HTTP Redirect 重定向请求,希望客户端按照
指定地址重新发起一次请求 ,比如客户登录成功后 , 重定向到后台系统首页 。 再比如客户端通
过 POST 提交了一个名单 , 可 以返回 一个重定向请求到此订单明细的请求地址。这样做的好处
是 , 如果用户再次刷新页面, 则访 问的是订单详情地址,而不会再次提交订单 。
Controller 中 重定向可 以 返回 以 “redirect
: ”为前缀的 URI:
Spring MVC 也支持 foward
前缀,用来在 Controller 执行完毕后,再执行另外一个 Controller
的方法。
3.8 通用错误处理
在 Spring Boot 中,Controller 中抛出的异常默认交给了/error 来处理,应用程序可以将/error 映射
到一个特定的 Controller 中处理来代替 Spring Boot 的默认实现, 应用可以继承 AbstractErrorController
来统一处理系统的各种异常。
3.9 @Service和@Transactional
到目前为止 , Spring Boot 的 Controller 介绍完毕 , 在 Spring Boot 中 , Controller 调用业务逻
辑处理交给了被@Service 注解的类,这也是个普通的 JavaBean,Controller 中可以自动注入这
种 Bean,并调用其方法完成主要的业务逻辑。正如 Controller 注解经常和@RequestMapping 搭
配使用一样,@Service 和@Transactional 配合使用 。
声明一个Service类:
在 Spring Boot 应用中 , 业务逻辑集中在 Service 中处理 , 井自动注入到 Controller 中处理。
实现一个 Service 类,需要定义一个业务的接口,比如根据 id 查询用户及更新用户状态 :
package com.bee.sample.ch3.service;
import java.util.List;
import com.bee.sample.ch3.entity.User;
public interface UserService {
public List<User> allUser();
public User getUserById(Long id);
public void updateUser(Long id,Integer type);
}
然后实现此业务接口 ,不要忘记增加@Service 来引起 Spring Boot 的“注意”,同时搭配上
@Transactional, Spring Boot 会对这样的 Bean 进行事务增强。
package com.bee.sample.ch3.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.bee.sample.ch3.entity.User;
import com.bee.sample.ch3.service.UserService;
@Service
public class UserServiceImpl implements UserService {
public List<User> allUser() {
return sampleUser(5);
}
public User getUserById(Long id) {
User user = sampleUser(1).get(0);
user.setId(id);
return user;
}
private List<User> sampleUser(int num){
List<User> list = new ArrayList<User>(num);
for(int i=0;i<num;i++){
User user = new User();
user.setId((long)i);
user.setName("mame"+i);
list.add(user);
}
return list;
}
public void updateUser(Long id, Integer type) {
// TODO Auto-generated method stub
}
}
尽管 Spring Boot 不要求 Service 必须实现某个接口,但笔者建议还是为 Service 定义接
口,这样可以允许为这个 Service 提供不同的实现 , 比如模拟业务实现 , 单元测试中通
过Mock 来模拟一个 Service 实现 。
事务管理:
Spring 简单地实现了事务管理 ,通过在 Service 类中使用@Transanal 来让Service参与事务管理。为了使用事务 , 需要在 pom 中添加 以下依赖:
spring-boot-starter-jdbc
@Transactional 可以作用在类上,这样,所有的接口方法都会参与事务管理。也可以放到
方法上,上述的 update 方法可以使用@Transactional 来声明调用该方法会处于事务上下文中。
当 Controller调用 Service 方法的时候,会开启 一个事务上下文,随后的调用都将处于这个事务上下文中。如果调用这个 Service 方法抛出 RuntimeException , 事务会自动回滚。否则,事务将提交。
3.10 curl命令
如果你的开发环境是 Mac或者 Linux , 会自带 curl;如果是 Windows 系统, 需要从https://curl.haxx.se/下载 window 版本;如果你安装了 git shell ,也自带了 curl 。
Ch rome 插件 Postman 能可视化地完成像 curl 这样的工作,如果不喜欢 curl 命令行方式,
推荐使用 Postman 。