SpringBoot
基础入门-SpringBoot-HelloWorld
系统要求
- Java 8
- Maven 3.3+
- IntelliJ IDEA 2019.1.2
Maven配置文件
新添内容:
1 | <!-- 存放jar包的位置 --> |
HelloWorld项目
需求:浏览发送/hello请求,响应 “Hello,Spring Boot 2”
创建maven工程
引入依赖
1 | <parent> |
创建主程序
1 | import org.springframework.boot.SpringApplication; |
编写业务
1 | import org.springframework.web.bind.annotation.RequestMapping; |
运行&测试
- 运行
MainApplication
类 - 浏览器输入
http://localhost:8888/hello
,将会输出Hello, Spring Boot 2!
。
设置配置
maven工程的resource文件夹中创建application.properties文件。
1 | //设置端口号 |
打包部署
在pom.xml添加
1 | <build> |
在IDEA的Maven插件上点击运行 clean 、package,把helloworld工程项目的打包成jar包,
打包好的jar包被生成在helloworld工程项目的target文件夹内。
用cmd运行java -jar boot-01-helloworld-1.0-SNAPSHOT.jar,既可以运行helloworld工程项目。
将jar包直接在目标服务器执行即可。
运行之后不关闭IDEA在次运行会报端口错误
netstat -ano | findstr “端口号” //查看特定的端口号的信息
taskkill -pid 进程号 -f //根据上面查出的端口号的信息,杀死进程,-或/
或者打开任务管理器–》详细信息–》找到javax的进程–》终止进程
基础入门-SpringBoot-依赖管理特性
父项目做依赖管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
上面项目的父项目如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
它几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制无需关注版本号,自动版本仲裁
- 引入依赖默认都可以不写版本
- 引入非版本仲裁的jar,要写版本号。
可以修改默认版本号
查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
在当前项目里面重写配置,如下面的代码。
1
2
3<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
IDEA快捷键
ctrl + shift + alt + U
:以图的方式显示项目中依赖之间的关系。alt + ins
:相当于Eclipse的 Ctrl + N,创建新类,新包等。
基础入门-SpringBoot-自动配置特性
自动配好Tomcat
引入Tomcat依赖。
配置Tomcat
1
2
3
4
5
6<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
自动配好SpringMVC
引入SpringMVC全套组件
自动配好SpringMVC常用组件(功能)
自动配好Web常见功能,如:字符编码问题
SpringBoot帮我们配置好了所有web开发的常见场景
1
2
3
4
5
6
7
8
9
10public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
默认的包结构
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
无需以前的包扫描配置
想要改变扫描路径
@SpringBootApplication(scanBasePackages=“com.lun”)
@ComponentScan 指定扫描路径
1
2
3
4
5
等同于
//指定扫描的包
各种配置拥有默认值
- 默认配置最终都是映射到某个类上,如:MultipartProperties
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
按需加载所有自动配置项
- 非常多的starter
- 引入了哪些场景这个场景的自动配置才会开启
- SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
基础入门-容器功能
组件(component):
组件也是抽象的概念,可以理解为一些符合某种规范的类组合在一起就构成了组件。他可以提供某些特定的功能。J2EE来说,有什么servlet,jsp, javabean,ejb都是组件。但实际他们都是类,只不过有他们特殊的规定。组件和类的关系:符合某种规范的类的组合构成组件
容器(Container):
容器也叫做组件容器,组件容器是一种比较特殊的组件,它可以包含其他的组件。我们可以把组件放在组件容器中。
组件添加
@Configuration详解
基本使用
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/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)
* Lite(proxyBeanMethods = false)(每个@Bean方法被调用多少次返回的组件都是新创建的)
*/
//告诉SpringBoot这是一个配置类 == 创建了一个配置文件
public class MyConfig {
/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
//给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
// 给组件命名
public Pet tomcatPet(){
return new Pet("tomcat");
}
}最佳实战:如果组件后面还有组件需要用(有依赖关系),则使用Lite模式,减少判断。有依赖关系,使用Full
@Configuration测试代码如下:
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
public class MainApplication {
public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
//3、从容器中获取组件
Pet tom01 = run.getBean("tom", Pet.class);
Pet tom02 = run.getBean("tom", Pet.class);
System.out.println("组件:"+(tom01 == tom02));
// 配置类本身也是组件
//4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
//如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
//保持组件单实例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user == user1);
User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);
System.out.println("用户的宠物:"+(user01.getPet() == tom));
}
}最佳实战
- 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
- 配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)
IDEA快捷键:
Alt + Ins
:生成getter,setter、构造器等代码。Ctrl + Alt + B
:查看类的具体实现代码。
@Import导入组件
@Bean、@Component、@Controller、@Service、@Repository,它们是Spring的基本标签,在Spring Boot中并未改变它们原来的功能。
用法一:
@Import({User.class, DBHelper.class})给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
1 | //告诉SpringBoot这是一个配置类 == 配置文件public class MyConfig {} |
测试类:
1 | //1、返回我们IOC容器 |
**用法二:**定义一个类,来实现ImportSelector
接口的 selectImports方法
1 | public class MyImportSelector implements ImportSelector { |
**用法三:**通过 ImportBeanDefinitionRegistrar 方式导入的类。
这种方式目的是Spring让用户自己可以手动的注入Bean,Spring定义了ImportBeanDefinitionRegistrar这个接口
1 | public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { |
@Conditional条件装配
条件装配:满足Conditional指定的条件,则进行组件注入(Ctrl+H快捷键可以查看实现)

用@ConditionalOnMissingBean举例说明
1 |
|
原生配置文件引入
@ImportResource导入Spring配置文件
比如,公司使用bean.xml文件生成配置bean,然而你为了省事,想继续复用bean.xml,@ImportResource登场。
bean.xml:
1 | <beans ..."> |
使用方法:
1 |
|
测试类:
1 | public static void main(String[] args) { |
配置绑定
@ConfigurationProperties配置绑定
如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用
传统方法:
1 | public class getProperties { |
第一种配置绑定方法
@ConfigurationProperties + @Component
假设有配置文件application.properties
1 | mycar.brand=BYD |
只有在容器中的组件,才会拥有SpringBoot提供的强大功能
1 |
|
第二种配置绑定方法
有些场景是需要给第三方的组件进行配置绑定。没办法加@Component注解
@ConfigurationProperties+@EnableConfigurationProperties
开启Car配置绑定功能
把这个Car这个组件自动注册到容器中
实体类中:
1
2
3
4
5
6
7
public class Car {
private String brand;
private Integer price;
...
}配置类中:
1
2
3
4
public class MyConfig {
...
}
对于自定义配置文件没有提示的问题
第一种方法,添加spring-boot-configuration-processor
依赖。
1 | <dependency> |
第二种方法,使用@ConfigurationProperties
+@EnableConfigurationProperties
注解。
基础入门-自动配置原理入门
结构:
- @SpringBootApplication
- @SpringBootConfiguration
- @Configuration
- @EnableAutoConfiguration
- @AutoConfigurationPackage
- @Import({Registrar.class})
- @Import({AutoConfigurationImportSelector.class})
- @AutoConfigurationPackage
- @ComponentScan()
- @SpringBootConfiguration
Spring Boot应用的启动类:
1 |
|
分析@SpringBootApplication
1 |
|
重点分析@SpringBootConfiguration
,@EnableAutoConfiguration
,@ComponentScan
。
@SpringBootConfiguration
1 |
|
@Configuration
代表当前是一个配置类。
@ComponentScan
指定扫描哪些Spring注解。
@EnableAutoConfiguration(重点)
1 |
|
重点分析@AutoConfigurationPackage
,@Import(AutoConfigurationImportSelector.class)
。
@AutoConfigurationPackage
标签名直译为:自动配置包,指定了默认的包规则。
1
2
3
4
5
6
7
8
9
10
11
//利用Registrar给容器中导入一系列组件,将指定一个包下的所有组件导入进来,注册组件,证明组件的存在,而不是扫描
// AutoConfigurationPackages.Registrar.class:将主类所在的包的类路径下面的所有组件注册进来
public AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}1
2
3
4
5
6
7
8
9
10
11
12
13static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}- 利用Registrar给容器中导入一系列组件
- 将指定的一个包下的所有组件导入进MainApplication所在包下。
@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector中存在
selectImports()
方法,利用getAutoConfigurationEntry(annotationMetadata)
方法给容器中批量导入一些组件getAutoConfigurationEntry()调用
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
方法获取到所有需要导入容器中的配置类getCandidateConfigurations()方法中调用了SpringFactoriesLoader的
loadFactoryNames()
静态方法。该静态方法内部调用本类(SpringFactoriesLoader)的loadSpringFactories()
静态方法。利用工厂加载
Map<String, List<String>> loadSpringFactories(ClassLoader classLoader);
得到所有的组件Enumeration urls = classLoader.getResources("META-INF/spring.factories");
从META-INF/spring.factories加载一个文件- 默认扫描我们当前系统里面所有
META-INF/spring.factories
位置的文件 spring-boot-autoconfigure-2.3.4.RELEASE.jar
包里面也有META-INF/spring.factories
,文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
1
2
3
4
5
6
7
8
9# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
...
按需开启自动配置
@Conditional注解,按条件装配
虽然我们127个场景的所有自动配置启动的时候默认全部加载了,但是哪些生效哪些不生效,按照条件装配规则,最终会按需配置。
总结:
- SpringBoot会在底层配置好所有的组件。但是如果用户自己配置了以用户的优先。
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定)
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了
- 定制化配置
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration —>@EnableConfigurationProperties() 组件 —> xxxxProperties里面拿值 —-> application.properties(yaml)设置的值
最佳实践:
引入场景依赖
查看自动配置了哪些(选做)
1
2# 自动配置报告
debug: true自己分析,引入场景对应的自动配置一般都生效了
配置文件中debug=true开启自动配置报告。
- Negative(不生效)
- Positive(生效)
是否需要修改
参照文档修改配置项
自己分析。xxxxProperties绑定了配置文件的哪些。
自定义加入或者替换组件
- @Bean、@Component…
自定义器 XXXXXCustomizer;
dev-tools
1 | <dependency> |
CTRL+F9重新编译,不用重启项目
付费:JRebel
核心功能
配置文件
YAML
1 |
|
1 | person: |
自定义类绑定的配置提示
添加依赖
1 | <dependency> |
这样,写一些自定义绑定的功能时YAML文件就有提示了
请求参数源码解析
静态资源访问
只要静态资源放在类路径下: /static ( /public,/resources,/META-INF/resources)
访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射/**。
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面。
也可以改变默认的静态资源路径,/static
,/public
,/resources
, /META-INF/resources
失效
1 | resources: |
静态资源访问前缀
1 | spring: |
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
自定义Favicon
指网页标签上的小图标。
favicon.ico 放在静态资源目录下即可。
1 | spring: |
静态资源配置原理(源码分析)
SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
SpringMVC功能的自动配置类
WebMvcAutoConfiguration
,生效1
2
3
4
5
6
7
8
9
10
11
12
13
public class WebMvcAutoConfiguration {
...
}**WebMvcAutoConfigurationAdapter()**给容器中配置的内容:
配置文件的相关属性的绑定:WebMvcProperties==spring.mvc、ResourceProperties==spring.resources
1
2
3
4
5
6
7
8
9
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
...
}配置类只有一个有参构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//有参构造器中的值都从容器中确定
// ResourceProperties resourceProperties; 获取和spring.resources绑定的所有对象的值。
// WebMvcProperties mvcProperties; 获取和spring.mvc绑定的所有对象的值。
// ListableBeanFactory beanFactory; Spring的BeanFactory
// HttpMessageConverters 找到所有的HttpHttpMessageConverters
// ResourceHandlerRegistrationCustomizer; 找到资源处理器的自定义器
// ServletRegistrationBean; 给应用注册原生的servlet、Filter
public EnableWebMvcConfiguration(
ResourceProperties resourceProperties,
WebMvcProperties mvcProperties,
WebProperties webProperties,
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ListableBeanFactory beanFactory) {
this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ?
resourceProperties : webProperties.getResources());
this.mvcProperties = mvcProperties;
this.webProperties = webProperties;
this.mvcRegistrations = (WebMvcRegistrations)mvcRegistrationsProvider.getIfUnique();
this.resourceHandlerRegistrationCustomizer =
(WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)
resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.beanFactory = beanFactory;
}资源处理的默认规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
ServletContext servletContext = this.getServletContext();
//webjars的的规则
this.addResourceHandler(registry, "/webjars/**",
"classpath:/METAINF/resources/webjars/");
//静态资源的访问目录
//mvcProperties中的staticPathPattern属性可以设置静态资源访问前缀,
//getStaticLocations()里有默认的静态资源访问路径,staticLocations属性可以设置路径指定路径
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(),
(registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (servletContext != null) {
registration.addResourceLocations(new Resource[]{
new ServletContextResource(servletContext, "/")});
}
});
}根据上述代码,我们可以同过配置禁用所有静态资源规则。
1
2
3spring:
resources:
add-mappings: 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
26//HandlerMapping:处理器映射器。保存了每一个Handler能处理哪些请求。
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext,
this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(
this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
return welcomePageHandlerMapping;
}
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
if (welcomePage != null && "/**".equals(staticPathPattern)) {
//要使用欢迎页功能,必须使用/**,如果加有前缀,index又存在,视图就不会显示
logger.info("Adding welcome page: " + welcomePage);
this.setRootViewName("forward:index.html");
}
else if (this.welcomeTemplateExists
(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
请求处理-【源码分析】-Rest映射及源码解析
@xxxMapping;
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:
/getUser 获取用户
/deleteUser 删除用户
/editUser 修改用户
/saveUser保存用户 - 现在: /user
GET-获取用户
DELETE-删除用户
PUT-修改用户
POST-保存用户 - 核心Filter;HiddenHttpMethodFilter
- 以前:
使用方法:
开启页面表单的Rest功能
1
2
3
4
5
6# 使用Result风格的请求的时候,默认使用是false,要手动开启
spring:
mvc:
hiddenmethod:
filter:
enabled: true页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<form action="/user" method="get">
<input value="REST-GET提交" type="submit" />
</form>
<form action="/user" method="post">
<input value="REST-POST提交" type="submit" />
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="DELETE"/>
<input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT" />
<input value="REST-PUT提交"type="submit" />
<form>编写请求映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public String getUser(){
return "GET-张三";
}
public String saveUser(){
return "POST-张三";
}
public String putUser(){
return "PUT-张三";
}
public String deleteUser(){
return "DELETE-张三";
}
Rest原理(表单提交要使用REST的时候)
表单提交会带上
\_method=PUT
请求过来被
WebMvcAutoConfiguration
的HiddenHttpMethodFilter
(OrderedHiddenHttpMethodFilter是HiddenHttpMethodFilter的子类)拦截1
2
3
4
5
6
7
8
9
10
11
12
13protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}- 请求是否正常,并且是POST
- 获取到_method的值,如果是小写,转换成大写
- 兼容以下请求;PUT,DELETE,PATCH
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
- 请求是否正常,并且是POST
修该默认的_method的值
@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration { ... @Bean @ConditionalOnMissingBean({HiddenHttpMethodFilter.class}) @ConditionalOnProperty( prefix = "spring.mvc.hiddenmethod.filter",name = {"enabled"},matchIfMissing = false) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); } ... }
将`_method`改成`_m`1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)意味着在没有HiddenHttpMethodFilter时,才执行hiddenHttpMethodFilter()。因此,我们可以自定义filter,改变默认的\_method。例如:
~~~java
@Configuration(proxyBeanMethods = false)
public class WebConfig{
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}1
2
3
4<form action="/user" method="post">
<input name="_m" type="hidden" value="DELETE"/>
<input value="REST-DELETE 提交" type="submit"/>
</form>
请求映射原理(源码分析)
具体映射到那个方法上面处理请求
每个请求都会被DispatcherServlet进行拦截,DispatcherServlet继承FrameworkServlet–>HttpServletBean–>HttpServlet
但是,doGet等方法是在FrameworkServlet中进行重写的,所有方法都调用了本类的
processRequest()
方法在processRequest中又创建了
doService()
的抽象方法,并在子类DispatcherServlet中进行了实现重写的doService()中调用了本类的
doDispatch()
方法SpringMVC功能分析都从
org.springframework.web.servlet.DispatcherServlet
->doDispatch()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = this.getHandler(processedRequest);
//HandlerMapping:处理器映射。/xxx->>xxxx
....
}getHandler()
方法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
// 找到具体的映射处理器
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}this.handlerMappings
在Debug模式下展现的内容:其中,保存了所有
@RequestMapping
和handler
的映射规则。有的请求映射都在HandlerMapping中:
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
SpringBoot自动配置了默认 的
RequestMappingHandlerMapping
请求进来,挨个尝试所有的
HandlerMapping
看是否有请求信息。如果有就找到这个请求对应的handler
如果没有就是下一个 HandlerMapping
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
IDEA快捷键:
- Ctrl + Alt + U : 以UML的类图展现类有哪些继承类,派生类以及实现哪些接口。
- Crtl + Alt + Shift + U : 同上,区别在于上条快捷键结果在新页展现,而本条快捷键结果在弹窗展现。
- Ctrl + H : 以树形方式展现类层次结构图。
请求处理常用参数注解使用
注解:
- @PathVariable 路径变量(Result风格的请求)
- @RequestHeader 获取请求头
- @RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2)
- @CookieValue 获取Cookie值
- @RequestBody 获取请求体[POST]
- @RequestAttribute 获取request域属性(获取请求域中的值)
- @MatrixVariable 矩阵变量
- @ModelAttribute
使用案例:
1 |
|
前端页面:
1 | <body> |
@RequestAttribute 的用法
使用案例:
访问goto请求转发到success中
1 |
|
@MatrixVariable 矩阵变量的用法
语法-请求路径:
- /cars/sell;low=34;brand=byd,audi,yd(将sell分号后面的和sell看成一个整体,多个值以分号分开)
页面开发,cookie的值被禁用了,session怎么使用
session.set(a,b)—>存在jsessionid—>保存在cookie中—>每次请求携带
解决方法:url重写/abc;jsessionid=xxx 把cookie的值以矩阵变量的方式进行传递。
- SpringBoot默认是禁用了矩阵变量的功能
- 手动开启:原理。对于路径的处理。UrlPathHelper的removeSemicolonContent设置为false,让其支持矩阵变量的。
- 矩阵变量必须有url路径变量才能被解析
手动开启矩阵变量
WebMvcAutoConfiguration中有一个内部类实现了WebMvcConfigurer接口,重写了configurePathMatch方法();
1 |
|
两种办法解决:
实现WebMvcConfigurer重写configurePathMatch()方法
向容器中添加组件
1 | @Configuration(proxyBeanMethods = false) |
参数处理原理(源码分析)
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
HandlerMapping中找到处理请求的
Handler
(Controller的哪个方法)为当前找到的Handler招一个适配器HandlerAdapter;
RequestMappingHandlerAdapter
- 0 - 支持方法上标注@RequestMapping
- 1 - 支持函数式编程
执行目标方法
在RequestMappingHandlerAdapter类里面执行了目标方法
1
2
3
4
5RequestMappingHandlerAdapter
// 执行目标方法
mav = invokeHandlerMethod(request, response, handlerMethod);
// invokeHandlerMethod 方法里面执行了 invokeAndHandle
invocableMethod.invokeAndHandle(webRequest, mavContainer);
参数解析器(invokeHandlerMethod方法里面)
确定将要执行的目标方法的每一个参数值是什么
当前解析器是否支持解析这种参数,是就调用resolveArgument进行解析
1 |
|
返回值处理器(invokeHandlerMethod方法里面)
确定目标方法每一个参数的值
挨个判断所有参数解析器
1 | ServletInvocableHandlerMethod |
1 | protected Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { |
解析这个参数的值
1 | 调用HandlerMethodArgumentResolver的resolveArgument方法即可 |
自定义类型参数 封装POJO
ModelAttributeMethodProcessor
这个参数解析器支持自定义对象参数。
1 | public static boolean isSimpleValueType(Class<?> type) { |
1 |
|
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
web数据绑定器,将请求参数的值绑定到指定的javaBean里面。
WebDataBinder 利用它里面的Converters将请求数据转成指定的数据类型。
目标方法执行完成
将所有数据放在ModelAndViewContainer
里面;包含要去的页面地址View,还包含Model的数据。
处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
复杂参数
1 | // Controller用一下参数接受时 |
Map和Model类型的参数会返回mavContainer.getModel()获取到值的。
1 | render(mv, request, response); |
自定义对象参数原理
1 |
|
ModelAttributeMethodProcessor
这个参数解析器支持自定义对象参数。
自定义Converter
1 |
|
数据响应与内容协商
返回值解析器原理
只要导入了spring-boot-starter-web的依赖,就会自动导入json的依赖
1 | <dependency> |
1 | public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, |
1 |
|
返回值处理器判断是否支持这种类型的返回值supportsReturnType
,如果支持在调用返回值处理器handleReturnValue
1 |
|
RequestResponseBodyMethodProcessor
可以处理返回值标注了@ResponseBody注解的。
利用
MessageConverters
进行处理 将数据写为json- 内容协商(浏览器会以请求头的方式告诉服务器,它能接受什么样的类型)
- 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据。
- SpringMVC会挨个遍历所有容器底层的消息转换器
HttpMessageConverter
HttpMessageConverter看是否支持将此Class类型的数据转成MediaType类型的数据。
举例:是否支持将Person类型的数据转换成json类型的数据。
- 得到MappingJackson2HttpMessageConverter可以将对象转成JSON
系统默认的MessageConverter
内容协商
根据不同的客户端接收能力不同,返回不同类型的数据。
比如说:网页返回JSON数据,安卓客户端返回xml类型的数据。
引入xml依赖
1 | <dependency> |
内容协商原理
1 | protected <T> void writeWithMessageConverters( T value, MethodParameter returnType, |
- 判断当前响应头中是否已经有确定的媒体类型。MediaType。
- 获取客户端支持接收的内容类型。(获取客户端的Accepte请求头)
- 遍历当前所有系统的MessageConverter,看谁支持操作这个对象(返回值:person)
- 找到支持操作person的
消息转换器MessageConverter
,把converter支持的所有媒体类型统计出来。

- 客户端需要application/xml。服务端的能力【10种】

- 进行内容协商的最佳匹配
- 用支持将对象转为最佳匹配的媒体类型的converter。将它进行转换。
开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能。
1 | spring: |
请求参数中添加format
参数
发请求:http://localhost:8080/person?format=json
http://localhost:8080/person?format=xml
自定义 MessageConverter
1 | public class GuiguMessageConverter implements HttpMessageConverter<Person> { |
1 |
|
thymeleaf的使用
引入thymeleaf的依赖
1 | <dependency> |
自动配好的策略
所有thymeleaf的配置值都在 ThymeleafProperties
配置好了 SpringTemplateEngine主引擎
配好了 ThymeleafViewResolver视图解析器
我们只需要直接开发页面
1
2public static final String DEFAULT_PREFIX = "classpath:/templates/";//模板放置处
public static final String DEFAULT_SUFFIX = ".html";//文件的后缀名
视图解析源码分析
- 目标方法处理过程中,所有数据都会被放在
ModelAndViewContainer
里面。包括数据和视图地址。 - 方法的参数是一个自定义类型对象(从请求参数中确定的)。把重新放在
ModelAndViewContainer
里 - 任何目标方法执行完成都会返回一个ModelAndView对象(数据和视图地址)。
- processDispatchResult 处理派发结果(页面如何响应)
- render(mv,request,response);进行页面渲染
- 根据方法的String返回值得到View对象
- 所有视图解析器尝试是否能根据当前返回值得到View对象
- 得到了 redirect:/main:html –> Thymeleaf new RedirectView()
- ContentNegotiationViewResolver 里面包含了下面所有的视图,内部还是利用下面所有视图解析器得到视图对象。
- 调用view.render(mv.getModelInternal(), request, response);进行页面渲染工作
- 根据方法的String返回值得到View对象
- render(mv,request,response);进行页面渲染
拦截器
- 编写一个拦截器实现
HandlerInterceptor
接口 - 拦截器注册到容器中(实现
WebMvcConfigurer
的addInterceptors()
) - 指定拦截规则(注意,如果是拦截所有,静态资源也会被拦截
使用示例:
编写一个实现HandlerInterceptor
接口的拦截器:
1 | /** |
拦截器注册到容器中 && 指定拦截规则:
1 |
|
拦截器执行机制
- 根据当前请求,找到HandlerExecutionChin【可以处理请求的handler的所有拦截器】
- 按照先来顺序执行所有拦截器的
preHandle
方法。- 如果当前拦截器返回true。则执行下一个拦截器
- 如果返回false。直接触发倒序执行所有已经执行了的拦截器的
afterCompletion
- 如果任何一个拦截器执行失败。直接跳出,不执行目标方法。
- 所有拦截器返回ture,则执行目标方法
- 倒序执行所有拦截器的
postHandle
。 - 前面步骤有任何异常都会直接触发拦截器的
afterCompletion
- 页面渲染完成后也会倒序触发
afterCompletion
1 | doDispatch |
文件上传和下载
使用案例:
1 | <form th:action="@{/upload}" method="post" enctype="multipart/form-data"> |
1 |
|
1 | # 单次文件上传的大小不能超过 |
文件上传参数解析器
文件上传自动配置类:MultipartAutoConfiguration - MultipartProperties
自动配置好了
StandardServletMultipartResolver
[文件上传解析器]原理步骤
- 请求进来使用文件上传解析器判断(isMultipart)并封装文件上传请求。返回文件上传请求
HttpServletRequest
- 参数解析器解析请求中文件内容封装成MutipartFile
- 将request中的文件信息封装成一个Map
- 请求进来使用文件上传解析器判断(isMultipart)并封装文件上传请求。返回文件上传请求
文件下载
1 |
|
数据访问
SQL
不导入驱动是因为,官方不知道我们接下来操作什么数据库
数据源的自动配置:
导入jdbc的场景
1
2
3
4
5
6
7
8
9
10
11
12<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
<scope>compile</scope>
</dependency>分析自动配置
DataSourceAutoConfiguration:数据源的自动配置
- 因为DataSourceAutoConfiguration与DataSourceProperties进行绑定,修改数据源相关的配置,只需要在配置文件中修改spring.datasource即可
- 数据库连接池的配置,是自己自己容器中没有DataSource才配置的
- 底层配置好的连接池是:HikariDataSource
DataSourceTransactionManagerAutoConfiguration:事务管理器的自动配置
JdbcTemplateAutoConfiguration:JdbcTemplate的自动配置,可以对数据库进行crud操作
- 可以修改**@ConfigurationProperties(prefix = “spring.jdbc”)**来修改
jdbcTemplate
- @Bean @Primary JdbcTemplate,容器中有这个组件,要使用,直接注入即可
XADataSourceAutoConfiguration:分布式事务相关的
修改配置项
1
2
3
4
5
6
7
8
9
10
11spring:
datasource:
url: jdbc:mysql://localhost:3306/dd
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.zaxxer.hikari.HikariDataSource
jdbc:
template:
# 查询超时时间,如果3秒没查到,认为查询超时
query-timeout: 3测试
1
2
3
4
5
void contextLoads() {
Integer integer = jdbcTemplate.queryForObject("select count(*) from login", Integer.class);
System.out.println(integer);
}
数据源的配置
Spring Boot整合第三方技术的两种方式:
- 自定义
- 找starter场景
自定义方式
添加依赖
1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>配置Druid数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyConfig {
//复用配置文件的数据源配置
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
//属性抽取成配置文件
//druidDataSource.setUrl();
//druidDataSource.setUsername();
//druidDataSource.setPassword();
return druidDataSource;
}
}数据源的属性配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14spring:
# 这是数据源的配置
datasource:
url: jdbc:mysql://localhost:3306/dd
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.zaxxer.hikari.HikariDataSource
# 这是jdbcTemplate的配置
jdbc:
template:
# 查询超时时间,如果3秒没查到,认为查询超时
query-timeout: 3更多配置项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_user}" />
<property name="password" value="${jdbc_password}" />
<property name="filters" value="stat" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="6000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
<property name="asyncInit" value="true" />
</bean>配置druid的监控页功能
Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。官方文档 - 配置_StatViewServlet配置。这个StatViewServlet的用途包括:
- 提供监控信息展示的html页面
- 提供监控信息的JSON API
Druid内置提供一个
StatFilter
,用于统计监控信息。官方文档 - 配置_StatFilterWebStatFilter
用于采集web-jdbc关联监控的数据,如SQL监控、URI监控Druid提供了
WallFilter
,它是基于SQL语义分析来实现防御SQL注入攻击的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
public class MyDataSource {
public DataSource dataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
// 开启URI监控功能(stat),wall开启防火墙功能
dataSource.setFilters("stat,wall");
//属性抽取成配置文件
return dataSource;
}
/**
* 配置druid的监控页
* @return
*/
public ServletRegistrationBean statViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean =
new ServletRegistrationBean<>(statViewServlet, "/druid/*");
//设置监控页的账号密码
Map<String,String > map = new HashMap<String,String >();
map.put("loginUsername","admin");
map.put("loginPassword","123456");
registrationBean.setInitParameters(map);
return registrationBean;
}
/**
* 开启web监控
* @return
*/
public FilterRegistrationBean webStatFilter(){
WebStatFilter statFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> registrationBean =
new FilterRegistrationBean<WebStatFilter>(statFilter);
registrationBean.setUrlPatterns(Arrays.asList("/*"));
registrationBean.addInitParameter(
"exclusions","*.js,*.gif,*.jgp,*.png,*.css,*.ico,/druid/*");
return registrationBean;
}
}
数据源整合stater的配置
引入druid-starter
1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>分析自动配置
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
扩展配置项: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
8private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
配置示例:
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
39spring:
# 数据源的配置
datasource:
url: jdbc:mysql://localhost:3306/dd
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.zaxxer.hikari.HikariDataSource
druid:
# 这个包下的所有东西都进行监控,监控spring
aop-patterns: com.thymeleaf.springbootthymeleaf.*
# 开启sql监控功能(stat),开启监控防火墙功能(wall)
filters: stat,wall
#开启了监控页功能
stat-view-servlet:
enabled: true
# 监控页的登录配置
login-username: admin
login-password: 123456
reset-enable: false # 将重置按钮禁用
# web监控功能
web-stat-filter:
enabled: true
url-pattern: /* # 匹配的哪些
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' # 排除哪些
#配置单个的
filter:
stat:
slow-sql-millis: 1000 # 慢查询的世界,单位毫秒,
enabled: true
log-slow-sql: true
wall:
enabled: true
config:
drop-table-allow: false #禁用掉所有删表操
整合Mybatis操作
starter的命名方式:
- SpringBoot官方的Starter:
spring-boot-starter-*
- 第三方的:
*-spring-boot-starter
引入依赖
1 | <dependency> |
配置模式:
- SqlSessionFactory:自动配置好了
- SqlSession:自动配置了SqlSessionTemplate 组合了SqlSession
- @Import(AutoConfiguredMapperScannerRegistrar.class)
- Mapper: 只要我们写的操作MyBatis的接口标准了**@Mapper**就会被自动扫描进来
使用案例:
mapper接口:
1 |
|
对应的映射文件:
1 | <mapper namespace="com.thymeleaf.springbootthymeleaf.mapper.UserMapper"> |
service:
1 |
|
Controller:
1 |
|
yaml:
1 | spring: |
- 简单DAO方法就写在注解上。复杂的就写在配置文件里。
- 使用
@MapperScan("com.lun.boot.mapper")
简化,Mapper接口就可以不用标注@Mapper
注解。
1 |
|
最佳实战:
- 简单方法使用注解
- 复杂方式使用接口绑定映射
Mybatis-Plus整合
你可以通过Spring Initializr添加MyBatis的Starer。
Mybatis-Plus是什么?
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
- **
MybatisPlusAutoConfiguration
配置类,MybatisPlusProperties
**配置项绑定。 - **
SqlSessionFactory
**自动配置好,底层是容器中默认的数据源。 mapperLocations
自动配置好的,有默认值classpath*:/mapper/**/*.xml
,这表示任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件放在 mapper下。- 容器中也自动配置好了SqlSessionTemplate。
- @Mapper 标注的接口也会被自动扫描,建议直接 **@MapperScan(“com.lun.boot.mapper”)**批量扫描。
- MyBatisPlus优点之一:只需要我们的Mapper继承MyBatisPlus的BaseMapper 就可以拥有CRUD能力,减轻开发工作。
Generator
1 | <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator --> |
1 | package com.xinke.springboot.utils; |
NoSQL
Reids的自动配置
1 | <dependency> |
自动配置:
- RedisAutoConfiguration 自动配置类。
- 连接工厂是准备好的。LettuceConnectionConfiguration,JedisConnectionConfiguration
- 自动注入了RedisTemplate<Object, Object>
- 自动注入了StringRedisTemplate。key,value都是String
- key和vue都运行是Object
- 底层只要我们使用StringRedisTemplate,RedisTemplate就可以操作Redis
1 | spring: |
切换jedis
1 | <dependency> |
1 | spring: |
单元测试
Junit5
JUnit Platform:Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
JUnit Jupiter:JUnit Jupiter提供了J儿Unit5的新的编程模型,是儿nit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。
JUnit Vintage:由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4,xJunit3.x的测试引擎。
SpringBoot 2.4以上版本移除了默认对Vintage
的依赖。如果需要兼容junit4需要自行引入〔不能使用junit4的功能Test)
如果要兼容junit4需要自行引入bintage引擎。
1 | <!--junit5--> |
Springboot整合Junit以后
- 编写测试方法:@Test标注
- Junit具有Spring的功能。比如使用@Transactional注解标注测试方法,测试完后会自动回滚。
常用注解
- @Test:表示方法是测试方法。但是与Junit4的@Test不同。他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest:表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest:表示方法可重复执行,下方会有详细介绍
- @DisplayName:为测试类或者测试方法设置展示名称
- @BeforeEach:表示在每个单元测试之前执行
- @AfterEach:表示在每个单元测试之后执行
- @BeforeAll:表示在所有单元测试之前执行
- @AfterAll:表示在所有单元测试之后执行
- @Tag:表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @lgnore@Timeout:表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith:为测试类或测试方法提供扩展类引用
1 |
|
参数化测试
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增—个参数就新增一个单元测试,省去了很多冗余代码。
- @ValueSource:为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
- @NullSource:表示为参数化测试提供一个null的入参
- @EnumSource:表示为参数化测试提供一个枚举入参
- @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
- @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
1 |
|
指标监控
SpringBoot Actuator
未来每一个微服务在云上部署以后,我们都需要对其进行监控追踪、审计、控制等。Springboot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
1 | <dependency> |
使用
1 | management: |
测试:
http://localhost:8080/actuator/beans
最常用的:
- Health:健康状况
- Metrics:运行时指标
- Loggers:日志记录
定制Health信息
1 | management: |
1 |
|
定制metrics
1 | class MyService{ |
定制Endpoint
Profile功能
为了方便多环境适配,springboot简化了profile功能。
- 默认配置文件application.yaml;任何时候都会加载
- 指定环境配置文件application-{env}.yaml
- 激活指定环境
- 配置文件激活
- 命令行激活
- 默认配置与环境配置同时生效
- 同名配置项,profile配置优先
1 |
|
根据环境的不同会取不同的值
1 | application.yaml |
还有一种打成jar包,以某一个环境运行jar包
1 | java -jar jar包 --spring.profiles.active=test |
@Profile
1 |
|
自定义Starter
https://www.bilibili.com/video/BV19K4y1L7MT?p=83&vd_source=56837d02f74d5690be7060165a14c9b6
Starter启动原理
远程调用
Spring给我们提供了一个RestTemplate的API,可以方便的实现Http请求的发送。
- 注入Bean
1 |
|
- 使用
1 | Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet()); |