springboot的启动流程


spring boot的启动流程图

启动流程图
springboot的流程启动主要为三个部分

  • SpringApplication的初始化,配置基础的环境变量,构造器,监听器等。
  • 流程启动,主要为启动流程的监听,加载配置环境,创建上下文
  • 自动化配置

springboot的主启动类入口

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@MapperScan(value = "cn.dingdm.website.mapper")
@EnableCaching
public class WebsiteApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebsiteApplication.class, args);
    }
}

SpringBootApplication注解详解

@Target({ElementType.TYPE}) //注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented // 表明这个注解应该被javadoc记录
@Inherited // 子类可以继承该注解
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan( // 扫描路径设置
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {

ElementType枚举类

public enum ElementType {
    TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    TYPE_PARAMETER,
    TYPE_USE
}

生命周期枚举类

public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}

1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

注解 父类 作用
SpringBootConfiguration Configuration 标注当前类是Java config配置类,会被扫描并加载到ioc容器
ComponentScan 扫描默认包或指定包下面符合条件的组件并加载
EnableAutoConfiguration 从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfig.EnableAutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的java config形式的ioc容器配置类,汇总并加载到ioc容器
@AutoConfigurationPackages 注册当前主程序类的同级以及子级的包中的符合条件的Bean的定义
@Import(AutoConfigurationImportSelector.class) 扫描各个组件jar META-INIF目录下的spring.dactories文件,将下面的包名.类名中的工厂类全部加载到IOC容器中;将所有符合条件的bean的定义加载到ioc容器中

springboot启动

执行主函数main方法
main函数
创建SpringApplication对象对象,并运行SpringApplication对象的run方法
创建对象
加载接口
加载接口

        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = this.deduceWebApplicationType();
       // 扫描当前路径下META-INF/spring.factories文件的,加载ApplicationContextInitializer接口实例 
	   	this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 扫描当前路径下META-INF/spring.factories文件的,加载ApplicationListener接口实例
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();

ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
ApplicationListener 当springboot启动时事件change后都会触发

这两个接口均可自己进行定义
实现接口后自己配置自己的实现类

org.springframework.context.ApplicationContextInitializer=\
org.admin.starter.test.listener.StarterApplicationContextInitializer
org.springframework.context.ApplicationListener=\
org.admin.starter.test.listener.StarterApplicationListener

实现run方法
run方法

public ConfigurableApplicationContext run(String... args) {
        // 计时器
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection exceptionReporters = new ArrayList<>();
        // 设置环境变量
        configureHeadlessProperty();
        // 获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
            // 参数args封装成DefaultApplicationArguments
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
            // 把环境跟spring上下文绑定好,并且执行environmentPrepared()方法
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
            // 判断一些环境的值,设置一些环境的值
			configureIgnoreBeanInfo(environment);
            // 打印banner
			Banner printedBanner = printBanner(environment);
            // 根据项目类型创建上下文
			context = createApplicationContext();
            // 获取异常报告事件监听
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            // 准备上下文,执行完成后调用contextPrepared()方法,contextLoaded()方法
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
            // spring启动的代码,这里就回去里面就回去扫描并且初始化单实列bean了
            // 这个refreshContext()加载了bean,启动了内置web容器
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
           // 执行ApplicationRunListeners中的started()方法
			listeners.started(context);
            
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, listeners, exceptionReporters, ex);
			throw new IllegalStateException(ex);
		}
		listeners.running(context);
		return context;
	}

根据项目类型创建上下文,并且注入几个核心组件类。
创建上下文
refreshContext(context)方法启动spring的代码加载了bean,还启动了内置web容器
加载bean

public void refresh() throws BeansException, IllegalStateException {
        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();
            }

        }
    }

onRefresh钩子方法

钩子方法,它会钩到它子类重写onRefresh()方法。所以去看子类里面的onRefresh()
子类实现
内置容器
内置容器
内置容器
容器选择
容器选择
tomcat容器tomcat

springboot的自动化配置

自动化配置
该配置模块的主要使用到了SpringFactoriesLoader,即Spring工厂加载器,该对象提供了loadFactoryNames方法,入参为factoryClass和classLoader,即需要传入上图中的工厂类名称和对应的类加载器,方法会根据指定的classLoader,加载该类加器搜索路径下的指定文件,即spring.factories文件,传入的工厂类为接口,而文件中对应的类则是接口的实现类,或最终作为实现类,所以文件中一般为如下图这种一对多的类名集合,获取到这些实现类的类名后,loadFactoryNames方法返回类名集合,方法调用方得到这些集合后,再通过反射获取这些类的类对象、构造方法,最终生成实例。
接口配置
EnableAutoConfiguration最终实现了ImportSelector(选择器)和BeanClassLoaderAware(bean类加载器中间件),重点关注一下AutoConfigurationImportSelector的selectImports方法
类信息列表
该方法在springboot启动流程——bean实例化前被执行,返回要实例化的类信息列表。我们知道,如果获取到类信息,spring自然可以通过类加载器将类加载到jvm中,现在我们已经通过spring-boot的starter依赖方式依赖了我们需要的组件,那么这些组建的类信息在select方法中也是可以被获取到的
方法中的getCandidateConfigurations方法,通过方法注释了解到,其返回一个自动配置类的类名列表,方法调用了loadFactoryNames方法

    protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

自动配置器会根据传入的factoryClass.getName()到项目系统路径下所有的spring.factories文件中找到相应的key,从而加载里面的类
加载类

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry entry = (Entry)var6.next();
                        List factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                        result.addAll((String)entry.getKey(), factoryClassNames);
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var9) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
            }
        }
    }

自动配置
自动配置

@Configuration
@ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class})
@ConditionalOnProperty(
    prefix = "spring.aop",
    name = {"auto"},
    havingValue = "true",
    matchIfMissing = true
)
public class AopAutoConfiguration {
    public AopAutoConfiguration() {
    }

    @Configuration
    @EnableAspectJAutoProxy(
        proxyTargetClass = true
    )
    @ConditionalOnProperty(
        prefix = "spring.aop",
        name = {"proxy-target-class"},
        havingValue = "true",
        matchIfMissing = true
    )
    public static class CglibAutoProxyConfiguration {
        public CglibAutoProxyConfiguration() {
        }
    }

    @Configuration
    @EnableAspectJAutoProxy(
        proxyTargetClass = false
    )
    @ConditionalOnProperty(
        prefix = "spring.aop",
        name = {"proxy-target-class"},
        havingValue = "false",
        matchIfMissing = false
    )
    public static class JdkDynamicAutoProxyConfiguration {
        public JdkDynamicAutoProxyConfiguration() {
        }
    }
}

@Configuration,是一个通过注解标注的springBean
@ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class})这个注解的意思是:EnableAspectJAutoProxy.class, Aspect.class,Advice.class,AnnotatedElement.class这几个类时才解析AopAutoConfiguration配置类,否则不解析这一个配置类
因为maven依赖的传递性,我们只要依赖starter就可以依赖到所有需要自动配置的类,实现开箱即用的功能。也体现出Springboot简化了Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发。


文章作者: dinggc
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 dinggc !
评论
 上一篇
记一次服务器部署内容 记一次服务器部署内容
由于目前国内的域名需要备案,因此购买国外的域名,同时由于国外的域名解析到国内的服务器也需要备案,因此服务器也使用国外的,当然,需要一个备案的国内域名用于七牛CDN使用。 1.服务器域名选择 之前一直用的搬瓦工的服务器,但是现在搬瓦工的服务
2020-07-28
下一篇 
计算机网络 计算机网络
应用进程跨越网络的通信 解决的问题:一些特定的应用需要互联网但是不能使用标准化的互联网应用协议 系统调用: 流程:应用进程启动系统调用,控制进程传递给了系统调用接口,接口将控制权交给操作系统。即应用进程的控制权和操作系统的权限转换的接口。
  目录