一、springboot简介
springboot是对整个Spring技术栈的一个整合,以自动配置的方式简化了简化了Spring应用开发的相关配置。Spring Boot将所有的功能场景都抽取出来,做成一个个的starters(启动器),只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来。
二、入门案例
1、创建一个maven工程
编写一个Hello World案例:浏览器发送hello请求,服务器接受请求并处理,响应Hello World字符串:
(1)导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.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>
</plugin>
</plugins>
</build>
(2)建立标注主程序类
@SpringBootApplication
public class HelloWorldMainApplication {
public static void main(String[] args) {
// Spring应用启动起来
SpringApplication.run(HelloWorldMainApplication.class,args);
}
}
(3)编写Controller
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "Hello World!";
}
}
2、pom依赖探究
pom文件中导入的依赖包括了一个parent,如图
打开后续parent
最后的类即为Spring Boot的版本仲裁中心,对各个依赖的版本进行了管理,故在dependencies里面管理的依赖就不需要声明版本号。
3、@SpringBootApplication分析
(1) @SpringBootApplication:标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用。
(2)初步了解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@SpringBootConfiguration :标注在某个类上,表示这是一个Spring Boot的配置类,底层其实是spring中的@Configuration
@EnableAutoConfiguration:开启自动配置功能,次注解中包含了两个注解,如下
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage :自动配置包中又引入了一个Registrar.class
,AutoConfigurationPackages.Registrar.class将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
@Import(EnableAutoConfigurationImportSelector.class):给容器中导入组件
EnableAutoConfigurationImportSelector:导入哪些组件的选择器;将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中;
会给容器中导入非常多的自动配置类(xxxAutoConfiguration);Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作;ssm中自己配置的东西,自动配置类都帮我们
3、使用Idea搭建springboot环境
(1)使用 Spring Initializer快速创建项目
(2)resources文件夹中目录结构
- static:保存所有的静态资源; js css images;
- templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf);
- application.properties:Spring Boot应用的配置文件;可以修改一些默认设置;
4、配置文件
SpringBoot使用一个全局的配置文件,配置文件名是固定的。
因为SpringBoot在底层都给我们自动配置好了,所以建立配置文件的作用是修改SpringBoot自动配置的默认值;
•application.properties
•application.yml
(1)yml的格式:
server:
port: 8081
(2)yml基本语法
k:(空格)v:表示一对键值对(空格必须有),以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的。属性和值也是大小写敏感;
若漏写空格则会报错Caused by: org.yaml.snakeyaml.scanner.ScannerException: while scanning a simple key
server:
//port 和path为同一层级
port: 8081
path: /hello
(3)yml值得写法
•字面量:普通的值(数字,字符串,布尔):
按照k: v 字面直接来写;
字符串默认不用加上单引号或者双引号;
"":双引号;会转编字符串里面的特殊字符;特殊字符会作为本身想表示的意思
name: "zhangsan \n lisi":输出;zhangsan 换行 lisi
'':单引号;不会转编特殊字符,特殊字符最终只是一个普通的字符串数据
name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi
•对象、Map(属性和值)(键值对):
k: v:在下一行来写对象的属性和值的关系;注意缩进
friends:
lastName: zhangsan
age: 20
//行内写法
friends: {lastName: zhangsan,age: 18}
•数组(List、Set):
用- 值表示数组中的一个元素
pets:
- cat
- dog
- pig
//行内写法
pets:[cat,dog,pig]
5、配置文件注入
person:
lastName: hello
age: 18
boss: false
birth: 2017/12/12
maps: {k1: v1,k2: 12}
lists:
- lisi
- zhaoliu
dog:
name: 小狗
age: 12
配置文件占位符
(1)、随机数
${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}
(1)、占位符获取之前配置的值,如果没有可以是用:指定默认值
person.last-name=张三${random.uuid}
person.age=${random.int}
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.hello:hello}_dog
person.dog.age=15
javaBean:
/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
* @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
* prefix = "person":配置文件中哪个下面的所有属性进行一一映射
*
* 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
*
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
针对配置文件配置时,可以导入配置文件处理器,以后编写配置就有提示了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
@Value:属性注入
public class Person {
/**
* <bean class="Person">
* <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property>
* <bean/>
*/
@Value("${person.last-name}")
private String lastName;
@Value("#{11*2}")
private Integer age;
@Value("true")
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
@Value 与@ConfigurationProperties对比
(1)松散绑定:
@ConfigurationProperties:在上述Person中对lastName进行属性注入时,配置文件既可以写成last-name,也可以写成lastName;
@Value:在上述Person中对lastName进行属性注入时,注入时的value中的值必须与配置文件中属性名称相同;
(2)SpEl:即Spring表达式语言
@ConfigurationProperties:不支持
@Value*:支持,例如:#{2*2}
;
(3)JSR303数据校验:
@ConfigurationProperties*:可通过@Validated
对每个属性进行数据校验
@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {
//判断注入的值是否为email格式
@Email
private String lastName;
@Value*:不支持;
(4)复杂属性封装
@ConfigurationProperties:可以注入map,对象,list等属性
@Value*:只能注入基本类型的数据;
—注意—:
A、单独使用@ConfigurationProperties
注解进行属性注入时默认从全局配置文件中获取值,即application.yml/application.properties
,但是在项目中不可能只在这两个配置文件中中进行配置,故还需配合@PropertySource
一起使用。例如:
@PropertySource(value = {"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
//@Value("${person.last-name}")
private String lastName;
//@Value("#{11*2}")
private Integer age;
//@Value("true")
private Boolean boss;
B、在springboot中没有Spring的配置文件,有时我们自己编写配置文件,但是编写的配置文件并不能直接生效,故需要导入Spring的配置文件,让配置文件里面的内容生效,使用@ImportResource
加在配置类上以导入配置文件使其生效。
@InmportResource(location =“classpath:beans.xml”)
C、SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式
使用@Configuration
配置配置类,使用@Bean
向容器添加组件。
值得注意的是:此处的方法名helloService02
为容器id;
@Configuration
public class MyConfig(){
@Bean
public HelloService helloService02(){
return new HelloService();
}
}
6、Profile
(1)多Profile文件
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml,但是默认使用application.properties
的配置,例如:application-dev.properties、application-dev.properties
若需使用其他配置,则需要进行激活配置
spring.profiles.active=dev
(2)yml支持多文档块方式
使用— — —分块
server:
port: 8081
spring:
profiles:
active: prod
---
server:
port: 8083
spring:
profiles: dev
---
server:
port: 8084
spring:
profiles: prod #指定属于哪个环境
(3)激活制定profie
1、在配置文件中指定 spring.profiles.active=dev
2、cmd命令行:
java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;
可以直接在测试的时候,配置传入命令行参数
3、虚拟机参数;
-Dspring.profiles.active=dev
7、配置文件加载优先级
–file:./config/ 类路径下的config文件下的配置文件优先级最高
–file:./ 类路径下的配置文件优先级其次
–classpath:/config/ 类资源路径下的config文件下的配置文件优先级再次之
–classpath:/ 类资源路径下的配置文件优先级最低
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件,并互补配置;例如:
在高优先级配置端口,低优先级中设置端口和访问路径:
高优先级:server.port=8089
低优先级:server.port=8090 server.servlet.context-path=/web
最终访问为:localhost:8089/web
此外,项目打包好以后,我们可以使用命令行参数spring.config.location
的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置。
还可以根据还可以通过命令行中使用`来改变默认的配置文件位置
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties
8、properties和yml加载顺序
正常的情况是先加载yml,接下来加载properties文件。如果相同的配置存在于两个文件中。最后会使用properties中的配置。
9、外部配置加载顺序
1.命令行参数
所有的配置都可以在命令行上进行指定,多个配置用空格分开; --配置项=值
当需要修改的配置较多时,建议使用外部配置配置文件的方式进行启动。
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc
2.来自java:comp/env的JNDI属性
3.Java系统属性(System.getProperties())
4.操作系统环境变量
5.RandomValuePropertySource配置的random.*属性值
由jar包外向jar包内进行寻找;
优先加载带profile
6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
再来加载不带profile
8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件
9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件
10.@Configuration注解类上的@PropertySource
11.通过SpringApplication.setDefaultProperties指定的默认属性所有支持的配置加载来源;
**
三、自动配置原理
**
1、SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration
2、@EnableAutoConfiguration
获取自动配置类:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
- 利用
EnableAutoConfigurationImportSelector
给容器中导入一些组件。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
- 通过
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)
获取候选的配置
protected AutoConfigurationImportSelector.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 AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
进入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;
}
进入loadFactoryNames()
,执行到返回值值时,调用loadSpringFactories(classLoaderToUse)
,在这个方法中使用classLoader.getResources("META-INF/spring.factories")
去META-INF/spring.factories目录中扫描了各个自动配置类的url,把扫描到的这些文件的内容包装成properties对象,从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加到容器中。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
3、拿到自动配置类后,自动配置。
以HttpEncodingAutoConfiguration
(Http编码自动配置)为例解释自动配置原理,其源码为
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ServerProperties.class})
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
prefix = "server.servlet.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
@Configuration
:表示这是一个配置类可以给容器中添加组件
@EnableConfigurationProperties
:启动ServerProperties
类的@ConfigurationProperties
属性输入功能。将ServerProperties
属性与springboot的配置文件建立映射关系,并把ServerProperties加入到ioc容器中。
@ConditionalOnWebApplication
:可以拆开看,Conditional On Web Application,即判断项目是否在web环境下,如果是,当前配置类生效。
@ConditionalOnClass
:判断当前项目有没有这个类CharacterEncodingFilter过滤器这个类
@ConditionalOnProperty
:判断配置文件中是否存在以spring.http.encoding开头的enabled属性;matchIfMissing = true
表示如果不存在,判断也是成立的,即配置文件中不配置spring.http.encoding.enabled=true
,也是默认生效的;
4、当条件都满足时,将会自动在容器中添加组件对象
在添加对象时,会通过properties调用this.properties.getCharset().name()
获取相关数据,这个propertie使用的就是HttpEncodingAutoConfiguration
中定义的proerties,而在其构造方法中,通过ServerPropertiesd给propertie赋值。又由于该类中只有一个带参构造器,其对象初始化的试的时候就会从容器中去取所需参数对象。就使得propertie的属性值可以通过ServerProperties得到。同时,我们就可以根据peopertis类的需求在配置文件中进行相关配置。
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.RESPONSE));
return filter;
}
补充:
1、@ConditionnalonXX
2、通过配置 debug=true属性;来让控制台打印自动配置报告,可以得到哪些类被自动配置,哪些没有自动配置。