IoC 容器的初始化过程(Spring IoC 系列之二)

337 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

我们上次已经在整体上说了一下 Spring IoC 的设计与实现,下面就详细说说 Spring IoC 容器的初始化,初始化可分为三个步骤,分别是 BeanDefiniton 的 Resource 定位,载入和注册。

Resource 定位

就像用水桶装水要先找到水源一样,我们使用 IoC 容器来管理对象的第一步就是要先找到 Bean 在哪里。在 Spring 中不管你是何种形式(Class Path、FileSystem、URL、Path)的资源,都使用 Resouce 进行 I/O 封装。

以 ApplicationContext 的常用实现 FileSystemXmlApplicationContext 为例来说明这个过程,还记得上次说的 refresh 方法不,这就是启动 IoC 容器的入口。

我们也知道了,每一个 BeanDefinition 的 Resource 都需要有相应的 Reader 来读取它,这里当然也不例外,但是在 FileSystemXmlApplicationContext 中却看不到任何有关 Reader 的信息。

原来这些都封装在了它的父类 AbstractRefreshableApplicationContext 中了,还是要贴一段代码。

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

上面这个重启容器的方法中貌似也没有看到 Resource 相关的东西,这里我必须要多说一点,我们的 BeanDefinition 的 load 之前肯定是要定位 Resource 资源的,定位说的有点高级,我的理解就是得到 Resource 对象。

那我们就继续看看 loadBeanDefinitions 这个方法中都有什么东西吧。就这么一顿点过去你就会发现这么两行关键代码。

AbstractBeanDefinitionReaderResource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);

噢,终于明白了吧,原来方法名字叫 loadBeanDefinitions 可是背地里却把 Resource 定位一起解决了。

我们就看 getResource 是怎么 get 的就行。

public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                // Try to parse the location as a URL...
                URL url = new URL(location);
                return new UrlResource(url);
            }
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }

我们可以看到这里方法不仅能处理 Class Path、URL、还能处理 Path,但是呢,这个 getResourceByPath 在具体处理 Path 的时候是由具体的子类来实现的,FileSystemXmlApplicationContext 就实现了这个方法。

话说这个 getResourceByPath 在设计的时候,使用了模板模式,方法被修饰为 protected ,虽然本身实现了,但是在子类中也会有不同的实现,比方说在 FileSystemXmlApplicationContext 中的实现如下

@Override
protected Resource getResourceByPath(String path) {
    if (path != null && path.startsWith("/")) {
          path = path.substring(1);
    }
        return new FileSystemResource(path);
}

这样,我们就算把 getResource 这个过程看完了,这也就是 Resource 定位,真的是,这个词让我迷糊了好久,直接说得到 Resource 对象多好。

其实再多想一点,Spring 中的 I/O 操作都是通过 Resource 来定义的,我们得到了 Resource 就相当于得到了 Input 流啊,然后我们再对输入流进行处理……封装成容器中 POJO 的抽象形式 BeanDefinition。完美!

BeanDefinition 的载入和解析

因为涉及的到代码太多了,我又不想贴代码了,强烈建议大家点开 IDE 看看源码,当然,这需要时间,在实现类非常多的情况下,我们可以首先定位几个关键的类以及实现。

先说说这一步 Spring 是在干什么,我们在 Resource 定位之后,已经得到了 Resource 对象,也就得到了 Input 流,我们现在做的事情就是将 Input 中的信息首先通过 XML 解析器解释一个 XML 文档,也就是一个 Document 对象。

然后再通过 DocumentReader 来读取 DOM 中的信息,这些信息封装在 BeanDefinitionHolder 中,这其中包含 BeanDefinition 信息以及 bean 的 name,别名等。

通过 DocumentReader 载入信息,通过 BeanDefinitionParserDelegate 来实现对载入的信息进行解析。解析不同的元素,元素下面不同的节点,很是复杂。

最终的效果就是我们在 BeanDefinition 中封装了 XML 中的信息,但是这还没有注册呢。此时解析到 BeanDefinition 中的 Bean 包含的是 XML 配置的 class 信息,并没有实例化呢。

这里有两个细节,在同一个 bean 中如果有多个属性同名,在解析时只会解析第一个 property 节点。在同一个容器中,如果有同名的 Bean 被解析到,那起作用的是第二个。

BeanDefinition 注入

BeanDefinition 的注入我只想用二行代码表示

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, 
BeanDefinition>(64);beanDefinitionMap.put(beanName, beanDefinition);

说到这里,我们的 BeanDefinition 信息都在 Map 中了,也就完成了 IoC 的初始化,但是我们的 Bean 还是没有实例化,更没有注入相关的依赖。

关于 Bean 的实例化和注入依赖下一次说……