• 145558

    文章

  • 857

    评论

  • 13

    友链

  • 最近新加了换肤功能,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

SpringBoot原理与自定义starter


1. 从SpringBootApplication开始

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Start {

        public static void main(String[] args) {
                SpringApplication.run(Start.class, args);
        }

}

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

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

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

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

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

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {
                switch (webApplicationType) {
                case SERVLET:
                        return new AnnotationConfigServletWebServerApplicationContext();
                case REACTIVE:
                        return new AnnotationConfigReactiveWebServerApplicationContext();
                default:
                        return new AnnotationConfigApplicationContext();
                }
        }
        catch (Exception ex) {
                throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                                + "you may need a custom ApplicationContextFactory", ex);
        }
};

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

2. @SpringBootApplication注解

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

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

SpringBoot中的新重点是@EnableAutoConfiguration:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

这个注解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类。

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
			ObjectProvider<WebListenerRegistrar> webListenerRegistrars) {
		return new ServletWebServerFactoryCustomizer(serverProperties,
				webListenerRegistrars.orderedStream().collect(Collectors.toList()));
	}
}

它又会通过@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 {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"vip.mycollege.User","vip.mycollege.Teacher"};
    }
}
 
public class ImportBeanDefinitionRegistrarTest implements ImportBeanDefinitionRegistrar {
 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Teacher.class);
        registry.registerBeanDefinition("teacher", beanDefinition);
    }
}

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

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

for (SourceClass candidate : importCandidates) {
        //处理ImportSelector
        if (candidate.isAssignable(ImportSelector.class)) {
                if (selector instanceof DeferredImportSelector) {
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                }else {
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                        processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                }
        }
        //处理ImportBeanDefinitionRegistrar
        else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                Class<?> candidateClass = candidate.loadClass();
                ImportBeanDefinitionRegistrar registrar =
                                ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                                this.environment, this.resourceLoader, this.registry);
                configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        }
        //处理其他类
        else {
                this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
        }
}

5. @EnableConfigurationProperties

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

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 {

	/**
	 * Server HTTP port.
	 */
	private 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:

@Configuration
@EnableConfigurationProperties({NBProperties.class})
@Import({NBConfiguration.class})
public class NBDataSourceAutoConfigure {

    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        return new NBDataSourceWrapper();
    }
}

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

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

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

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

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

7. 资料参考

SpringBoot属性配置说明

Spring核心流程梳理

Spring Conditional原理与实例

Spring @Configuration流程概述


695856371Web网页设计师②群 | 喜欢本站的朋友可以收藏本站,或者加入我们大家一起来交流技术!

0条评论

Loading...


发表评论

电子邮件地址不会被公开。 必填项已用*标注

自定义皮肤 主体内容背景
打开支付宝扫码付款购买视频教程
遇到问题联系客服QQ:419400980
注册梁钟霖个人博客