之前学完SSM整合的时候,写了以下的配置文件:
springMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.wzw.jsTest.controller"/>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/html/"/>
<property name="suffix" value=".html"/>
</bean>
<mvc:interceptors>
<mvc:interceptor>
<!-- <mvc:mapping path="/**/**.html"/>-->
<mvc:mapping path="/user/**"/>
<mvc:exclude-mapping path="/user/goLogin"/>
<mvc:exclude-mapping path="/user/goInfo"/>
<mvc:exclude-mapping path="/user/goRegister"/>
<mvc:exclude-mapping path="/user/name"/>
<mvc:exclude-mapping path="/user/session"/>
<mvc:exclude-mapping path="/user/exit"/>
<bean class="com.wzw.jsTest.Interceptor.loginInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/admin/**"/>
<mvc:exclude-mapping path="/admin/goLogin"/>
<mvc:exclude-mapping path="/admin/login"/>
<bean class="com.wzw.jsTest.Interceptor.adminInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.ort/DTD Config 3.0//EN"
"http://mybatis.ort/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.wzw.jsTest.bean"/>
</typeAliases>
<mappers>
<package name="com.wzw.jsTest.mapper"/>
</mappers>
</configuration>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.wzw.jsTest.service"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!-- <context:component-scan base-package="com.wzw.mapper"/>-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.wzw.jsTest.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 启用事务注解-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
</beans>
因为
单单只是整合SSM就已经写了这么多配置文件了,那如果需要整合Spring生态圈的其他应该怎么办?
所以在springboot之前,java的基于spring的开发被称为配置地狱
所以 SpringBoot应运而生,SpringBoot是为了简化配置而出现的,减少繁琐的配置
SpringBoot有什么优点?
Create stand-alone Spring applications
创建独立Spring应用
Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
内嵌web服务器,不用再部署到tomcat了,它内置了web服务器
Provide opinionated 'starter' dependencies to simplify your build configuration
自动starter依赖,简化构建配置,不用导入jar包,避免了版本
Automatically configure Spring and 3rd party libraries whenever possible
自动配置Spring以及第三方功能
Provide production-ready features such as metrics, health checks, and externalized configuration
提供生产级别的监控、健康检查及外部化配置
Absolutely no code generation and no requirement for XML configuration
无代码生成、无需编写XML
SpringBoot是整合Spring技术栈的一站式框架
SpringBoot是简化Spring技术栈的快速开发脚手架
HelloWord
目录结构
![](https://i-blog.csdnimg.cn/blog_migrate/2f3f4b74cd9f948a39c77119762e5809.png)
MainApplication----程序的入口
@SpringBootApplication说明这是一个SpringBoot应用
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}
HelloController----控制器
@RestController = @ResponseBody + @Controller
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "helloWord";
}
}
application.propertiest 配置文件
server.port=8888
pom.xml
引入父版本,一般用于控制版本,就是依赖管理,保证整个项目的版本都是一样的
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springboot-01-helloword</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 自定义版本号-->
<properties>
<mysql.version>5.1.6</mysql.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- 把项目打成jar包,直接在目标服务器执行即可。-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.4.RELEASE</version>
</plugin>
</plugins>
</build>
</project>
自动配置
自动配好Tomcat
引入Tomcat依赖。
配置Tomcat
自动配好SpringMVC
引入SpringMVC全套组件
自动配好SpringMVC常用组件(功能)
public static void main(String[] args) { // 获取容器对象 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 获取容器里自动配置的组件 String[] names = run.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } }
自动配好Web常见功能
SpringBoot帮我们配置好了所有web开发的常见场景
比如:字符编码问题,视图解析器之类的,所以我们不用配置了,Boot自动配置好了
![](https://i-blog.csdnimg.cn/blog_migrate/99b4d4f7d8563dcf1b628dbffd516467.png)
默认的包结构
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
![](https://i-blog.csdnimg.cn/blog_migrate/7fcbeb030e7bb7df9629faf86a544fd5.png)
无需以前的包扫描配置
想要改变扫描路径,
@SpringBootApplication(scanBasePackages="com.wzw")
或者@ComponentScan 指定扫描路径
各种配置拥有默认值
默认配置最终都是映射到某个类上,如:MultipartProperties
配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
按需加载所有自动配置项
非常多的starter
引入了哪些场景这个场景的自动配置才会开启
SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
如果不开启场景的配置,就不会加载(引入)
例子:引入web场景,自动添加web场景相关的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
注解
@Configuration
该注解主要作用在代理对象上
proxyBeanMethods=true 开启代理
该类叫做配置类
@Configuration(proxyBeanMethods=true)
public class MyConfig {
@Bean
public People people(){
Pet cat = new Pet("cat");
People zs = new People("zs", 10, cat);
return zs;
}
@Bean
public Pet pet(){
Pet cat = new Pet("cat");
return cat;
}
}
当proxyBeanMethods=true 的时候
代理对象调用方法的时候,每次SpringBoot都会在容器里这个组件存不存在,如果存在,直接返回,如果不存在,创建一个------保持单实例对象
这句话中的容器指的是ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);容器对象
查找哪个组件?这里的例子是通过代理对象创建bean,所以这个组件就是bean
提一句 个人理解:通过注解注入的对象,比如Controller,Service,bean对象都是组件,或者说交给Spring管理的对象都是组件
当proxyBeanMethods=false 的时候
就不用保持单实例了,每次都创建新的
@Configuration(proxyBeanMethods=true)
<!-- ***************--!>
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
Pet pet3 = bean.pet();
Pet pet4 = bean.pet();
System.out.println(pet3 == pet4);
<!--通过代理对象调用people()方法返回people对象-->
People p3 = bean.people();
People p4 = bean.people();
System.out.println(p3 == p4);
<!-- ***************-->
com.wzw.boot.config.MyConfig$$EnhancerBySpringCGLIB$$6b6ab8ea@7ea4d397
true
true
@Configuration(proxyBeanMethods=false)
<!-- ***************--!>
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
Pet pet3 = bean.pet();
Pet pet4 = bean.pet();
System.out.println(pet3 == pet4);
People p3 = bean.people();
People p4 = bean.people();
System.out.println(p3 == p4);
<!-- ***************-->
com.wzw.boot.config.MyConfig@3003697
false
false
什么时候用True 什么时候用 false?
当一个组件(A)依赖于另一个组件(B)的时候,就要用true,为什么?Full()模式
因为proxyBeanMethods=true 的时候是单实例模式,用到B的时候,不会新建,如果每次都新建那A就会一直变
(新建的是什么暂时不知道,我觉得是对象,但是对象也是组件,所以我输出了前后容器的组件长度,发现并没有变化)
当一个组件(A)不依赖于其他组件的时候,就要用false,为什么? Lite()模式
因为不用每次都检测调用的该方法返回的东西存不存在,会加快SpringBoot的启动速度
上面例子,返回的是People,Pet对象,所以每次返回的时候都要看一下该People,Pet对象存不存在
@Import
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false)
public class MyConfig {
}
@Import({User.class, DBHelper.class})
//给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
//com.wzw.boot.bean.User
@Conditional
@Conditional及其派生注解,在@Conditional上按ctrl+H查看继承树
@Configuration(proxyBeanMethods=true)
public class MyConfig {
// @Bean("cat")
public Pet pet(){
Pet cat = new Pet("cat");
return cat;
}
@Bean("people")
@ConditionalOnBean(name = "cat")
public People people(){
People zs = new People("zs", 10);
zs.setPet(pet());
return zs;
}
}
当存在name=“cat”的组件时,创建name=“people”的组件
boolean people = run.containsBean("people");
System.out.println("people->"+people);
boolean pet = run.containsBean("cat");
System.out.println("cat->"+pet);
people->false
cat->false
@Configuration(proxyBeanMethods=true)
public class MyConfig {
@Bean("cat")
public Pet pet(){
Pet cat = new Pet("cat");
return cat;
}
@Bean("people")
@ConditionalOnBean(name = "cat")
public People people(){
People zs = new People("zs", 10);
zs.setPet(pet());
return zs;
}
}
people->true
cat->true
@ConfigurationProperties()
第一种
@ConfigurationProperties() + @EnableConfigurationProperties()
在配置类上标注:@EnableConfigurationProperties(value = Car.class)
作用:1.开启Car配置的绑定功能 2.把这个Car组件自动注册到容器中
@Configuration(proxyBeanMethods=true)
@EnableConfigurationProperties(value = Car.class)
public class MyConfig {
@Bean("cat")
public Pet pet(){
Pet cat = new Pet("cat");
return cat;
}
@Bean("people")
@ConditionalOnBean(name = "cat")
public People people(){
People zs = new People("zs", 10);
zs.setPet(pet());
return zs;
}
}
Car类
@ConfigurationProperties(prefix = "car")
public class Car {
private String name;
private Double price;
配置文件:application.properties
car.name=xxxx
car.price=100000
检测:
@RestController
public class HelloController {
@Resource
Car car;
@RequestMapping("/car")
public Car car(){
return car;
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/be4c8b4bc757912e9fcc18ff06acd888.png)
第二种
@ConfigurationProperties() + @Component
@ConfigurationProperties(prefix = "car")
@Component
public class Car {
private String name;
private Double price;
这两种方式有什么区别呢?
如果需要引入第三方的类,他第三方没有标注@Component注解,那我们不能去改别人的源码,那就可以使用@EnableConfigurationProperties
自动配置原理
从Main方法出发
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {}
之前我们知道@SpringBootApplication的作用是告诉Spring这个是一个Springboot应用
进入注解@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
发现@SpringBootApplication是一个复合注解
先看 @SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
发现 @SpringBootConfiguration也是一个复合注解,其中@Configuration代表这个注解 注解的类是一个配置类
例如:上面举的例子:
这个就是一个配置类
@Configuration(proxyBeanMethods=true) @EnableConfigurationProperties(Car.class) public class MyConfig { @Bean("cat") public Pet pet(){ Pet cat = new Pet("cat"); return cat; } }
而这个 @SpringBootConfiguration 出现在Main方法上,说明这个类也是一个配置类,说明main方法是一个核心配置类
再看@ComponentScan
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
包扫描
再看@EnableAutoConfiguration
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
@AutoConfigurationPackage自动配置包
@Import({AutoConfigurationPackages.Registrar.class})
发现导入了一个AutoConfigurationPackages.Registrar.class类 注册
通过断点发现这个类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
利用register给容器中导入一系列组件,这个一系列组件在哪找?
其中:(new PackageImports(metadata)).getPackageNames()就是获取了该注解所在类的包名,
![](https://i-blog.csdnimg.cn/blog_migrate/4f3ca44ecdd6c40f1af01456769a83f2.png)
意思就是通过这个包名,导入这个包名下的组件(注册)
在@EnableAutoConfiguration中还有一个@import
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
进入这个类看一下:
第一个方法:selectImports根据名字知道这个方法是选择导入(注册)哪些类,调用了一个本类中叫getAutoConfigurationEntry的方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
进入方法 getAutoConfigurationEntry 根据本类中的getCandidateConfigurations返回一个List集合,名字叫做configurations,能猜测到返回的是所有的组件
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//重点
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/afed0f9df3e2de0118dc0d881a978e6d.png)
第三步:getCandidateConfigurations 调用了一个SpringFactoriesLoader类中的loadFactoryNames,工厂加载器
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
第四步: 进入SpringFactoriesLoader,通过类加载器获取一个资源路径叫做:"META-INF/spring.factories"
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//通过类加载器获取一个资源路径叫做:"META-INF/spring.factories"
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
MultiValueMap<String, String> result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/9746e4f20fbf23274525a14ef1fa80ea.png)
发现跟猜测的一样根据资源路径读取
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
其中Auto Configure写死了127个全限定类
注意!
虽然springboot会一次把所有场景的自动配置导入,但是并不会全部开启,因为有很多类中都加了条件配置@Conditional,只有需要的时候才会开启装配,这就是按需装配
帮助理解
@Import
@Import(要导入的组件)容器会自动注册这个组件
ImportSelector:返回需要导入的组件的全类名数组
ImportBeanDefinitionRegistrar:手动注册
AnnotationMetadata:当前类的注解信息
BeanDefinitionRegistry:BeanDefinition注册类,把所有需要添加到容器的bean,调用BeanDefinitionRegistry.registerBeanDefinition手动注册进来
自动配置dispatcherServlet的类
public class DispatcherServletAutoConfiguration {}
总结
在springboot的底层中总能发现一个现象:springboot会默认在底层配好所有的组件,但是如果用户自己配置了就以用户的优先
springboot会加载所有的自动配置类
每个自动配置类按照条件进行生效(@Conditional),默认都会绑定配置文件指定的值(xxxProperties),而这个xxxProperties又是绑定了一个配置文件,有点抽象?
![](https://i-blog.csdnimg.cn/blog_migrate/c6c13730ce850d273ab35ab1f3281026.png)
![](https://i-blog.csdnimg.cn/blog_migrate/879e4e0a8ba47d2523f0930c44cb2d54.png)
生效的配置类就会给容器中装配组件
只要容器中有这些组件,相当于这些功能就有了
定制化配置
用户直接自己@Bean替换底层的组件,自己写配置
@Bean public CharacterEncodingFilter filter(){ return null; }
用户去看这个组件是获取的配置文件什么值就去修改。
![](https://i-blog.csdnimg.cn/blog_migrate/9b695d9b4c3110a28920fcf50c319712.png)
![](https://i-blog.csdnimg.cn/blog_migrate/52e21e00fb549209101b61c22bdbb9d3.png)
在配置文件中修改:application.properties
server.servlet.encoding.charset=UTF-8
可以设置debug=true,查看自动配置报告
debug=true
自定义器 XXXXXCustomizer;
SpringBoot的run方法做了什么事情?
推断应用的类型是普通的项目还是web项目
查找并加载所有可用初始化器,设置到initializers属性中
找出所有的应用程序监听器,设置到listeners属性中
推断并设置main方法的定义类,找到运行的主类
简化开发
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
@Data
@NoArgsConstructor
//@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class People {
private String name;
private int age;
private Pet pet;
public People(String name, int age) {
this.name = name;
this.age = age;
}
}
@Data 自动生成get,set方法
@NoArgsConstructor 自动生成无参构造方法
@AllArgsConstructor 自动生成有参构造方法,需要定制的时候就自己写
@ToString 自动生成toString方法
@EqualsAndHashCode 自动生成equal和hashcode方法
@Slf4j
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
log.info("hello请求");
return "helloWord";
}
}
@Slf4j 自动导入日志
配置文件yaml
基本语法
key: value;kv之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
'#'表示注释
字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义
字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
对象:键值对的集合。map、hash、set、object
行内写法: k: {k1:v1,k2:v2,k3:v3}
#或
k:
k1: v1
k2: v2
k3: v3
数组:一组按次序排列的值。array、list、queue
行内写法: k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
案例
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "person")
@Component
public class Person {
private String name;
private Integer age;
private List<String> hobbies;
private Map<String,Pet> Pets;
private Map<String,List<String>> address;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Pet {
private String name;
}
person:
name: 张三
age: 20
# hobbies:
# - 篮球
# - 足球
# - 羽毛球
hobbies: [篮球,足球,乒乓球]
# Pets:
# pet1:
# name: 阿花
# pet2:
# name: 阿曹
Pets: {pet3: {name: 啊哈},pet4: {name: 哈哈}}
address:
address1:
- 北京
- 上海
- 深圳
address2:
- 广东
- 广西
结果:
Person(name=张三, age=20, hobbies=[篮球, 足球, 乒乓球], Pets={pet3=Pet(name=), pet4=Pet(name=)}, address={address1=[北京, 上海, 深圳], address2=[广东, 广西]})
yaml的配置值
yaml的配置值在哪看,如何获取?
yaml的值来自于一个自动配置类比如: xxxAutoConfiguration,作用是给容器中添加组件
而这个自动配置类会绑定一个properties比如:xxxProperties,作用是封装配置文件中相关属性
我们需要写的值就在这个绑定的properties当中
xxxConfigurer 这个东西的意思是功能拓展
静态资源
静态资源访问
只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
优先级:resource > static > public
原理: 静态映射/**。
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
改变默认的静态资源路径
spring:
mvc:
static-path-pattern: /res/**
resources:
static-locations: [classpath:/haha/]
相当于说:我访问资源文件的请求:/res/xx,会去haha这个文件夹里面找,而不是去/static (or /public or /resources or /META-INF/resources这里找
WebJars
将资源打包成jar包,以依赖的形式引入:比如说jquery
![](https://i-blog.csdnimg.cn/blog_migrate/fe2a06820dd24357d4bab59e06d80041.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3c2a6b80ca945f70038a6734cbbe377f.png)
那如何访问?这个资源也是在META-INF下的resources包所以是符合springboot的要求的,可以访问到
http://localhost:8888/webjars/jquery/3.5.1/jquery.js
静态资源访问前缀
默认无前缀
spring:
mvc:
static-path-pattern: /res/**
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
欢迎页
静态资源路径下放一个index.html,会自动当作欢迎页
但是如果配置了访问前缀,就不能自动识别index.html和favicon.ico(算是一个bug)
![](https://i-blog.csdnimg.cn/blog_migrate/230327acba1115d9f2b94caf647b50d7.png)
底层写死了要满足static-path-pattern是/**才会转发到index.html页面
restFul风格
默认是禁用了
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"},
matchIfMissing = false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
开启restFul
hidden-method:
filter:
enabled: true
form表单提交
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
![](https://i-blog.csdnimg.cn/blog_migrate/c3b3748c37dcdfeaa9121056aed9d605.png)
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
兼容PUT,DELETE,PATCH提交方式
如果是form表单提交的话,底层会先去看是不是post请求,然后看提交的value里面有没有_method,如果有且开启了restFul,那就将获取的值转成大写,最后new一个HttpMethodRequestWrapper
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper
public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest
由此可见其实这个HttpMethodRequestWrapper也是一个HttpServletRequest,只不过它经过了包装,重写了getMethod()
创建这个HttpMethodRequestWrapper对象的时候需要传一个method进来,把这个“PUT”参数传进去了,最后过滤器链放行的时候传的是wrapper包装类,所以后面的方法使用request的时候,就能获取到“PUT”这个参数,通过getMethod()
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
如果开启了ResulFul,那么每一次form表单提交都会经过这个过滤器校验
总结
所以!在进请求映射之前,先经过这个过滤器,看看是不是post请求
如果是,获取_method,
如果没有,说明是post请求,放行之后,映射器读取request的getMethod(),根据请求方式把请求分发出去,发到post请求的mapping
如果有,经过requestWarpper这个包装类,重写request的getMethod(),把通过_method读取到的value,传进requestWarpper,传入过滤器,此后映射器读取request的getMethod(),现在读取到的就是 PUT , DELETE , PATCH ,根据请求方式分发请求-----> PutMapping DeleteMapping
如果不是,直接放行,说明是get请求
自此所有的请求都区分出来了
拓展
如果需要更改_method怎么办
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"},
matchIfMissing = false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class}) 当没有这个HiddenHttpMethodFilter类的时候,系统自动注册注解的这个类
所以:
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter h = new HiddenHttpMethodFilter();
h.setMethodParam("m");
return h;
}
案例
spring:
mvc:
static-path-pattern: /res/**
hidden-method:
filter:
enabled: true
resources:
add-mappings: true
cache:
period: 10000
@RestController
public class UserController {
// @RequestMapping(value = "/user",method = RequestMethod.GET)
@GetMapping("/user")
public String getUser(){
return "GET-张三";
}
// @RequestMapping(value = "/user",method = RequestMethod.POST)
@PostMapping("/user")
public String saveUser(){
return "POST-张三";
}
// @RequestMapping(value = "/user",method = RequestMethod.PUT)
@PutMapping("/user")
public String putUser(){
return "PUT-张三";
}
// @RequestMapping(value = "/user",method = RequestMethod.DELETE)
@DeleteMapping("user")
public String deleteUser(){
return "DELETE-张三";
}
}
<form method="post" action="http://localhost:8888/user">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="delete提交">
</form>
<form method="get" action="http://localhost:8888/user">
<input type="submit" value="get提交">
</form>
<form method="post" action="http://localhost:8888/user">
<input type="hidden" name="_method" value="put">
<input type="submit" value="put提交">
</form>
<form method="post" action="http://localhost:8888/user">
<input type="submit" value="post提交">
</form>
![](https://i-blog.csdnimg.cn/blog_migrate/94bbc6ed5b702efe525d968be9e133a9.png)
DELETE-张三 GET-张三 PUT-张三 POST-张三
多环境切换
配置文件优先级
file: ./config/
file: ./
classpath:config/
classpath:/
![](https://i-blog.csdnimg.cn/blog_migrate/793ad6218ca0f8972d8c4c80af8aa842.png)
环境切换:
方法1
application.yaml
spring:
profiles:
active: test
application-dev.yaml
application-test.yaml
方法2
在yaml中通过---代表不同的模块
dog:
name: zs
spring:
profiles:
active: dev
---
server:
port: 8083
dog:
name: zsdev
spring:
profiles: dev
---
server:
port: 8082
dog:
name: zstest
spring:
profiles: test
理解:
需要定制化功能的时候,只需要实现接口,然后将它注册到容器里,springboot会根据需要,自己选择场景适用
![](https://i-blog.csdnimg.cn/blog_migrate/0a83fdc9b6877111b2857f2df6cb1f86.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ce16417aa60e7c13025b8325798ddf0c.png)
国际化
i18n