SpringBoot配置外部Tomcat项目启动流程源码分析(下)

Posted Java小叮当

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot配置外部Tomcat项目启动流程源码分析(下)相关的知识,希望对你有一定的参考价值。

前言

SpringBoot应用默认以Jar包方式并且使用内置Servlet容器(默认Tomcat),该种方式虽然简单但是默认不支持JSP并且优化容器比较复杂。故而我们可以使用习惯的外置Tomcat方式并将项目打War包。一键获取SpringBoot笔记

【6】SpringApplication.run方法详细分析-准备环境

③ prepareEnvironment–环境构建

ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);

跟进去该方法:

 private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) 
        // Create and configure the environment
        //获取对应的ConfigurableEnvironment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //配置
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        //发布环境已准备事件,这是第二次发布事件
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (this.webApplicationType == WebApplicationType.NONE) 
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        
        ConfigurationPropertySources.attach(environment);
        return environment;
    

来看一下getOrCreateEnvironment()方法,前面已经提到,environment已经被设置了servlet类型,所以这里创建的是环境对象是StandardServletEnvironment。

 private ConfigurableEnvironment getOrCreateEnvironment() 
        if (this.environment != null) 
            return this.environment;
        
        if (this.webApplicationType == WebApplicationType.SERVLET) 
            return new StandardServletEnvironment();
        
        return new StandardEnvironment();
    


枚举类WebApplicationType是SpringBoot2新增的特性,主要针对spring5引入的reactive特性。

枚举类型如下:

public enum WebApplicationType 
    //不需要再web容器的环境下运行,普通项目
    NONE,
    //基于servlet的web项目
    SERVLET,
    //这个是spring5版本开始的新特性
    REACTIVE

Environment接口提供了4种实现方式,StandardEnvironment、StandardServletEnvironment和MockEnvironment、StandardReactiveWebEnvironment,分别代表普通程序、Web程序、测试程序的环境、响应式web环境,

配置环境代码如下:

protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) 
		configurePropertySources(environment, args);
		configureProfiles(environment, args);
	

在返回return new StandardServletEnvironment();对象的时候,会完成一系列初始化动作,主要就是将运行机器的系统变量和环境变量,加入到其父类AbstractEnvironment定义的对象MutablePropertySources中,MutablePropertySources对象中定义了一个属性集合:

 private final List<PropertySource<?>> propertySourceList;

    public MutablePropertySources() 
        this.propertySourceList = new CopyOnWriteArrayList();
        this.logger = LogFactory.getLog(this.getClass());
    

执行到这里,系统变量和环境变量已经被载入到配置文件的集合中,接下来就行解析项目中的配置文件。

关于CopyOnWriteArrayList可以参考博文浅谈从fail-fast机制到CopyOnWriteArrayList使用

④ listeners.environmentPrepared(environment);–第二次发布事件

来看一下listeners.environmentPrepared(environment);,上面已经提到了,这里是第二次发布事件。什么事件呢?顾名思义,系统环境初始化完成的事件。

跟进方法:

继续跟:

@Override
public void environmentPrepared(ConfigurableEnvironment environment) 
	this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
			this.application, this.args, environment));

这里将要广播ApplicationEnvironmentPreparedEvent事件了

@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) 
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) 
			Executor executor = getTaskExecutor();
			if (executor != null) 
				executor.execute(() -> invokeListener(listener, event));
			
			else 
				invokeListener(listener, event);
			
		
	

发布事件的流程上面已经讲过了,这里不在赘述。来看一下根据事件类型获取到的监听器:

遍历监听器,调用不同监听器对该事件的处理。

可以看到获取到的监听器和第一次发布启动事件获取的监听器有几个是重复的,这也验证了监听器是可以多次获取,根据事件类型来区分具体处理逻辑。上面介绍日志监听器的时候已经提到。

主要来看一下ConfigFileApplicationListener,该监听器非常核心,主要用来处理项目配置。项目中的 properties 和yml文件都是其内部类所加载。

首先方法执行入口:

调用onApplicationEnvironmentPreparedEvent方法:

private void onApplicationEnvironmentPreparedEvent(
			ApplicationEnvironmentPreparedEvent event) 
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		for (EnvironmentPostProcessor postProcessor : postProcessors) 
			postProcessor.postProcessEnvironment(event.getEnvironment(),
					event.getSpringApplication());
		

首先还是会去读spring.factories 文件,List postProcessors = loadPostProcessors();获取的处理类有以下四种:

SystemEnvironmentPropertySourceEnvironmentPostProcessor(加载系统环境变量)SpringApplicationJsonEnvironmentPostProcessor@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) 
		MutablePropertySources propertySources = environment.getPropertySources();
		StreamSupport.stream(propertySources.spliterator(), false)
				.map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst()
				.ifPresent((v) -> processJson(environment, v));
	

在执行完上述三个监听器流程后,ConfigFileApplicationListener会执行该类本身的逻辑。由其内部类Loader加载项目制定路径下的配置文件:

// Note the order is from least to most specific (last one wins)
private static final String DEFAULT_SEARCH_LOCATIONS = 
"classpath:/,classpath:/config/,file:./,file:./config/";
ConfigFileApplicationListener

@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) 
		addPropertySources(environment, application.getResourceLoader());
	

内部类Loader构造函数:

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) 
			this.environment = environment;
			this.resourceLoader = (resourceLoader != null ? resourceLoader
					: new DefaultResourceLoader());
			this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
					PropertySourceLoader.class, getClass().getClassLoader());
		

其获取的propertySourceLoaders 如下:

至此使用ConfigFileApplicationListener将应用配置文件加载进来,接下来该其它六个监听器依次处理。

监听器处理完毕返回到ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments)处,此时environment对象如下:

接下来将environment绑定到SpringApplication上。

返回到SpringApplication类中ConfigurableApplicationContext run(String… args)方法处:

【7】SpringApplication.run方法详细分析-创建容器

⑤ createApplicationContext();创建容器

在SpringBootServletInitializer.createRootApplicationContext(ServletContext servletContext)中设置过contextClass:

builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);

看创建容器的代码:

protected ConfigurableApplicationContext createApplicationContext() 
		Class<?> contextClass = this.applicationContextClass;
		//如果contextClass 为null,就根据webApplicationType加载对应class;
		//这里不为null,是AnnotationConfigServletWebServerApplicationContext
		if (contextClass == null) 
			try 
				switch (this.webApplicationType) 
				case SERVLET:
					contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				
			
			catch (ClassNotFoundException ex) 
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			
		
		//这里根据反射实例化容器
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	

创建的context如下:

⑥ 报告错误信息

这里还是以同样的方式获取 spring.factories文件中的指定类:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) 
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	


如果应用启动失败,则会打印失败信息:

@Override
	public void report(FailureAnalysis failureAnalysis) 
		if (logger.isDebugEnabled()) 
			logger.debug("Application failed to start due to an exception",
					failureAnalysis.getCause());
		
		if (logger.isErrorEnabled()) 
			logger.error(buildMessage(failureAnalysis));
		
	

【8】SpringApplication.run方法详细分析-容器刷新之前准备

⑦ prepareContext–准备容器

这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。

private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) 
			//设置容器环境,包括各种变量
		context.setEnvironment(environment);
		//执行容器后置处理
		postProcessApplicationContext(context);
		 //执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
		applyInitializers(context);
		//发送容器已经准备好的事件,通知各监听器
		listeners.contextPrepared(context);
		//打印log
		if (this.logStartupInfo) 
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		

		// Add boot specific singleton beans
		 //注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
				//设置banner
		if (printedBanner != null) 
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		

		// Load the sources
		 //获取我们的启动类指定的参数,可以是多个
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		//加载我们的启动类,将启动类注入容器
		load(context, sources.toArray(new Object[0]));
		//发布容器已加载事件。
		listeners.contextLoaded(context);
	

容器的后置处理:

protected void postProcessApplicationContext(ConfigurableApplicationContext context) 
		if (this.beanNameGenerator != null) 
			context.getBeanFactory().registerSingleton(
					AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		
		if (this.resourceLoader != null) 
			if (context instanceof GenericApplicationContext) 
				((GenericApplicationContext) context)
						.setResourceLoader(this.resourceLoader);
			
			if (context instanceof DefaultResourceLoader) 
				((DefaultResourceLoader) context)
						.setClassLoader(this.resourceLoader.getClassLoader());
			
		
	

这里默认不执行任何逻辑,因为beanNameGenerator和resourceLoader默认为空。之所以这样做,是springBoot留给我们的扩展处理方式,类似于这样的扩展,spring中也有很多。

初始化器初始化方法调用

protected void applyInitializers(ConfigurableApplicationContext context) 
		for (ApplicationContextInitializer initializer : getInitializers()) 
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
					initializer.getClass(), ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		
	

上面提到过,有7个初始化器,这里将会依次调用initialize方法:

ServletContextApplicationContextInitializer.initialize:

public void initialize(ConfigurableWebApplicationContext applicationContext) 
//给创建的容器设置servletContext引用
		applicationContext.setServletContext(this.servletContext);
		//判断true or false ,如果为true,将applicationContext放到servletContext
		//这里为false。
		if (this.addApplicationContextAttribute) 
			this.servletContext.setAttribute(
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
					applicationContext);
		
	
ContextIdApplicationContextInitializer.initialize:

public void initialize(ConfigurableApplicationContext applicationContext) 
		ContextId contextId = getContextId(applicationContext);
		applicationContext.setId(contextId.getId());
		applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(),
				contextId);
	

为applicationContext设置ID。

listeners.contextPrepared(context)发布事件–第三次发布事件了

public void contextPrepared(ConfigurableApplicationContext context) 
		for (SpringApplicationRunListener listener : this.listeners) 
			listener.contextPrepared(context);
		
	

这里listener为EventPublishingRunListener,其contextPrepared方法为空方法:

@Override
public void contextPrepared(ConfigurableApplicationContext context) 


获取primarySources放到allSources

可以看到我们的主启动类在里面。

加载启动指定类(重点)

这里会将我们的启动类加载spring容器beanDefinitionMap中,为后续springBoot 自动化配置奠定基础,springBoot为我们提供的各种注解配置也与此有关。

这里参数即为我们项目启动时传递的参数:SpringApplication.run(SpringbootwebprojectApplication.class, args);

protected void load(ApplicationContext context, Object[] sources) 
		if (logger.isDebugEnabled()) 
			logger.debug(
					"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		
		//根据BeanDefinitionRegistry创建BeanDefinitionLoader
		BeanDefinitionLoader loader = createBeanDefinitionLoader(
				getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) 
			loader.setBeanNameGenerator(this.beanNameGenerator);
		
		if (this.resourceLoader != null) 
			loader.setResourceLoader(this.resourceLoader);
		
		if (this.environment != null) 
		//设置环境
			loader.setEnvironment(以上是关于SpringBoot配置外部Tomcat项目启动流程源码分析(下)的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot配置外部Tomcat项目启动流程源码分析(下)

SpringBoot配置外部Tomcat项目启动流程源码分析(下)

SpringBoot配置外部Tomcat项目启动流程源码分析

SpringBoot中如何使用外置tomcat服务器

springboot使用JNDI指定数据源配置由外部tomcat配置

SpringBoot项目部署到外部Tomcat的相关配置