springboot源码分析

源码分析 — spring容器初始化的过程

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable 


	/** Map from serialized id to factory instance */
	private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories =
			new ConcurrentHashMap<String, Reference<DefaultListableBeanFactory>>(8);

	/** Optional id for this factory, for serialization purposes */
	private String serializationId;

	/** Whether to allow re-registration of a different definition with the same name */
	private boolean allowBeanDefinitionOverriding = true;

	/** Whether to allow eager class loading even for lazy-init beans */
	private boolean allowEagerClassLoading = true;

	/** Optional OrderComparator for dependency Lists and arrays */
	private Comparator<Object> dependencyComparator;

	/** Resolver to use for checking if a bean definition is an autowire candidate */
	private AutowireCandidateResolver autowireCandidateResolver = new SimpleAutowireCandidateResolver();

	/** Map from dependency type to corresponding autowired value */
	private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<Class<?>, Object>(16);

	/** Map of bean definition objects, keyed by bean name */
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

	/** Map of singleton and non-singleton bean names, keyed by dependency type */
	private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);

	/** Map of singleton-only bean names, keyed by dependency type */
	private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);

	/** List of bean definition names, in registration order */
	private volatile List<String> beanDefinitionNames = new ArrayList<String>(256);

	/** List of names of manually registered singletons, in registration order */
	private volatile Set<String> manualSingletonNames = new LinkedHashSet<String>(16);

	/** Cached array of bean definition names in case of frozen configuration */
	private volatile String[] frozenBeanDefinitionNames;

	/** Whether bean definition metadata may be cached for all beans */
	private volatile boolean configurationFrozen = false;

注解驱动理解

spring-boot-dependencies 提供默认依赖版本信息

<activemq.version>5.14.5</activemq.version>
		<antlr2.version>2.7.7</antlr2.version>
		<appengine-sdk.version>1.9.59</appengine-sdk.version>
		<artemis.version>1.5.5</artemis.version>
		<aspectj.version>1.8.13</aspectj.version>
		<assertj.version>2.6.0</assertj.version>
		<atomikos.version>3.9.3</atomikos.version>
		<bitronix.version>2.1.4</bitronix.version>
		<caffeine.version>2.3.5</caffeine.version>
		<cassandra-driver.version>3.1.4</cassandra-driver.version>
		<classmate.version>1.3.4</classmate.version>
		<commons-beanutils.version>1.9.3</commons-beanutils.version>
		<commons-collections.version>3.2.2</commons-collections.version>
		<commons-codec.version>1.10</commons-codec.version>
		<commons-dbcp.version>1.4</commons-dbcp.version>
		<commons-dbcp2.version>2.1.1</commons-dbcp2.version>
		<commons-digester.version>2.1</commons-digester.version>
		<commons-pool.version>1.6</commons-pool.version>
		<commons-pool2.version>2.4.3</commons-pool2.version>
		<couchbase-client.version>2.3.7</couchbase-client.version>
		<couchbase-cache-client.version>2.1.0</couchbase-cache-client.version>
		<crashub.version>1.3.2</crashub.version>
		<derby.version>10.13.1.1</derby.version>
		<dom4j.version>1.6.1</dom4j.version>
		<dropwizard-metrics.version>3.1.5</dropwizard-metrics.version>
		<ehcache.version>2.10.4</ehcache.version>
		<ehcache3.version>3.2.3</ehcache3.version>
		<embedded-mongo.version>1.50.5</embedded-mongo.version>
		<flyway.version>3.2.1</flyway.version>
		<freemarker.version>2.3.27-incubating</freemarker.version>
		<elasticsearch.version>2.4.6</elasticsearch.version>
		<gemfire.version>8.2.7</gemfire.version>
		<glassfish-el.version>3.0.0</glassfish-el.version>
		<gradle.version>2.9</gradle.version>
		<groovy.version>2.4.13</groovy.version>
		<gson.version>2.8.2</gson.version>
		<h2.version>1.4.196</h2.version>
		<hamcrest.version>1.3</hamcrest.version>
		<hazelcast.version>3.7.8</hazelcast.version>
		<hazelcast-hibernate4.version>3.7.1</hazelcast-hibernate4.version>
		<hazelcast-hibernate5.version>1.1.3</hazelcast-hibernate5.version>
		<hibernate.version>5.0.12.Final</hibernate.version>
		<hibernate-validator.version>5.3.6.Final</hibernate-validator.version>
		<hikaricp.version>2.5.1</hikaricp.version>
		<hikaricp-java6.version>2.3.13</hikaricp-java6.version>
		<hikaricp-java7.version>2.4.13</hikaricp-java7.version>
		<hsqldb.version>2.3.5</hsqldb.version>
		<htmlunit.version>2.21</htmlunit.version>
		<httpasyncclient.version>4.1.3</httpasyncclient.version>
		<httpclient.version>4.5.3</httpclient.version>
		<httpcore.version>4.4.8</httpcore.version>
		<infinispan.version>8.2.8.Final</infinispan.version>
		<jackson.version>2.8.10</jackson.version>
		<janino.version>2.7.8</janino.version>
		<javassist.version>3.21.0-GA</javassist.version> <!-- Same as Hibernate -->
		<javax-cache.version>1.0.0</javax-cache.version>
		<javax-mail.version>1.5.6</javax-mail.version>
		<javax-transaction.version>1.2</javax-transaction.version>
		<javax-validation.version>1.1.0.Final</javax-validation.version>
		<jaxen.version>1.1.6</jaxen.version>
		<jaybird.version>2.2.13</jaybird.version>
		<jboss-logging.version>3.3.1.Final</jboss-logging.version>
		<jboss-transaction-spi.version>7.6.0.Final</jboss-transaction-spi.version>
		<jdom2.version>2.0.6</jdom2.version>
		<jedis.version>2.9.0</jedis.version>
		<jersey.version>2.25.1</jersey.version>
		<jest.version>2.0.4</jest.version>
		<jetty.version>9.4.7.v20170914</jetty.version>
		<jetty-jsp.version>2.2.0.v201112011158</jetty-jsp.version>
		<jetty-el.version>8.0.33</jetty-el.version>
		<jms-api.version>1.1-rev-1</jms-api.version>
		<jmustache.version>1.13</jmustache.version>
		<jna.version>4.2.2</jna.version>
		<joda-time.version>2.9.9</joda-time.version>
		<jolokia.version>1.3.7</jolokia.version>
		<jooq.version>3.9.6</jooq.version>
		<json.version>20140107</json.version>
		<jsonassert.version>1.4.0</jsonassert.version>
		<json-path.version>2.2.0</json-path.version>
		<jstl.version>1.2</jstl.version>
		<jtds.version>1.3.1</jtds.version>
		<junit.version>4.12</junit.version>
		<liquibase.version>3.5.3</liquibase.version>
		<log4j2.version>2.7</log4j2.version>
		<logback.version>1.1.11</logback.version>
		<lombok.version>1.16.18</lombok.version>
		<mariadb.version>1.5.9</mariadb.version>
		<mssql-jdbc.version>6.1.0.jre7</mssql-jdbc.version>
		<mockito.version>1.10.19</mockito.version>
		<mongodb.version>3.4.3</mongodb.version>
		<mysql.version>5.1.44</mysql.version>
		<narayana.version>5.5.30.Final</narayana.version>
		<nekohtml.version>1.9.22</nekohtml.version>
		<neo4j-ogm.version>2.1.5</neo4j-ogm.version>
		<postgresql.version>9.4.1212.jre7</postgresql.version>
		<querydsl.version>4.1.4</querydsl.version>
		<reactor.version>2.0.8.RELEASE</reactor.version>
		<reactor-spring.version>2.0.7.RELEASE</reactor-spring.version>
		<selenium.version>2.53.1</selenium.version>
		<selenium-htmlunit.version>2.21</selenium-htmlunit.version>
		<sendgrid.version>2.2.2</sendgrid.version>
		<servlet-api.version>3.1.0</servlet-api.version>
		<simple-json.version>1.1.1</simple-json.version>
		<slf4j.version>1.7.25</slf4j.version>
		<snakeyaml.version>1.17</snakeyaml.version>
		<solr.version>5.5.5</solr.version>
		<spock.version>1.0-groovy-2.4</spock.version>
		<spring.version>4.3.13.RELEASE</spring.version>
		<spring-amqp.version>1.7.4.RELEASE</spring-amqp.version>
		<spring-cloud-connectors.version>1.2.5.RELEASE</spring-cloud-connectors.version>
		<spring-batch.version>3.0.8.RELEASE</spring-batch.version>
		<spring-data-releasetrain.version>Ingalls-SR9</spring-data-releasetrain.version>
		<spring-hateoas.version>0.23.0.RELEASE</spring-hateoas.version>
		<spring-integration.version>4.3.12.RELEASE</spring-integration.version>
		<spring-integration-java-dsl.version>1.2.3.RELEASE</spring-integration-java-dsl.version>
		<spring-kafka.version>1.1.7.RELEASE</spring-kafka.version>
		<spring-ldap.version>2.3.2.RELEASE</spring-ldap.version>
		<spring-loaded.version>1.2.8.RELEASE</spring-loaded.version>
		<spring-mobile.version>1.1.5.RELEASE</spring-mobile.version>
		<spring-plugin.version>1.2.0.RELEASE</spring-plugin.version>
		<spring-restdocs.version>1.1.3.RELEASE</spring-restdocs.version>
		<spring-retry.version>1.2.1.RELEASE</spring-retry.version>
		<spring-security.version>4.2.3.RELEASE</spring-security.version>
		<spring-security-jwt.version>1.0.8.RELEASE</spring-security-jwt.version>
		<spring-security-oauth.version>2.0.14.RELEASE</spring-security-oauth.version>
		<spring-session.version>1.3.1.RELEASE</spring-session.version>
		<spring-social.version>1.1.4.RELEASE</spring-social.version>
		<spring-social-facebook.version>2.0.3.RELEASE</spring-social-facebook.version>
		<spring-social-linkedin.version>1.0.2.RELEASE</spring-social-linkedin.version>
		<spring-social-twitter.version>1.1.2.RELEASE</spring-social-twitter.version>
		<spring-ws.version>2.4.2.RELEASE</spring-ws.version>
		<sqlite-jdbc.version>3.15.1</sqlite-jdbc.version>
		<statsd-client.version>3.1.0</statsd-client.version>
		<sun-mail.version>${javax-mail.version}</sun-mail.version>
		<thymeleaf.version>2.1.6.RELEASE</thymeleaf.version>
		<thymeleaf-extras-springsecurity4.version>2.1.3.RELEASE</thymeleaf-extras-springsecurity4.version>
		<thymeleaf-extras-conditionalcomments.version>2.1.2.RELEASE</thymeleaf-extras-conditionalcomments.version>
		<thymeleaf-layout-dialect.version>1.4.0</thymeleaf-layout-dialect.version>
		<thymeleaf-extras-data-attribute.version>1.3</thymeleaf-extras-data-attribute.version>
		<thymeleaf-extras-java8time.version>2.1.0.RELEASE</thymeleaf-extras-java8time.version>
		<tomcat.version>8.5.23</tomcat.version>
		<undertow.version>1.4.21.Final</undertow.version>
		<unboundid-ldapsdk.version>3.2.1</unboundid-ldapsdk.version>
		<webjars-hal-browser.version>9f96c74</webjars-hal-browser.version>
		<webjars-locator.version>0.32-1</webjars-locator.version>
		<wsdl4j.version>1.6.3</wsdl4j.version>
		<xml-apis.version>1.4.01</xml-apis.version>

		<!-- Plugin versions -->
		<build-helper-maven-plugin.version>1.10</build-helper-maven-plugin.version>
		<exec-maven-plugin.version>1.5.0</exec-maven-plugin.version>
		<git-commit-id-plugin.version>2.2.3</git-commit-id-plugin.version>
		<maven-antrun-plugin.version>1.8</maven-antrun-plugin.version>
		<maven-assembly-plugin.version>2.6</maven-assembly-plugin.version>
		<maven-clean-plugin.version>2.6.1</maven-clean-plugin.version>
		<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
		<maven-dependency-plugin.version>2.10</maven-dependency-plugin.version>
		<maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version>
		<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
		<maven-enforcer-plugin.version>1.4</maven-enforcer-plugin.version>
		<maven-failsafe-plugin.version>2.18.1</maven-failsafe-plugin.version>
		<maven-install-plugin.version>2.5.2</maven-install-plugin.version>
		<maven-invoker-plugin.version>1.10</maven-invoker-plugin.version>
		<maven-help-plugin.version>2.2</maven-help-plugin.version>
		<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
		<maven-javadoc-plugin.version>2.10.4</maven-javadoc-plugin.version>
		<maven-resources-plugin.version>2.7</maven-resources-plugin.version>
		<maven-shade-plugin.version>2.4.3</maven-shade-plugin.version>
		<maven-site-plugin.version>3.5.1</maven-site-plugin.version>
		<maven-source-plugin.version>2.4</maven-source-plugin.version>
		<maven-surefire-plugin.version>2.18.1</maven-surefire-plugin.version>
		<maven-war-plugin.version>2.6</maven-war-plugin.version>
		<versions-maven-plugin.version>2.2</versions-maven-plugin.version>

@SpringBootApplication

@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 {
    ........
}

(1)@SpringBootConfiguration注解: Spring Boot的配置类,标注在某个类上,表示这是一个Spring Boot的配置类;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration  //@Configuration注解中源码有@Component)
public @interface SpringBootConfiguration {

}

2)@EnableAutoConfiguration注解:开启自动配置功能;

以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //自动配置包
@Import({AutoConfigurationImportSelector.class})//Spring的底层注解@Import,给容器中导入一个组件;
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
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;
    }

全类名的方式返回
将其类路径下 META-INF/spring.factories里面配置的所有EnableAutoConfiguration的值加入到了容器中;
查看spring-boot-autoconfigure-2.0.3.RELEASE.jar包的/spring.factories文件
在这里插入图片描述
Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作
“J2EE的整体整合解决方案”和”自动配置”都体现在spring-boot-autoconfigure-1.5.9.RELEASE.jar;可着重阅读这一块的源码。
在这里插入图片描述
YML配置文件书写

字符串默认不用加上单引号或者双引号。
name:zhangsan
​”“:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思。
name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi
”:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据。
name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi

对象、Map(属性和值)(键值对):k: v:在下一行来写对象的属性和值的关系;注意缩进

数组(List、Set):用 - 值表示数组中的一个元素

person:
    lastName: hello
    age: 18
    boss: false
    birth: 2017/12/12
    maps: {k1: v1,k2: 12}
    lists:
      - lisi
      - zhaoliu
    dog:
      name: 小狗
      age: 12

提示:我们可以导入配置文件处理器,以后编写配置就有提示了.相当于如果实体类已经定义好了,当在配置文件中定义该类时,key会有自动提示!

/**
 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
 * prefix = "person":配置文件中哪个前缀下面的所有属性进行一一映射
 * @Component:只有这个组件是容器中的组件,才能容器提供的@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;

配置注入的两种方式
对于这两种方式我们使用上该如何做选择呢?
如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;

3、配置文件注入值数据校验

@Component
	@ConfigurationProperties(prefix = "person")
	@Validated//表明需要校验
	public class Person {
	    //lastName必须是邮箱格式
	    @Email
	    private String lastName;

@Bean:给容器中添加组件(配合@Configuration注解使用)

/**
 * @Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件
 */

@Configuration
public class MyAppConfig {
    //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
    @Bean
    public HelloService helloService02(){
        System.out.println("配置类@Bean给容器中添加组件了...");
        return new HelloService();
    }
}

配置文件占位符
1.随机数

//随机数在占位符的用法
${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}

2.前置配置项

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.hello=hello
person.dog.name=${person.hello:hello}_dog
person.dog.age=15

Profile的使用

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml
yml支持多文档块方式(用“—”来区分文档块)  详细可参见APLLO 配置中心

激活指定profile(三种方式)

​ 1、在配置文件中指定 spring.profiles.active=dev

​ 2、命令行: 
​ java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar –spring.profiles.active=dev; 
​ 可以直接在测试的时候,配置传入命令行参数

​ 3、虚拟机参数,不论全局配置文件配置的是什么,都会运行都会制定激活虚拟机参数所配置的信息; 
Idea – Edit Congigurations — 左侧选中要执行的主类 — VM options — 输入:-Dspring.profiles.active=dev

主配置文件扫描顺序
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件。
Tables
file:./config/ (file指的时候项目根目录)
file:./
classpath:/config/
classpath:/
优先级由高到底,高优先级的配置会覆盖低优先级的配置;SpringBoot会从这四个位置全部加载主配置文件。形成互补配置;
总而言之多个配置文件就会形成覆盖且又能互补的配置效果。

另外,我们还可以通过spring.config.location来改变默认的配置文件位置

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties

5.自动配置加载

//Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;    
// 判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;有当前配置生效
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在配置:  spring.http.encoding.enabled;如果不存在,则使用默认值true
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

结论:
根据当前不同的条件判断,决定配置类HttpEncodingAutoConfiguration是否生效,一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

@Configuration //表示这是一个配置类,也可以给容器中添加组件(配合@Bean使用)

@ConditionalOnClass(KafkaTemplate.class)

//启动指定类(KafkaProperties.class)的ConfigurationProperties功能;
//将配置文件中对应的值和KafkaProperties绑定起来,并把KafkaProperties加入到ioc容器中
@EnableConfigurationProperties(KafkaProperties.class)
@Import(KafkaAnnotationDrivenConfiguration.class)
public class KafkaAutoConfiguration {

private final KafkaProperties properties;

public KafkaAutoConfiguration(KafkaProperties properties) {
	this.properties = properties;
}

@Bean  //给容器中添加一个组件,这个组件的某些值需要从properties中获取
@ConditionalOnMissingBean(KafkaTemplate.class)
public KafkaTemplate<?, ?> kafkaTemplate(
		ProducerFactory<Object, Object> kafkaProducerFactory,
		ProducerListener<Object, Object> kafkaProducerListener) {
	KafkaTemplate<Object, Object> kafkaTemplate = new KafkaTemplate<Object, Object>(
			kafkaProducerFactory);
	kafkaTemplate.setProducerListener(kafkaProducerListener);
	kafkaTemplate.setDefaultTopic(this.properties.getTemplate().getDefaultTopic());
	return kafkaTemplate;
}

@Bean
@ConditionalOnMissingBean(ProducerListener.class)
public ProducerListener<Object, Object> kafkaProducerListener() {
	return new LoggingProducerListener<Object, Object>();
}

@Bean
@ConditionalOnMissingBean(ConsumerFactory.class)
public ConsumerFactory<?, ?> kafkaConsumerFactory() {
	return new DefaultKafkaConsumerFactory<Object, Object>(
			this.properties.buildConsumerProperties());
}

@Bean
@ConditionalOnMissingBean(ProducerFactory.class)
public ProducerFactory<?, ?> kafkaProducerFactory() {
	return new DefaultKafkaProducerFactory<Object, Object>(
			this.properties.buildProducerProperties());
}

}

6.yml配置文件如何进行加载
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着‘;配置文件能配置什么就可以参照某个功能对应的这个属性类xxxxProperties

如:HttpEncodingAutoConfiguration.class上注解有引入配置类:

@EnableConfigurationProperties({HttpEncodingProperties.class})

查看配置类:HttpEncodingProperties.class源码:

@ConfigurationProperties(
    prefix = "spring.http.encoding"//从配置文件中获取指定的值和bean的属性进行绑定
)
public class HttpEncodingProperties {
    public static final Charset DEFAULT_CHARSET;
    private Charset charset;
    private Boolean force;
    private Boolean forceRequest;
    private Boolean forceResponse;
    private Map<Locale, Charset> mapping;

所以当我们不使用SpringBoot的默认配置时,我们可以在配置文件中指定自己的配置值: 覆盖默认配置项

spring.http.encoding.charset=UTF-8

7.自动配置中的其他细节
1、@Conditional派生注解(Spring注解版原生的@Conditional作用)
核心:自动配置类必须在一定的条件下才能生效;
所以我们启动时加载的大量配置类,但是并不是所有的生效的。

我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

@Conditional扩展注解	作用(判断是否满足当前指定条件)
@ConditionalOnJava	系统的java版本是否符合要求
@ConditionalOnBean	容器中存在指定Bean;
@ConditionalOnMissingBean	容器中不存在指定Bean;
@ConditionalOnExpression	满足SpEL表达式指定
@ConditionalOnClass	系统中有指定的类
@ConditionalOnMissingClass	系统中没有指定的类
@ConditionalOnSingleCandidate	容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty	系统中指定的属性是否有指定的值
@ConditionalOnResource	类路径下是否存在指定资源文件
@ConditionalOnWebApplication	当前是web环境
@ConditionalOnNotWebApplication	当前不是web环境
@ConditionalOnJndi	JNDI存在指定项

8.日志框架
日志的抽象层
JCL(Jakarta Commons Logging)、SLF4j(Simple Logging Facade for Java)、jboss-logging
日志实现
Log4j、 JUL(java.util.logging)、Logback

一般的日志实现就是从表格中选择一个 【日志抽象层+日志实现】

SpringBoot日志处理:底层是Spring框架,而Spring框架默认是用JCL(common-logging不导入都会出错);
但是SpringBoot对此进行升级,SpringBoot选用 SLF4j和logback。

SLF4j使用
以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

spring日志框架遗留问题
1.一个系统中可能出现:
SpringBoot(slf4j+logback)+ Spring(commons-logging)+ Hibernate(jboss-logging)+ MyBatis(xxx)等多种日志框架。

SpringBoot是如何让系统中所有的日志都统一到slf4j?

1、将系统中其他日志框架先排除出去;

2、用中间包来替换原有的日志框架(偷梁换柱);

3、我们导入slf4j其他的实现。
在这里插入图片描述
查看依赖关系可以得到下面结论:

​ 1)、SpringBoot底层也是使用slf4j+logback的方式进行日志记录

​ 2)、SpringBoot也把其他的日志都替换成了slf4j;

​ 3)、中间替换包过程如下代码,将其他的日志工厂接口在实例化的时候都替换成了SLF4JLogFactory(此处使用自己的依赖包( xxx-to/over-slf4j.jar这种类型的依赖包 )源码查看)

@SuppressWarnings("rawtypes")
public abstract class LogFactory {
    //将所有的日志工厂都实例化为SLF4J的日志工厂
    static LogFactory logFactory = new SLF4JLogFactory();

如果我们要引入其他框架依赖,一定要把其他框架的默认日志依赖移除掉
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架依赖的时候,只需要把这个框架依赖的日志框架排除掉即可;

​ 比如Spring框架,Spring框架用的是commons-logging;

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
    //记录器
    Logger logger = LoggerFactory.getLogger(getClass());
    @Test
    public void contextLoads() {
        //日志的级别;
        //由低到高   trace < debug < info < warn < error
        //可以调整输出的日志级别;日志就只会在这个级别以以后的高级别生效
        logger.trace("这是trace日志...");
        logger.debug("这是debug日志...");
        //SpringBoot默认给我们使用的是info级别的,没有指定级别的就用SpringBoot默认规定的级别;也称root级别
        logger.info("这是info日志...");
        logger.warn("这是warn日志...");
        logger.error("这是error日志...");
    }
logging.level.com.atguigu=trace

#(1)不指定路径,仅指定日志文件名会在当前项目下生成springboot.log日志
#logging.file=springboot.log

#(2) 也可以指定完整的路径;
#logging.file=G:/springboot.log

#(3)在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用 spring.log 作为默认文件:
logging.path=/spring/log

# 在“控制台”输出的日志的格式:
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n

# 指定“文件”中日志输出的格式:
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n

日志配置模板

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志的根目录 -->
    <property name="LOG_HOME" value="/app/log" />
    <!-- 定义日志文件名称 -->
    <property name="appName" value="atguigu-springboot"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日志输出格式:
            %d表示日期时间,
            %thread表示线程名,
            %-5level:级别从左显示5个字符宽度
            %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 
            %msg:日志消息,
            %n是换行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->  
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!--
        当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
        TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!-- 
            可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
            且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
            那些为了归档而创建的目录也会被删除。
            -->
            <MaxHistory>365</MaxHistory>
            <!-- 
            当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志输出格式: -->     
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
        </layout>
    </appender>

    <!-- 
        logger主要用于存放日志对象,也可以定义日志类型、级别
        name:表示匹配的logger类型前缀,也就是包的前半部分
        level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
        additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
        false:表示只用当前logger的appender-ref,true:
        表示当前logger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!-- hibernate logger -->
    <logger name="com.atguigu" level="debug" />
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false"></logger>

    <!-- 
    root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
    要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 
    -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration> 

SpringBoot自动配置内容
在这里插入图片描述

首先要看看SpringBoot web依赖模块的源码: ResourceProperties.class:

再看看自动配置依赖包下web模块的 WebMvcAutoConfiguration .class中的部分源码:

静态资源映射访问
(1)映射规则总结
(1)、所有 /webjars/**请求,都去 classpath:/META-INF/resources/webjars/ 找资源。
(webjars:以jar包的方式引入静态资源(如jquery等静态资源)。资源网站:http://www.webjars.org/)
如springboot引入jquery:

<!--引入jquery-webjar-->
<!--在访问的时候只需要写webjars下面资源的名称即可-->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.3.1</version>
        </dependency>

当访问【localhost:8080/webjars/jquery/3.3.1/jquery.js 】 ,就相当于去依赖包下的【classpath:/META-INF/resources/webjars/】找相应的【jquery.js】文件--------》》》》引入node.js
在这里插入图片描述
(2)、所有”/**”的请求,如果没有对应的处理,都去(静态资源的文件夹,如下)找相映射的资源:

"classpath:/META-INF/resources/", 
"classpath:/resources/",
"classpath:/static/", 
"classpath:/public/" 
"/":当前项目的根路径

如:【 localhost:8080/abc】(当/abc请求没有对应的处理,那么SpringBoot会去去静态资源文件夹里面找abc)
(3)、欢迎页; 静态资源文件夹下的所有index.html页面;被”/**”映射;==
​ 即访问localhost:8080/ Springboot会处理 自动去静态文件夹 找index页面

(4)、所有的 **/favicon.ico 都是在静态资源文件下找;==
如: 直接找个favicon.ico放在静态资源文件夹下即可

(5)、当自己使用 spring.resources.static-locations 配置指定静态文件夹,那么前面那些默认的静态资源文件夹就会失效。
如: spring.resources.static-locations = classpath:/hello/,classpath:/hello2/

10.springMVC配置
在这里插入图片描述
SpringBoot默认配置了SpringMVC:
1 引入ContentNegotiatingViewResolver和BeanNameViewResolver beans。
2 对静态资源的支持,包括对WebJars的支持。
3 自动注册Converter,GenericConverter,Formatter beans。
4 对HttpMessageConverters的支持。
5 自动注册MessageCodeResolver。
6 对静态index.html的支持。
7 对自定义Favicon的支持。
8 字段使用 ConfigurableWebBindingInitializer bean

注意点(重点结论): 
(1)自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(是转发还是重定向)页面); 
(2)ContentNegotiatingViewResolver:组合所有的视图解析器的; 
(3)如何定制自己的视图解析器?我们可以自己给容器中添加一个视图解析器,SpringBoot会自动的将其组合进来;因为从源码分析可以返现ContentNegotiatingViewResolver所组合的视图解析器都是从容器中获取的

自定义时间格式化器
SpringBoot配置了Converter, GenericConverter, Formatter 等beans。

(1):Converter(转换器):类型转换使用Converter(将页面参数自动转化为需要的类型,如1,2017-01-01分别转换为int,Date类型); 
(2):Formatter (格式化器) ; 2017.12.17 === > Date;

分析一个日期格式化器部分源码:

  @Bean
    //在文件中配置日期格式化的规则,日期格式化器才会生效
    @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
    public Formatter<Date> dateFormatter() {
        return new DateFormatter(this.mvcProperties.getDateFormat());
    }

从源码中我们可以发现,仅有在配置文件中配置了,SpringBoot配置的日期格式化器才会生效。同时通过格式化器的注解@Bean可以发现该组件在容器中,所以当我们自己需要自定义的格式化器,只需要将其加入容器中即可。(@Bean)

自定义消息转化器
HttpMessageConverters,其主要作用是SpringMVC用来转换Http请求和响应的;User —> Json
看看WebMvcAutoConfiguration.class中的函数:

 public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, @Lazy HttpMessageConverters messageConverters, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
            this.resourceProperties = resourceProperties;
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConverters = messageConverters;
            this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
        }

源码方法参数中@Lazy HttpMessageConverters messageConverters可以看出messageConverters是通过容器懒加载获得的,所以也可以得出一个结论:要自定义消息转换器,只需要自己给容器中添加自定义的HttpMessageConverter即可。

自定义WebDataBinder
ConfigurableWebBindingInitializer : 其主要作用就是 初始化WebDataBinder;将请求的参数转化为对应的JavaBean,并且会结合上面的类型、格式转换一起使用。
查看WebMvcAutoConfiguration.class中函数源码:

  protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
            try {
                //从容器中获取
                return (ConfigurableWebBindingInitializer)this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
            } catch (NoSuchBeanDefinitionException var2) {
                return super.getConfigurableWebBindingInitializer();
            }
        }

可以发现ConfigurableWebBindingInitializer是从容器(beanFactory)中获取到的,所以我们可以配置一个ConfigurableWebBindingInitializer来替换默认的,只需要在容器中添加一个我们自定义的转换器即可。

总结
1)、SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(如ViewResolver)将用户配置的和自己默认的组合起来;

2)、用户可以借助添加 WebMvcConfigurerAdapter类型的 @Configuration 类,而不需要注解@EnableWebMvc来拓展Springmvc的自定义配置。

3)、用户可以在配置类加上了@EnableWebMvc注解,实现全面控制SpringMvc配置。

4)、整个SpringBoot框架中(不仅仅是针对web模块),在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置和有很多的’有很多的xxxCustomizer帮助我们进行定制配置’帮助我们进行定制配置。这些在后期会慢慢遇上。

springboot 错误处理响应设置
总体而言SpringBoot默认处理错误的机制原理是:
①、一但系统出现4xx(404)或者5xx(500)之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则,发起/error)请求;
②、 /error请求就会被BasicErrorController处理;
③、响应数据由DefaultErrorAttributes获取;
④、响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的

springboot启动流程
运行流程分析、自定义事件监听回调机制
一、运行流程分析
1、启动流程:
首先观察SpringBoot启动类源码:

SpringApplication.run(SpringbootDemoApplication.class, args);

//进入run方法===》发现:

     public static ConfigurableApplicationContext run(Object source, String... args) {
            return run(new Object[]{source}, args);
        }

        //再进入run方法===》发现:

        public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
                return (new SpringApplication(sources)).run(args);
            }
public class SpringApplication {

	/**
	 * The class name of application context that will be used by default for non-web
	 * environments.
	 */
	public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
			+ "annotation.AnnotationConfigApplicationContext";

	/**
	 * The class name of application context that will be used by default for web
	 * environments.
	 */
	public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

	private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	/**
	 * Default banner location.
	 */
	public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;

	/**
	 * Banner location property key.
	 */
	public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;

	private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

	private static final Log logger = LogFactory.getLog(SpringApplication.class);

	private final Set<Object> sources = new LinkedHashSet<Object>();

	private Class<?> mainApplicationClass;

	private Banner.Mode bannerMode = Banner.Mode.CONSOLE;

	private boolean logStartupInfo = true;

	private boolean addCommandLineProperties = true;

	private Banner banner;

	private ResourceLoader resourceLoader;

	private BeanNameGenerator beanNameGenerator;

	private ConfigurableEnvironment environment;

	private Class<? extends ConfigurableApplicationContext> applicationContextClass;

	private boolean webEnvironment;

	private boolean headless = true;

	private boolean registerShutdownHook = true;

	private List<ApplicationContextInitializer<?>> initializers;

	private List<ApplicationListener<?>> listeners;

	private Map<String, Object> defaultProperties;

	private Set<String> additionalProfiles = new HashSet<String>();

由最后的方法可以发现,启动应用主要做了两件事:①、创建SpringApplication对象;②、运行run方法。
对此进行分析:

①、创建SpringApplication对象:(initialize(sources))

private void initialize(Object[] sources) {
    /* 1、保存主配置类 */
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    /*2、判断当前是否一个web应用*/
    this.webEnvironment = deduceWebEnvironment();
    //3、从依赖包类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    //4、从依赖包类路径下找到META-INF/spring.factories配置的所有ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //5、从多个配置类中找到有main方法的主配置类
    this.mainApplicationClass = deduceMainApplicationClass();
}

通过源码可以发现其有两个关键点:(附断点调试截图)
保存所有的ApplicationContextInitializer:
在这里插入图片描述
保存所有的ApplicationListener
在这里插入图片描述

到此SpringApplication对象就创建完了,接下来就运行其run方法。

②、运行run方法:

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   //1、声明一个空的IOC容器
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();

   //2、获取SpringApplicationRunListeners;从类路径下META-INF/spring.factories
   SpringApplicationRunListeners listeners = getRunListeners(args);
    //3、回调所有的获取SpringApplicationRunListener.starting()方法
   listeners.starting();
   try {
       //4、封装传入的命令行参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //5、准备环境:创建环境完成后回调SpringApplicationRunListener.environmentPrepared(),表示环境准备完成
      ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

      //6、打印banner信息(就是spring的图标,可以自己替换)
      Banner printedBanner = printBanner(environment);

       //7、创建ApplicationContext;决定创建web的IOC还是普通的IOC
      context = createApplicationContext();

      //8、用来处理异常错误报告的,【catch】模块中使用
      analyzers = new FailureAnalyzers(context);


       //9、准备上下文环境;将【environment】对象保存到IOC中;
       //回调第一步中保存的所有的【ApplicationContextInitializer】的【initialize()】方法
       //回调所有第一步中保存的【SpringApplicationRunListener】的【contextPrepared()】方法;
      prepareContext(context, environment, listeners, applicationArguments,printedBanner);
       //10、【prepareContext()】运行完成以后回调所有的第一步中保存的【SpringApplicationRunListener】的【contextLoaded()】方法;

       //11、刷新容器:既开始IOC容器初始化(如果是web应用还会创建嵌入式的Tomcat);
       //12、扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
       refreshContext(context);

       //13、从IOC容器中获取所有的【ApplicationRunner】和【CommandLineRunner】进行回调
       //【ApplicationRunner】先回调,【CommandLineRunner】再回调
      afterRefresh(context, applicationArguments);

       //14、所有的【SpringApplicationRunListener】回调【finished()】方法
      listeners.finished(context, null);


      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
       //15、整个SpringBoot应用启动完成以后返回IOC容器;
      return context;
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}
@Override
public void refresh() throws BeansException, IllegalStateException {
   // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛
   synchronized (this.startupShutdownMonitor) {

      // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
      prepareRefresh();

      // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
      // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
      // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
      // 这块待会会展开说
      prepareBeanFactory(beanFactory);

      try {
         // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
         // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】

         // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
         // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
         postProcessBeanFactory(beanFactory);
         // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
         invokeBeanFactoryPostProcessors(beanFactory);

         // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
         // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
         // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化
         registerBeanPostProcessors(beanFactory);

         // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了
         initMessageSource();

         // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了
         initApplicationEventMulticaster();

         // 从方法名就可以知道,典型的模板方法(钩子方法),
         // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
         onRefresh();

         // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过
         registerListeners();

         // 重点,重点,重点
         // 初始化所有的 singleton beans
         //(lazy-init 的除外)
         finishBeanFactoryInitialization(beanFactory);

         // 最后,广播事件,ApplicationContext 初始化完成
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // 把异常往外抛
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

在这里插入图片描述
2、事件回调机制:
由上述源码run()方法中发现几个重要的事件回调机制:
类型 描述 获取方式

ApplicationContextInitializer	
IOC容器初始化时被回调	需要配置在META-INF/spring.factories,因为SpringBoot启动流程中是从spring.factories中获取的
SpringApplicationRunListener	
SpringBoot启动过程中多次被回调	需要配置在META-INF/spring.factories,因为SpringBoot启动流程中是从spring.factories中获取的
ApplicationRunner	
容器启动完成后被回调	需要放在IOC容器中,因为SpringBoot启动流程中是从IOC容器中取出的
CommandLineRunner	
ApplicationRunner之后被回调	需要放在IOC容器中,因为SpringBoot启动流程中是从IOC容器中取出的

二、自定义事件监听机制
1、ApplicationContextInitializer(配置在META-INF/spring.factories)
①、创建自定义ApplicationContextInitializer:

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    /* 初始化时会执行,并传入容器 */
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("【初始化时】 -------  ApplicationContextInitializer...initialize..."+applicationContext);
    }
}

②、配置META-INF/spring.factories
在resources文件夹下创建文件夹META-INF,而后在创建文件spring.factories

#仿照依赖包中配置方式,第一行【接口】第二行【实现类】
org.springframework.context.ApplicationContextInitializer=\
com.wangcw.springboot.listener.MyApplicationContextInitializer

2、SpringApplicationRunListener(配置在META-INF/spring.factories)
①、创建自定义SpringApplicationRunListener:

public class MySpringApplicationRunListener implements SpringApplicationRunListener {
//必须有的构造器,否则启动会报错
public MySpringApplicationRunListener (SpringApplication application, String[] args){

}

/* 有启动流程分析可知,其执行顺序是由上至下的 */
@Override
public void starting() {
    System.out.println("【应用运行监听】----------SpringApplicationRunListener...starting...");
}

/* 参数传入环境信息 */
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
    Object o = environment.getSystemProperties().get("os.name");
    System.out.println("【应用运行监听】----------SpringApplicationRunListener...environmentPrepared.."+o);
}

/* 参数传入容器 */
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
    System.out.println("【应用运行监听】----------SpringApplicationRunListener...contextPrepared...");
}

@Override
public void contextLoaded(ConfigurableApplicationContext context) {
    System.out.println("【应用运行监听】----------SpringApplicationRunListener...contextLoaded...");
}

@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
    System.out.println("【应用运行监听】----------SpringApplicationRunListener...finished...");
}

}

②、配置META-INF/spring.factories

#仿照依赖包中配置方式,第一行【接口】第二行【实现类】
org.springframework.context.ApplicationContextInitializer=\
com.wangcw.springboot.listener.MyApplicationContextInitializer

org.springframework.boot.SpringApplicationRunListener=\
com.wangcw.springboot.listener.MySpringApplicationRunListener

3、ApplicationRunner(只需要放在ioc容器中)
创建ApplicationRunner并加入到容器即可。

@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("【容器启动完成后被回调】--------------ApplicationRunner...run....");
    }
}

4、CommandLineRunner(只需要放在ioc容器中)
创建CommandLineRunner并加入到容器即可。

@Component
public class HelloCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("【容器启动完成后被回调】--------------CommandLineRunner...run..."+ Arrays.asList(args));
    }
}

启动验证即可!

自定义SpringBoot中的Starter
1、一般开发中经常还会遇见许多场景是我们经常会使用到的,那我们可以将其作为自定义的Starter。实现这些场景自动配置。其他应用使用时只需要导入依赖即可。
2、如何实现场景自动配置呢?我们参考下SpringBoot中的自动配置实现流程:
①、编写一个自动配置类

@Configuration     //指定这个类是一个配置类
@ConditionalOnXXX  //在指定条件成立的情况下自动配置类生效
@AutoConfigureAfter//指定自动配置类的顺序
@Bean              //给容器中添加组件
@ConfigurationPropertie       //结合相关xxxProperties类来绑定相关的配置
@EnableConfigurationProperties//让xxxProperties生效加入到容器中

②、自动配置要生效需要配置在META-INF/spring.factories

#类型指定为自动配置接口
#第二行配置具体实现类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

3、Starter启动器的实现模式:

①、启动器只用来做依赖导入,即整个模块只需要导入其依赖的jar; 
②、编写一个自动配置模块,以jar方式打包; 
③、启动器依赖自动配置模块,用户只需要引入启动器(starter)就可以实现自动配置; 
④、启动器模块命名规则:XXX(自定义启动器名)-spring-boot-starter(如:mybatis-spring-boot-starter;) 
⑤、自动配置模块命名规则:XXX(自定义启动器名)-spring-boot-starter-autoconfigurer

二、实现自定义starter
场景:目前某个SpringBoot应用,有一个对外的请求处理,即当用户访问 " /hello?name = xxx "时,其需要返回字符串,
" AAA xxx BBB ",即在参数name前加上 prefix:AAA 和 后面加上 suffix : BBB。
并且AAA、BBB的值可配置在application.yml配置文件中。

即需要有个启动器可以将我们需要实现我们要的业务场景,并把业务类自动配置进容器中,方便其他应用使用从容器中获取使用。

1、自动配置模块实现:
(1)、创建自动配置模块,并引入spring-boot-starter(所有starter的基本配置)

<?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>com.wanngcw.starter</groupId>
   <artifactId>jfeng-spring-boot-starter-autoconfigurer</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>wanngcw-spring-boot-starter-autoconfigurer</name>
   <description>Wanngcw Autoconfigurer Module for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.10.RELEASE</version>
      <relativePath/>
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <!--引入spring-boot-starter;所有starter的基本配置-->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter</artifactId>
      </dependency>
   </dependencies>

</project>

(2)、指定对应的XxxProperties类,用于定义可配置的值。

import org.springframework.boot.context.properties.ConfigurationProperties;

//需要加入该注解指定配置前缀
@ConfigurationProperties(prefix = "jfeng.hello")
public class HelloProperties {

private String prefix;
private String suffix;

public String getPrefix() {
    return prefix;
}

public void setPrefix(String prefix) {
    this.prefix = prefix;
}

public String getSuffix() {
    return suffix;
}

public void setSuffix(String suffix) {
    this.suffix = suffix;
}

}

(3)、定义业务类,用于获取用户配置信息,并且实现我们的业务场景。

public class HelloService {
private HelloProperties helloProperties;

public HelloProperties getHelloProperties() {
    return helloProperties;
}

public void setHelloProperties(HelloProperties helloProperties) {
    this.helloProperties = helloProperties;
}

/* 实现我们的业务场景 */
public String appendString(String name){
    return helloProperties.getPrefix()+":" + name + ":" + helloProperties.getSuffix();
}

}

(4)、定义自动配置类,并且需要将其我们的业务类实例化置于容器中。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnWebApplication //web应用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @Autowired
    private HelloProperties helloProperties;
    @Bean
    public HelloService helloService(){
        HelloService service = new HelloService();
        service.setHelloProperties(helloProperties);
        return service;
    }
}

(5)、要让自动配置类能生效,需要在’resources\META-INF\spring.factories’文件配置出来。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.jfeng.starter.HelloServiceAutoConfiguration

(6)、执行mvn install安装到仓库中。

2、Starter启动器模块实现:
(1)、创建启动器配置模块,并引入wanngcw-spring-boot-starter-autoconfigurer自动配置的依赖。

<?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>com.wangcw.starter</groupId>
    <artifactId>wangcw-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--启动器-->
    <dependencies>
        <!--引入自动配置模块-->
        <dependency>
           <groupId>com.jfeng.starter</groupId>
           <artifactId>jfeng-spring-boot-starter-autoconfigurer</artifactId>
           <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

(2)、执行mvn install安装到仓库中。到此,其他用户就可以通过引入该Starter依赖,
然后从容器中获取HelloService组件实现该业务。

3、测试自定义的starter:
(1)、新建SpringBoot应用,并导入web依赖和我们自定义的依赖jfeng-spring-boot-starter。

<?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>com.jfeng.springbootdemo</groupId>
<artifactId>springbootdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>springbootdemo</name>
<description>Demo project for Spring Boot</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    <relativePath/>
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <!-- 引入web模块 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--引入自定义的starter-->
    <dependency>
        <groupId>com.jfeng.starter</groupId>
        <artifactId>jfeng-spring-boot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>


<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

(2)、在配置为文件中配置相关信息

jfeng:
  hello:
    prefix: Welcome
    suffix: ----HelloWorld!

(3)、编写Controller

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String hello(String name){
        return helloService.appendString(name);
    }
}

(4)、启动并访问测试:
todo…

**

END

**

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值