springboot源码(一)启动流程+自动配置原理分析

前言

springboot一直在使用,省去了手动添加很多配置,非常方便;今天总结下对springboot自动配置的一些理解(基于springboot2.1.4,注意2.X和1.X区别较大)。
springboot项目,实际上是一段代码,带有main方法入口的代码,甚至内嵌了tomcat容器的全部代码;
新创建的springboot项目,都会默认生成一个Application.java类(没有生成的,自己去手动创建),如下图:
在这里插入图片描述
打开这个类:

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

发现只有一个main方法和一个@SpringBootApplication注解,所有的奥秘,都只能在这两个点上了。

但是,在分析这个启动类之前,我们还需要先了解下springboot打包后产生的jar包。但是如果你比较着急想知道spingboot自动配置相关内容,那就直接去看第二节。

一. FAT jar

我们都知道,springboot打包后的jar文案,可以使用 java -jar x.jar来运行该jar文件,那么是如何执行的呢,怎么就执行了我们业务中的启动类中的main方法呢?

spring 打包后产生的jar包,与平时我们maven依赖的jar包有所不同,专业的术语叫做FAT jar,也就是胖jar,顾名思义,它要比普通jar胖一点,也就是内容多一点,我们解压两种jar看看内容:

随便打开一个jar包,比如gson-2.8.6.jar ,如下:
在这里插入图片描述
然后springboot的fat jar (名字是demo.jar)如下:
在这里插入图片描述
区别
很明显,也很简单,Fat jar 多了一个BOOT-INF目录,其他一样(com和org一样,都是包路径以及内部包含一些类文件)。

1.1 BOOT-INF

BOOT-INF里面的内容很明显,是我们的业务类文件以及我们业务依赖的一些jar包,jar在lib中。

1.2 META-INF

META-INF是jar文件规范里面要求的内容,包含MANIFEST.MF文件,我们打开springboot的jar中的MANIFEST.MF文件,如下:

Manifest-Version: 1.0
Implementation-Title: demo
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.example.demo.DemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.0.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher

关于此文件的作用以及使用规范,本文不在展开,可以参考相关文章:https://www.cnblogs.com/EasonJim/p/6485677.html

其中,如下两个属性需要注意:
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.demo.DemoApplication

Main-Class才是springboot真正的程序入口,一旦定义了此属性,就可以使用 java -jar x.jar来运行该jar文件。同时也表明,java -jar x.jar的执行方式,并不是springboot专有的,你也可以定义打包插件,让打出的包中包含你自己定义的JarLauncher.class,然后按照你自定义的方式运行程序。

springboot在JarLauncher中,调用了Start-Class,也就是我们工程代码中的启动类。
再往后,才执行工程中的main方法。

注意下,这个JarLauncher.class是打包在jar文件中的,如果想在工程中查看源码,需要增加如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
    <scope>provided</scope>
</dependency>

二.main方法

这里先简单总结下,然后再分开详细说;
1.main方法里的SpringApplication.run肯定是springboot的程序入口,主要做了处理应用上下文ConfigurableApplicationContext等操作,并且刚才说的@SpringBootApplication注解,也将在这里被触发;
2.@SpringBootApplication注解用来导入外部模块的配置类,这里的模块是指在创建springboot项目时,选择的模块,比如web模块、数据库层的jpa模块等,体现在pom.xml的依赖上:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

还是先说@SpringBootApplication注解的机制吧,比如具体怎么导入初始化配置的;至于这个注解什么时候触发执行的,放在后边的SpringApplication.run()方法中说;

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

几个传统注解不单独说了,下面重点说下其余2个:
1.@SpringBootConfiguration,打开


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

没啥东西,等同于@Configuration;
2.@EnableAutoConfiguration,所有奥秘都在这个注解里了,打开:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)//这里是重点
public @interface EnableAutoConfiguration {

先说明下,不懂@import注解的作用的话,很难继续往下理解了,import注解就是用来导入配置文件的,怎么用,具体看另外一个帖子:《spring @Import注解的作用和几种使用方式》
继续说:AutoConfigurationImportSelector这个类,打开,

    @Override
	public void process(AnnotationMetadata annotationMetadata,
			DeferredImportSelector deferredImportSelector) {
		Assert.state(
				deferredImportSelector instanceof AutoConfigurationImportSelector,
				() -> String.format("Only %s implementations are supported, got %s",
						AutoConfigurationImportSelector.class.getSimpleName(),
						deferredImportSelector.getClass().getName()));
		AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
				.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
						annotationMetadata);
		this.autoConfigurationEntries.add(autoConfigurationEntry);
		for (String importClassName : autoConfigurationEntry.getConfigurations()) {
			this.entries.putIfAbsent(importClassName, annotationMetadata);
		}
	}

找到上面重写的process方法,继续按照这个调用链路:getAutoConfigurationEntry()->getCandidateConfigurations()->
SpringFactoriesLoader.loadFactoryNames()->SpringFactoriesLoader.loadSpringFactories,地方到了

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));//看这里!!!
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

就是从META-INF/spring.factories这个路径下,读取配置文件; 我们在demo工程里搜一下这个文件,发现好多jar包里面都有:
在这里插入图片描述
回过头,在看下SpringFactoriesLoader.loadFactoryNames的入参,第一个参数值是getSpringFactoriesLoaderFactoryClass(),
打开看下,

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

发现是EnableAutoConfiguration这个类;也就是说,加载的就是spring.factories中key为EnableAutoConfiguration这个类的配置项
springboot这种约定机制,给我们扩展功能带来方便;比如我们项目中自己开发公共的代码部分,可以打包成一个jar包,里面也可以加上这个文件,里面写一些我们想初始化的类;这样其他项目引用了自定义的jar包,一些需要的配置就会被sprinboot读取到;
打开springboot的默认配置文件:
在这里插入图片描述
在这里插入图片描述
里面的存储结构是key=value的形式,value可以写多个,用逗号隔开;
可以看到,springboot默认加载了es、jpa、redis等非常多的功能模块;
打开其中的org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,我们看下springboot是怎么配置redis参数的;
在这里插入图片描述
这个类上有个@EnableConfigurationProperties(RedisProperties.class),打开RedisProperties,
在这里插入图片描述
可以发现,springboot读取了配置文件中前缀是spring.redis的配置参数;
@SpringBootApplication就先粗浅的写这些吧;

2.2.Run方法执行流程

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

new了一个SpringApplication对象,然后执行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));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
  

1.这里入参primarySources就是启动类本身,那么后边代码就能通过反射获取到这个类中的所有内容,主要是注解信息;
2.setInitializers,设置初始化类,用的依然是SpringFactoriesLoader,加载spring.factories文件中,key是ApplicationContextInitializer类的配置项目;
3.setListeners,同样的,加载spring.factories文件中,key是ApplicationListener的配置项目;
继续看run方法:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            //Initializer类在这里被执行!!!!
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //看这里!!!
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }
        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

挑重点说,因为主要想和上边的@SpringBootApplication注解串起来,其他的后边在慢慢说,贪多嚼不烂;
我们refreshContext()这个方法,打开,

private void refreshContext(ConfigurableApplicationContext context) {
        this.refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
                ;
            }
        }
    }

继续打开refresh方法,

protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext)applicationContext).refresh();
    }

继续打开refresh,因为这个applicationContext是ServletWebServerApplicationContext类型的,因此进入ServletWebServerApplicationContext.refresh()方法:

@Override
public final void refresh() throws BeansException, IllegalStateException {
	try {
		super.refresh();
	}
	catch (RuntimeException ex) {
		stopAndReleaseWebServer();
		throw ex;
	}
}

super.refresh(),父类是AbstractApplicationContext,也就是执行AbstractApplicationContext.refresh()方法,
到这里,已经不再是springboot的代码了,已经进入了spring的代码中,

public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);
            try {
                this.postProcessBeanFactory(beanFactory);
                //看这里
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

invokeBeanFactoryPostProcessors执行后,就会走到@EnableAutoConfiguration注解中@import的AutoConfigurationImportSelector类的process方法中;至此,run方法和启动类上的注解就关联起来了;
其他@EnableXX的注解发挥作用的流程,也是一样的道理,关键就是那个@import注解。

refresh()方法中的具体内容,参考spring最新版本代码分析《spring5源码阅读(二)refresh()方法》

内嵌tomcat

主线内容还差一点,继续:
this.finishRefresh();在执行的时候,由于ServletWebServerApplicationContext作为子类,重写了此方法:

@Override
protected void finishRefresh() {
	super.finishRefresh();
	WebServer webServer = startWebServer();
	if (webServer != null) {
		publishEvent(new ServletWebServerInitializedEvent(webServer, this));
	}
}

子类在执行完父类的super.finishRefresh();后,还额外执行了
WebServer webServer = startWebServer();
跟进去,你就会发现,springboot内嵌的tomcat,就是在这里触发启动的。

先记录到这里吧,springboot内容还是比较多的,先挑主线,后期再慢慢丰富内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值