Spring源码解析(二)BeanDefinition的Resource定位

   IOC容器的初始化过程主要包括BeanDefinition的Resource定位、载入和注册。在实际项目中我们基本上操作的都是ApplicationContex的实现,我们比较熟悉的ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、XmlWebapplicationContext等。ApplicationContext的具体继承体系如下图所示:

  

   其实,不管是XmlWebApplicationContext还是ClasspathXmlApplicationContext 他们的区别只是Bean的资源信息来源不一样而已,最终都会解析为统一数据结构BeanDefinition。

下面我们源码的解析就从高富帅的ClassPathXmlApplicationContext开始。

        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("classpath*:test.xml");

构造方法:

/**
     * Create a new ClassPathXmlApplicationContext, loading the definitions
     * from the given XML file and automatically refreshing the context.
     * @param configLocation resource location
     * @throws BeansException if context creation failed
     */
    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

最终调用构造方法:

/**
     * Create a new ClassPathXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files.
     * @param configLocations array of resource locations
     * @param refresh whether to automatically refresh the context,
     * loading all bean definitions and creating all singletons.
     * Alternatively, call refresh manually after further configuring the context.
     * @param parent the parent context
     * @throws BeansException if context creation failed
     * @see #refresh()
     */
    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

1.设置父级上下文,最终是给AbstractApplicationContext的parent属性赋值,AbstractApplicationContext是ApplicationContext最顶层的实现类。

2.设置XML文件的位置,调用了AbstractRefreshableConfigApplicationContext的setConfigLocations方法

/**
     * Set the config locations for this application context.
     * <p>If not set, the implementation may use a default as appropriate.
     */
    public void setConfigLocations(String[] locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
    }

3.刷新容器,调用了AbstractApplicationContext的refresh方法,这是一个模板方法,具体的操作都是有子类去实现的。

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            //刷新容器前的准备工作
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            //由子类实现容器的刷新(重启)
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            /*容器使用前的准备工作*/
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                //甚至beanFacotry的后置处理
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                //调用BeanFactory的后置处理器,这些后置处理是在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.
                //初始化其他特殊的Bean
                onRefresh();

                // Check for listener beans and register them.
                //向容器注册监听Bean
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                //实例化所有非延迟加载的Bean
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                //发布容器事件,结束refresh过程
                finishRefresh();
            }

            catch (BeansException ex) {
                logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }
        }
    }
prepareRefresh():主要是设置启动时间、状态等等;
我们着重看一下刷新容器的obtainFreshBeanFactory()方法:
/**
     * Tell the subclass to refresh the internal bean factory.
     * @return the fresh BeanFactory instance
     * @see #refreshBeanFactory()
     * @see #getBeanFactory()
     */
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        //销毁已有容器,重新创建容器并加载Bean
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }
AbstractApplicationConetxt的refreshBeanFactory()方法是一个抽象方法,是由它的子类AbstractRefreshableApplicationContext实现的,从类的命名上可以看出这个类主要就是进行容器Refresh用的。
/**
     * 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 {
        //如果容器已经存在则销毁容器中的bean并关闭容器
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //创建beanFacotry
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            //根据bean定义的方式(XML、注解等)不同,由子类选择相应的BeanDefinitionReader去解析
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

第一步: 这个方法会判断如果已存在容器,则先销毁所有的Bean并且关闭容器,这也是为了保证容器的唯一性。

第二步:createBeanFactory()创建了一个DefaultListableBeanFactory,这个类是BeanFacotry最高级的实现,有了它就有个容器最基本的功能了。

/**
     * Create an internal bean factory for this context.
     * Called for each {@link #refresh()} attempt.
     * <p>The default implementation creates a
     * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
     * with the {@linkplain #getInternalParentBeanFactory() internal bean factory} of this
     * context's parent as parent bean factory. Can be overridden in subclasses,
     * for example to customize DefaultListableBeanFactory's settings.
     * @return the bean factory for this context
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
     */
    protected DefaultListableBeanFactory createBeanFactory() {
        //新建一个DefaultListableBeanFactory
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }

 DefaultListableBeanFactory的继承关系:

我们看到DefaultListableBeanFactory实现了BeanDefinitionRegistry接口,也就是说最终BeanDefinition的注册工作是由它和它的子类来完成的。

第三步:loadBeanDefinitions(beanFactory),这个方法也是一个抽象方法。因为Bean定义方式不同(XML、注解等),会有多个子类分别去实现具体的解析。

此处,调用的是AbstractXmlApplicationContext的实现:

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

        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        //ApplicationContext继承了ResourceLoader接口,所以this是可以直接使用的
        beanDefinitionReader.setResourceLoader(this);
        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);
        //委派模式,具体事情委派给beanDefinitionReader去做
        loadBeanDefinitions(beanDefinitionReader);
    }

 进入loadBeanDefinitions(XmlBeanDefinitionReader reader):

/**
     * Load the bean definitions with the given XmlBeanDefinitionReader.
     * <p>The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory}
     * method; hence this method is just supposed to load and/or register bean definitions.
     * @param reader the XmlBeanDefinitionReader to use
     * @throws BeansException in case of bean registration errors
     * @throws IOException if the required XML document isn't found
     * @see #refreshBeanFactory
     * @see #getConfigLocations
     * @see #getResources
     * @see #getResourcePatternResolver
     */
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }

 

该方法调用了XmlBeanDefinitionReader父类AbstractBeanDefinitionReader的loadBeanDefinitions方法:

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;
    }

 

l循环加载location并返回加载个数,最终调用了本类的loadBeanDefinitions(String location, Set<Resource> actualResources)方法,actualResources为null:

/**
     * Load bean definitions from the specified resource location.
     * <p>The location can also be a location pattern, provided that the
     * ResourceLoader of this bean definition reader is a ResourcePatternResolver.
     * @param location the resource location, to be loaded with the ResourceLoader
     * (or ResourcePatternResolver) of this bean definition reader
     * @param actualResources a Set to be filled with the actual Resource objects
     * that have been resolved during the loading process. May be {@code null}
     * to indicate that the caller is not interested in those Resource objects.
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #getResourceLoader()
     * @see #loadBeanDefinitions(org.springframework.core.io.Resource)
     * @see #loadBeanDefinitions(org.springframework.core.io.Resource[])
     */
    public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }
        //resourceLoader是ClasspathXmlApplicationContext,ApplicationContext接口本身继承了ResourcePatternResolver接口
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                //location转为Resource完成定位工作
                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;
        }
    }
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location)将location转成了Resource[],这一步完成了资源的定位工作。
它调用了PathMatchingResourcePatternResolver的getResources方法:
 1 public Resource[] getResources(String locationPattern) throws IOException {
 2         Assert.notNull(locationPattern, "Location pattern must not be null");
 3         //是否以classpath*:开头
 4         if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
 5             // a class path resource (multiple resources for same name possible)
 6             //是否为Ant-style路径
 7             //? 匹配任何单字符
 8             //* 匹配0或者任意数量的字符
 9             //** 匹配0或者更多的目录
10             if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
11                 // a class path resource pattern
12                 return findPathMatchingResources(locationPattern);
13             }
14             else {
15                 // all class path resources with the given name
16                 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
17             }
18         }
19         else {
20             // Only look for a pattern after a prefix here
21             // (to not get fooled by a pattern symbol in a strange prefix).
22             int prefixEnd = locationPattern.indexOf(":") + 1;
23             if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
24                 // a file pattern
25                 return findPathMatchingResources(locationPattern);
26             }
27             else {
28                 // a single resource with the given name
29                 return new Resource[] {getResourceLoader().getResource(locationPattern)};
30             }
31         }
32     }

 

 根据location写法,解析方式也不同:

1、前缀为classpath*

  1)文件路径路径中包含*和?

    调用findPathMatchingResources方法

  2)文件路径中不含*和?

    调用findAllClassPathResources方法

2.、前缀为classpath

  1)文件路径路径中包含*和?

  调用findPathMatchingResources方法
 2)文件路径中不含*和?
  调用DefaultResourceLoader的getResource方法new一个ClasspathResource并返回,如果资源文件根本就不存在,此处也不会校验。
findPathMatchingResources和findAllClassPathResources具体都干了什么呢?
先看一下findAllClassPathResources:
/**
     * Find all class location resources with the given location via the ClassLoader.
     * @param location the absolute path within the classpath
     * @return the result as Resource array
     * @throws IOException in case of I/O errors
     * @see java.lang.ClassLoader#getResources
     * @see #convertClassLoaderURL
     */
    protected Resource[] findAllClassPathResources(String location) throws IOException {
        String path = location;
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        ClassLoader cl = getClassLoader();
        Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
        Set<Resource> result = new LinkedHashSet<Resource>(16);
        while (resourceUrls.hasMoreElements()) {
            URL url = resourceUrls.nextElement();
            result.add(convertClassLoaderURL(url));
        }
        return result.toArray(new Resource[result.size()]);
    }

   protected Resource convertClassLoaderURL(URL url) {
return new UrlResource(url);
}
这个方法很简单,根据具体的location通过classLoader的getResources方法返回RUL集合,根据URL创建UrlResource并返回UrlResource的集合。
再来看一下findPathMatchingResources方法:
/**
     * Find all resources that match the given location pattern via the
     * Ant-style PathMatcher. Supports resources in jar files and zip files
     * and in the file system.
     * @param locationPattern the location pattern to match
     * @return the result as Resource array
     * @throws IOException in case of I/O errors
     * @see #doFindPathMatchingJarResources
     * @see #doFindPathMatchingFileResources
     * @see org.springframework.util.PathMatcher
     */
    protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
        String rootDirPath = determineRootDir(locationPattern);
        String subPattern = locationPattern.substring(rootDirPath.length());
        Resource[] rootDirResources = getResources(rootDirPath);
        Set<Resource> result = new LinkedHashSet<Resource>(16);
        for (Resource rootDirResource : rootDirResources) {
            rootDirResource = resolveRootDirResource(rootDirResource);
            if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
                result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
            }
            else if (isJarResource(rootDirResource)) {
                result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
            }
            else {
                result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
        }
        return result.toArray(new Resource[result.size()]);
    }
1.String rootDirPath = determineRootDir(locationPattern),获取location前缀classpath*/classpath
2.
Resource[] rootDirResources = getResources(rootDirPath),调用的上面讲到的getResources方法,返回classpath根路径的Resource[],如果是classpath会返回一个Resource,
如果是classpath*会放回所有的classpath路径。
3.遍历根路径Resource[],doFindPathMatchingFileResources方法就是获取给定路径下的所有文件,根据指定的文件名test*.xml去模糊匹配,返回的是FileSystemResource。所以location为classpath:test*.xml可能会找不到文件。

 

 

 



posted @ 2018-03-05 16:21  两条闲鱼  阅读(816)  评论(0编辑  收藏  举报