频道栏目
首页 > 资讯 > 其他 > 正文

Spring Framework--ApplicationComtext详解AbstractApplicationContext.refresh()

18-01-10        来源:[db:作者]  
收藏   我要投稿

Spring Framework--ApplicationComtext详解AbstractApplicationContext.refresh()

上篇博客主要以ClassPathXmlApplicationContext为线,debug走了一遍构造函数。本篇严格来说是上一篇博客的继续,因此本文将继续以ClassPathXmlApplicationContext为线走进AbstractApplicationContext.refresh()。

概述

? refresh方法位于ConfigurableApplicationContext接口类中,被AbstractApplicationContext抽象类继承,初步实现了ApplicationContext的一般功能,并且抽象类中使用了模板方法模式,给以后要实现的子类提供了统一的模板。

// 注代码中的中文只是单纯的翻译,以及简单的个人理解。

/**
     * Load or refresh the persistent representation of the configuration,
     * which might an XML file, properties file, or relational database schema.
     * 

As this is a startup method, it should destroy already created singletons * if it fails, to avoid dangling resources. In other words, after invocation * of that method, either all or no singletons at all should be instantiated. * @throws BeansException if the bean factory could not be initialized * @throws IllegalStateException if already initialized and multiple refresh * attempts are not supported */ /** * 加载或则刷新持久表示的配置,这个配置可能来自一个xml文件,配置文件或则关系型数据库对象的集合。由于这是一个 * 启动方法,它应该销毁已经创建的单例。 如果失败了,就避免了悬空的资源。 换句话说,在调用该方法之后,应该 * 实例化所有或者全部的no singletons(这里不知道怎么翻译)。 */ void refresh() throws BeansException, IllegalStateException; public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. // 为刷新准备上下文,获取容器的当时时间,同时给容器设置同步标识 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // 告诉子类需要刷新的内部bean工厂 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. // 在上下文中准备bean工厂为了之后使用。 // 为bean工厂配置容器特征,例如类加载器,事件处理器等 prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. // 在上下文子类中,允许对bean工厂进行后期处理 postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. // 在上下文中,调用工厂处理器注册成为bean // 调用bean工厂的后处理器,这些处理器是在bean定义中想容器注册的。 invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. // 注册用于拦截bean创建的bean处理器 registerBeanPostProcessors(beanFactory); // Initialize message source for this context. // 为上下文初始化消息资源 initMessageSource(); // Initialize event multicaster for this context. // 初始化上下文中的事件多路广播--->事件多播器 // 初始化上下文中的事件机制 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. // 初始化指定context子类中的特殊bean onRefresh(); // Check for listener beans and register them. // 注册并检查bean监听器 registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. // 初始化剩下的非延迟加载(non-lazy-init)单例beans finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. // 最后一步,发布相关的容器事件 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created single?tons to avoid dangling resources. // 销毁已经创建的单例bean destroyBeans(); // Reset 'active' flag. // 取消refresh操作,充值‘activity’标志位 cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }

正文

prepareRefresh();

先来看看一下prepareRefresh的时序图

这里写图片描述

再看看代码

/**
 * Prepare this context for refreshing, setting its startup date and
 * active flag as well as performing any initialization of property sources.
 */
/*
为刷新准备上下文,设置它的开始时间和活动标志以及执行所有属性资源的初始化。
*/
protected void prepareRefresh() {
    // 设置启动时间
        this.startupDate = System.currentTimeMillis();
        // 设置关闭标志位false,运行标志位true.
        // close 和activityAtomicBoolean 
    this.closed.set(false);
    this.active.set(true);

    if (logger.isInfoEnabled()) {
        logger.info("Refreshing " + this);
    }

    // Initialize any placeholder property sources in the context environment
       //  初始化上下文环境中的所有占位符属性元
        //  默认为空
    initPropertySources();

    // Validate that all properties marked as required are resolvable
    // see ConfigurablePropertyResolver#setRequiredProperties
       // 验证被标记为必要属性的可解析性
       getEnvironment().validateRequiredProperties();
       // 上面方法单步进去,看到如下代码。这里对requiredProperties属性的set还不知道在哪。
       // public void validateRequiredProperties() {
// MissingRequiredPropertiesException ex = new  MissingRequiredPropertiesException();
//       for (String key : this.requiredProperties) {
//           if (this.getProperty(key) == null) {
//               ex.addMissingRequiredProperty(key);
//           }
//       }
//       if (!ex.getMissingRequiredProperties().isEmpty()) {
//           throw ex;
//       }
//   }


    // Allow for the collection of early ApplicationEvents,
    // to be published once the multicaster is available...
       // 在早期就建立起ApplicationEvent事件容器,一旦多路广播可用就可以发布了。
    this.earlyApplicationEvents = new LinkedHashSet<>();
}   

?

obtainFreshBeanFactory()—— 告诉子类刷新内部bean工厂

  protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {

    /**
     * This implementation performs an actual refresh of this context's underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context's lifecycle.
     */
    // 这个实现执行一个上下文的底层bean工厂刷新,关闭之前已有的bean工厂,并为上下文生命周期的下一个阶段初始化bean工厂
    refreshBeanFactory(); 

    // 获取bean工厂
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();

    if (logger.isDebugEnabled()) {
    logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }?
        return beanFactory;
    }

##### 2.1 refreshBeanFactory()

首先来看看时序图:

这里写图片描述

    /**
     * This implementation performs an actual refresh of this context's underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context's lifecycle.
     */
    @Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
                // 抽象方法,交由子类实现。
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

refreshBeanFactory中主要分为以下几步:

判断是否之前是否存在bean工厂,如果存在,将bean工厂、缓存等信息清空 创建一个DefaultListableBeanFactory工厂 定制当前bean工厂,能否进行重写及循环引用 –customizeBeanFactory(beanFactory)

加载所有bean到beanFactory

2.2 loadBeanDefinitions(beanFactory)

在2.1中refreshBeanFactory中,loadBeanDefinitions是一个抽象方法,其具体实现交由子类。现在来看看AbstractXmlApplicationContext是如何实现的。

/**
 * Loads the bean definitions via an XmlBeanDefinitionReader.
 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
 * @see #initBeanDefinitionReader
 * @see #loadBeanDefinitions
 */ 
// 通过XmlBeanDefinitionReader加载bean定义
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 创建一个XmlBeanDefinitionReader

    // Configure the bean definition reader with this context's
    // resource loading environment.
    beanDefinitionReader.setEnvironment(this.getEnvironment()); // 设置当前的environment的对象
    beanDefinitionReader.setResourceLoader(this); // 设置对应的ResourceLoader.ApplicationContext是ResourceLoader的子类
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // Allow a subclass to provide custom initialization of the reader,
    // then proceed with actually loading the bean definitions.
      // 钩子方法,子类不重载就不做任何操作
    initBeanDefinitionReader(beanDefinitionReader);    
    loadBeanDefinitions(beanDefinitionReader);   // 加载对应BeanDefinition对象
}
2.2.1 loadBeanDefinitions

上面loadBeanDefinitions对应的实现

// 在AbstractXmlApplicationContext中loadBeanDefinitions的实现
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {

    Resource[] configResources = getConfigResources();   // 为空
    if (configResources != null) {
        reader.loadBeanDefinitions(configResources);
    }
    // 在AbstractRefreshableConfigApplicationContext中的getConfigLocations获取配置路径
    String[] configLocations = getConfigLocations(); // configLocations=/org/springframework/context/support/test/context*.xml
    if (configLocations != null) {
            // 调用xml
        reader.loadBeanDefinitions(configLocations);
    }
}

// 在AbstractBeanDefinitionReader中loadBeanDefinitions()方法解析lications数组
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    Assert.notNull(locations, "Location array must not be null");
    int counter = 0;
    for (String location : locations) {
        counter += loadBeanDefinitions(location);
    }
    return counter;
}

具体的加载beanDefinition的实现在AbstractBeanDefinitionReader中

下面是它的时序图

这里写图片描述

// 上面for循环中loadBeanDefinitions(location)方法的在AbstractBeanDefinitionReader中的
 public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = getResourceLoader();  // 获取资源加载器
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);   // 将给定的位置模式解析为资源对象
                int loadCount = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    for (Resource resource : resources) {
                        actualResources.add(resource);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                }
                return loadCount;
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        }
        else {
            // Can only load single resources by absolute URL.
            Resource resource = resourceLoader.getResource(location);
            int loadCount = loadBeanDefinitions(resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
            }
            return loadCount;
        }
    }

在上面的getResources()方法之后又调用了一下这些方法:

这里写图片描述
由上图可知从refresh—>getResourses—>doRetrievenMatchingFiles.其中从getResourses到doRetrievenMatchingFiles主要做了以下这些事:

? 1) 看location里面是否以”classpath*:“,”war:“,”*/“开头,会做一系列事(这里流程没有进去)。这里主要在PathMatchingResourcePatternResolver#getResources(String locationPattern)

? 2) 找到location的上层路径rootDirResource,同时分离出外层子路径subPattern

? 3)转而根据rootDirResource获取完整文件路径rootDir

? 4)再将完整文件路径rootDir和外层自路径subPattern拼接得到fullPattern

? 5)最后找出rootDir下所有文件的全路径进行遍历,与fullPattern进行匹配.

protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set result) throws IOException {
    if (logger.isDebugEnabled()) {
        logger.debug("Searching directory [" + dir.getAbsolutePath() +
                "] for files matching pattern [" + fullPattern + "]");
    }
    File[] dirContents = dir.listFiles();
    if (dirContents == null) {
        if (logger.isWarnEnabled()) {
            logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
        }
        return;
    }
    Arrays.sort(dirContents);
    for (File content : dirContents) {
        String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
        if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
            if (!content.canRead()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
                            "] because the application is not allowed to read the directory");
                }
            }
            else {
                doRetrieveMatchingFiles(fullPattern, content, result);
            }
        }
        if (getPathMatcher().match(fullPattern, currPath)) {
            result.add(content);
        }
    }
}

上述方法中匹配方法调用 getPathMatcher().match(fullPattern, currPath)

最终匹配算法:

  AntPathMatcher#doMatch(
          String pattern, 
          String path,boolean fullMatch,
          @Nullable Map uriTemplateVariables) 

匹配算法的大致步骤(中间省略了一些):

? 1)将需要匹配的两个路径按照路径分割符分割,得到两个有序数组pathDirs(路径下的子文件全路径分割后的字符数组),pattDirs(要匹配的模式分割后的字符串数组)

? 2)在得到pttDirs后会去看这里面是否存在潜在的匹配,主要是为了去匹配正则表达式中的{ ‘*’, ‘?’, ‘{’ },若不存在潜在的匹配则返回false

? 3)遍历pattDirs,如果包含”**“,直接跳出遍历;记录当前位置,与pathDirs对应位置的字符串进行比较,不同则返回false

? 4)第3步全部匹配结束后,

? 在我们的路径中

pattern = /Users/chonglou/IdeaProject/spring-framework/spring-context/out/test/resources/org/springframework/context/support/test/context*.xml
path = /Users/chonglou/IdeaProject/spring-framework/spring-context/out/test/resources/org/springframework/context/support/test/contextC.xml

? 最后只是去看看我们的模式匹配路径和全路径是否都是以”/“结束,显然上述两个均没有以”/“结束,返回false

这里我们只是粗略介绍,之后,会详细介绍。

注:
在AntPathMatcherTests测试用例中,还暂时未发现通过

  - {spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring"

也就是通过花括弧,这种形式去匹配xml路径的。

不过却发现通过

  result = pathMatcher.extractUriTemplateVariables(
  "{symbolicName:[\\w\\.]+}-sources-{version:[\\d\\.]+}-{year:\\d{4}}{month:\\d{2}}{day:\\d{2}}.jar","com.example-sources-1.0.0-20100220.jar");

这样去解析jar包或则url路径的。之后我们会有单独的博客进行讲解。

总结

refresh 的一些方法但看确实能懂一些,但是要串起来,感觉有点难啊。加油吧。。。 prepareBeanFactory()看了一部分了,后面其他方法也会陆续跟上。
相关TAG标签
上一篇:HttpServlet、HttpServletRequest、HttpServletResponse
下一篇:微信公众号开发时hashcode为定值(da39a3ee5e6b4b0d3255bfef95601890afd80709)问题如何解决?
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站