目录
5.2.1.5.1 Java Bean Validation
5.2.1.5.5 ValidationAutoConfiguration
5.3.1 DispatcherServlet是一个Servlet
5.4.1 DispatcherServletAutoConfiguration.class
5.4.2 WebMvcConfigurationSupport
5.4.3 ServletWebServerFactoryAutoConfiguration
5.5 Servlets, Filters, and Listeners
5.5.1.2 ServletRegistrationBean
5.5.2.2 FilterRegistrationBean
5.8.3 ProblemDetail [SpringBoot 3]
5.8.3.3 Spring支持Problem Detail
5.8.3.7 扩展ErrorResponseException
第五章 说说Web服务
基于浏览器的B/S结构应用十分流行。Spring Boot非常适合Web应用开发。可以使用嵌入式Tomcat、Jetty、Undertow或Netty创建一个自包含的HTTP服务器。一个Spring Boot的Web应用能够自己独立运行,不依赖需要安装的Tomcat,Jetty等。
Spring Boot可以创建两种类型的Web应用
- 基于Servlet体系的Spring Web MVC应用
- 使用spring-boot-starter-webflux模块来构建响应式,非阻塞的Web应用程序
Spring WebFlux是单独一个体系的内容,其他课程来说。 当前文档讲解 Spring Web MVC。又被称为“Spring MVC”。Spring MVC是“model view controller”的框架。专注web应用开发。我们快速的创建控制器(Controller),接受来自浏览器或其他客户端的请求。并将业务代码的处理结果返回给请求方。
Spring MVC处理请求:
5.1 高效构建Web应用
创建Web应用,Lession12-quick-web。 依赖选择spring-web 包含了Spring MVC , Restful, Tomcat这些功能。再选择Thymeleaf(视图技术,代替jsp),Lombok依赖。包名 com.bjpowernode.quickweb。 项目结构:
5.1.1 html页面视图
step1: Maven依赖
spring-web starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 视图技术 Thymeleaf模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
step2: 创建Controller
在根包的下面,创建子包controller,并创建QuickController
@Controller
public class QuickController {
@RequestMapping("/exam/quick")
public String quick(Model model){
//业务处理结果数据,放入到Model模型
model.addAttribute("title", "Web开发");
model.addAttribute("time", LocalDateTime.now());
return "quick";
}
}
step3: 创建视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>视图</title>
</head><body>
<!--格式化,排列数据,视图在浏览器显示给用户-->
<h3>显示请求处理结果</h3>
<p th:text="${title}"></p>
<p th:text="${time}"></p>
</body>
</html>
step4:代码编写完成,现在运行启动类,在浏览器访问exam/quick url地址
编写Spring MVC的应用分成三步:
- 编写请求页面(在浏览器直接模拟的请求)
- 编写Controller
- 编写视图页面
5.1.2 JSON视图
上面的例子以Html文件作为视图,可以编写复杂的交互的页面,CSS美化数据。除了带有页面的数据,还有一种只需要数据的视图。比如手机应用app,app的数据来自服务器应用处理结果。app内的数据显示和服务器无关,只需要数据就可以了。主流方式是服务器返回json格式数据给手机app应用。我们可以通过原始的HttpServletResponse应该数据给请求方。 借助Spring MVC能够无感知的处理json。
step1:创建Controller
@Data
public class User {
private String name;
private Integer age;
}
@Controller
public class JSONViewController {
//HttpServletResponse
@RequestMapping("/exam/json")
public void exam1(HttpServletResponse response) throws IOException {
String data="{\"name\":\"lisi\",\"age\":20}";
response.getWriter().println(data);
}
//@ResponseBody
@RequestMapping("/exam/json2")
@ResponseBody public User exam2() {
User user = new User();
user.setName("张三");
user.setAge(22);
return user;
}
}
注意:从Spring6. Spring Boot3开始 javax包名称,修改为jakarta。
原来:
javax.servlet.http.HttpServletRequest;
修改后:
jakarta.servlet.http.HttpServletRequest;
step2:浏览器测试两个地址
构建前-后端分离项目经常采用这种方式。
5.1.3 给项目加favicon
什么是favicon.ico :
favicon.ico是网站的缩略标志,可以显示在浏览器标签、地址栏左边和收藏夹,是展示网站个性的logo标志。
我们自己的网站定制logo。首先找一个在线工具创建favicon.ico。比如https://quanxin.org/favicon , 用文字,图片生成我们需要的内容。生成的logo文件名称是favicon.ico
step1:将生成的favicon.ico拷贝项目的resources/ 或 resources/static/ 目录。
step2:在你的视图文件,加入对favicon.ico的引用。
在视图的<head>部分加入
<link rel="icon" href="../favicon.ico" type="image/x-icon"/>
代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>视图</title>
<link rel="icon" href="../favicon.ico" type="image/x-icon"/>
</head>
<body>
测试:浏览器访问项目地址http://localhost:8080/favicon.ico
注意:
- 关闭缓存,浏览器清理缓存
- 如果项目有过滤器,拦截器需要放行对favicon.ico的访问
5.2 Spring MVC
Spring MVC是非常著名的Web应用框架,现在的大多数Web项目都采用Spring MVC。它与Spring有着紧密的关系。是Spring 框架中的模块,专注Web应用,能够使用Spring提供的强大功能,IoC , Aop等等。
Spring MVC框架是底层是基于Servlet技术。遵循Servlet规范,Web组件Servlet,Filter,Listener在SpringMVC中都能使用。同时 Spring MVC也是基于MVC架构模式的,职责分离,每个组件只负责自己的功能,组件解耦。 学习Spring MVC首先具备Servlet的知识,关注MVC架构的M、V、C在 Spring MVC框架中的实现。掌握了这些就能熟练的开发Web应用。
Spring Boot的自动配置、按约定编程极大简化,提高了Web应用的开发效率
5.2.1 控制器Controller
控制器一种有Spring管理的Bean对象,赋予角色是“控制器”。作用是处理请求,接收浏览器发送过来的参数,将数据和视图应答给浏览器或者客户端app等。
控制器是一个普通的Bean,使用@Controller或者@RestController注释。 @Controller被声明为@Component。所以他就是一个Bean对象。源代码如下:
如何创建控制器对象?
创建普通Java类,其中定义public方法。类上注解@Controller或者@RestController。
控制器类中的方法作用:
Controller类中的方法处理对应uri的请求, 一个(或多个)uri请求交给一个方法处理。就是一个普通的方法。方法有参数,返回值。方法上面加入@RequestMapping,说明这个uri由这个方法处理。
5.2.1.1 匹配请求路径到控制器方法
SpringMVC支持多种策略,匹配请求路径到控制器方法。AntPathMatcher 、 PathPatternParser
从SpringBoot3推荐使用 PathPatternParser策略。比之前AntPathMatcher提示6-8倍吞吐量。
我们看一下PathPatternParser中有关uri的定义
通配符:
- ? : 一个字符
- * : 0或多个字符。在一个路径段中匹配字符
- **:匹配0个或多个路径段,相当于是所有
- 正则表达式: 支持正则表达式
RESTFul的支持路径变量
{变量名}
{myname:[a-z]+}: 正则皮a-z的多个字面,路径变量名称“myname”。@PathVariable(“myname”)
{*myname}: 匹配多个路径一直到uri的结尾
例子:
@GetMapping("/file/t?st.html")
?匹配只有一个字符
( GET http://localhost:8080/file/test.html
( GET http://localhost:8080/file/teest.html
@GetMapping("/file/t?st.html")
public String path1(HttpServletRequest request){
return "path请求="+request.getRequestURI();
}
@GetMapping ("/images/*.gif")
*:匹配一个路径段中的0或多个字符
( GET http://localhost:8080/images/user.gif
( GET http://localhost:8080/images/cat.gif
( GET http://localhost:8080/images/.gif
( GET http://localhost:8080/images/gif/header.gif
( GET http://localhost:8080/images/dog.jpg
@GetMapping ("/images/*.gif")
public String path2(HttpServletRequest request){
return "* 请求="+request.getRequestURI();
}
@GetMapping ("/pic/**")
** 匹配0或多段路径, 匹配/pic开始的所有请求
( GET http://localhost:8080/pic/p1.gif
( GET http://localhost:8080/pic/20222/p1.gif
( GET http://localhost:8080/pic/user
( GET http://localhost:8080/pic/
@GetMapping ("/pic/**")
public String path3(HttpServletRequest request){
return "/pic/**请求="+request.getRequestURI();
}
RESTFul
@GetMapping("/order/{*id}")
{*id} 匹配 /order开始的所有请求, id表示order后面直到路径末尾的所有内容。
id自定义路径变量名称。与@PathVariable一样使用
( GET http://localhost:8080/order/1001
( GET http://localhost:8080/order/1001/2023-02-16
@GetMapping("/order/{*id}")
public String path4(@PathVariable("id") String orderId, HttpServletRequest request){
return "/order/{*id}请求="+request.getRequestURI() + ",id="+orderId;
}
注意:@GetMapping("/order/{*id}/{*date}")无效的, {*..}后面不能在有匹配规则了
@GetMapping("/pages/{fname:\\w+}.log")
:\\w+ 正则匹配, xxx.log
( GET http://localhost:8080/pages/req.log
( GET http://localhost:8080/pages/111.log
( GET http://localhost:8080/pages/req.txt
( GET http://localhost:8080/pages/###.log
@GetMapping("/pages/{fname:\\w+}.log")
public String path5(@PathVariable("fname") String filename, HttpServletRequest request){
return "/pages/{fname:\\w}.log请求="+request.getRequestURI() + ",filename="+filename;
}
5.2.1.2 @RequestMapping
@RequestMapping:用于将web请求映射到控制器类的方法。此方法处理请求。可用在类上或方法上。 在类和方法同时组合使用。
重要的属性
- value:别名path 表示请求的uri, 在类和方法方法同时使用value,方法上的继承类上的value值。
- method:请求方式,支持GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE。
值为:RequestMethod[] method() , RequestMethod是enum类型。
快捷注解
- @GetMapping: 表示get请求方式的@RequestMapping
- @PostMapping:表示post请求方式的@RequestMapping
- @PutMapping:表示put请求方式的@RequestMapping
- @DeleteMapping: 表示delete请求方式的@RequestMapping
对于请求方式get,post,put,delete等能够HttpMethod表示,Spring Boot3之前他是enum,Spring Boot3他是class
public enum HttpMethod Spring Boot3之前他是enum
public final class HttpMethod Spring Boot3他是class
5.2.1.3 控制器方法参数类型与可用返回值类型
参数类型
类型 | 作用 |
WebRequest, NativeWebRequest | 访问请求参数,作用同ServletRequest, |
jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse | Servlet API中的请求,应答 |
jakarta.servlet.http.HttpSession | Servlet API的会话 |
jakarta.servlet.http.PushBuilder | Servlet 4.0 规范中推送对象 |
HttpMethod | 请求方式 |
java.io.InputStream, java.io.Reader | IO中输入,读取request body |
java.io.OutputStream, java.io.Writer | IO中输入,访问response body |
@PathVariable,@MatrixVariable,@RequestParam,@RequestHeader,@CookieValue,@RequestBody,@RequestPart,@ModelAttribute | uri模板中的变量,访问矩阵,访问单个请求参数,访问请求header,访问cookie,读取请求 body, 文件上传, 访问model中属性 |
Errors, BindingResult | 错误和绑定结果 |
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap | 存储数据Map,Model,ModelMap |
其他参数 | String name, Integer , 自定义对象 |
完整https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments |
返回值:
返回值类型 | 作用 |
@ResponseBody | 将response body属性序列化输出 |
HttpEntity<B>, ResponseEntity<B> | 包含http状态码和数据的实体 |
String | 实体名称或字符串数据 |
自定义对象 | 如果有json库,序列化为json字符串 |
ErrorResponse | 错误对象 |
ProblemDetail | RFC7807,规范的错误应答对象 |
void | 无返回值 |
ModelAndView | 数据和视图 |
java.util.Map, org.springframework.ui.Model | 作为模型的数据 |
...https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-types |
5.2.1.4 接收请求参数
用户在浏览器中点击按钮时,会发送一个请求给服务器,其中包含让服务器程序需要做什么的参数。 这些参数发送给控制器方法。控制器方法的形参列表接受请求参数。
接受参数方式:
- 请求参数与形参一一对应,适用简单类型。形参可以有合适的数据类型,比如String,Integer ,int等。
- 对象类型,控制器方法形参是对象,请求的多个参数名与属性名相对应。
- @RequestParam注解,将查询参数,form表单数据解析到方法参数,解析multipart文件上传。
- @RequestBody,接受前端传递的json格式参数。
- HttpServletRequest 使用request对象接受参数, request.getParameter(“...”)
- @RequestHeader ,从请求header中获取某项值
解析参数需要的值,SpringMVC 中专门有个接口来干这个事情,这个接口就是:HandlerMethodArgumentResolver,中文称呼:处理器方法参数解析器,说白了就是解析请求得到 Controller 方法的参数的值。
5.2.1.4.1 接收json
step1:创建控制器类
@Data
public class User {
private String name;
private Integer age;
}
@RestController
public class ParamController {
@PostMapping("/param/json")
public String getJsonData(@RequestBody User user){
System.out.println("接收的json:"+user.toString());
return "json转为User对象"+user.toString();
}
}
IDEA Http Client测试:
POST http://localhost:8080/param/json
Content-Type: application/json
{"name":"lisi","age":22}
接收 json array
step1:创建控制器方法
@PostMapping("/param/jsonarray")
public String getJsonDataArray(@RequestBody List<User> users){
System.out.println("接收的json array:"+users.toString());
return "json转为List<User>对象"+users.toString();
}
测试:
POST http://localhost:8080/param/jsonarray
Content-Type: application/json
[
{"name":"lisi","age":22},
{"name":"zhangesan","age":26},
{"name":"zhouli","age":30}
]
5.2.1.4.2 Reader-InputStream
Reader 或 InputStream 读取请求体的数据, 适合post请求体的各种数据。具有广泛性。
step1:创建新的控制器方法
@PostMapping("/param/json2")
public String getJsonData2(Reader in) {
StringBuffer content = new StringBuffer("");
try(BufferedReader bin = new BufferedReader(in)){
String line = null;
while( (line=bin.readLine()) != null){
content.append(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return "读取请求体数据"+ content.toString();
}
IDEA Http Client测试:
POST http://localhost:8080/param/json2
Content-Type: application/json 可无
{"name":"lisi","age":26}
5.2.1.4.3 数组参数接收多个值
数组参数接收多个值 数组作为形参,接受多个参数值 ,请求格式 参数名=值1&参数名=值2...
@GetMapping("/param/vals")
public String getMultiVal(Integer [] id){
List<Integer> idList = Arrays.stream(id).toList();
return idList.toString();
}
测试请求:
GET http://localhost:8080/param/vals?id=11&id=12&id=13
GET http://localhost:8080/param/vals?id=11,12,13,14,15
都是成功的方式
5.2.1.5 验证参数
服务器端程序,Controller在方法接受了参数,这些参数是由用户提供的,使用之前必须校验参数是我们需要的吗,值是否在允许的范围内,是否符合业务的要求。比如年龄不能是负数,姓名不能是空字符串,email必须有@符号,phone国内的11位才可以。
验证参数
- 编写代码,手工验证,主要是if语句,switch等等。
- Java Bean Validation : JSR-303是JAVA EE 6 中的一项子规范,叫做 Bean Validation, 是一个运行时的数据验证规范,为 JavaBean 验证定义了相应的元数据模型和API。
5.2.1.5.1 Java Bean Validation
Spring Boot使用Java Bean Validation验证域模型属性值是否符合预期,如果验证失败,立即返回错误信息。 Java Bean Validation将验证规则从controller,service集中到Bean对象。一个地方控制所有的验证。
Bean的属性上,加入JSR-303的注解,实现验证规则的定义。JSR-3-3是规范,hibernate-validator是实现。
JSR-303: https://beanvalidation.org/ ,最新3.0版本,2020年10.
hibernate-validator:https://hibernate.org/validator/ https://docs.jboss.org/hibernate/validator/4.2/reference/en-US/html/
5.2.1.5.2 JSR-303注解
JSR-303定义的常用注解:
注解 | 作用 |
---|---|
@Null | 被注释的元素必须为 null |
@Null | 被注释的元素必须不为 null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一 |