美文网首页
迟早要还系列,Spring Boot 2.2.8启动过程代码走读

迟早要还系列,Spring Boot 2.2.8启动过程代码走读

作者: 大继 | 来源:发表于2020-07-15 18:23 被阅读0次

前言

在很久很久以前看走读过比较旧的spring core,context,beans的代码由于当时比较年轻,决定再系统化复习一下Spring Boot全家桶。
配合官方文档和某度,并实现相关例子。

记忆中的Spring 核心的主要流程

入口 :ApplicationContext ->
通常使用: ClassPathXmlApplicationContext ->
Bean工厂:DefaultListableBeanFactory ->
创建类: AbstractBeanFactory.doGetBean ->

知道了主要流程和类后,开始跟踪Spring Boot 主要针对: 配置文件加载、注解加载、创建类的过程(Cglib)、IoC、AOP。

Spring boot 启动过程

1.入口
public static void main(String[] args) {
        SpringApplication.run(SpringDemoApplication.class);
}
2.初始化SpringApplication
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
                //默认null
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
                //用于读取包目录,猜:是用于读取这个包下面的配置级注解
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                //猜:用于加载WEB 输出方式
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
                //跟踪进去代码,获取一个java的类加载器
                //加载xxx-starter 里面的META-INF/spring.factories-> ApplicationContextInitializer 相关
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
                //加载xxx-starter 里面的META-INF/spring.factories-> ApplicationListener 相关
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
               //通过运行时堆栈,找到运行时的main 方法相关信息.
        this.mainApplicationClass = deduceMainApplicationClass();
    }
3.加载META-INF/spring.factories 配置类,监听器和需要初始化的类 SpringFactoriesLoader
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        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()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
里面有我们感兴趣的初始化内容
4.开始启动 SpringApplication.run
public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();//运行计时器,非常好用,可以用于多个。
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty(); //是否开启图片设备动作支持等,spring默认是开启的。
        SpringApplicationRunListeners listeners = getRunListeners(args);   //EventPublishingRunListener 
         //通知所有SpringApplicationListeners 应用开始启动了
        listeners.starting();
        try {
                       //把args以 ApplicationArguments的形式放入context
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
//划重点,初始化BeanFactory的地方
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                        //打印stopWatch 日志。
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
 //运行ApplicationRunner,和CommanderRunner 
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
                        //通知listeners开始运行
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

ok ,经过上面代码的读取,知道了Spring Boot 大致启动过程,初始化-加载各种starter 的spring.factories 需要加载的监听器及Starter 相关的ApplicationListener、ApplicationContextInitializer然后各种通知日志初始化 ,异常监听。

那么开始接下来进入我们最关心的部分。

何时读取注解配置及BeanFactory初始化及初始化的时候做了什么

SpringApplication.load(context, sources.toArray(new Object[0]))
->AbstractApplicationContext.prepareBeanFactory(beanFactory)
-> invokeBeanFactoryPostProcessors
->PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
->invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
-> ConfigurationClassPostProcessor.invokeBeanDefinitionRegistryPostProcessors
->ConfigurationClassParser.doProcessConfigurationClass
->ClassPathBeanDefinitionScanner.doScan
->findCandidateComponents
->scanCandidateComponents
->PathMatchingResourcePatternResolver
利用各种套娃得到编译后的 main入口及子包目录 例如cn.fobe.spring.SpringDemoApplication
返回 URL [file:/Users/daji/IdeaProjects/van/examples/spring/target/classes/cn/fobe/spring/]
如果是jar会使用jar的读取模式。
使用dir.listFiles();读取所有class文件
CachingMetadataReaderFactory.getMetadataReader()
终于来到了高潮org.springframework.asm.ClassReader
其实就是一个InpuStream 然后吧class文件读取进来配合ClassVisitor把.class转MetadataReader换成SimpleAnnotationMetadata ->最终生成BeanDefinition
然后进行各种通知,实例化非懒加载的类,默认都加载。

Spring 到底经历了什么。套娃套的如此厉害.

创建类的过程、IoC、AOP

经过上面代码的走读发现,类默认都是直接加载,为了代码跟踪简化,弄个Lazy的类,然后使用BeanFactory弄出来,跟踪


ConfigurableApplicationContext context =  SpringApplication.run(SpringDemoApplication.class,args);

HiService hiService = (HiService) context.getBeanFactory().getBean("hiServiceImpl");

AbstractAutowireCapableBeanFactory
.createBean
->doCreateBean
->createBeanInstance
->instantiateBean
->CglibSubclassingInstantiationStrategy
->SimpleInstantiationStrategy
->BeanUtils.instantiateClass
->使用BeanWrapperImpl 对实例化的bean 进行加工
java.lang.reflect.field.set(bean, value); 对需要注入的属性进行填充

AbstractAutowireCapableBeanFactory
BeanPostProcessor 对Bean进行再次加工AOP

TIP:AnnotationAwareAspectJAutoProxyCreator(BeanPostProcessor)
在pom 导入后添加了这个BeanPostProcessor 但是没看到
Spring-boot-starter-aop 里面有spring.factories文件


image.png

其实spring.factories都在他的子依赖里面着点比较骚气。

何时加载了application.yml

在spring boot启动是加载的PropertySourceLoader
YamlPropertySourceLoader 进行加载。

附:spring-boot-starter-jpa

关注点:接口类Repository 为何可以直接执行、HibernateSessionFactory getSession 发生了什么事情、是否通过线程来规定

附: 自己实现一个starter 来巩固一下知识

https://www.jianshu.com/p/dc1ff2102bc5

附:spring-boot-starter-web

都复习到这个程度了,顺便复习一下最最常用的Web加载过程,关注点:内嵌Tomcat、URL过程、WebApplicationContext 与 ApplicationContext的关系、Spring MVC如何生产Servlet。(注:其它FilterChain 和 Servlet 的执行过程,直接去看Servlet的文档即可)

相关文章

网友评论

      本文标题:迟早要还系列,Spring Boot 2.2.8启动过程代码走读

      本文链接:https://www.haomeiwen.com/subject/xdqeqktx.html