探究SpringBoot实现原理

注意:必须完成SSM阶段源码解析部分的学习,链接:https://www.cnblogs.com/zwtblog/tag/源码/

我们在前面的学习中切实感受到了SpringBoot为我们带来的便捷,那么它为何能够实现如此快捷的开发模式,starter又是一个怎样的存在,它是如何进行自动配置的,我们现在就开始研究。

启动原理

首先我们来看看,SpringBoot项目启动之后,做了什么事情,SpringApplication中的静态run方法:

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

套娃如下:

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

我们发现,这里直接new了一个新的SpringApplication对象,传入我们的主类作为构造方法参数,并调用了非static的run方法,我们先来看看构造方法里面做了什么事情:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//这里是关键,这里会判断当前SpringBoot应用程序是否为Web项目,并返回当前的项目类型
//deduceFromClasspath是根据类路径下判断是否包含SpringBootWeb依赖,如果不包含就是NONE类型,包含就是SERVLET类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
//创建所有ApplicationContextInitializer实现类的对象
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}

关键就在这里了,它是如何知道哪些类是ApplicationContextInitializer的实现类的呢?

这里就要提到spring.factories了,它是 Spring 仿造Java SPI实现的一种类加载机制。

它在 META-INF/spring.factories 文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的SPI机制是 Spring Boot Starter 实现的基础。

SPI的常见例子:

  • 数据库驱动加载接口实现类的加载:JDBC加载不同类型数据库的驱动
  • 日志门面接口实现类加载:SLF4J加载不同提供商的日志实现类

说白了就是人家定义接口,但是实现可能有很多种,但是核心只提供接口,需要我们按需选择对应的实现,这种方式是高度解耦的。

我们来看看getSpringFactoriesInstances方法做了什么:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//获取当前的类加载器
ClassLoader classLoader = this.getClassLoader();
//获取所有依赖中 META-INF/spring.factories 中配置的对应接口类的实现类列表
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//根据上方列表,依次创建实例对象
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//根据对应类上的Order接口或是注解进行排序
AnnotationAwareOrderComparator.sort(instances);
//返回实例
return instances;
}

其中SpringFactoriesLoader.loadFactoryNames正是读取配置的核心部分,我们后面还会遇到。

接着我们来看run方法里面做了什么事情。

public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
//获取所有的SpringApplicationRunListener,并通知启动事件,默认只有一个实现类EventPublishingRunListener
//EventPublishingRunListener会将初始化各个阶段的事件转发给所有监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass); try {
//环境配置
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
//打印Banner
Banner printedBanner = this.printBanner(environment);
//创建ApplicationContext,注意这里会根据是否为Web容器使用不同的ApplicationContext实现类
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//初始化ApplicationContext
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//执行ApplicationContext的refresh方法
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
....
}

我们发现,实际上SpringBoot就是Spring的一层壳罢了,离不开最关键的ApplicationContext,也就是说,在启动后会自动配置一个ApplicationContext,只不过是进行了大量的扩展。

我们来看ApplicationContext是怎么来的,打开createApplicationContext方法:

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

我们发现在构造方法中applicationContextFactory直接使用的是DEFAULT:

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

DEFAULT是直接编写的一个匿名内部类,其实已经很明确了,正是根据webApplicationType类型进行判断,如果是SERVLET,那么久返回专用于Web环境的AnnotationConfigServletWebServerApplicationContext对象(SpringBoot中新增的),否则返回普通的AnnotationConfigApplicationContext对象,也就是到这里为止,Spring的容器就基本已经确定了。

注意AnnotationConfigApplicationContext是Spring框架提供的类,从这里开始相当于我们在讲Spring的底层源码了,我们继续深入,AnnotationConfigApplicationContext对象在创建过程中会创建AnnotatedBeanDefinitionReader,它是用于通过注解解析Bean定义的工具类:

public AnnotationConfigApplicationContext() {
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
}

其构造方法:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
...
//这里会注册很多的后置处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
....
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet(8);
RootBeanDefinition def;
if (!registry.containsBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor")) {
//注册了ConfigurationClassPostProcessor用于处理@Configuration、@Import等注解
//注意这里是关键,之后Selector还要讲到它
//它是继承自BeanDefinitionRegistryPostProcessor,所以它的执行时间在Bean定义加载完成后,Bean初始化之前
def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"));
} if (!registry.containsBeanDefinition("org.springframework.context.annotation.internalAutowiredAnnotationProcessor")) {
//AutowiredAnnotationBeanPostProcessor用于处理@Value等注解自动注入
def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalAutowiredAnnotationProcessor"));
} ...

回到SpringBoot,我们最后来看,prepareContext方法中又做了什么事情:

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//环境配置
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
this.applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
} //将Banner注册为Bean
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
} if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory)beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
} if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
} //这里会获取我们一开始传入的项目主类
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//这里会将我们的主类直接注册为Bean,这样就可以通过注解加载了
this.load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}

因此,在prepareContext执行完成之后,我们的主类成功完成Bean注册,接下来,就该类上注解大显身手了。

自动配置原理

既然主类已经在初始阶段注册为Bean,那么在加载时,就会根据注解定义,进行更多的额外操作。所以我们来看看主类上的@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 {

我们发现,@SpringBootApplication上添加了@ComponentScan注解,此注解我们此前已经认识过了,但是这里并没有配置具体扫描的包,因此它会自动将声明此接口的类所在的包作为basePackage。

因此当添加@SpringBootApplication之后也就等于直接开启了自动扫描,但是一定注意不能在主类之外的包进行Bean定义,否则无法扫描到,需要手动配置。

接着我们来看第二个注解@EnableAutoConfiguration,它就是自动配置的核心了,我们来看看它是如何定义的:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {

老套路了,直接一手@Import,通过这种方式来将一些外部的Bean加载到容器中。我们来看看AutoConfigurationImportSelector做了什么事情:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
}

我们看到它实现了很多接口,包括大量的Aware接口,实际上就是为了感知某些必要的对象,并将其存到当前类中。

其中最核心的是DeferredImportSelector接口,它是ImportSelector的子类,它定义了selectImports方法,用于返回需要加载的类名称,在Spring加载ImportSelector类型的Bean时,会调用此方法来获取更多需要加载的类,并将这些类一并注册为Bean:

public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata); @Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}

到目前为止,我们了解了两种使用@Import有特殊机制的接口:ImportSelector(这里用到的)和ImportBeanDefinitionRegistrar(之前Mybatis-spring源码有讲)当然还有普通的@Configuration配置类。

我们可以来阅读一下ConfigurationClassPostProcessor的源码,看看它到底是如何处理@Import的:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList();
//注意这个阶段仅仅是已经完成扫描了所有的Bean,得到了所有的BeanDefinition,但是还没有进行任何区分
//candidate是候选者的意思,一会会将标记了@Configuration的类作为ConfigurationClass加入到configCandidates中
String[] candidateNames = registry.getBeanDefinitionNames();
String[] var4 = candidateNames;
int var5 = candidateNames.length; for(int var6 = 0; var6 < var5; ++var6) {
String beanName = var4[var6];
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
} else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { //判断是否添加了@Configuration注解
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
} if (!configCandidates.isEmpty()) {
//...省略 //这里创建了一个ConfigurationClassParser用于解析配置类
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
//所有配置类的BeanDefinitionHolder列表
Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidates);
//已经解析完成的类
HashSet alreadyParsed = new HashSet(configCandidates.size()); do {
//这里省略,直到所有的配置类全部解析完成
//注意在循环过程中可能会由于@Import新增更多的待解析配置类,一律丢进candidates集合中
} while(!candidates.isEmpty()); ... }
}

我们接着来看,ConfigurationClassParser是如何进行解析的:

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
//@Conditional相关注解处理
//后面会讲
if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
...
} ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass, filter); do {
//核心
sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
} while(sourceClass != null); this.configurationClasses.put(configClass, configClass);
}
}

最后我们再来看最核心的doProcessConfigurationClass方法:

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
... processImports(configClass, sourceClass, getImports(sourceClass), true); // 处理Import注解 ... return null;
}
private void processImports(ConfigurationClass configClass, ConfigurationClassParser.SourceClass currentSourceClass, Collection<ConfigurationClassParser.SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) {
if (!importCandidates.isEmpty()) {
if (checkForCircularImports && this.isChainedImportOnStack(configClass)) {
this.problemReporter.error(new ConfigurationClassParser.CircularImportProblem(configClass, this.importStack));
} else {
this.importStack.push(configClass); try {
Iterator var6 = importCandidates.iterator(); while(var6.hasNext()) {
ConfigurationClassParser.SourceClass candidate = (ConfigurationClassParser.SourceClass)var6.next();
Class candidateClass;
//如果是ImportSelector类型,继续进行运行
if (candidate.isAssignable(ImportSelector.class)) {
candidateClass = candidate.loadClass();
ImportSelector selector = (ImportSelector)ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
//如果是DeferredImportSelector的实现类,那么会走deferredImportSelectorHandler的handle方法
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);
//否则就按照正常的ImportSelector类型进行加载
} else {
//调用selectImports方法获取所有需要加载的类
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<ConfigurationClassParser.SourceClass> importSourceClasses = this.asSourceClasses(importClassNames, exclusionFilter);
//递归处理,直到没有
this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
//判断是否为ImportBeanDefinitionRegistrar类型
} else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
//往configClass丢ImportBeanDefinitionRegistrar信息进去,之后再处理
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
//否则按普通的配置类进行处理
} else {
this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
this.processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
} catch (BeanDefinitionStoreException var17) {
throw var17;
} catch (Throwable var18) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", var18);
} finally {
this.importStack.pop();
}
} }
}

不难注意到,虽然这里额外处理了ImportSelector对象,但是还针对ImportSelector的子接口DeferredImportSelector进行了额外处理,Deferred是延迟的意思,它是一个延迟执行的ImportSelector,并不会立即进处理,而是丢进DeferredImportSelectorHandler,并且在parse方法的最后进行处理:

public void parse(Set<BeanDefinitionHolder> configCandidates) {
... this.deferredImportSelectorHandler.process();
}

我们接着来看DeferredImportSelector正好就有一个process方法:

public interface DeferredImportSelector extends ImportSelector {
@Nullable
default Class<? extends DeferredImportSelector.Group> getImportGroup() {
return null;
} public interface Group {
void process(AnnotationMetadata metadata, DeferredImportSelector selector); Iterable<DeferredImportSelector.Group.Entry> selectImports(); public static class Entry {
...

最后经过ConfigurationClassParser处理完成后,通过parser.getConfigurationClasses()就能得到通过配置类导入了哪些额外的配置类。最后将这些配置类全部注册BeanDefinition,然后就可以交给接下来的Bean初始化过程去处理了。

this.reader.loadBeanDefinitions(configClasses);

最后我们再去看loadBeanDefinitions是如何运行的:

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
ConfigurationClassBeanDefinitionReader.TrackedConditionEvaluator trackedConditionEvaluator = new ConfigurationClassBeanDefinitionReader.TrackedConditionEvaluator();
Iterator var3 = configurationModel.iterator(); while(var3.hasNext()) {
ConfigurationClass configClass = (ConfigurationClass)var3.next();
this.loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
} } private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, ConfigurationClassBeanDefinitionReader.TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
} this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
} else {
if (configClass.isImported()) {
this.registerBeanDefinitionForImportedConfigurationClass(configClass); //注册配置类自己
} Iterator var3 = configClass.getBeanMethods().iterator(); while(var3.hasNext()) {
BeanMethod beanMethod = (BeanMethod)var3.next();
this.loadBeanDefinitionsForBeanMethod(beanMethod); //注册@Bean注解标识的方法
} //注册`@ImportResource`引入的XML配置文件中读取的bean定义
this.loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//注册configClass中经过解析后保存的所有ImportBeanDefinitionRegistrar,注册对应的BeanDefinition
this.loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
}

这样,整个@Configuration配置类的底层配置流程我们就大致了解了。

接着我们来看AutoConfigurationImportSelector是如何实现自动配置的,可以看到内部类AutoConfigurationGroup的process方法,它是父接口的实现,因为父接口是DeferredImportSelector,那么很容易得知,实际上最后会调用process方法获取所有的自动配置类:

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
//获取所有的Entry,其实就是,读取spring.factories来查看有哪些自动配置类
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator(); while(var4.hasNext()) {
String importClassName = (String)var4.next();
this.entries.putIfAbsent(importClassName, annotationMetadata);
} }

我们接着来看getAutoConfigurationEntry方法:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
//判断是否开启了自动配置,是的,自动配置可以关
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//根据注解定义获取一些属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//得到spring.factories文件中所有需要自动配置的类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
... 这里先看前半部分
}
}

注意这里并不是spring.factories文件中所有的自动配置类都会被加载,它会根据@Condition注解的条件进行加载。这样就能实现我们需要什么模块添加对应依赖就可以实现自动配置了。

所有的源码看不懂,都源自于你的心中没有形成一个完整的闭环!一旦一条线推到头,闭环形成,所有疑惑迎刃而解。

自定义Starter

我们仿照Mybatis来编写一个自己的starter,Mybatis的starter包含两个部分:

<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>
<parent>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot</artifactId>
<version>2.2.0</version>
</parent>
<!-- starter本身只做依赖集中管理,不编写任何代码 -->
<artifactId>mybatis-spring-boot-starter</artifactId>
<name>mybatis-spring-boot-starter</name>
<properties>
<module.name>org.mybatis.spring.boot.starter</module.name>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 编写的专用配置模块 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
</project>

因此我们也将我们自己的starter这样设计:

我们设计三个模块:

  • spring-boot-hello:基础业务功能模块
  • spring-boot-starter-hello:启动器
  • spring-boot-autoconifgurer-hello:自动配置依赖

首先是基础业务功能模块,这里我们随便创建一个类就可以了:

public class HelloWorldService {

}

启动器主要做依赖管理,这里就不写任何代码,只写pom文件:

<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>spring-boot-autoconfigurer-hello</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

导入autoconfigurer模块作为依赖即可,接着我们去编写autoconfigurer模块,首先导入依赖:

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.6.2</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.6.2</version>
<optional>true</optional>
</dependency> <dependency>
<groupId>org.example</groupId>
<artifactId>spring-boot-hello</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

接着创建一个HelloWorldAutoConfiguration作为自动配置类:

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@ConditionalOnClass(HelloWorldService.class)
@EnableConfigurationProperties(HelloWorldProperties.class)
public class HelloWorldAutoConfiguration { Logger logger = Logger.getLogger(this.getClass().getName()); @Resource
HelloWorldProperties properties; @Bean
public HelloWorldService helloWorldService(){
logger.info("自定义starter项目已启动!");
logger.info("读取到自定义配置:"+properties.getValue());
return new HelloWorldService();
}
}

对应的配置读取类:

@ConfigurationProperties("hello.world")
public class HelloWorldProperties { private String value; public void setValue(String value) {
this.value = value;
} public String getValue() {
return value;
}
}

最后再编写spring.factories文件,并将我们的自动配置类添加即可:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hello.autoconfigurer.HelloWorldAutoConfiguration

最后再Maven根项目执行install安装到本地仓库,完成。接着就可以在其他项目中使用我们编写的自定义starter了。

Runner接口

在项目中,可能会遇到这样一个问题:我们需要在项目启动完成之后,紧接着执行一段代码。

我们可以编写自定义的ApplicationRunner来解决,它会在项目启动完成后执行:

@Component
public class TestRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("我是自定义执行!");
}
}

当然也可以使用CommandLineRunner,它也支持使用@Order或是实现Ordered接口来支持优先级执行。

实际上它就是run方法的最后:

public ConfigurableApplicationContext run(String... args) {
.... listeners.started(context, timeTakenToStartup);
//这里已经完成整个SpringBoot项目启动,所以执行所有的Runner
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
} try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}

Spring-实现原理的更多相关文章

  1. Spring工作原理

    一. IoC(Inversion of control): 控制反转1.IoC:概念:控制权由对象本身转向容器:由容器根据配置文件去创建实例并创建各个实例之间的依赖关系核心:bean工厂:在Sprin ...

  2. spring MVC原理

    spring MVC原理   Spring MVC工作流程图   图一   图二    Spring工作流程描述       1. 用户向服务器发送请求,请求被Spring 前端控制Servelt D ...

  3. 【Spring】Spring IOC原理及源码解析之scope=request、session

    一.容器 1. 容器 抛出一个议点:BeanFactory是IOC容器,而ApplicationContex则是Spring容器. 什么是容器?Collection和Container这两个单词都有存 ...

  4. spring原理案例-基本项目搭建 03 创建工程运行测试 spring ioc原理实例示例

    下面开始项目的搭建 使用 Java EE - Eclipse 新建一 Dynamic Web Project Target Runtime 选 Apache Tomcat 7.0(不要选 Apache ...

  5. Spring MVC 原理探秘 - 容器的创建过程

    1.简介 在上一篇文章中,我向大家介绍了 Spring MVC 是如何处理 HTTP 请求的.Spring MVC 可对外提供服务时,说明其已经处于了就绪状态.再次之前,Spring MVC 需要进行 ...

  6. 面试问烂的 Spring AOP 原理、SpringMVC 过程(求求你别问了)

    Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以.但今天笔者带大家一起深入浅出源码,看看他的原理.以期让印象更加深刻,面试的时候游刃有余. Sp ...

  7. Spring MVC 原理探秘 - 一个请求的旅行过程

    1.简介 在前面的文章中,我较为详细的分析了 Spring IOC 和 AOP 部分的源码,并写成了文章.为了让我的 Spring 源码分析系列文章更为丰富一些,所以从本篇文章开始,我将来向大家介绍一 ...

  8. Spring 注解原理(三)@Qualifier @Value

    Spring 注解原理(三)@Qualifier @Value Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.Aut ...

  9. Spring 注解原理(一)组件注册

    Spring 注解原理(一)组件注册 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 当我们需要使用 Spring 提供的 ...

  10. Spring工作原理与单例

    最近看到spring管理的bean为单例的,当它与web容器整合的时候始终搞不太清除,就网上搜索写资料, Tomcat与多线程, servlet是多线程执行的,多线程是容器提供的能力. servlet ...

随机推荐

  1. 从零开始,开发一个 Web Office 套件(10):捕获键盘事件,输入文字

    这是一个系列博客,最终目的是要做一个基于 HTML Canvas 的.类似于微软 Office 的 Web Office 套件(包括:文档.表格.幻灯片--等等). 博客园:<从零开始, 开发一 ...

  2. CF1385G口胡

    只能说很神秘??? 首先观察题面,假设给出的第一个序列为 \(a\),第二个序列为 \(b\).对于 \((a_i,b_i)\) 我们连一条边. 得到的是一个 \(n\) 个点 \(n\) 条边的不一 ...

  3. 用两行代码实现重试功能,spring-retry真是简单而优雅

    背景 最近做的一个需求,需要调用第三方接口.正常情况下,接口的响应是符合要求的,只有在网络抖动等极少数的情况下,会存在超时情况.因为是小概率事件,所以一次超时之后,进行一次重试操作应该就可以了.重试很 ...

  4. knative入门指南

    尽管Knative自2018年以来一直由社区维护,但最近一直有关于该项目的传言,因为谷歌最近将Knative提交给了云原生计算基金会(CNCF),作为一个孵化项目考虑. 太酷了!但Knative到底是 ...

  5. Excel 使用 公式

    1. 查询单元数据在另一列中是否存在 =VLOOKUP(E2,F:F,1,FALSE) 2.判断一列数据是否重复 =IF(COUNTIF(A:A,A2)>1,"重复",&qu ...

  6. QFramework Pro 开发日志(七)v0.4 版本审核通过 与 对话编辑器功能预告

    经过一周的工作,v0.4 版本总算完成了. 就在刚刚笔者在 AssetStore 提交了 v0.4 版本. v0.4 版本主要内容有两个 一键生成简单继承类图功能 底层兼容 QFramework v0 ...

  7. 使用阿里云镜像站NTP服务搭建NTP服务器(基于CentOS 7系统)

    镜像下载.域名解析.时间同步请点击 阿里云开源镜像站 一.NTP服务器介绍 网络时间协议(Network Time Protocol,NTP)服务器,也就是日常所说的NTP服务器,用来提供同步时间服务 ...

  8. 3、Lambda表达式

    Lambda表达式 Lambda表达式(lambda expression),是一种匿名函数,即没有函数名的函数. Lambda表达式不仅在C#中使用,在Java.Phtyon.C++ 中都有使用. ...

  9. bzoj4671 异或图(斯特林反演,线性基)

    bzoj4671 异或图(斯特林反演,线性基) 祭奠天国的bzoj. 题解时间 首先考虑类似于容斥的东西. 设 $ f_{ i } $ 为至少有 $ i $ 个连通块的方案数, $ g_{ i } $ ...

  10. redux和vuex以及dva?

    redux: 通过store存储,通过action唯一更改,reducer描述如何更改.dispatch一个action dva: 基于redux,结合redux-saga等中间件进行封装 vuex: ...