SpringBoot-2
发表于:2024-04-04 | 分类: 框架
字数统计: 7.5k | 阅读时长: 34分钟 | 阅读量:

1. 配置文件

配置文件的编写有两种形式:

properties

1
2
3
4
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///test
username=root
password=123456

这种方式一直伴随我们,不再赘述

yaml

YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

以 .yaml 或 .yml 为后缀都可

基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格(在 idea 中使用Tab键也不报错)
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • ‘#’表示注释
  • 字符串无需加引号,如果要加,’’与””表示字符串内容会被转义/不转义

比如 ‘\n’ 输出在控制台就是字符串\n;”\n”输出在控制台是换行

数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
1
k: v
  • 对象:键值对的集合。map、hash、object
1
2
3
4
5
6
7
#行写法:
k: {k1: v1,k2: v2,k3: v3}
#列写法
k:
k1: v1
k2: v2
k3: v3
  • 数组:一组按次序排列的值。array、list、queue、set
1
2
3
4
5
6
7
#行写法:
k: [v1,v2,v3]
#列写法
k:
- v1
- v2
- v3

属性导入示例:

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
person:
name: John
boss: false
birth: 1998/12/12 20:12:33
age: 25
pet:
name: wang
age: 2
interests: [ 篮球,唱歌,跳舞 ]
animal:
- jerry
- tom
score:
#对象
math:
#属性
first: 90
second: 95
#list
english: [ 85,88 ]
salarys: [ 9000,10000.66,11000 ]
allPets:
cat:
- name: jerry
age: 3
dog: [ { name: tom,age: 4 } ]

myString:
test: hello,world!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Data
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salarys;
private Map<String, List<Pet>> allPets;
@Value("${myString.test}")
private String stringTest;
}
============================================================
@Data
public class Pet {
private String name;
private Integer age;
}

注释处理器

当我们在配置文件中添加参数时,SpringBoot内部的参数会有提示,但我们设置自定义的类和配置文件时没有提示,我们可以添加SpringBoot官方声明的注释处理器:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

得先运行以便才能生效

官方给的注意项:

如果在项目中使用AspectJ,则需要确保注释处理器只运行一次。有几种方法可以做到这一点。使用Maven,您可以显式配置maven-apt-plugin,并仅在那里将依赖项添加到注释处理器。您还可以让AspectJ插件运行所有处理,并在maven-compiler-plugin配置中禁用注释处理,如下所示:

1
2
3
4
5
6
7
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<proc>none</proc>
</configuration>
</plugin>

如果您在项目中使用Lombok,则需要确保其注释处理器在spring-boot-configuration-processor之前运行。使用Maven时,可以使用Maven编译器插件的annotationProcessors属性以正确的顺序列出注释处理器。如果您没有使用此属性,并且注释处理器是由类路径上可用的依赖项获取的,请确保在spring-boot-configuration-processor依赖项之前定义了lombok依赖项。

2. Web开发

55b42d889744c6c15faf7b023e74a1f2.png

官方给我们自定义配置的建议:

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

声明 WebMvcRegistrations 改变默认底层组件

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

使用 Spring Initailizr 创建一个 web工程:

e07ec30eb7e59a6bd4fb48624ed2f829.png

①静态资源访问

1
2
By default, Spring Boot serves static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext.
默认情况下,Spring Boot从类路径中名为/static(或/public或/resources或/META-INF/resources)的目录或ServletContext的根目录提供静态内容。

直接使用项目根路径/+静态资源名就可以访问,例http://localhost:8080/1.jpg

1
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

可以在配置文件中修改spring.web.static-locations:

1
2
3
4
By default, resources are mapped on /**, but you can tune that with the spring.mvc.static-path-pattern property. For instance, relocating all resources to /resources/** can be achieved as follows:
默认情况下,资源映射在/**上,但您可以使用spring.mvc进行调整。静态路径模式属性。例如,将所有资源重新定位到/resources/**可以通过以下方式实现:

spring.mvc.static-path-pattern=/resources/**

资源路径不需要改变,只是添加了静态资源的访问前缀

webjar

WebJars是打包到JAR(Java存档)文件中的客户端web库(例如jQuery和Bootstrap)。

自动映射 /webjars/**

例:http://localhost:8080/webjars/jquery/3.5.1/jquery.js,后面地址要按照依赖里的包路径

欢迎页

  • 静态资源路径下的 index.html,SpringBoot会自动识别为欢迎页。

注意:如果给静态资源添加了前缀不能直接访问欢迎页

  • 使用 controller 处理欢迎页

Favicon

网络图标;直接将 favicon.ico 放在静态资源目录下即可。

静态资源添加前缀也会使 Favicon 功能失效

Favicon 默认是session级别资源,谷歌浏览器可以使用 ctrl+F5 去除缓存重新请求

Spring Boot启动打印默认logo的类是SpringApplicationBannerPrinter类,SpringBoot 默认寻找 Banner的顺序是: 首先依次在 Classpath下找文件banner.gif,banner.jpg和 banner.png,使用优先找到的 若没找到上面文件的话,继续 Classpath下找 banner.txt 若上面都没有找到的话, 用默认的 SpringBootBanner,也就是上面输出的 Spring Boot logo 一般是把banner.txt文件放在 src/main/resources/目录下。

静态资源配置原理

SpringBoot 的自动配置类:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

1
2
3
4
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
1
2
3
4
5
6
@ConfigurationProperties(
prefix = "spring.mvc"
)
public class WebMvcProperties {...}
@ConfigurationProperties("spring.web")
public class WebProperties {...}

不同版本会有细微区别,就不在此展示详细规则

②请求参数处理

请求映射

restful 风格:

SpringBoot 中默认是关闭的

1
2
3
4
5
6
7
8
9
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}

手动开启:

1
spring.mvc.hiddenmethod.filter.enabled=ture

当然我们也可以自定义配置:

1
2
3
4
5
6
7
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");//给 _method 改名
return methodFilter;
}

HiddenHttpMethodFilter 还兼容 PUT、DELETE、PATCH 请求;其内部使用了包装模式 requestWrapper

当使用客户端工具时,不需要开启 filter,如PostMan可以直接发送put,delete请求。

请求映射原理:

org.springframework.web.servlet.DispatcherServlet#doDispatch

1
2
3
mappedHandler = getHandler(processedRequest);//确定当前请求的处理程序。
//确定当前请求的处理程序适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

420f2d6b31fcb16c7b862bedc109449d.png
17eca3edcf33a7db625849852a4e6273.png

RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则

如图,所有的请求映射都在 HandlerMapping 中。收到请求后,会遍历 HandlerMapping 看是否有请求信息,有就找到对应的handler

普通参数与基本注解

@PathVariable(路径变量)、@RequestHeader(请求头)、@RequestBody(请求体)、@RequestParam(请求参数)、@ModelAttribute、@MatrixVariable(矩阵变量)、@CookieValue(获取cookie值)、@RequestAttribute(可以将request中的值直接赋值给方法中的参数)

其中 @ModelAttribute 注解在方法上,会在每一个@RequestMapping标注的方法前执行,
如果有返回值,则自动将该返回值加入到ModelMap中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// /person/John?age=26&hobby=1&hobby=2
@GetMapping("/person/{name}")
public Map<String, Object> test(@PathVariable("name") String name,
@PathVariable Map<String, Object> map,
@RequestParam(value = "age", required = false) Integer age,
@RequestParam("hobby") List<String> hobby,
@RequestParam Map<String, Object> map1,
@RequestHeader(value = "User-Agent", required = false) String rh,
@RequestHeader Map<String, Object> map2,
Cookie cookie,
@CookieValue(value = "ga", required = false) String ga) {
System.out.println(cookie.getName()+cookie.getValue());//Johnnull
System.out.println(ga);
return map;
}

其中有个问题,浏览器没显示cookie,但服务器收到了

1
2
3
4
@PostMapping("/rb")
public String test3(@RequestBody String rb) {
return rb;
}

只有post请求有请求体,其它(put、delete..)都没有

矩阵变量(较少使用):

问题引入:当浏览器禁用了 cookie ,怎么使用session中的数据?

1
2
3
4
5
session.set(a,b) -> jsessionid -> cookie -> 每次请求携带
解决方式:把cookie的值使用矩阵变量的方式进行传递
url重写:/abc;jsessionid=xxx
我们甚至可以使用矩阵变量携带参数:
/1;name=John;age=20;hobby=1,2/2;name=Tom;age=30

SpringBoot 默认是禁用了矩阵变量的功能

1
2
//webMvcAutoConfiguration.WebMvcAutoConfigurationAdapter.configurePathMatch -> UrlPathHelper
private boolean removeSemicolonContent = true;

分号后面的内容都删除

手动开启方式一:

1
2
3
4
5
6
7
8
//根据官方文档,使用 @Configuration + WebMvcConfigurer 自定义规则,我们在 WebMvcAutoConfiguration 能看到:
@Configuration(
proxyBeanMethods = false
)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

我们点进 WebMvcConfigurer 可以看到它内部声明了很多默认方法。

1
2
3
4
5
6
7
8
9
10
11
//照葫芦画瓢
@Configuration(proxyBeanMethods = false)
public class MyConfiguration implements WebMvcConfigurer {

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper up=new UrlPathHelper();
up.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(up);
}
}

方式二:

1
2
3
4
5
6
7
8
9
10
11
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper up = new UrlPathHelper();
up.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(up);
}
};
}

测试:

1
2
3
4
5
6
7
8
9
10
11
// /emp/1;name=John;hobby=1,2
@GetMapping("/emp/{id}")
public Map<String, Object> test3(@PathVariable("id") Integer id,
@MatrixVariable("name") String name,
@MatrixVariable("hobby") List<String> list) {
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("name", name);
map.put("hobby", list);
return map;
}
1
2
3
4
5
6
7
8
9
// /emp/1;name=John;/2;name=Tom
@GetMapping("/emp/{emp1}/{emp2}")
public Map<String, Object> test3(@MatrixVariable(value = "name",pathVar = "emp1") String emp1,
@MatrixVariable(value = "name",pathVar = "emp2") String emp2) {
Map<String, Object> map = new HashMap<>();
map.put("emp1", emp1);
map.put("emp2", emp2);
return map;
}

矩阵变量是绑定在路径变量中的,矩阵变量必须有url路径变量才能被解析

Servlet API

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver 将以上的参数进行了处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}

复杂参数

MapModel、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

自定义对象参数

可以自动类型转换与格式化,可以级联封装

pojo 底层都是使用 ServletModelAttributeMethodProcessor (类)来封装的

1
WebDataBinder:web数据绑定器,将请求的参数的值绑定到指定的 JavaBean 中;其内部使用了 Converters 将请求转成指定的数据类型。

自定义 Converter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//方式一:使用 @Configuration + WebMvcConfigurer 自定义规则
//方式二:
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if (!StringUtils.isEmpty(source)) {
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}

③参数处理原理

SpringBoot底层对控制器方法的注解循环遍历,根据注解类型决定处理方式:

1
2
3
4
5
6
7
8
9
10
//debug进入顺序:
//1.DispatcherServlet:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//2.AbstractHandlerMethodAdapter:
return this.handleInternal(request, response, (HandlerMethod)handler);
//3.RequestMappingHandlerAdapter:
mav = this.invokeHandlerMethod(request, response, handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);//保存所有的参数解析器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);//保存所有的返回值解析器
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);//真正执行的目标方法

b4b30fe5eea2fa4f4d6c6625cb668661.png

9759ca61f2a15f9c9efe47bb3f3e242a.png

获取方法所有的参数:

1
2
3
4
5
6
7
8
9
//debug进入顺序:
//1.RequestMappingHandlerAdapter:
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);//真正执行的目标方法
//2.ServletInvocableHandlerMethod:
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
//3.InvocableHandlerMethod:
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
//4.返回参数 -> ServletInvocableHandlerMethod:
mavContainer.setRequestHandled(true);

map 和 model 类型参数底层调用的方法都是 mavContainer.getModel();最终调用的还是 request.setAttribute

目标方法执行完成:

目标方法执行完成后,所有的数据都会放在 ModelAndViewContainer,包含要去的页面地址 View 和 Model 数据。

0de7b619a9196cebd70552781503ff5c.png

控制器是内部转发请求所以 view 为 null

最终将数据传回 DispatcherServlet 中的 mv属性,再使用 processDispatchResult 方法处理派发结果

528101907365a8dd4e7ac0bcf7337cf7.png

④数据响应与内容协商

9dac9c351561c4fd414caddfaba8156e.png

响应JSON

jackson.jar+@ResponseBody

SpringBoot的web场景自动引入了json场景

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.7.6</version>
</dependency>

从此处开始原理部分不清不楚,自己debug还老跑丢,以下涉及源码部分不一定对,以后回来补充

返回值解析器逻辑:

遍历所有的返回值处理器,内部判断是否支持当前类型的返回值;找到合适的返回值处理器后根据请求头中的accept信息进行内容协商(浏览器能接受且服务器支持),内部循环遍历得到合适的 HttpMessageConverter (转换器)

构造器中初始化:

0d95bbb2f5ed46efe63539b2e262245f.png

1
2
3
4
5
6
7
8
9
10
0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true
8 - true
9 - 支持注解方式xml处理的。

内容协商

引入xml依赖:

1
2
3
4
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

SpringBoot底层会将浏览器的接受方式和服务器的转换方式进行匹配(双层循环),得到所有符合浏览器条件的媒体类型,再循环转换器转换数据,最后将所有转换完成的数据根据媒体权重等信息进行排序取首位(最佳匹配媒体类型)

当我们引入xml依赖,由于浏览器中xml数据的权重比较高,最终以xml形式返回

1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

开启浏览器参数方式内容协商功能

1
spring.mvc.contentnegotiation.favor-parameter=true

底层添加了 parameter 策略(优先级更高),默认只有请求头一个策略

在地址栏中添加 format 参数,例:http://localhost:8080/person?format=json

默认参数只支持 json 和 xml

自定义 MessageConverter

1
2
3
4
5
6
7
8
9
10
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MymesssageConverter());
}
}
}
1
2
//自定义converter
public class MymesssageConverter implements HttpMessageConverter<T>{...}

⑤视图解析与模板引擎

SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。

SpringBoot工程的打包结果是一个jar包,是压缩包,JSP不支持在压缩包中被编译运行,所以SpringBoot默认不支持JSP。

thymeleaf

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

919ea1afe5f1328b62666aafb0fc5bf6.png

基本语法(此处仅作补充):7. Thymeleaf

表达式名字 语法 用途
变量取值 ${…} 获取请求域、session域、对象等值
选择变量 *{…} 获取上下文对象值
消息 #{…} 获取国际化等值
链接 @{…} 生成链接
片段表达式 ~{…} jsp:include 作用,引入公共页面片段

设置属性值:th:attr

1
2
3
4
5
6
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>

多个属性用逗号分割:th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}"

以上两个的代替写法 th:xxx;此方式常用不再赘述

⑥拦截器

HandlerInterceptor 接口:

创建拦截器需要继承此接口

1
public class MyInterceptor implements HandlerInterceptor {..}

配置拦截器

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
/**
* 1、编写一个拦截器实现HandlerInterceptor接口
* 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") //所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
}
}

@Slf4j
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("执行控制器前。。。");
return HandlerInterceptor.super.preHandle(request, response, handler);
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("执行控制器后。。。");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("渲染视图后。。。");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}

e74df758ba065f1b2bb2fdd561d50597.png

⑦文件上传

1
2
3
4
5
6
<form th:action="@{/form}" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="name"><br>
头像:<input type="file" name="img"><br>
照片:<input type="file" name="photos" multiple><br>
<input type="submit" name="submit">
</form>

multiple 属性可以支持多文件上传

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
@Slf4j
@Controller
public class MyController {
@PostMapping("/form")
public String test8(@RequestParam("name") String name,
@RequestPart("img") MultipartFile img,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("接收的信息:name={},img={},photos={}", name, img.getSize(), photos.length);
UUID uuid = UUID.randomUUID();
if (!img.isEmpty()) {
String fileName=img.getOriginalFilename();
String hz = fileName.substring(fileName.lastIndexOf("."));
//保存到指定目录
img.transferTo(new File("H:\\"+uuid + hz));
File file = new File("H:\\photos");
if (!file.exists())
file.mkdir();
for (int i=0;i<photos.length;i++) {
uuid = UUID.randomUUID();
photos[i].transferTo(new File(file.getName() + "\\" + uuid + hz));
}
}
return "hello";
}
}

Tomcat 接受到的文件会先默认放在一个指定的临时文件夹,误删后上传会报错

1
2
3
4
5
6
#设置单文件上传大小
spring.servlet.multipart.max-file-size=10MB
#设置多文件上传大小
spring.servlet.multipart.max-request-size=100MB
#设置临时文件夹,
spring.servlet.multipart.location=H:\\temp

文件上传自动配置类:MultipartAutoConfiguration -> MultipartProperties

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】

  • 原理步骤:

    • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
    • 2、参数解析器来解析请求中的文件内容封装成MultipartFile
    • 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile> FileCopyUtils。实现文件流的拷贝

⑧异常处理

默认情况下,SpringBoot 提供/error 处理所有错误映射,对于浏览器客户端,以HTML格式呈现错误数据,对于其它客户端生成JSON响应相同错误数据

我们可以在templates目录下创建 error 文件夹,SpringBoot会自动解析其目录下的4xx、5xx页面(有精确的状态码页面就匹配精确,没有就找 4xx.html)

⑨Web原生组件注入(Servlet、Filter、Listener)

官方文档表示SpringBoot提供嵌入式Servlet容器支持,不但可以自动配置多种服务器(Tomcat, Jetty, and Undertow),还支持使用Servlet API

方式一(推荐):

1
2
3
When using an embedded container, automatic registration of classes annotated with @WebServlet, @WebFilter, and @WebListener can be enabled by using @ServletComponentScan.

当使用嵌入式容器时,可以使用@ServletComponentScan启用用@WebServlet、@WebFilter和@WebListener注释的类的自动注册。
  • @ServletComponentScan(basePackages = “com”) :指定原生Servlet组件位置
  • @WebServlet(urlPatterns = “/my”):效果:直接响应,不经过Spring的拦截器
  • @WebFilter(urlPatterns={“/css/*“,”/images/*“})
  • @WebListener

示例:

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
@ServletComponentScan(basePackages = {"com"})//不写默认是当前配置类所在及以下的包
@SpringBootApplication(scanBasePackages = "com")
public class Demo2Application {
public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}

}

@WebServlet(urlPatterns = "/servlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// resp.sendRedirect("index.html");
req.getRequestDispatcher("/hello").forward(req,resp);
// resp.getWriter().write("Servlet!");
}
}

@Slf4j
@WebFilter("/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter init 初始化");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("执行过滤操作");
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {
log.info("过滤器销毁");
}
}

@Slf4j
@WebListener
public class MyListener implements ServletContextListener {

@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("监听器初始化");
ServletContextListener.super.contextInitialized(sce);
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("监听器销毁");
ServletContextListener.super.contextDestroyed(sce);
}
}

“/*” 是原生servlet的写法,”/**”是Spring生态的写法

我们运行后会发现:原生select不经过拦截器

此时服务器中有两个servlet,DispatcherServlet 走Spring流程,而MyServlet 由Tomcat处理。

  • DispatcherServlet :filter -> interceptor -> handler
  • MyServlet :filter -> servlet
  • 都能处理到同一层路径,精确优选原则

方式二:

1
2
3
If convention-based mapping is not flexible enough, you can use the ServletRegistrationBean, FilterRegistrationBean, and ServletListenerRegistrationBean classes for complete control.

如果基于约定的映射不够灵活,则可以使用ServletRegistrationBean、FilterRegistrationBean和ServletListenerRegistrationBeans类来实现完全控制。

不推荐这种方式,官方给的注意项较多,用不好容易出bug

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
@Configuration
public class MyRegistConfig {

@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();

return new ServletRegistrationBean(myServlet,"/my","/my02");
}


@Bean
public FilterRegistrationBean myFilter(){

MyFilter myFilter = new MyFilter();
//return new FilterRegistrationBean(myFilter,myServlet());
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
return filterRegistrationBean;
}

@Bean
public ServletListenerRegistrationBean myListener(){
MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
return new ServletListenerRegistrationBean(mySwervletContextListener);
}
}

SpringBoot底层也是通过 ServletRegistrationBean<DispatcherServlet>把 DispatcherServlet 配置进来,默认映射的是 / 路径

⑩切换嵌入式容器

默认支持的webServer

  • Tomcat, Jetty, or Undertow
  • ServletWebServerApplicationContext容器启动寻找ServletWebServerFactory并引导创建服务器

先在web场景包中排除tomcat依赖,再引入其它服务器依赖:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>

原理:

  • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext

  • 该容器启动时会寻找 ServletWebServerFactory(Servlet 的web服务器工厂---> Servlet 的web服务器)

  • SpringBoot底层默认有很多的WebServer工厂:TomcatServletWebServerFactory, JettyServletWebServerFactory, UndertowServletWebServerFactory

  • 底层有一个自动配置类:ServletWebServerFactoryAutoConfiguration

  • 它导入了ServletWebServerFactoryConfiguration(配置类)

  • 配置类动态判断导入了哪个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory

  • TomcatServletWebServerFactory创建出Tomcat服务器并启动;TomcatWebServer的构造器拥有初始化方法initialize---this.tomcat.start();

  • 内嵌服务器,其实就是把启动服务器的代码调用

定制化Servlet容器

方式一:

实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>

  • 把配置文件的值和ServletWebServerFactory进行绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);//修改端口号
...
}

}

方式二:
修改配置文件 server.xxx
方式三:
直接自定义ConfigurableServletWebServerFactory

定制化原理

定制化常见方式:

  1. 修改配置文件
  2. xxxxxCustomizer:定制化器,可以改变xxxx的默认规则
  3. 编写自定义的配置类:xxxConfiguration;+ @Bean替换、增加容器中默认组件

如Web应用编写一个配置类实现 WebMvcConfigurer(即可定制化web功能)+ @Bean给容器中扩展一些组件

  1. @EnableWebMvc + WebMvcConfigurer

原理

  1. WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页…..

  2. 一旦使用 @EnableWebMvc 。它会 @Import(DelegatingWebMvcConfiguration.class)

  3. DelegatingWebMvcConfiguration 的作用:只保证SpringMVC最基本的使

    • 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
    • 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
    • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
  4. WebMvcAutoConfiguration 里面的配置要能生效必须满足@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

  5. @EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。

3. 数据访问

SQL

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

SpringBoot在我们导入依赖时会自动给我们配置一个数据源:HikariDataSource

注意:其中没有导入数据库驱动

1
2
3
4
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)

自动配置

DataSourceAutoConfiguration : 数据源的自动配置

  • 修改数据源相关的配置:spring.datasource
  • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
  • 底层配置好的连接池是:HikariDataSource
1
2
3
4
5
6
7
@Configuration(
proxyBeanMethods = false
)
@Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class})
protected static class PooledDataSourceConfiguration {
  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置

  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud

    • 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
    • @Bean@Primary JdbcTemplate;容器中有这个组件
  • JndiDataSourceAutoConfiguration: jndi的自动配置

  • XADataSourceAutoConfiguration: 分布式事务相关的

修改配置项

1
2
3
4
5
6
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

更换数据源

druid官方github地址:https://github.com/alibaba/druid

b8d7c46c0cb7bce926a8e8b49bf74b40.png

整合第三方技术的两种方式:自定义、starter

  1. 自定义:
1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>

创建数据源:

1
2
3
4
5
6
7
8
@Configuration
public class MyConfig(){
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource(){
return new DruidDataSource();
}
}

druid 内置了监控页面:StatViewServlet

StatViewServlet的用途包括:

  • 提供监控信息展示的html页面
  • 提供监控信息的JSON API
1
2
3
4
5
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
StatViewServlet statViewServlet = new StatViewServlet();
return new ServletRegistrationBean<>(statViewServlet, "/druid/*");
}

WebStatFilter:用于采集web-jdbc关联监控的数据

1
2
3
4
5
6
7
8
@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>(webStatFilter);
bean.setUrlPatterns(Arrays.asList("/*"));
bean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return bean;
}

根据官方介绍配置就好,其它还有很多功能,不在此赘述

  1. starter
1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>

分析自动配置:

  • 扩展配置项 spring.datasource.druid
  • DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
  • DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
  • DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
  • DruidFilterConfiguration.class 所有Druid自己filter的配置
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
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver

druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: 123456
resetEnable: false

web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'

filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false

有需要直接按照官方文档配置即可,这里的一小部分仅供参考

整合 MyBatis

MyBatis官方github地址:https://github.com/mybatis

1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>

配置模式

  1. 全局配置文件:mybatis-config.xml
  2. Mapper 映射文件

底层自动配置了 SqlSessionFactory 和 SqlSessionTemplate (组合了SqlSession)

1
2
3
4
5
6
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}

@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties

注解模式

  • 编写mapper接口,@Mapper注解或配置类上@MapperScan(“com.mapper”)
  • 编写sql映射文件并绑定mapper接口
  • 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议配置在mybatis.configuration)

@Import(AutoConfiguredMapperScannerRegistrar.class) -> @Mapper

只要我们在接口上注明了@Mapper,底层就会扫描并将代理类放在容器中

MybatisProperties 中也可以看到,我们在配置文件中的内容都可以迁移到application.properties

1
2
3
4
# 配置mybatis规则
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置

Spring配置文件中的配置和mybatis全局配置文件不能重复设置

示例:

1
2
3
4
5
6
7
8
9
@Mapper
public interface CityMapper {

@Select("select * from city where id=#{id}")
public City getById(Long id);
//@Option
public void insert(City city);

}

当然我们也可以使用混合模式,如上的insert方法

@Options 注释可以设置标签的设置项,例如insert标签中的useGeneratedKeys

整合 MyBatis-Plus

引入依赖:

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>

自动配置:

  • MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制

  • 自动配置好SqlSessionFactory 。底层是容器中默认的数据源

  • 自动配置好mapperLocations 。有默认值:classpath*:/mapper/**/*.xml任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下

  • 容器中自动配置了 SqlSessionTemplate

  • @Mapper 标注的接口也会被自动扫描;建议直接使用@MapperScan

Mapper继承 BaseMapper 就可以拥有crud功能,详情见8. MyBatisPlus

NoSQL

引入依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

自动配置:

  • RedisAutoConfiguration 自动配置类。RedisProperties 属性类 –> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自动注入了RedisTemplate<**Object**, **Object**> : xxxTemplate;
  • 自动注入了StringRedisTemplate;k:v都是String
  • 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis
1
2
3
4
5
6
7
8
@Test
void testRedis(){
ValueOperations<String, String> operations
= redisTemplate.opsForValue();
operations.set("hello","world");
String hello = operations.get("hello");
System.out.println(hello);
}

如果使用指定泛型为<String, Object>只能使用@Resource,或者使用对应的工厂类自定义模板

切换 Jedis

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--导入jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

配置:

1
2
3
4
5
6
7
8
9
spring:
redis:
host: 192.168.1.100
port: 6379
password: 123456
client-type: jedis
jedis:
pool:
max-active: 10
下一篇:
SpringBoot-1