zoukankan      html  css  js  c++  java
  • springboot启动流程(六)ioc容器刷新前prepareContext

    所有文章

    https://www.cnblogs.com/lay2017/p/11478237.html

    prepareContext方法核心逻辑

    上一篇文章中,我们通过createApplicationContext方法创建了一个ApplicationContext的实例对象。本文将阅读一下在ApplicationContext在refresh之前的prepareContext中做了哪些事情。

    我们跟进prepareContext方法

    private void prepareContext(
            ConfigurableApplicationContext context, 
            ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, 
            ApplicationArguments applicationArguments, 
            Banner printedBanner
            ) {
        // 设置Environment
        context.setEnvironment(environment);
        // 后置处理
        postProcessApplicationContext(context);
        // 调用ApplicationContextInitializer接口的实现
        applyInitializers(context);
        // 发布ApplicationContext准备事件
        listeners.contextPrepared(context);
        
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 注册args参数为单例bean
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            // 注册banner为单例bean
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            // 设置beanFactory中是否允许重复bean覆盖
            ((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
    
        // 加载main方法所在类
        Set<Object> sources = getAllSources();
        // 注册main方法所在类到beanFactory
        load(context, sources.toArray(new Object[0]));
        // 发布Context加载事件
        listeners.contextLoaded(context);
    }

    我们看到prepareContext方法主要逻辑包含了三块内容

    1)基本的初始化,如设置Environment,调用ApplicationContextInitializer接口的实现类

    2)注册现有的对象为单例bean,如args、banner

    3)加载main方法所在的主类

    load方法加载主类

    很显然,加载main方法的主类是我们关注的重点。我们先看看getAllSources方法返回

    public Set<Object> getAllSources() {
        Set<Object> allSources = new LinkedHashSet<>();
        // 添加主类
        if (!CollectionUtils.isEmpty(this.primarySources)) {
            allSources.addAll(this.primarySources);
        }
        // 添加附加资源类
        if (!CollectionUtils.isEmpty(this.sources)) {
            allSources.addAll(this.sources);
        }
        return Collections.unmodifiableSet(allSources);
    }

    primarySources是在SpringApplication初始化的时候设置的,而sources默认是没有的。所在getAllSoures方法将返回main方法所在的主类。

    下面,我们跟进load方法,阅读一下加载main所在主类的逻辑

    protected void load(ApplicationContext context, Object[] sources) {
        // 获取BeanDefinition加载器
        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();
    }

    load方法中,先是获得了BeanDefinitionLoader,然后去加载资源。这里要注意!为什么是BeanDefinitionLoader呢?

    首先,我们得知道Spring的bean资源来自各种地方,如xml、annotation等,那么这些bean在配置的时候也就是对bean进行定义,而这些定义映射到内存中的对象就是BeanDefinition的对象,Spring后续会根据BeanDefinition再获取具体的bean。

    简单来说就是:配置 --> BeanDefinition --> Bean 这样一个逻辑

    所以,后续我们会看到BeanDefinitionLoader会将主类加载成BeanDefinition,然后注册到ApplicationContext当中。

    先跟进getBeanDefinitionRegistry方法,看看BeanDefinition会被注册到哪里去

    private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
        // 返回当前ApplicationContext
        if (context instanceof BeanDefinitionRegistry) {
            return (BeanDefinitionRegistry) context;
        }
        if (context instanceof AbstractApplicationContext) {
            return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
        }
        throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
    }

    我们注意,springboot的AnnotationConfigServletWebServerApplicationContext这个ApplicationContext的实现类,随着继承链路向上走是继承自GenericApplicationContext的,而GenericApplicationContext实现了BeanDefinitionRegistry。

    所以,getBeanDefinitionRegistry将最终返回强转过的ApplicationContext。也就是说BeanDefinition将被注册到ApplicationContext里面。

    回到load方法中,我们再跟进createBeanDefinitionLoader方法

    protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
        return new BeanDefinitionLoader(registry, sources);
    }

    再跟进构造方法

    BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
        this.sources = sources;
        // 注解方式的读取器
        this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
        // xml方式的读取器
        this.xmlReader = new XmlBeanDefinitionReader(registry);
    
        // 类路径下的扫描器
        this.scanner = new ClassPathBeanDefinitionScanner(registry);
        // 扫描排除当前main方法的主类
        this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
    }

    我们看到加载器支持注解、xml两种方式。类路径下的扫描器排除了当前的主类

    回到load方法

    protected void load(ApplicationContext context, Object[] sources) {
        // 获取BeanDefinition加载器
        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();
    }

    此时,我们已经获取了BeanDefinitionLoader,下面调用该loader的load方法开始加载

    跟进第二个load方法

    public int load() {
        int count = 0;
        for (Object source : this.sources) {
            count += load(source);
        }
        return count;
    }

    再跟进第三个load方法

    private int load(Object source) {
        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());
    }

    由于我们的主类是一个class,所以进入第一个if分支的load方法

    继续跟进

    private int load(Class<?> source) {
        // 省略
        if (isComponent(source)) {
            this.annotatedReader.register(source);
            return 1;
        }
        return 0;
    }

    该方法先通过isComponent方法判断了主类是不是被@Component注解,如果是,那么调用注解方式的阅读器,注册该资源。

    跟进isComponent方法,看看怎么判断的

    private boolean isComponent(Class<?> type) {
        // 找到是否匹配@Component注解
        if (AnnotationUtils.findAnnotation(type, Component.class) != null) {
            return true;
        }
        // 省略
    }

    其实就是找这个类是否有@Component注解,但请注意我们通常都使用@SpringBootApplication这个注解,并没有直接注解@Component。而@SpringBootApplication是一个组合注解,其中就组合了@Component

    而AnnotationUtils.findAnnotation方法将会递归遍历注解,最终找到@Component。

    isComponent判断为true以后,我们再跟进annotationReader.register(source)阅读一下读取主类的过程

    public void register(Class<?>... annotatedClasses) {
        for (Class<?> annotatedClass : annotatedClasses) {
            registerBean(annotatedClass);
        }
    }

    继续跟进registerBean方法

    public void registerBean(Class<?> annotatedClass) {
        doRegisterBean(annotatedClass, null, null, null);
    }

    再跟进doRegisterBean方法,该方法比较长,我们省略掉一些次要的部分

    <T> void doRegisterBean(
            Class<T> annotatedClass, 
            @Nullable Supplier<T> instanceSupplier, 
            @Nullable String name,
            @Nullable Class<? extends Annotation>[] qualifiers, 
            BeanDefinitionCustomizer... definitionCustomizers
            ) {
        // 先包装成BeanDefinition
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
        
        // 解析scope元数据
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
    
        // 生成bean的名
        String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
    
        // 解析一些常见的注解元数据
        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        // 注册BeanDefinition到ApplicationContext
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
    }

    可以看到,doRegisterBean方法的主要逻辑就是包装并解析出一个BeanDefinition,然后调用registerBeanDefinition方法把BeanDefinition给注册到ApplicationContext中。

    注册相关的本文就不再继续展开了,后续的文章会跟进这些内容。

    总结

    总的来说,prepareContext方法主要就是为了加载并注册主类的BeanDefinition到ApplicationContext。这里注意!我们一直都在说注册到ApplicationContext,但熟悉spring的都会知道无论是Bean还是BeanDefinition都是注册到BeanFactory中的。但我们一直没有严格区分它,后续的文章我们将会把ApplicationContext和BeanFactory进行区分。

  • 相关阅读:
    PHP 获取请求里的 header字段以及发送header
    宝塔链接数据库
    链表中头结点和头指针
    git How to resolve a Git “CONFLICT (modify/delete)”?
    yii 执行mysql:既要添加数据又要更新数据
    recv() failed (104: Connection reset by peer) while reading response header from upstream
    Linux磁盘空间满了,定位到mysql日志,删除
    mysql 字符乱码
    php message queue
    CGI 、fastcgi、php-cgi、php-fpm
  • 原文地址:https://www.cnblogs.com/lay2017/p/11487463.html
Copyright © 2011-2022 走看看