Spring-实现原理
探究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-实现原理的更多相关文章
- Spring工作原理
一. IoC(Inversion of control): 控制反转1.IoC:概念:控制权由对象本身转向容器:由容器根据配置文件去创建实例并创建各个实例之间的依赖关系核心:bean工厂:在Sprin ...
- spring MVC原理
spring MVC原理 Spring MVC工作流程图 图一 图二 Spring工作流程描述 1. 用户向服务器发送请求,请求被Spring 前端控制Servelt D ...
- 【Spring】Spring IOC原理及源码解析之scope=request、session
一.容器 1. 容器 抛出一个议点:BeanFactory是IOC容器,而ApplicationContex则是Spring容器. 什么是容器?Collection和Container这两个单词都有存 ...
- spring原理案例-基本项目搭建 03 创建工程运行测试 spring ioc原理实例示例
下面开始项目的搭建 使用 Java EE - Eclipse 新建一 Dynamic Web Project Target Runtime 选 Apache Tomcat 7.0(不要选 Apache ...
- Spring MVC 原理探秘 - 容器的创建过程
1.简介 在上一篇文章中,我向大家介绍了 Spring MVC 是如何处理 HTTP 请求的.Spring MVC 可对外提供服务时,说明其已经处于了就绪状态.再次之前,Spring MVC 需要进行 ...
- 面试问烂的 Spring AOP 原理、SpringMVC 过程(求求你别问了)
Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以.但今天笔者带大家一起深入浅出源码,看看他的原理.以期让印象更加深刻,面试的时候游刃有余. Sp ...
- Spring MVC 原理探秘 - 一个请求的旅行过程
1.简介 在前面的文章中,我较为详细的分析了 Spring IOC 和 AOP 部分的源码,并写成了文章.为了让我的 Spring 源码分析系列文章更为丰富一些,所以从本篇文章开始,我将来向大家介绍一 ...
- Spring 注解原理(三)@Qualifier @Value
Spring 注解原理(三)@Qualifier @Value Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.Aut ...
- Spring 注解原理(一)组件注册
Spring 注解原理(一)组件注册 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 当我们需要使用 Spring 提供的 ...
- Spring工作原理与单例
最近看到spring管理的bean为单例的,当它与web容器整合的时候始终搞不太清除,就网上搜索写资料, Tomcat与多线程, servlet是多线程执行的,多线程是容器提供的能力. servlet ...
随机推荐
- Netty异步Future源码解读
本文地址: https://juejin.im/post/5df771ee6fb9a0161d743069 说在前面 本文的 Netty源码使用的是 4.1.31.Final 版本,不同版本会有一些差 ...
- Golang 基础之基础语法梳理 (二)
大家好,今天将梳理出的 Go语言基础语法内容,分享给大家. 请多多指教,谢谢. 本次<Go语言基础语法内容>共分为三个章节,本文为第二章节 Golang 基础之基础语法梳理 (一) Gol ...
- mysqkl修改存放位置 列的数据
1.表一旦创建后,列的数据是否可以修改?如果可以说明语法 update 表名称 set 列名='新内容' where 列名=某值 橙色代表条件列 2.修改存放数据的文件夹 先关闭mysql 把da ...
- 遇到REMOTE HOST IDENTIFICATION HAS CHANGED怎么办?
今日遇到如下问题: 警告的大概意思就是,主机密钥发生变更,并提示安全风险(可能存在中间人攻击) 但是事实是,这是因为我重装系统之后遇到的问题.重装系统后,指纹当然会发生变化了...在Xshell实验中 ...
- P5017 [NOIP2018 普及组] 摆渡车
P5017 [NOIP2018 普及组] 摆渡车 题目 P5017 思路 将实际问题抽象后,不难发现这是一个 区间 \(DP\) 我们不妨认为时间是一条数轴,每名同学按照到达时刻分别对应数轴上可能重合 ...
- 4、传统三层架构与DDD分层架构
4.传统三层架构与DDD分层架构 模型是抽象的 现实是形象的 技巧是重要的 思想是永恒的 从传统三层架构与DDD分层架构的编程演变其实是思想的演变. 传统三层架构,即用户界面层UI.业务逻辑层BAL. ...
- VUE开发--环境配置(一)(转)
无剑_君关注 0.312019.05.09 11:53:43字数 1,073阅读 19,627 https://www.jianshu.com/p/a494417def99?utm_so ...
- 高度不定,宽100%,内一div高不确定,如何实现垂直居中?
verticle-align: middle; 绝对定位50%加translateY(-50%) 绝对定位,上下左右全0,margin:auto
- Delete、truncate、drop都是删除语句,它们有什么分别?
delete 属于DML语句,删除数据,保留表结构,需要commit,可以回滚,如果数据量大,很慢. truncate 属于DDL语句,删除所有数据,保留表结构,自动commit,不可以回滚,一次全部 ...
- python 字典dict 增删改查操作
初始化: a. data_dict = {} b. data_dict1 = dict() c. data_dict2 = {'key':'value'} 新增: a. data_dict[key]= ...