一、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,如图

Springboot什么业务场景会自己写单例 springboot简单例子_spring boot


打开后续parent

Springboot什么业务场景会自己写单例 springboot简单例子_配置文件_02


Springboot什么业务场景会自己写单例 springboot简单例子_加载_03


最后的类即为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

Springboot什么业务场景会自己写单例 springboot简单例子_配置文件_04


@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对比

Springboot什么业务场景会自己写单例 springboot简单例子_spring boot_05


(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

Springboot什么业务场景会自己写单例 springboot简单例子_spring boot_06

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

Springboot什么业务场景会自己写单例 springboot简单例子_spring boot_07


2、通过配置 debug=true属性;来让控制台打印自动配置报告,可以得到哪些类被自动配置,哪些没有自动配置。