目录
springboot是一种简化springweb开发的框架,类似springmvc,他提供各种默认配置,达到开箱即用、敏捷开发的效果。本文主要介绍springboot的依赖管理、自动配置、web开发、thymeleaf与视图解析、拦截器、文件上传、异常处理、web原生组件注入、整合数据源Druid与Redis、Junit单元测试、spring Acutuator性能监控、高级特性与springboot启动原理。
依赖管理
父项目依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
</parent>
作用:依赖管理及版本管理。
父项目的父项目:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.1</version>
</parent>
其中默认引入了许多依赖,自动版本仲裁无需关注版本,我们可以不用配置某些依赖的版本,但是第三方需要。根据maven就近原则,默认使用本项目pom.xml配置好的依赖,若需要修改依赖版本号:
<properties>
<java.version>8</java.version>
<mysql.version>5.2.41</mysql.version>
</properties>
starter场景启动器
starter也叫依赖管理器,spring-boot-starter-*是用来开发某一场景的一组依赖,引入starter就导入了相关的开发依赖。
starter基本依赖为pring-boot-starter,web开发还需要引入spring-boot-starter-json、spring-boot-starter-tomcat、spring-web、spring-webmvc。
自动配置
启动时加载所有自动配置和组件,条件装配按需配置。自动配置tomcat、自动配置springMVC、自动配置web常见功能、自动扫描springboot主程序所在包及子包下的bean组件。
@SpringBootApplication
注解相当于@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
注解。
自动配置按需加载,pom.xml引入依赖后该依赖的自动配置才会生效。配置会映射到某个配置类中,springboot默认配置会映射到名为xxProperties.class配置类中,配置类也是组件。
@Configuration
声明配置类,参数proxyBeanMethods默认为true,开启代理,即使用cglib生成代理对象,只会在容器中生成一个组件示例。若为false,则不会生成代理对象,每次调用组件都会创建一个实例对象。
@Bean
给容器中添加组件,以类名或方法名做id,返回类型为组件类型,返回值即组件在容器中的实例。@Bean
注解的方法名默认作为对象的名字,也可以用name属性定义对象的名字。@bean
分为两种模式,一种是Lite Mode(轻量模式),这种模式下被@bean
定义的方法需要在@Component
下或者原生类下,效果类似于@Component
注册在类上的效果。@Configuration
下的@bean
被称为是Full mode,bean的创建是通过cglib代理生成的被@Configuration
定义的类的增强类,因为@Configuration
定义的类的bean,默认都是被Spring通过Cglib增强的子类。
@Bean
注解参数:
value:定义bean在IOC容器中的id属性。
name :定义bean在IOC容器中的id属性。
autowire:装配方式
Autowire.NO (默认设置)
Autowire.BY_NAME
Autowire.BY_TYPE
initMethod:指定初始化方法 相当于xml文件中 init-method
destroyMethod:指定销毁的方法 相当于xml文件中 destroy-method
@Bean 和@Component 的区别:
@Bean
和@Component
都是将Spring Bean添加到Spring Context 中。
1)作用域
@Component
注解表明一个类会作为组件类,并告知 Spring 要为这个类创建 bean。@Bean
不能作用在类上,只能作用于方法。
@Bean
注解告诉 Spring 这个方法将会返回一个对象,这个对象要注册为 Spring 应用上下文中的 bean。要获取这个 bean 的时候,Spring 要按照这种方式、去获取这个 bean。
2)注册方式
@Component
注解表明一个类会作为组件类,并告知 Spring 要为这个类创建 bean。@Bean
注解告诉 Spring 这个方法将会返回一个对象,这个对象要注册为 Spring 应用上下文中的 bean。通常方法体中包含了最终产生bean实例的逻辑。
当我们引用第三方库中的类需要装配到 Spring 容器时,则只能通过@Bean
来实现。
3)使用方式
@Component
(@Controller
、@Service
、@Repository
)通常是通过类路径扫描来侦测及自动装配到 Spring 容器中。@Bean
一般结合@Configuration
一起使用,也可以配置在类的方法中。
容器功能注解
组件添加:
1、@Configuration
2、@Bean、@Component、@Controller、@Service
3、@ComponentScan
4、@Conditional
原生配置文件导入:
@ImportResource
配置绑定:
1、@ConfigurationProperties
2、@EnableConfigurationProperties+@ConfigurationProperties
3、@Component+@ConfigurationProperties
@Import({xx.class,xx.class})
,通过类型在容器中创建组件实例,组件名为全类名。
@Conditional
,条件装配注解,满足指定条件进行组件注册。参数value为一个class泛型数组。@ConditionalOnBean(name = “xxx”),当ioc容器中存在该对象时,为该组件注册实例。
@ImportResource("classpath:beans.xml")
,导入spring原生配置文件,支持xml配置。
@EnableConfigurationProperties(xx.class)
,为xx.class开启配置属性绑定。
@AutoConfigurationPackage
,自动配置包原则,利用Register批量地将主程序所在包下的所有组件批量注册进容器。
@ConfigurationProperties(prefix="spring")
,为核心配置文件中的属性绑定前缀,在映射的实体类上添加注解,表示将该类注册成bean并配置属性值。
简化开发
lombok开发
1、在maven项目的pom.xml文件中添加lombok依赖
2、执行maven导入依赖
3、添加@Data注解,免去get和set方法
4、添加@ToString重写toString方法。
spring Initailizr开发,再idea中使用spring Initailizr创建初始化项目,勾选依赖配置和版本,maven自动导入,实现快速初始化项目。
yaml(yaml ain’t markup language)配置,key-value写法,大小写敏感,缩进表层级,#为注释。字符串值用单引号或双引号包围,单引号将\n作为字符串输出,双引号将\n作为换行输出。
注:标记语言是一种将标记以及文本相关的其他信息结合起来,展现出关于文档结构和数据处理细节的文字编码。
spring-boot-configuration-processor配置处理器,显示配置提示信息,添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--在打包项目的时候排除以下简化开发的插件-->
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
web场景开发
src/main/java;src/main/resources都是classes类路径的根路径,下面的所有文件都在该类路径下。当请求来时,java先动态处理,无法处理后寻找静态资源。默认的静态资源路径有[/static,/resources,/META-INF,/public]
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };
欢迎页处理:
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
//要用欢迎页功能,必须是/**
logger.info("Adding welcome page: " + welcomePage.get());
setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
// 调用Controller /index
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
请求处理
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete"/>
<input type="submit" value="delete提交"/>
</form>
核心Filter:HiddenHttpMethodFilter;用法: 表单method=post,隐藏域 _method=put,SpringBoot中手动开启。
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
设置自定义的methodFilter,编写webconfig配置类,创建filter对象,调用HiddenHttpMethodFilter的setMethodParam方法。
Rest原理
-
表单提交会带上
_method=PUT
-
请求过来被
HiddenHttpMethodFilter
拦截 -
请求是否正常,并且是POST
-
获取到
_method
的值。- 兼容以下请求;PUT、DELETE、PATCH
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper。
Rest使用客户端工具,PostMan直接发送Put、delete等方式请求,无需Filter。
请求映射原理:
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet->doDispatch()
RequestMappingHandlerMapping
:保存了所有@RequestMapping
和handler的映射规则。
所有的请求映射都在HandlerMapping中。
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping ,访问 /能访问到index.html
- SpringBoot自动配置了默认的RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。如果有就找到这个请求对应的handler,如果没有就是下一个 HandlerMapping。
基本注解:
路径变量@PathVariable
、获取请求头@RequestHeader
、模型属性@ModelAttribute
、声明请求参数@RequestParam
、获取post请求体@RequestBody
、声明请求属性@RequestAttribute
、矩阵变量@MatrixVariable
、获取cookie值@CookieValue
、请求处理映射路径@RequestMapping
。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/hello")
public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
return String.format("Hello %s!", name);
}
}
Servlet API:
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId。
复杂参数:
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
自定义对象参数:
可以自动类型转换与格式化,可以级联封装
@Data
public class Person {
private String userName;
private Integer age;
private Pet pet;
}
@Data
public class Pet {
private String name;
private String age;
}
参数处理原理
- HandlerMapping中找到能处理请求的Handler(Controller.method())
- 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
- 适配器执行目标方法并确定方法参数的每一个值
参数解析器-HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么,SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
响应请求
响应JSON , jackson+@ResponseBody(返回数据不返回页面视图)
SpringMVC到底支持以下返回值:
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
@ResponseBody---> RequestResponseBodyMethodProcessor;
返回值解析器原理
-
返回值处理器判断是否支持这种类型返回值 supportsReturnType
-
返回值处理器调用 handleReturnValue 进行处理
-
RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的
-
底层利用 MessageConverters 进行处理 ,将数据写为json
-
1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理
-
得到MappingJackson2HttpMessageConverter可以将对象写为json
根据客户端接收能力不同,返回不同媒体类型的数据,只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
导入了jackson处理xml的包,xml的converter就会自动进来:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
开启浏览器参数方式内容协商功能:
spring:
contentnegotiation:
favor-parameter: true
#开启基于请求参数内容协商策略
内容协商原理
1、判断当前响应头中是否已经有确定的媒体类型MediaType。
2、获取客户端支持接收的内容类型。(获取客户端Accept请求头字段)。
contentNegotiationManager内容协商管理器两种内容协商策略:
(1)ParameterContentNegotiationStrategy 基于format参数(mediaType有json;xml)
(2)HeaderContentNegotiationStrategy 默认基于请求头(mediaType为xml)
3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象。
4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。
5、客户端需要【application/xml】,服务端能力【10种、json、xml】。
6、进行内容协商的最佳匹配媒体类型。
7、用支持将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
自定义返回值mediaType数据格式,配置内容协商策略,添加自定义的mediaType类型。
**自定义MessageConverter **
实现多协议数据兼容。json、xml…
(1)、@ResponseBody 返回响应数据出去,调用 RequestResponseBodyMethodProcessor 处理
(2)、Processor 处理方法返回值,通过 MessageConverter 处理
(3)、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
(4)、内容协商找到最终的 messageConverter
thymeleaf
一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。它是一个开源的现代化服务端Java模板引擎,是整合 Spring MVC 的可选模块,在应用开发中,使用 Thymeleaf 来代替 JSP或其他模板引擎。官网
注:由于SpringBoot打包是以jar的方式,不是war。其次我们的tomcat是嵌入式的,所以现在SpringBoot默认不支持jsp。Spring官方文档
基本语法 :
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${…} | 获取请求域、session域、对象等值 |
选择变量 | *{…} | 获取上下文对象值 |
消息 | #{…} | 获取国际化等值 |
链接 | @{…} | 生成链接 |
片段表达式 | ~{…} | jsp:include 作用,引入公共页面片段 |
2、字面量
文本值: ‘one text’ , ‘Another one!’ ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false
空值: null
变量: one,two,… 变量不能有空格
3、文本操作
字符串拼接: +
变量替换: |The name is ${name}|
4、数学运算
运算符: + , - , * , / , %
5、布尔运算
运算符: and , or
一元运算: ! , not
6、比较运算
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )
7、条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
8、特殊操作
无操作: _
迭代:
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
使用thymeleaf
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在相关页面中定义命名空间:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form class="layui-form" lay-filter="userForm" id="userForm">
<!--/*@thymesVar id="myUserName" type="ch"*/-->
<input type="text" th:text="${myUserName}" name="userName" lay-verify="required" placeholder="请输入用户名" autocomplete="off" class="layui-input">
<a href="www.xxxx.com" th:href="${link}">百度</a>
</form>
</body>
</html>
controller返回对应页面:
@RequestMapping("/editUser")
public String editUser(Model model){
User u = userService.getUser();
model.addAttribute("myUserName",u.getUserName());
model.addAttribute("myNickName",u.getNickName());
model.addAttribute("link","www.baidu.com");
return "user";
}
视图解析原理流程
1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer
里面。包括数据和视图地址
2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
4、processDispatchResult 处理派发结果(页面改如何响应)
- render(mv, request, response); 进行页面渲染逻辑
- 根据方法的String返回值得到 View 对象(定义了页面的渲染逻辑),所有的视图解析器尝试是否能根据当前返回值得到View对象,得到了 redirect:/main.html --> Thymeleaf new RedirectView(),
ContentNegotiationViewResolver
里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。view.render(mv.getModelInternal(), request, response),视图对象调用自定义的render进行页面渲染工作。 - RedirectView 如何渲染【重定向到一个页面】
- 1、获取目标url地址
- 2、response.sendRedirect(encodedURL)
视图解析:
1)返回值以 forward: 开始: new InternalResourceView(forwardUrl)–> 转发request.getRequestDispatcher(path).forward(request, response);
2)返回值以 redirect: 开始: new RedirectView() -> render就是重定向
3)返回值是普通字符串: new ThymeleafView()
HandlerInterceptor拦截器
定义拦截器,实现HandlerInterceptor接口,重写preHandler方法:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取session
HttpSession httpSession = request.getSession();
if(httpSession.getAttribute("loginUser")==null){
response.sendRedirect("/f/toLogin");
return false;
}
return true;
}
}
webMvcConfig重写addInterceptor方法:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/","/login.html","/f/toLogin",
"/user/login","/img/*","/layui/**");
}
}
拦截器执行过程:
preHandler->目标方法->postHandler->afterCompletion
原理:
1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有拦截器】
2、先顺序执行 所有拦截器的 preHandle方法
- 如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle。
- 如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion。
3、如果任何一个拦截器返回false。直接跳出不执行目标方法。
4、所有拦截器都返回True,执行目标方法。
5、倒序执行所有拦截器的postHandle方法。
6、前面的步骤有任何异常都会直接倒序触发 afterCompletion
7、页面成功渲染完成以后,也会倒序触发 afterCompletion
文件上传
文件上传自动配置类-MultipartAutoConfiguration
,spring boot自动配置好了 StandardServletMultipartResolver
文件上传解析器。
原理步骤:
1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求。
2、参数解析器来解析请求中的文件内容封装成MultipartFile。
3、将request中多个文件信息封装为一个Map ,MultiValueMap<String, MultipartFile> 。
注:可以FileCopyUtils文件复制工具类的copy方法,实现文件流的拷贝。
核心配置文件中配置spring文件上传大小:
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
前端页面表单:
<!--post提交,上传至/upload,contentType="multipart/form-data"-->
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
后端接口:
/**
* MultipartFile 自动封装上传过来的文件
* @RequestPart从请求中取multipartFile文件
*/
@PostMapping("/upload")
public String upload(@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos)
throws IOException {
return "main";
}
异常处理
错误处理默认规则
-
默认情况下,Spring Boot提供
/error
处理所有错误的映射 -
对于服务器端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器端,响应一个“ WhitelabelErrorView”,以HTML格式呈现。
-
要对其进行自定义,添加
View
解析为error
-
要完全替换默认行为,可以实现
ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。 -
templates/error/下的4xx,5xx页面会被自动解析;
定制错误处理逻辑
-
自定义错误页
-
error/404.html error/5xx.html,有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页。
-
@ControllerAdvice+@ExceptionHandler处理全局异常,底层是使用ExceptionHandlerExceptionResolver
-
@ResponseStatus(返回状态码)+自定义异常 ,底层是使用ResponseStatusExceptionResolver,把@ResponseStatus注解的信息封装成ModelAndView并返回,底层调用 response.sendError(statusCode, resolvedReason)。
-
Spring底层的异常,如参数类型转换异常,使用DefaultHandlerExceptionResolver 处理框架底层的异常。
-
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage())
-
自定义实现 HandlerExceptionResolver接口的异常解析器,可以作为默认的全局异常处理规则。
-
ErrorViewResolver实现自定义处理异常,response.sendError(411,“自定义异常”),error请求就会转给controller。异常没有任何人能处理,则tomcat底层调用response.sendError,error请求就会转给controller。BasicErrorController要去的页面地址是ErrorViewResolver 。
异常处理自动配置
-
ErrorMvcAutoConfiguration 自动配置异常处理规则
-
容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes。
-
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver。
DefaultErrorAttributes:定义错误页面中可以包含哪些数据。
-
容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)。
-
处理默认 /error 路径的请求,页面响应 new ModelAndView(“error”, model)。
- 容器中有组件 View->id是error(响应默认错误页)。
-
容器中放组件 BeanNameViewResolver,按照返回的视图名作为组件的id去容器中找View对象。
-
容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver。
-
如果发生错误,会以HTTP的状态码作为视图页地址(viewName),找到真正的页面。
- viewName:error/404、5xx.html。
如果想要返回页面,就会找error视图StaticView类型的defaultErrorView,默认是一个白页。其中定义了类WhitelabelErrorViewConfiguration。
异常处理步骤流程
1、执行目标方法,目标方法运行期间有任何异常都会被catch,而且标志当前请求结束,并且用 dispatchException封装 。
2、进入视图解析页面渲染
mav = processDispatchResult(processedRequest, response, mappedHandler, mav, dispatchException);
3、处理handler发生的异常,处理完成返回ModelAndView(跳转地址和页面数据)。
-
遍历所有的handlerExceptionResolvers,找到HandlerExceptionResolver处理器异常解析器。
-
默认异常解析器(DefaultErrorAttributes、DefaultHandlerExceptionResolver、ExceptionHandlerExceptionResolver、ResponseHandlerExceptionResolver)
-
DefaultErrorAttributes定义错误信息(exception、status、stack_trace、error、message、path),把异常信息保存到request域,并且返回null。默认没有任何人能处理异常,所以异常会被抛出
-
如果没有任何人能处理最终底层就会发送 /error 请求,会被底层的BasicErrorController处理。解析错误视图,遍历所有的 ErrorViewResolver解析,找到默认的DefaultErrorViewResolver(作用是把响应状态码作为错误页的地址),模板引擎最终响应这个页面error/xxx.html。
Web原生组件注入(Servlet、Filter、Listener)
使用Servlet API
@ServletComponentScan(basePackages = "com.xxx.xxx")
:指定原生Servlet组件都放在那里
@WebServlet(urlPatterns = "/my")
:效果:直接响应,没有经过Spring的拦截器。
@WebFilter(urlPatterns={"/css/\*","/images/\*"})
@WebListener
DispatchServlet 如何注册进来
- 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
- 通过
ServletRegistrationBean<DispatcherServlet>
把 DispatcherServlet 配置进来。 - 默认映射的是 / 路径。
Tomcat-Servlet;
多个Servlet都能处理到同一层路径,精确优选原则
A: /my/
B: /my/1
使用RegistrationBean
ServletRegistrationBean`, `FilterRegistrationBean`, and `ServletListenerRegistrationBean
嵌入式Servlet容器
默认支持的webServer有Tomcat
, Jetty
, or Undertow
,ServletWebServerApplicationContext
容器启动寻找ServletWebServerFactory 并引导创建服务器。
原理
(1) SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat,web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
。ServletWebServerApplicationContext
启动的时候寻找 ServletWebServerFactory
(Servlet 的web服务器工厂—> Servlet 的web服务器)。
(2) SpringBoot底层默认有很多的WebServer工厂,TomcatServletWebServerFactory
, JettyServletWebServerFactory
, or UndertowServletWebServerFactory
。底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration
,它导入了ServletWebServerFactoryConfiguration
。
(3) ServletWebServerFactoryConfiguration
配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有TomcatServletWebServerFactory
,它创建出Tomcat服务器并启动。TomcatWebServer 的构造器拥有初始化方法initialize—this.tomcat.start()。
(4) 内嵌服务器就是手动把启动服务器的代码调用,前提是tomcat核心jar包存在。
定制Servlet容器
-
实现
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
接口的xxxCustomizer定制化器,可以改变xxxx的默认规则。 -
把配置文件的值和ServletWebServerFactory 进行绑定。
-
修改核心配置文件,server.xxx。
-
直接自定义 ConfigurableServletWebServerFactory
-
编写一个配置类实现 WebMvcConfigurer 即可定制化web功能+ @Bean给容器中再扩展一些组件。
Druid数据源
- Druid主要解决的问题就是
传统数据库无法解决的大数据量查询性能的问题
。 - 本质就是
一个分布式支持实时数据分析的数据存储系统
。
在实际应用中,MyBatis 可以利用 Druid 作为其连接池,这样可以避免频繁地创建和关闭连接。MyBatis 会指定 Druid 作为连接池,并从中获取连接。这样MyBatis 从 Druid 中获得连接的管理工作,专注于数据处理本身。总结来说,Druid 为 MyBatis 提供了一个高效且可靠的连接池服务,使得MyBatis能够在不需要手动管理连接的情况下进行数据库操作。
引入druid-starter:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
系统中所有filter:
别名 | Filter类名 |
---|---|
default | com.alibaba.druid.filter.stat.StatFilter |
stat | com.alibaba.druid.filter.stat.StatFilter |
mergeStat | com.alibaba.druid.filter.stat.MergeStatFilter |
encoding | com.alibaba.druid.filter.encoding.EncodingConvertFilter |
log4j | com.alibaba.druid.filter.logging.Log4jFilter |
log4j2 | com.alibaba.druid.filter.logging.Log4j2Filter |
slf4j | com.alibaba.druid.filter.logging.Slf4jLogFilter |
commonlogging | com.alibaba.druid.filter.logging.CommonsLogFilter |
核心配置:
spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/db
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
aop-patterns: com.xxx.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
整合Redis
Redis 是一个开源的内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
核心配置:
spring:
redis:
host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
port: 6379
password: lfy:Lfy123456
#client-type: jedis
# jedis:
# pool:
# max-active: 10
自动配置:
- RedisAutoConfiguration 自动配置类。RedisProperties 属性类 ,spring.redis.xxx是对redis的配置。
- 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration。
- 自动注入了RedisTemplate<Object, Object> : xxxTemplate。
- 自动注入了StringRedisTemplate;k:v都是String。
- 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis。
连接测试:
@Test
void testRedis(){
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello","world");
String hello = operations.get("hello");
System.out.println(hello);
}
单元测试
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库,作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
1)JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
2)JUnit Jupiter: 提供了JUnit5的新的编程模型,是JUnit5新特性的核心,内部包含了一个测试引擎,用于在Junit Platform上运行。
3)JUnit Vintage: 提供了兼容JUnit4.x,Junit3.x的测试引擎。
相关变化:
- 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
- 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
- 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
- 把@Ignore 替换成@Disabled。
- 把@Category 替换成@Tag。
- 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。
注意:SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖,不能使用junit4的功能 @Test,JUnit 5’s Vintage Engine 从spring-boot-starter-test中移除,如果需要继续兼容junit4需要自行引入vintage。
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--兼容junit4-->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
SpringBoot整合Junit以后,编写测试方法:@Test标注(注意需要使用junit5版本的注解),Junit类具有Spring的功能,@Autowired、@Transactional 标注测试方法,测试完成后自动回滚。
JUnit5常用注解
@Test
:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试@ParameterizedTest
:表示方法是参数化测试@RepeatedTest
:表示方法可重复执行@DisplayName
:为测试类或者测试方法设置展示名称@BeforeEach
:表示在每个单元测试之前执行@AfterEach
:表示在每个单元测试之后执行@BeforeAll
:表示在所有单元测试之前执行@AfterAll
:表示在所有单元测试之后执行@Tag
:表示单元测试类别,类似于JUnit4中的@Categories@Disabled
:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore@Timeout
:表示测试方法运行如果超过了指定时间将会返回错误@ExtendWith
:为测试类或测试方法提供扩展类引用
断言assertions
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
1、简单断言
用来对单个值进行简单的验证,前面的断言失败,后面的代码不会执行。
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
2、数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
3、组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
4、异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用**@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows()** ,配合函数式编程就可以进行使用。
@Test
@DisplayName("异常测试")
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0));
}
5、超时断言
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间
@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
6、快速失败
通过 fail 方法直接使得测试失败
@Test
@DisplayName("fail")
public void shouldFail() {
fail("This should fail");
}
前置条件(assumptions)
JUnit 5 中的前置条件类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止直接跳过。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
@DisplayName("assumeTrue")
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
@DisplayName("assumingThat")
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested
注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach
和@AfterEach
注解,而且嵌套的层次没有限制。但是,嵌套内部的@BeforeEach
和@AfterEach
方法不会对外部的@Test
单元测试生效。相反,外层的会对内层的单元测试生效。
class xxx {
@Test
@DisplayName("is empty")
void isEmpty() {}
@Nested
@DisplayName("after")
class After {}
}
参数化测试
参数化测试是JUnit5很重要的一个新特性,用不同的参数多次运行测试,为我们的单元测试带来许多便利。
相关注解:
@ParameterizedTest:代表这是一个参数化测试单元,而不是普通测试单元。
@ValueSource:为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型,使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@NullSource: 表示为参数化测试提供一个null的入参。
@EnumSource: 表示为参数化测试提供一个枚举入参。
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参。
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)。
@ParameterizedTest
@ValueSource(strings = {"x", "xx", "xxx"})
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}
@ParameterizedTest
@MethodSource("method") //指定方法名
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
}
static Stream<String> method() {
return Stream.of("xxx", "xx");
}
注:当然如果参数化测试仅仅只能做到指定普通的入参,他的强大之处的地方在于可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider
接口,任何外部文件都可以作为它的入参。
指标监控Actuator
未来每一个微服务在云上部署以后,都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.x版本与1.x版本不同在于在支持MVC的基础上支持webFlux函数式编程、注解扩展、丰富的安全策略,底层使用MicroMeter。
访问路径:localhost:8080/actuator,localhost:8080/actuator/EndPoint
相关配置:
#management是actuator的配置
management:
endpoints:
enabled-by-default: true #暴露所有JMX端点信息
web:
exposure:
include: '*' #以web方式暴露所有端点
Actuator Endpoint
常用的Endpoints监控端点
ID | 描述 |
---|---|
auditevents | 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件 。 |
beans | 显示应用程序中所有Spring Bean的完整列表。 |
caches | 暴露可用的缓存。 |
conditions | 显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops | 显示所有配置@ConfigurationProperties 。 |
env | 暴露Spring的属性ConfigurableEnvironment |
flyway | 显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway 组件。 |
health | 显示应用程序运行状况信息。 |
httptrace | 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository 组件。 |
info | 显示应用程序信息。 |
integrationgraph | 显示Spring integrationgraph 。需要依赖spring-integration-core 。 |
loggers | 显示和修改应用程序中日志的配置。 |
liquibase | 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase 组件。 |
metrics | 显示当前应用程序的“指标”信息。 |
mappings | 显示所有@RequestMapping 路径列表。 |
scheduledtasks | 显示应用程序中的计划任务。 |
sessions | 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown | 使应用程序正常关闭。默认禁用。 |
startup | 显示由ApplicationStartup 收集的启动步骤数据。需要使用SpringApplication 进行配置BufferingApplicationStartup 。 |
threaddump | 执行线程转储。 |
如果应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID | 描述 |
---|---|
heapdump | 返回hprof 堆转储文件。 |
jolokia | 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core 。 |
logfile | 返回日志文件的内容(如果已设置logging.file.name 或logging.file.path 属性)。支持使用HTTPRange 标头来检索部分日志文件的内容。 |
prometheus | 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus 。 |
最常用的Endpoint
- Health:监控状况
- Metrics:运行时指标
- Loggers:日志记录
Health Endpoint
健康检查端点,一般用于在云平台,平台会定时的检查应用的健康状况,status为up表示健康,为down不健康,web访问路径:localhost:8080/actuator/health。
Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被主动推送或者被动获取方式得到。相关metric访问路径:localhost:8080/actuator/metrics/http.server.requests。
管理Endpoints
1、开启与禁用Endpoints
- 默认所有的Endpoint除过shutdown都是开启的。
- 需要开启或者禁用某个Endpoint,配置模式为
management.endpoint.端点名.enabled = true
management:
endpoint:
health:
show-details: always
enabled: true
- 或者禁用所有的Endpoint然后手动开启指定的Endpoint
management:
endpoints:
enabled-by-default: false
endpoint:
beans:
enabled: true
health:
enabled: true
2、暴露Endpoints
支持的暴露方式
- HTTP:默认只暴露health和info
- JMX:默认暴露所有Endpoint,cmd使用
jconsole
本地访问 - 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
JMX (Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架,提供了一种简单的、标准的监控和管理资源的性能监控方式。
定制 Endpoint
1、定制 Health 信息
监控端点的类名须以HealthIndicator结尾,方式一实现HealthIndicator
接口:
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check();
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
}
方式二继承AbstractHealthIndicator
抽象类:
public class MyhealthHealthIndicator extends AbstractHealthIndicator{
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
Map<String,Object> map = new HashMap<>();
if(1 == 1){
// builder.up(); 健康
builder.status(Status.UP);
map.put("count",1);
map.put("ms",100);
}else {
// builder.down();
builder.status(Status.OUT_OF_SERVICE);
map.put("err","连接超时");
map.put("ms",3000);
}
builder.withDetail("code",100)
.withDetails(map);
}
}
访问:localhost:8080/actuator/health 会返回的所有health信息,包括Myhealth。
2、定制info信息
常用两种方式:
1、编写核心配置文件
info:
appName: MyAdmin
version: 1.0.0
#使用@@可以获取maven的pom文件值
mavenProjectName: @project.artifactId@
mavenProjectVersion: @project.version@
2、编写InfoContributor监控点
import java.util.Collections;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"))
.withDetail("hello","world");
}
}
访问:localhost:8080/actuator/info 会输出以上方式返回的所有info信息。
3、定制Metrics信息
SpringBoot支持自动适配的Metrics。
增加定制Metrics:
class MyService{
Counter counter;
public MyService(MeterRegistry meterRegistry){
//通过Registry注册metrics指标
counter = meterRegistry.counter("myservice.hello.count");
}
public void hello(){
//记录hello方法调用次数
counter.increment();
}
}
//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}
访问:localhost:8080/actuator/metrics/myservice.hello.count,会输出该监控指标信息。
4、自定义Endpoint
使用@EndPoint
注解,属性id为EndPointName。
@Component
@Endpoint(id = "EPname")
public class DockerEndpoint {
@ReadOperation
public Map getDockerInfo(){
return Collections.singletonMap("info","docker started...");
}
@WriteOperation
private void stopDocker(){
System.out.println("docker stopped....");
}
}
访问:localhost:8080/actuator/EPname 获取以上信息。
开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活。
可视化应用监控服务
引入springboot-admin-server依赖:
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.3.1</version>
</dependency>
在主配置类上添加@EnableAdminServer
注解开启admin监控,在核心配置文件中配置:
#admin访问地址
spring.boot.admin.url=http://localhost:8080
#应用以ip注册
spring.boot.admin.instance.prefer-ip=true
#应用名
spring.application.name=hello
management.endpoints.enabled-by-default=true
#暴露所有端点
management.endpoints.web.exposure.include=*
#以web方式暴露所有端点
高级特性与原理解析
Profile功能
为了方便多环境适配,springboot简化了profile功能。
1、application-profile功能
-
默认配置文件 application.yaml,任何时候都会加载。
-
指定环境配置文件: application-{env}.yaml,env可为test、prod。
-
激活指定环境并指定相关配置参数。
激活profile环境方式:
一、配置文件激活:
spring.profiles.active=prod
二、命令行激活:
java -jar xxx.jar --spring.profiles.active=prod --person.name=666
-
修改配置文件的任意值,命令行优先。
-
默认配置与环境配置同时生效。
-
同名配置项,profile配置优先。
2、@Profile
条件装配功能
该注解使类或方法等在指定配置环境下生效。
@Configuration(proxyBeanMethods = false)
@Profile("prod")//该类在生产环境下生效
public class ProductionConfiguration {
// ...
}
3、profile分组
批量加载配置文件,互补配置,注意不要冲突。
spring.profiles.group.myprod[0]=prod
spring.profiles.group.myprod[1]=test
spring.profiles.active=myprod
#激活
外部化配置
1、外部配置源
常用:Java属性文件、YAML文件、环境变量、命令行参数
2、springboot配置文件查找位置
(1) classpath 根路径
(2) classpath 根路径下config目录
(3) jar包当前目录
(4) jar包当前目录的config目录
(5) /config子目录的直接子目录
注:指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
3、配置文件加载顺序
- 当前jar包内部的application.properties和application.yml
- 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
- 引用的外部jar包的application.properties和application.yml
- 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
springboot启动过程
starter启动原理:
- autoconfigure包中配置使用
META-INF/spring.factories
中EnableAutoConfiguration
的值,使得项目启动加载指定的自动配置类。 - 编写自动配置类 xxxAutoConfiguration -> xxxxProperties
@Configuration
@Conditional
@EnableConfigurationProperties
@Bean
过程:引入starter —>xxxAutoConfiguration —> 容器中放入组件 —> 绑定xxxProperties.java —> 配置项
springboot应用启动过程:
-
创建 SpringApplication
- 保存一些信息。
- 判定当前应用的类型。
bootstrappers
:初始启动引导器,去spring.factories文件中找org.springframework.boot.Bootstrapper
- 找 ApplicationContextInitializer初始化器·,去spring.factories找
ApplicationContextInitializer
。 - 找 ApplicationListener应用监听器(事件通知),去spring.factories找
ApplicationListener
。
-
运行 SpringApplication
- StopWatch监控应用启停。
- 记录应用的启动时间。
- 创建引导上下文(Context环境)createBootstrapContext(),获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置。
- 让当前应用进入headless模式。
- 获取所有RunListener(运行监听器),
getSpringFactoriesInstances
去spring.factories
找SpringApplicationRunListener
。 - 遍历
SpringApplicationRunListener
调用 starting 方法,相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。 - 保存命令行参数
ApplicationArguments
- 准备环境 prepareEnvironment()
- 返回或者创建基础环境信息对象StandardServletEnvironment
- 配置环境信息对象,读取所有的配置源的配置属性值
- 绑定环境信息
- 监听器调用
listener.environmentPrepared()
通知所有的监听器当前环境准备完成 - 创建IOC容器createApplicationContext
- 根据项目类型(Servlet)创建容器
- 当前会创建AnnotationConfigServletWebServerApplicationContext
- 准备ApplicationContext IOC容器的基本信息 prepareContext()
- 保存环境信息
- IOC容器的后置处理流程
- 应用初始化器applyInitializers
- 遍历所有的 ApplicationContextInitializer,调用 initialize.来对ioc容器进行初始化扩展功能
- 遍历所有的 listener 调用 contextPrepared,EventPublishRunListenr,通知所有的监听器contextPrepared
- 所有的监听器 调用 contextLoaded,通知所有的监听器 contextLoaded。
- 刷新IOC容器refreshContext,创建容器中的所有组件
- 容器刷新完成后工作afterRefresh
- 所有监听器调用 listeners.started(context),通知所有的监听器started
- 调用所有runners,callRunners()
- 获取容器中的
ApplicationRunner
- 获取容器中的
CommandLineRunner
- 合并所有runner并且按照@Order进行排序
- 遍历所有的runner,调用 run 方法
- 获取容器中的
- 如果以上有异常,调用Listener 的 failed方法。
- 调用所有监听器的 running 方法listeners.running(context),通知所有的监听器应用运行中
- running如果有问题,继续通知 failed ,调用所有 Listener 的 failed,通知所有的监听器 failed