SpringBoot 第二章 配置
1、配置文件
SpringBoot使用一个全局的配置文件,配置文件名是固定的;
• application.properties
• application.yml
配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好;
YAML(YAML Ain't Markup Language)
- YAML A Markup Language:是一个标记语言
- YAML isn't Markup Language:不是一个标记语言;
标记语言:
- 以前的配置文件;大多都使用的是 xxxx.xml文件;
- YAML:以数据为中心,比json、xml等更适合做配置文件;
- YAML:配置例子
server: port: 8081
XML:
<server> <port>8081</port> </server>
2、YAML语法
k:(空格)v:表示一对键值对(空格必须有);
- 以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的
server: port: 8081 path: /hello
- 属性和值也是大小写敏感;
值的写法
字面量:普通的值(数字,字符串,布尔)
- k: v:字面直接来写;
字符串默认不用加上单引号或者双引号;
"":双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思 如果转移的话会 \n
name: "zhangsan \n lisi":输出;zhangsan 换行 lisi
'':单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据
name: 'zhangsan \n lisi':输出;zhangsan \n lisi
对象、Map(属性和值)(键值对):
k: v:在下一行来写对象的属性和值的关系;注意缩进
- 对象还是k: v的方式
friends: lastName: zhangsan age: 20
- 行内写法:
friends: {lastName: zhangsan,age: 18}
数组(List、Set):
- 用- 值表示数组中的一个元素
pets: - cat - dog - pig
- 行内写法
pets: [cat,dog,pig]
3、需要导入对应依赖包
<!--导入配置文件处理器,配置文件进行绑定就会有提示--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> 测试包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
- 注意一:在每一个属性需要添加空格 属性名:空格 属性值
- 注意二:需要增加get set 方法来设置值
- 注意三:isStudent => getStudent =x=> yaml中isStudent匹配不上
- 注意四:需要加入到容器中
/** * 将配置文件中配置的每一个属性的值,映射到这个组件中 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定; * prefix = "person":配置文件中哪个下面的所有属性进行一一映射 * 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能; */ @Component @ConfigurationProperties(prefix = "person") public class Person { private String nam; private Integer age; private Boolean student; private ArrayList<String> book; 略get set方法 + 略toString() }
测试类@RunWith(SpringRunner.class) @SpringBootTest public class MyTest { @Autowired private Person person; @Test public void myTestFunc(){ System.out.println(person); } } Person{name='cznczai', age=18, student=false, book=[java编程思想, 算法导论, 算法]}
application.yaml文件person: name: cznczai age: 18 Student: true book: {java编程思想,算法导论,算法}
application.properties文件 其他代码跟yaml一致person.name=cznczai person.age=18 person.book=java,c,python person.student=true Person{name='cznczai', age=18, student=true, book=[java, c, python]}
4、@Value
获取值和@ConfigurationProperties获取值比较
普通的配置文件
<bean class="Person"> <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property> </bean>
@value的代码
@Component public class Person { @Value("${person.name}") private String name; @Value("#{20+2}") private Integer age; @Value("true") private Boolean student; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", student=" + student + '}'; } } //Person{name='cznczai', age=22, student=true} @Component @Validated public class Person { @Value("${person.name}") @Email private String name; } 校验没出意外 不支持Validate校验
@ConfigurationProperties情况
@Component @ConfigurationProperties(prefix = "person") public class Person { private String name; private Integer age; private Boolean student; //必要的set方法 public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setStudent(Boolean student) { this.student = student; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", student=" + student + '}'; } } //不是一个合法的电子邮件地址 @Component @ConfigurationProperties(prefix = "person") @Validated public class Person { @Email private String name; } //person.age=#{18*2} 出错 报异常
控制层获取取值
@RestController public class MyController { @Value("${name}") private String name; @RequestMapping("hello") public String hello(){ return "hello,"+name; } }
配置文件yml还是properties他们都能获取到值;
如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;
@PropertySource:加载指定的配置文件;
@PropertySource(value="classpath:myperson.properties") @Component @ConfigurationProperties(prefix = "my") //与PropertySource搭配使用 设置前缀 //注释后 即使是没有前缀 也是没有值的 也就是失效了 不可以注释 public class Person { private String name; private Integer age; private Boolean student; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setStudent(Boolean student) { this.student = student; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", student=" + student + '}'; } } person.name=cznczai person.age=18 person.student=true my.name=wcl my.age=20 my.student=true
5、@ImportResource
导入Spring的配置文件,让配置文件里面的内容生效;spring.xml
Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;
想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上
@SpringBootApplication @ImportResource(locations = "classpath:spring.xml") //需要放在这里 但是没有执行main方法 public class MySpringBootApplication { public static void main(String[] args) { System.out.println("Main~~"); SpringApplication.run(MySpringBootApplication.class,args); } } <bean id="person" class="com.project.bean.Person"></bean> @Test public void myTestFunc(){ System.out.println(ioc.containsBean("person"));//true }
6、springboot推荐的方法
SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式
/** * @Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件 * 在配置文件中用<bean><bean/>标签添加组件 */ @Configuration //第一种方式 //@ImportResource(locations = "classpath:spring.xml") public class MyConfig { @Bean //第二种方式 //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名 public Person person(){ return new Person(); } }
7、占位符配置
占位符获取之前配置的值,如果没有可以是用:指定默认值
- RadomValuePropertySource:配置文件中可以使用随机数
${random.value} ${random.int} ${random.long} ${random.int(10)} ${random.int[1024,65536]}
- 属性配置占位符
@Component @ConfigurationProperties(prefix = "person") public class Person { } person.name=cznczai-${random.uuid} person.age=${random.int} person.student=${person.hello:true} :后面是默认值 Person{name='cznczai-8fc4ca51-eafd-490c-96e5-741474cac04b', age=2003572828, student=true}
8、多Profile文件
Profile是Spring对不同环境提供不同配置功能的支持,可以通过激活、指定参数等方式快速切换环境
- 多profile文件形式:
-格式:application-{profile}.properties:dev/prod - 多profile文档块模式
- 激活方式
-命令行--spring.profiles.active=dev
-配置文件 spring.profiles.active=dev
-jvm参数-Dspring.profiles.active=dev
我们在主配置文件编写的时候,文件名可以是
application-{profile}.properties/yml
默认使用application.properties的配置;
修改配置
------>application.yml<------ spring: profiles: active: dev server: port: 8081 ------>application-dev.yml<-------- server: port: 8085
同个配置文件 多个运行环境
server: port: 8081 spring: profiles: active: dev --- server: port: 8083 spring: profiles: dev --- server: port: 8085 spring: profiles: prod
在配置文件中指定
- 通过命令行配置
- 配置第二步
- 结果
即使在配置文件中已经配置了prod环境
命令行:java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;可以直接在测试的时候,配置传入命令行参数
虚拟机参数:-Dspring.profiles.active=dev
8、配置文件加载位置
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件
- –file:./config/ 最高
- –file:./
- –classpath:/config/
- –classpath:/ 最低
优先级由高到底,高优先级的配置会覆盖低优先级的配置;同种配置的话 先加载的最高还是会覆盖掉最低优先级的
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
config/application.yml 优先级√
server: port: 8087
application.yml
server: port: 8081 server.context.path=/boot <- 互补配置 不是说看最高优先级的配置文件就不看了
我们还可以通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;
9、外部配置加载顺序
SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置
修改默认配置
命令行参数
所有的配置都可以在命令行上进行指定
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc
多个配置用空格分开; --配置项=值来自java:comp/env的JNDI属性
Java系统属性(System.getProperties())
操作系统环境变量
RandomValuePropertySource配置的random.*属性值
由jar包外向jar包内进行寻找;
优先加载带profile
jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
再来加载不带profile
jar包外部的application.properties或application.yml(不带spring.profile)配置文件
jar包内部的application.properties或application.yml(不带spring.profile)配置文件
@Configuration注解类上的@PropertySource
通过SpringApplication.setDefaultProperties指定的默认属性
直接在同个文件夹增加信息的配置文件
启动就会加载外面的路径
10、自动配置原理
1)、SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration
2)、@EnableAutoConfiguration 作用:
- 利用AutoConfigurationImportSelector给容器中导入一些组件?【原先是有EnableAutoConfigurationImportSelector的类 但是归并到AutoConfigurationImportSelector】
利用selectImports() 来导入组件 @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry( autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
public String[] selectImports(AnnotationMetadata annotationMetadata) { AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata); } || 旧代码编程了现在的代码 \/ protected AutoConfigurationEntry getAutoConfigurationEntry( List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); } || \/ protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); } 扫描所有jar包类路径下 META-INF/spring.factories 把扫描到的这些文件的内容包装成properties对象 从properties中获取到getSpringFactoriesLoaderFactoryClass()==>EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中 || \/ public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); //url包装成properties URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); }
抽取一部分
将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;
每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
3)、每一个自动配置类进行自动配置功能;
||
\/
4)、以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;
代码
@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件 @EnableConfigurationProperties(HttpProperties.class) //启动指定类的ConfigurationProperties功能;并把HttpProperties加入到ioc容器中【之前的HttpEncodingProperties再新版本没有了 变成了静态内部类public static class Encoding {}】 @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; //判断当前应用是否是web应用,如果是,当前配置类生效 @ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的 //即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的; public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final HttpProperties.Encoding properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取//@EnableConfigurationProperties的作用 @ConditionalOnMissingBean//没有就配置 public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); return filter; } }
根据当前不同的条件判断,决定这个配置类是否生效?
一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
5)、所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘;配置文件能配置什么就可以参照某个功能对应的这个属性类
- 代码
@ConfigurationProperties(prefix = "spring.http")//从配置文件中获取指定的值和bean的属性进行绑定 public class HttpProperties { public static class Encoding { public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; } 字段 也是配置文件配置目标 字段 }
精髓:
1)、SpringBoot启动会加载大量的自动配置类
2)、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;
3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;
xxxxAutoConfigurartion:自动配置类; 需要♥
给容器中添加组件
xxxxProperties:封装配置文件中相关属性; 目标♥
@Conditional
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;@Conditional扩展注解 作用(判断是否满足当前指定条件) @ConditionalOnJava 系统的java版本是否符合要求 @ConditionalOnBean 容器中存在指定Bean; @ConditionalOnMissingBean 容器中不存在指定Bean; @ConditionalOnExpression 满足SpEL表达式指定 @ConditionalOnClass 系统中有指定的类 @ConditionalOnMissingClass 系统中没有指定的类 @ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean @ConditionalOnProperty 系统中指定的属性是否有指定的值 @ConditionalOnResource 类路径下是否存在指定资源文件 @ConditionalOnWebApplication 当前是web环境 @ConditionalOnNotWebApplication 当前不是web环境 @ConditionalOnJndi JNDI存在指定项 自动配置类必须在一定的条件下才能生效;
我们怎么知道哪些自动配置类生效;
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
Positive matches: (自动配置类启用的) CodecsAutoConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition) .... Negative matches: (没有启动,没有匹配成功的自动配置类) ActiveMQAutoConfiguration: Did not match: - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition) ...
springboot自动配置 idea
先ctrl alt shift n 找 *autoconfiguration