探究springboot自启动配置原理

我们在以前开发web应用中,需要各种配置,就是配置炼狱。springboot的出现让开发变得简单,约定大于配置,内嵌servlet容器,提供各种starter尽可能的自动装配。无需xml文件开箱即用。springboot的入门简单,但是想要精通我们就要去探究springboot的原理。

1、启动原理

重点关注对象:

  1. ApplicationContextInitializer:应用上下文初始化
  2. SpringApplicationRunListener:应用上下文运行监听

(1)springboot2.0开发web应用:

  • 导入jar包
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
</dependency>

(2)编写启动类

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

这是我们一个web应用就跑起来了,**SpringApplication.run()**方法在下面又做了什么呢?

(3)启动原理

我们用run方法,定位到:

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

1、new SpringApplication(primarySources)分析

我们生成了一个SpringApplication对象,传入primarySources数组,随后再看run方法:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { 
    this.resourceLoader = resourceLoader; //resourceLoader==null;
    
    Assert.notNull(primarySources, "PrimarySources must not be null");        				this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //将primarySources赋值 并且传入我们的primarySources,primarySources为{'com.dl.SpringbootMybatisApplication'}
    this.webApplicationType = WebApplicationType.deduceFromClasspath();   				
  //判断是什么应用
    setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));   
    //加载applicationcontent初始化类,将Initializer属性赋值,都是通过META-INF/spring.factories配置得到的
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));      	//加载监听器,将所需的Listener赋值
    this.mainApplicationClass = deduceMainApplicationClass();
    //
}

我们分析:

1、

this.webApplicationType = WebApplicationType.deduceFromClasspath(); 
  • 通过WebApplicationType的deduceFromClasspath(),根据我们是否有关键的类和判断我们是一个什么应用,普通应用还是基于servlet的web应用或者响应式web服务

  • 因为我们导入了spring-boot-starter-webjar包,所以是一个基于servlet的web应用

2、

 setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)); 

我们通过**(Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)**跟踪源码到:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// 拿到当前线程的类加载
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
         //我们通过loadFactoryNames方法
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
}

通过loadFactoryNames方法:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}

3 、loadSpringFactories(classLoader)

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader); //result为空
		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()) { //size=2
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url); 
               //resource 的两个值:1、spring-boot-autoconfigure-2.1.8.RELEASE.jar!/META-INF/spring.factories 2、spring-boot:2.1.8.RELEASE.jar!/META-INF/spring.factories
				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;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

两个jar包下的**/META-INF/spring.factories**是所有springboot启动需要加载的所有类路径信息。

1、run方法执行

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
       //加载时间类
		stopWatch.start();
    	//开始计时
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
        //开启headless模式,这个可以具体了解
		SpringApplicationRunListeners listeners = getRunListeners(args);
    	//生成 应用上下文运行监听器 
		listeners.starting();
    	//监听开始
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //封装args数组
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            //环境准备,创建我们基于servlet的web应用所需的环境,并且listenter监听调用environmentPrepared方法,环境已经准备好
			configureIgnoreBeanInfo(environment);
            //系统设置
			Banner printedBanner = printBanner(environment);
            //打印banner图
			context = createApplicationContext();
            //通过反射生成ConfigurableApplicationContext对象
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//对象生成,用于启动出错问题的回调
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //准备上下文环境;将environment保存到ioc中;而且applyInitializers(); 		      //applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法 
            //回调所SpringApplicationRunListener监听的contextPrepared()方法;
			refreshContext(context);
            //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
            //刷新容器ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat);
            //扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
			afterRefresh(context, applicationArguments);
            //所有的SpringApplicationRunListener回调finished方法
			stopWatch.stop();
            //整个过程使用时间停止
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

2、自动配置

很多东西我们都不需要自己去实现,而是springboot会自动的帮我们实现,这个是怎么做到的呢?

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

我们查看@SpringBootApplication是一个组合注解,下面有一个**@EnableAutoConfiguration**,我们在向下探究:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

1、@AutoConfigurationPackage分析:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

经过调试我们可以发先,我们自动配置扫描包就是我么启动类的所在包,所以我们写代码的时候要写在springboot的启动类下面才可以扫描到。

2、@Import(AutoConfigurationImportSelector.class)分析:

AutoConfigurationImportSelector有一个selectImports方法,返回我们那些类需要自动导入,会扫描META-INF/spring.factories文件下EnableAutoConfiguration.class下的所有类名

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //拿到所有
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

至此我们得到了那些需要自动装配,通过那些类存在判断最终需要自动装配的类实例。注册容器中。

# 框架 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×