zoukankan      html  css  js  c++  java
  • spring boot启动流程

    springboot 版本为 : 2.2.3

    概述

    spring boot 的启动过程主要是两个方面,一个是创建 SpringApplication 这个类,该类用于启动启动整个应用,是应用的启动类。另一方面是 SpringApplication 的 run 方法,该方法会初始化 listener 和 initialize ,并在分发事件到各个 listeners ,同时从资源中加载bean, 其中 listeners 和 initializer 两个类是用于 spring boot 的启动扩展

    @SpringBootApplication 注解

    SpringApplication.run(DownhtmlApplication.class, args);
    
    

    调用 SpringApplication 的 run 方法,会创建一个SpringApplication自身的一个对象,然后再调用 run方法。run方法会返回一个 ConfigurableApplicationContext 。

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

    看一下这个 方法是干什么的 ?

    Create a new {@link SpringApplication} instance. The application context will load beans from the specified primary sources (see {@link SpringApplication class-level documentation for details. The instance can be customized before calling {@link #run(String...)}.

    这个 application context 将会加载特定优先级别资源的bean,同时在调用run之前都会执行自定义的方法 初始化方法就是为字段赋值。 我们再看一下 SpringApplication 的注释 :

    Class that can be used to bootstrap and launch a Spring application from a Java main method. By default class will perform the following steps to bootstrap your application:
    - Create an appropriate ApplicationContext instance (depending on your classpath)
    - Register a CommandLinePropertySource to expose command line arguments as Spring properties
    - Refresh the application context, loading all singleton beans
    - Trigger any CommandLineRunner beans
    
    
    In most circumstances the static run(Class, String[]) method can be called directly from your main method to bootstrap your application:
       @Configuration
       @EnableAutoConfiguration
       public class MyApplication  {
      
         // ... Bean definitions
      
         public static void main(String[] args) {
           SpringApplication.run(MyApplication.class, args);
         }
       }
       
    For more advanced configuration a SpringApplication instance can be created and customized before being run:
       public static void main(String[] args) {
         SpringApplication application = new SpringApplication(MyApplication.class);
         // ... customize application settings here
         application.run(args)
       }
       
    SpringApplications can read beans from a variety of different sources. It is generally recommended that a single @Configuration class is used to bootstrap your application, however, you may also set sources from:
    - The fully qualified class name to be loaded by AnnotatedBeanDefinitionReader
    - The location of an XML resource to be loaded by XmlBeanDefinitionReader, or a groovy script to be loaded by GroovyBeanDefinitionReader
    - The name of a package to be scanned by ClassPathBeanDefinitionScanner
    
    Configuration properties are also bound to the SpringApplication. This makes it possible to set SpringApplication properties dynamically, like additional sources ("spring.main.sources" - a CSV list) the flag to indicate a web environment ("spring.main.web-application-type=none") or the flag to switch off the banner ("spring.main.banner-mode=off").
    
    

    有点长,但是很好理解,核心的就是根据你的 classpath 创建合适的 ApplicationContext 实例,在一个就是刷新 application context 加载所有的单例 bean 。

    从字面上也可以理解Application(例如上面的 SpringApplications)和 ApplicationContext 是一种提供的关系,ApplicationContext 更加抽象一点(应用上下文),它是由 Application 产生。

    SpringApplication构造函数

    下面 SpringApplication 的构造函数主要做了一下几个事情:

    1. 推断应用类型是 standard 还是 web
    2. 设置初始化器(Initializer)
    3. 设置监听器(Listener)
    4. 推断应用入口类
    	/**
    	 * Create a new {@link SpringApplication} instance. The application context will load
    	 * beans from the specified primary sources (see {@link SpringApplication class-level}
    	 * documentation for details. The instance can be customized before calling
    	 * {@link #run(String...)}.
    	 * @param resourceLoader the resource loader to use
    	 * @param primarySources the primary bean sources
    	 * @see #run(Class, String[])
    	 * @see #setSources(Set)
         * 
    	 */
    	@SuppressWarnings({ "unchecked", "rawtypes" })
    	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();
    	}
    
    

    设置初始化器(Initializer)

    这个步骤来说就是加载 ApplicationContextInitializer 的子类实现。

    	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    		return getSpringFactoriesInstances(type, new Class<?>[] {});
    	}
    
    
    	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
            //加载 classLoader
    		ClassLoader classLoader = getClassLoader();
            //set 保存实例保持实例唯一
    		// 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;
    	}
    
    

    那么加载这个 ApplicationContextInitializer 是干什么的呢

    在Spring上下文被刷新之前进行初始化的操作。典型地比如在Web应用中,注册Property Sources或者是激活Profiles。Property Sources比较好理解,就是配置文件。Profiles是Spring为了在不同环境下(如DEV,TEST,PRODUCTION等),加载不同的配置项而抽象出来的一个实体。

    项目中的 profiles : application.properties 文件下

    spring.profiles.active=test
    #spring.profiles.include: prod,dev,test,ppod_dev
    
    

    设置监听器(Listener)

    我们可以看到设置监听器(Listener)也是调用了 getSpringFactoriesInstances 方法 ,不同的是这次传入的类是 ApplicationListener 。 我们看一下这个 ApplicationListener 是什么东西 ?

    @FunctionalInterface
    public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    
    	/**
    	 * Handle an application event.
    	 * @param event the event to respond to
    	 */
    	void onApplicationEvent(E event);
    
    }
    
    
    Interface to be implemented by application event listeners.
    Based on the standard java.util.EventListener interface for the Observer design pattern.
    As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.
    
    

    给application event listener 提供的一个接口,基于java.util.EventListener 的原生实现。 Spring 3.0 以后可以定义和注册事件到 ApplicationContext 中去, SpringApplication 提供了方法 ,例如 :

            SpringApplication springApplication = new SpringApplication();
            ApplicationListener listener = new ApplicationListener() {
                @Override
                public void onApplicationEvent(ApplicationEvent event) {
    
                }
            };
            springApplication.addListeners(listener);
            springApplication.run();
    
    

    关于监听器还可以查看参考资料中的文章。

    SpringApplication run 方法。

    源码简要分析

    	/**
    	 * Run the Spring application, creating and refreshing a new
    	 * {@link ApplicationContext}.
    	 * @param args the application arguments (usually passed from a Java main method)
    	 * @return a running {@link ApplicationContext}
    	 */
    	public ConfigurableApplicationContext run(String... args) {
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		ConfigurableApplicationContext context = null;
    		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    		
    		// 设置java.awt.headless系统属性为true - 没有图形化界面
    		configureHeadlessProperty();
    		
    		// 获取 listener,并且启动ing listener 
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		listeners.starting();
    		
    		try {
    
    			//准备环境参数
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    			configureIgnoreBeanInfo(environment);
    			
    			//banner 展示 
    			Banner printedBanner = printBanner(environment);
    			
    			//创建 context 
    			context = createApplicationContext();
    			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    
    			//准备 context 		
    			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    			
    			//刷新 context 
    			refreshContext(context);
    			
    			//钩子方法 
    			afterRefresh(context, applicationArguments);
    			
    			stopWatch.stop();
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    			}
    
    			//listener 传入 context 形参,正式启动
    			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;
    	}
    
    

    获取 SpringApplicationRunListeners 对象的方法 。

    	private SpringApplicationRunListeners getRunListeners(String[] args) {
    		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    		return new SpringApplicationRunListeners(logger,
    				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    	}
    
    

    那么 SpringApplicationRunListeners 个是什么呢?SpringApplicationRunListeners 持有一个 SpringApplicationRunListener 的列表,实际上就是对各种时间转发到多个 SpringApplicationRunListener 上

    class SpringApplicationRunListeners {
    
    	private final Log log;
    
    	private final List<SpringApplicationRunListener> listeners;
    
    	...
    
    } 
    
    

    SpringApplicationRunListener 从名字就可以看到是监听器,监听各种事件,包括 contextprepare 等等。

    createApplicationContext 方法创建 ConfigurableApplicationContext 对象,利用发射创建特定的 context 。

    	protected ConfigurableApplicationContext createApplicationContext() {
    		Class<?> contextClass = this.applicationContextClass;
    		if (contextClass == null) {
    			try {
    				switch (this.webApplicationType) {
    				case SERVLET:
    					contextClass = Class.forName(DEFAULT_SERVLET_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);
    	}
    
    

    后面两个方法 prepareContext 和 refreshContext 进行了计较多的逻辑,refreshContext 方法此处不再分析 。

    	
    	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
    			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    		context.setEnvironment(environment);
    		postProcessApplicationContext(context);
    		applyInitializers(context);
    		listeners.contextPrepared(context);
    		if (this.logStartupInfo) {
    			logStartupInfo(context.getParent() == null);
    			logStartupProfileInfo(context);
    		}
    		// Add boot specific singleton beans 加上两个特别的 root 单例类 
    		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    		if (printedBanner != null) {
    			beanFactory.registerSingleton("springBootBanner", printedBanner);
    		}
    		if (beanFactory instanceof DefaultListableBeanFactory) {
    			((DefaultListableBeanFactory) beanFactory)
    					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    		}
    		if (this.lazyInitialization) {
    			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    		}
    		// 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);
    	}
    
    	//赋值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());
    			}
    		}
    		if (this.addConversionService) {
    			context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
    		}
    	}
    
    	// 可以看到重要的逻辑就是执行 initializer 的 initialize 方法 
    	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);
    		}
    	}
    
    
    	/**
    	 * load方法进行了重要的逻辑,加载 bean 进入到 context 中
    	 * Load beans into the application context.
    	 * @param context the context to load beans into
    	 * @param sources the sources to load
    	 */
    	protected void load(ApplicationContext context, Object[] sources) {
    		if (logger.isDebugEnabled()) {
    			logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    		}
    		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(this.environment);
    		}
    		loader.load();
    	}
    
    

    补充

    来自参考资料的总结 :

    本文分析了Spring Boot启动时的关键步骤,主要包含以下两个方面:

    1. SpringApplication实例的构建过程

    其中主要涉及到了初始化器(Initializer)以及监听器(Listener)这两大概念,它们都通过META-INF/spring.factories完成定义。

    1. SpringApplication实例run方法的执行过程

    其中主要有一个SpringApplicationRunListeners的概念,它作为Spring Boot容器初始化时各阶段事件的中转器,将事件派发给感兴趣的Listeners(在SpringApplication实例的构建过程中得到的)。这些阶段性事件将容器的初始化过程给构造起来,提供了比较强大的可扩展性。

    如果从可扩展性的角度出发,应用开发者可以在Spring Boot容器的启动阶段,扩展哪些内容呢:

    • 初始化器(Initializer)
    • 监听器(Listener)
    • 容器刷新后置Runners(ApplicationRunner或者CommandLineRunner接口的实现类)

    参考资料

    • https://my.oschina.net/dslcode/blog/1591523 (springboot 中 ApplicationListener 相关)
    • https://blog.csdn.net/dm_vincent/article/details/76735888
  • 相关阅读:
    7.12
    Powerdesigner使用方法
    数据库中float类型字段,转化到前端显示,统一保留两位小数
    【1】直接插入排序
    KMP算法
    ssm框架下web项目,web.xml配置文件的作用
    客户要求输入框要记录下上一次输入的内容
    tomcat启动闪退
    页面第一次加载,JS没有效果,刷新一下就好了
    机器学习,安装python的支持包
  • 原文地址:https://www.cnblogs.com/Benjious/p/12654869.html
Copyright © 2011-2022 走看看