SpringBoot原理与自定义starter

1. 从SpringBootApplication开始

1import org.springframework.boot.SpringApplication; 2import org.springframework.boot.autoconfigure.SpringBootApplication; 3@SpringBootApplication 4public class Start { 5 6 public static void main(String[] args) { 7 SpringApplication.run(Start.class, args); 8 } 9 10}

通过main方法启动,进入,跟进去:

1public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { 2 return new SpringApplication(primarySources).run(args); 3}

发现是通过SpringApplication的run来启动的,创建容器的方法是createApplicationContext:

1protected ConfigurableApplicationContext createApplicationContext() { 2 return this.applicationContextFactory.create(this.webApplicationType); 3}

最骚操作的是ApplicationContextFactory这个类,自己拿了一个自己的实现类,还是lambda表达式,所以很有迷惑性,当时我一直以为我错过了什么Java的重要特性了。

1ApplicationContextFactory DEFAULT = (webApplicationType) -> { 2 try { 3 switch (webApplicationType) { 4 case SERVLET: 5 return new AnnotationConfigServletWebServerApplicationContext(); 6 case REACTIVE: 7 return new AnnotationConfigReactiveWebServerApplicationContext(); 8 default: 9 return new AnnotationConfigApplicationContext(); 10 } 11 } 12 catch (Exception ex) { 13 throw new IllegalStateException("Unable create a default ApplicationContext instance, " 14 + "you may need a custom ApplicationContextFactory", ex); 15 } 16};

Spring容器就创建好了,后面基本就是进入Spring模式的核心refresh了,然后就开始扫描解析BeanDefinition等等操作。

2. @SpringBootApplication注解

1@SpringBootConfiguration 2@EnableAutoConfiguration 3@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), 4 @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) 5public @interface SpringBootApplication { 6}

@SpringBootConfiguration:其实就是@Configuration,会通过ConfigurationClassPostProcessor处理,会在Spring处理BeanFactoryPostProcessor阶段被处理 @ComponentScan:这个我们比较熟悉了扫描Bean的

SpringBoot中的新重点是@EnableAutoConfiguration:

1@AutoConfigurationPackage 2@Import(AutoConfigurationImportSelector.class) 3public @interface EnableAutoConfiguration { 4}

这个注解Import了AutoConfigurationImportSelector,AutoConfigurationImportSelector继承了ImportSelector,当被Import导入的时候就会执行它的selectImports。 关于Import注解的逻辑可以参考后面的Import相关的内容。

最终在AutoConfigurationImportSelector的getCandidateConfigurations方法中通过:

SpringFactoriesLoader#loadFactoryNames方法导入了配置文件META-INF/spring.factories配置的类。

spring.factories配置文件中配置了一些XXXAutoConfiguration类,这些类又会导入一些类,例如spring-boot依赖的spring-boot-autoconfigure的META-INF/spring.factories中配置了一个ServletWebServerFactoryAutoConfiguration类。

1@Configuration(proxyBeanMethods = false) 2@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 3@ConditionalOnClass(ServletRequest.class) 4@ConditionalOnWebApplication(type = Type.SERVLET) 5@EnableConfigurationProperties(ServerProperties.class) 6@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, 7 ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, 8 ServletWebServerFactoryConfiguration.EmbeddedJetty.class, 9 ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) 10public class ServletWebServerFactoryAutoConfiguration { 11 12 @Bean 13 public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties, 14 ObjectProvider<WebListenerRegistrar> webListenerRegistrars) { 15 return new ServletWebServerFactoryCustomizer(serverProperties, 16 webListenerRegistrars.orderedStream().collect(Collectors.toList())); 17 } 18}

它又会通过@Import、@Bean注解导入一些它需要的类。

有2个地方需要注意:

  1. 有@Conditional注解表示满足条件才导入,例如@ConditionalOnClass(ServletRequest.class),表示有ServletRequest这个类才导入这个类
  2. 我们在配置文件中添加server.port=8080为什么生效,就是@EnableConfigurationProperties(ServerProperties.class)配置的

具体的内容,可以看看后面的相关内容。

spring.factories不一定都是AutoConfiguration类,例如:

在spring-boot中:

在spring-boot-autoconfigure中:

3. @Conditional

关于了解一下下面的2个东西。

首先,它会在下面3个地方被处理:

  1. ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod:处理@Bean
  2. ConfigurationClassParser.processConfigurationClass(ConfigurationClass):处理@Configuration
  3. ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClass, SourceClass):处理@ComponentScan

常见的@Conditional注解,及其作用:

Conditional注解

作用

@ConditionalOnJndi

在JNDI存在的条件下查找指定的位置

@ConditionalOnBean

当容器里有指定Bean的条件下

@ConditionalOnJava

基于JVM版本作为判断条件

@ConditionalOnClass

当类路径下有指定的类的条件下

@ConditionalOnProperty

指定的属性是否有指定的值

@ConditionalOnResource

类路径是否有指定的值

@ConditionalOnExpression

基于SpEL表达式为true的时候作为判断条件才去实例化

@ConditionalOnMissingBean

当容器里没有指定Bean的情况下

@ConditionalOnMissingClass

当容器里没有指定类的情况下

@ConditionalOnWebApplication

当前项目时Web项目的条件下

@ConditionalOnOnSingleCandidate

当指定Bean在容器中只有一个,或者有多个但是指定首选的Bean

@ConditionalOnNotWebApplication

当前项目不是Web项目的条件下

关于@Conditional,可以看一下Spring Conditional原理与实例这篇文章。

4. @Import

@Import注解的参数有3种:

  1. 普通类直接注入

  2. 实现ImportSelector接口的类

  3. 实现ImportBeanDefinitionRegistrar接口的类

    public class ImportSelectorTest implements ImportSelector {

    1@Override 2public String[] selectImports(AnnotationMetadata importingClassMetadata) { 3 return new String[]{"vip.mycollege.User","vip.mycollege.Teacher"}; 4}

    }

    public class ImportBeanDefinitionRegistrarTest implements ImportBeanDefinitionRegistrar {

    1@Override 2public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 3 RootBeanDefinition beanDefinition = new RootBeanDefinition(Teacher.class); 4 registry.registerBeanDefinition("teacher", beanDefinition); 5}

    }

处理Import的类是ConfigurationClassPostProcessor,这是一个BeanFactoryPostProcessor。在Spring核心流程梳理中我们介绍了这个是在BeanDefinition都解析好之后,在AbstractApplicationContext#refresh方法中通过invokeBeanFactoryPostProcessors来处理。

其中:@Import注解的处理逻辑在:ConfigurationClassParser的processImports方法中

1for (SourceClass candidate : importCandidates) { 2 //处理ImportSelector 3 if (candidate.isAssignable(ImportSelector.class)) { 4 if (selector instanceof DeferredImportSelector) { 5 this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); 6 }else { 7 String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); 8 Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter); 9 processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); 10 } 11 } 12 //处理ImportBeanDefinitionRegistrar 13 else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { 14 Class<?> candidateClass = candidate.loadClass(); 15 ImportBeanDefinitionRegistrar registrar = 16 ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, 17 this.environment, this.resourceLoader, this.registry); 18 configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); 19 } 20 //处理其他类 21 else { 22 this.importStack.registerImport( 23 currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); 24 processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter); 25 } 26}

5. @EnableConfigurationProperties

1@Import(EnableConfigurationPropertiesRegistrar.class) 2public @interface EnableConfigurationProperties { 3}

EnableConfigurationPropertiesRegistrar又是一个ImportBeanDefinitionRegistrar。

主要注册了2个类:

  1. ConfigurationPropertiesBindingPostProcessor
  2. BoundConfigurationProperties

主要是ConfigurationPropertiesBindingPostProcessor是一个BeanPostProcessor,实现了postProcessBeforeInitialization,会在一个bean的属性值设置完之后,但是还没有执行init-method、 afterPropertiesSet等初始化之前被调用。

关于Spring Bean的生命周期可以看看:Spring Bean生命周期

具体处理逻辑是:

  1. 通过ConfigurationPropertiesBean的create方法找到@ConfigurationProperties注解并处理验证相关逻辑

  2. 通过ConfigurationPropertiesBindingPostProcessor自己的bind方法执行绑定操作

  3. bind的逻辑是在ConfigurationPropertiesBinder的bind方法,它会去找到并解析@ConfigurationProperties注解

    @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties {

    1/** 2 * Server HTTP port. 3 */ 4private Integer port;

    }

所以我们在配置文件中配置的server.port=8080会生效。

6. 自定义starter

了解了上面的原理之后,自定义starter就没啥难的了。

一般官方的artifactId是spring-boot-starter-web,这种spring-boot-starter-xxx。

我们自定义的artifactId会使用:druid-spring-boot-starter,这种xxx-spring-boot-starter

只需要定义一个XXXAutoConfigure:

1@Configuration 2@EnableConfigurationProperties({NBProperties.class}) 3@Import({NBConfiguration.class}) 4public class NBDataSourceAutoConfigure { 5 6 @Bean(initMethod = "init") 7 @ConditionalOnMissingBean 8 public DataSource dataSource() { 9 return new NBDataSourceWrapper(); 10 } 11}

通过@Import注解添加一下默认配置需要的类,通过@Bean注入一下默认需要的类实例。

然后在resources目录下创建一个META-INF目录,创建一个spring.factories文件,把自己的XXXAutoConfigure配置进去就可以了。

1org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2vip.mycollege.autoconfig.DruidDataSourceAutoConfigure

如果你高兴,还可以加一个XXXProperties:

1@ConfigurationProperties("wo.nb") 2public class NBProperties { 3 private String realNB;

7. 资料参考

SpringBoot属性配置说明

Spring核心流程梳理

Spring Conditional原理与实例

Spring @Configuration流程概述

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java将前端的json数组字符串转换为列表

记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang