• springboot情操陶冶-SpringApplication(二)


    承接前文springboot情操陶冶-SpringApplication(一),本文将对run()方法作下详细的解析

    SpringApplication#run()

    main函数经常调用的run()方法是我们分析的关键,先上源码

    	public ConfigurableApplicationContext run(String... args) {
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		ConfigurableApplicationContext context = null;
    		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    		// 读取java.awt.headless系统变量,默认为true.常用于linux图片的渲染
    		configureHeadlessProperty();
    		// 获取SpringApplicationRunListener接口集合并实例化调用公共接口starting()
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		listeners.starting();
    		try {
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    					args);
    			// environment configuration
    			ConfigurableEnvironment environment = prepareEnvironment(listeners,
    					applicationArguments);
    			// spring.beaninfo.ignore属性读取
    			configureIgnoreBeanInfo(environment);
    			// springboot的banner样图
    			Banner printedBanner = printBanner(environment);
    			// 创建spring应用上下文(尚未刷新)
    			context = createApplicationContext();
    			// SpringBootExceptionReporter接口集合读取
    			exceptionReporters = getSpringFactoriesInstances(
    					SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    			// applicationContext configuration
    			prepareContext(context, environment, listeners, applicationArguments,
    					printedBanner);
    			// 刷新spring应用上下文
    			refreshContext(context);
    			afterRefresh(context, applicationArguments);
    			stopWatch.stop();
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass)
    						.logStarted(getApplicationLog(), stopWatch);
    			}
    			// SpringApplicationRunListener接口的started()方法调用
    			listeners.started(context);
    			callRunners(context, applicationArguments);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, listeners);
    			throw new IllegalStateException(ex);
    		}
    
    		try {
    			// SpringApplicationRunListener接口的running()方法调用
    			listeners.running(context);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, null);
    			throw new IllegalStateException(ex);
    		}
    		return context;
    	}
    

    以上的代码注释有点多,笔者分块来进行罗列分析

    SpringApplication#getRunListeners()

    获取SpringApplicationRunListener接口集合并实例化,根据前文得知,读取的是METAspring.factories文件中的对应属性,
    此处笔者以org.springframework.boot.context.event.EventPublishingRunListener为例。


    先观察下其构造函数

    	public EventPublishingRunListener(SpringApplication application, String[] args) {
    		this.application = application;
    		this.args = args;
    		this.initialMulticaster = new SimpleApplicationEventMulticaster();
    		for (ApplicationListener<?> listener : application.getListeners()) {
    			this.initialMulticaster.addApplicationListener(listener);
    		}
    	}
    

    注意application.getListeners()方法,根据前文得知,其会拿到类型为ApplicationListener的集合并存入至SimpleApplicationEventMulticaster广播类中;
    其余方法,比如starting()/started()/environmentPrepared()等等方法均是由其统一调用所有的ApplicationListener接口的对应事件。笔者此处以starting()为例

    	@Override
    	public void starting() {
    		this.initialMulticaster.multicastEvent(
    				new ApplicationStartingEvent(this.application, this.args));
    	}
    

    其会找寻支持响应ApplicationStartingEvent事件的Listeners,并执行相应的事件方法。响应的监听器有LoggingApplicationListenerLiquibaseServiceLocatorApplicationListener

    SpringApplication#prepareEnvironment()

    Environment环境准备工作

    	private ConfigurableEnvironment prepareEnvironment(
    			SpringApplicationRunListeners listeners,
    			ApplicationArguments applicationArguments) {
    		// Create and configure the environment
    		ConfigurableEnvironment environment = getOrCreateEnvironment();
    		// 解析args参数和spring.profiles.active配置读取
    		configureEnvironment(environment, applicationArguments.getSourceArgs());
    		// 触发ApplicationEnvironmentPreparedEvent事件
    		listeners.environmentPrepared(environment);
    		bindToSpringApplication(environment);
    		if (this.webApplicationType == WebApplicationType.NONE) {
    			environment = new EnvironmentConverter(getClassLoader())
    					.convertToStandardEnvironmentIfNecessary(environment);
    		}
    		ConfigurationPropertySources.attach(environment);
    		return environment;
    	}
    

    内含代码内容过多,笔者此处针对自己的阅读作下小结

    1. args参数是会被包装为SimpleCommandLinePropertySource属性源,对应key为commandLineArgs。用户可通过系统变量设定spring.config.location/spring.config.name等属性

    2. spring.profiles.active系统属性读取,用于不同条件的配置

    3. ApplicationEnvironmentPreparedEvent事件触发,主要的有ConfigFileApplicationListener监听类(其会默认读取application.properties/application.xml/application.yaml配置文件)

      PS:ConfigFileApplicationListener这个类比较重要,其也会去读取配置文件中的spring.profiles.active属性加载Profile;
      另外也去读取EnvironmentPostProcessor接口来统一调用。有兴趣的读者可好好分析一下

    4. 将Environment含有的属性源绑定至key为configurationPropertiesPropertySources类型中,方便springboot全局搜索上下文所含有的属性

    SpringApplication#configureIgnoreBeanInfo()

    读取spring.beaninfo.ignore属性,默认为true,主要是用于忽略bean的基本信息

    SpringApplication#printBanner()

    创建banner打印对象,其默认打印的样图如下(作用于console)。具体的读者感兴趣可自行阅读原理

      .   ____          _            __ _ _
     /\ / ___'_ __ _ _(_)_ __  __ _    
    ( ( )\___ | '_ | '_| | '_ / _` |    
     \/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.0.3.RELEASE)
    

    SpringApplication#createApplicationContext()

    创建应用上下文对象,其会根据判断出来的应用类型来创建相应的上下文。

    应用类型 上下文对应class类
    SERVLET org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
    REACTIVE org.springframework.boot.web.servlet.context.AnnotationConfigReactiveWebServerApplicationContext
    NONE org.springframework.context.annotation.AnnotationConfigApplicationContext

    很明显,全部会应用注解方式来加载上下文。

    SpringApplication#prepareContext()

    对已创建的上下文对象作下预备工作

    SpringApplication#applyInitializers()

    启动相应的初始化类,这些初始化类均是ApplicationContextInitializer接口的实现类

    	protected void applyInitializers(ConfigurableApplicationContext context) {
    		for (ApplicationContextInitializer initializer : getInitializers()) {
    			// 检查相应的ApplicationContextInitializer实体类所接收的泛型实体class
    			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
    					initializer.getClass(), ApplicationContextInitializer.class);
    			// 只对接收的泛型为ConfigurableApplicationContext.class进行相应的初始化
    			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
    			initializer.initialize(context);
    		}
    	}
    

    针对上面的代码注释,初始化类基本有ConfigurationWarningsApplicationContextInitializer/ContextIdApplicationContextInitializer/DelegatingApplicationContextInitializer/ServerPortInfoApplicationContextInitializer等。
    有兴趣的可自行分析相应的初始化类都执行了哪些操作

    SpringApplication#load()

    加载beans到相应的上下文对象ApplicationContext中

    	// 此处的source一般为main函数所在的class,也可通过SpringApplication#setSource()来新增
    	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);
    		}
    		// 加载beans
    		loader.load();
    	}
    

    笔者此处最关心这个BeanDefinitionLoader会耍什么花样,继续往下


    先从构造函数开始

    	BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
    		Assert.notNull(registry, "Registry must not be null");
    		Assert.notEmpty(sources, "Sources must not be empty");
    		this.sources = sources;
    		// 注解解析类
    		this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
    		// xml解析类
    		this.xmlReader = new XmlBeanDefinitionReader(registry);
    		if (isGroovyPresent()) {
    			this.groovyReader = new GroovyBeanDefinitionReader(registry);
    		}
    		// classpath扫描类,并屏蔽指定的sources类
    		this.scanner = new ClassPathBeanDefinitionScanner(registry);
    		this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
    	}
    
    1. AnnotatedBeanDefinitionReader注解解析类,其会注册多个postProcessor接口供后续的上下文刷新操作被调用解析@Configuration/@Autowired/@Required

    2. XmlBeanDefinitionReaderXML解析类,其会解析XML配置

    3. ClassPathBeanDefinitionScanner扫描classpath环境下指定的包以及指定class类


    再看下处理方法load()

    	public int load() {
    		int count = 0;
    		for (Object source : this.sources) {
    			count += load(source);
    		}
    		return count;
    	}
    	
    	private int load(Object source) {
    		Assert.notNull(source, "Source must not be null");
    		if (source instanceof Class<?>) {
    			return load((Class<?>) source);
    		}
    		if (source instanceof Resource) {
    			return load((Resource) source);
    		}
    		if (source instanceof Package) {
    			return load((Package) source);
    		}
    		if (source instanceof CharSequence) {
    			return load((CharSequence) source);
    		}
    		throw new IllegalArgumentException("Invalid source type " + source.getClass());
    	}
    	// 笔者重点关注此处
    	private int load(Class<?> source) {
    		if (isGroovyPresent()
    				&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
    			// Any GroovyLoaders added in beans{} DSL can contribute beans here
    			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
    					GroovyBeanDefinitionSource.class);
    			load(loader);
    		}
    		// 查看class是否被@Component修饰过。此处多用于加载main类
    		if (isComponent(source)) {
    			// 注册bean
    			this.annotatedReader.register(source);
    			return 1;
    		}
    		return 0;
    	}	
    

    此处的load()方法主要是对设置的sources类进行判断是否被@Component注解,是则注入至bean工厂。即我们常写的main()函数类一般会加上@SpringApplication注解,其最终会被AnnotatedBeanDefinitionReader处理。

    SpringApplication#refresh()

    刷新上下文对象,笔者此处就不展开了,可参考之前的文章Spring源码情操陶冶-AbstractApplicationContext

    异常处理

    springboot的异常处理机制是通过读取SpringBootExceptionReporter接口类来对不同的异常进行不同的输出,感兴趣的可自行阅读

    小结

    通过上述的分析,基本对springboot的工作原理有了一定的了解,最主要的其实还是其会将公共的配置放置于METAspring.factories文件中,我们以后只要多关注此文件就会明白的更多。
    至于@SpringBootApplication注解是如何为springboot服务的,笔者后续再分析

  • 相关阅读:
    MySql中引擎
    Session和Cookie的区别和联系
    Global Round 2
    CF550 DIV3
    Java的反射机制
    IO多路复用
    简单DP内容
    Java 对象的创建以及类加载
    Java 一些常见问题(持续更新)
    红黑树的一些知识点
  • 原文地址:https://www.cnblogs.com/question-sky/p/9372997.html
Copyright © 2020-2023  润新知