Spring源码解析之IOC容器

2023-07-18 14:14:09 浏览数 (2)

在认真学习Rod.Johnson的三部曲之一:顺便也看了看源代码想知道个究竟,抛砖引玉,有兴趣的同志一起讨论研究吧!

以下内容引自博客:http://jiwenke-spring.blogspot.com/,欢迎指导

在Spring中,IOC容器的重要地位我们就不多说了,对于Spring的使用者而言,IOC容器实际上是什么呢?我们可以说BeanFactory就是我们看到的IoC容器,当然了Spring为我们准备了许多种IoC容器来使用,这样可以方便我们从不同的层面,不同的资源位置,不同的形式的定义信息来建立我们需要的IoC容器。

在Spring中,最基本的IOC容器接口是BeanFactory - 这个接口为具体的IOC容器的实现作了最基本的功能规定 - 不管怎么着,作为IOC容器,这些接口你必须要满足应用程序的最基本要求:

代码语言:javascript复制
 1public interface BeanFactory { 
 2    //这里是对 FactoryBean 的转义定义,因为如果使用 bean 的名字检索 FactoryBean 得到的对象是工厂生成的对象, 
 3    //如果需要得到工厂本身,需要转义 
 4    String FACTORY_BEAN_PREFIX = "&"; 
 5
 6    //这里根据 bean 的名字,在 IOC 容器中得到 bean 实例,这个 IOC 容器就是一个大的抽象工厂。 
 7    Object getBean(String name) throws BeansException; 
 8
 9    //这里根据 bean 的名字和 Class 类型来得到 bean 实例,和上面的方法不同在于它会抛出异常:如果根据名字取得的 bean 实例的 Class 类型和需要的不同的话。 
10    Object getBean(String name, Class requiredType) throws BeansException; 
11
12    //这里提供对 bean 的检索,看看是否在 IOC 容器有这个名字的 bean 
13    boolean containsBean(String name); 
14
15    //这里根据 bean 名字得到 bean 实例,并同时判断这个 bean 是不是单件 
16    boolean isSingleton(String name) throws NoSuchBeanDefinitionException; 
17
18    //这里对得到 bean 实例的 Class 类型 
19    Class getType(String name) throws NoSuchBeanDefinitionException; 
20
21    //这里得到 bean 的别名,如果根据别名检索,那么其原名也会被检索出来 
22    String[] getAliases(String name); 
23} 

在 BeanFactory 里只对 IOC 容器的基本行为作了定义,根本不关心你的 bean 是怎样定义怎样加载的 - 就像我们只关心从这个工厂里我们得到到什么产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心这些。如果要关心工厂是怎样产生对象的,应用程序需要使用具体的 IOC 容器实现- 当然你可以自己根据这个 BeanFactory 来实现自己的 IOC 容器,但这个没有必要,因为 Spring 已经为我们准备好了一系列工厂来让我们使用。比如 XmlBeanFactory 就是针对最基础的 BeanFactory 的 IOC 容器的实现 - 这个实现使用xml 来定义 IOC 容器中的 bean。

Spring 提供了一个 BeanFactory 的基本实现,XmlBeanFactory 同样的通过使用模板模式来得到对 IOC 容器的抽象- AbstractBeanFactory,DefaultListableBeanFactory 这些抽象类为其提供模板服务。其中通过 resource 接口来抽象 bean 定义数据,对Xml 定义文件的解析通过委托给 XmlBeanDefinitionReader 来完成。下面我们根据书上的例子,简单的演示 IOC 容器的创建过程:

代码语言:javascript复制
1ClassPathResource res = new ClassPathResource("beans.xml"); 
2DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); 
3XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 
4reader.loadBeanDefinitions(res); 

这些代码演示了以下几个步骤:

  1. 创建 IOC 配置文件的抽象资源
  2. 创建一个 BeanFactory
  3. 把读取配置信息的 BeanDefinitionReader,这里是 XmlBeanDefinitionReader 配置给 BeanFactory
  4. 从定义好的资源位置读入配置信息,具体的解析过程由 XmlBeanDefinitionReader 来完成,这样完成整个载入 bean 定义的过程。我们的 IoC 容器就建立起来了。在 BeanFactory 的源代码中我们可以看到:
代码语言:javascript复制
 1public class XmlBeanFactory extends DefaultListableBeanFactory { 
 2    //这里为容器定义了一个默认使用的 bean 定义读取器 
 3    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); 
 4    public XmlBeanFactory(Resource resource) throws BeansException { 
 5    this(resource, null); 
 6} 
 7//在初始化函数中使用读取器来对资源进行读取,得到 bean 定义信息。 
 8public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { 
 9    super(parentBeanFactory); 
10    this.reader.loadBeanDefinitions(resource); 
11} 

我们在后面会看到读取器读取资源和注册 bean 定义信息的整个过程,基本上是和上下文的处理是一样的,从这里我们可以看到上下文和 XmlBeanFactory 这两种 IOC 容器的区别,BeanFactory 往往不具备对资源定义的能力,而上下文可以自己完成资源定义,从这个角度上看上下文更好用一些。

仔细分析 Spring BeanFactory 的结构,我们来看看在 BeanFactory 基础上扩展出的 ApplicationContext - 我们最常使用的上下文。除了具备 BeanFactory 的全部能力,上下文为应用程序又增添了许多便利:

  • 可以支持不同的信息源,我们看到 ApplicationContext 扩展了 MessageSource
  • 访问资源 , 体现在对 ResourceLoader 和 Resource 的支持上面,这样我们可以从不同地方得到 bean 定义资源
  • 支持应用事件,继承了接口 ApplicationEventPublisher,这样在上下文中引入了事件机制而 BeanFactory 是没有的。

ApplicationContext 允许上下文嵌套 - 通过保持父上下文可以维持一个上下文体系 - 这个体系我们在以后对 Web 容器中的上下文环境的分析中可以清楚地看到。对于 bean 的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的 Spring 应用提供了一个共享的 bean 定义环境。这个我们在分析 Web 容器中的上下文环境时也能看到。

ApplicationContext 提供 IoC 容器的主要接口,在其体系中有许多抽象子类比如 AbstractApplicationContext 为具体的 BeanFactory 的实现,比如 FileSystemXmlApplicationContext 和 ClassPathXmlApplicationContext 提供上下文的模板,使得他们只需要关心具体的资源定位问题。当应用程序代码实例化 FileSystemXmlApplicationContext 的时候,得到 IoC 容器的一种具体表现 - ApplicationContext,从而 应用程序通过 ApplicationContext 来管理对 bean 的操作。

BeanFactory 是一个接口,在实际应用中我们一般使用 ApplicationContext 来使用 IOC 容器,它们也是 IOC 容器展现给应用开发者的使用接口。对应用程序开发者来说,可以认为 BeanFactory 和 ApplicationFactory 在不同的使用层面上代表了 SPRING 提供的 IOC 容器服务。

下面我们具体看看通过 FileSystemXmlApplicationContext 是怎样建立起 IOC 容器的, 显而易见我们可以通过 new 来得到 IoC 容器:

代码语言:javascript复制
1ApplicationContext = new FileSystemXmlApplicationContext(xmlPath); 

调用的是它初始化代码:

代码语言:javascript复制
1public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { 
2    super(parent); 
3    this.configLocations = configLocations; 
4    if (refresh) { 
5        //这里是 IoC 容器的初始化过程,其初始化过程的大致步骤由 AbstractApplicationContext 来定义 
6        refresh(); 
7    } 
8} 

refresh 的模板在 AbstractApplicationContext:

代码语言:javascript复制
 1public void refresh() throws BeansException, IllegalStateException { 
 2    synchronized (this.startupShutdownMonitor) { 
 3        synchronized (this.activeMonitor) { 
 4            this.active = true; 
 5        } 
 6
 7        // 这里需要子类来协助完成资源位置定义,bean 载入和向 IOC 容器注册的过程 
 8        refreshBeanFactory(); 
 9        ...
10} 

这个方法包含了整个BeanFactory初始化的过程,对于特定的FileSystemXmlBeanFactory,我们看到定位资源位置由refreshBeanFactory()来实现: 在 AbstractXmlApplicationContext 中定义了对资源的读取过程,默认由 XmlBeanDefinitionReader 来读取:

代码语言:javascript复制
 1protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException { 
 2    // 这里使用 XMLBeanDefinitionReader 来载入 bean 定义信息的 XML 文件 
 3    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); 
 4
 5    //这里配置 reader 的环境,其中 ResourceLoader 是我们用来定位 bean 定义信息资源位置的 
 6    //因为上下文本身实现了 ResourceLoader 接口,所以可以直接把上下文作为 ResourceLoader 传递给 XmlBeanDefinitionReader 
 7    beanDefinitionReader.setResourceLoader(this); 
 8    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); 
 9
10    initBeanDefinitionReader(beanDefinitionReader); 
11    //这里转到定义好的 XmlBeanDefinitionReader 中对载入 bean 信息进行处理 
12    loadBeanDefinitions(beanDefinitionReader); 
13} 

转到 beanDefinitionReader 中进行处理:

代码语言:javascript复制
 1protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { 
 2    Resource[] configResources = getConfigResources(); 
 3    if (configResources != null) { 
 4        //调用 XmlBeanDefinitionReader 来载入 bean 定义信息。 
 5        reader.loadBeanDefinitions(configResources); 
 6    } 
 7    String[] configLocations = getConfigLocations(); 
 8    if (configLocations != null) { 
 9        reader.loadBeanDefinitions(configLocations); 
10    } 
11} 

而在作为其抽象父类的 AbstractBeanDefinitionReader 中来定义载入过程:

代码语言:javascript复制
 1public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { 
 2    //这里得到当前定义的 ResourceLoader,默认的我们使用 DefaultResourceLoader 
 3    ResourceLoader resourceLoader = getResourceLoader(); 
 4    .........//如果没有找到我们需要的 ResourceLoader,直接抛出异常 
 5    if (resourceLoader instanceof ResourcePatternResolver) { 
 6        // 这里处理我们在定义位置时使用的各种 pattern,需要 ResourcePatternResolver 来完成 
 7        try { 
 8            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 
 9            int loadCount = loadBeanDefinitions(resources); 
10            return loadCount; 
11        } 
12        ...
13    }else { 
14        // 这里通过 ResourceLoader 来完成位置定位 
15        Resource resource = resourceLoader.getResource(location); 
16        // 这里已经把一个位置定义转化为 Resource 接口,可以供 XmlBeanDefinitionReader 来使用了 
17        int loadCount = loadBeanDefinitions(resource); 
18        return loadCount; 
19    } 
20}

当我们通过 ResourceLoader 来载入资源,别忘了了我们的 GenericApplicationContext 也实现了 ResourceLoader 接口:

代码语言:javascript复制
 1public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { 
 2    public Resource getResource(String location) { 
 3        //这里调用当前的 loader 也就是 DefaultResourceLoader 来完成载入 
 4        if (this.resourceLoader != null) { 
 5            return this.resourceLoader.getResource(location); 
 6        } 
 7        return super.getResource(location); 
 8    } 
 9    ... 
10} 

而我们的 FileSystemXmlApplicationContext 就是一个 DefaultResourceLoader - GenericApplicationContext()通过DefaultResourceLoader:

代码语言:javascript复制
 1public Resource getResource(String location) { 
 2    //如果是类路径的方式,那需要使用 ClassPathResource 来得到 bean 文件的资源对象 
 3    if (location.startsWith(CLASSPATH_URL_PREFIX)) { 
 4        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); 
 5    }else { 
 6        try { 
 7            // 如果是 URL 方式,使用 UrlResource 作为 bean 文件的资源对象 
 8            URL url = new URL(location); 
 9            return new UrlResource(url); 
10        }catch (MalformedURLException ex) { 
11            // 如果都不是,那我们只能委托给子类由子类来决定使用什么样的资源对象了 
12            return getResourceByPath(location); 
13        }
14    }
15}

我们的 FileSystemXmlApplicationContext 本身就是是 DefaultResourceLoader 的实现类,他实现了以下的接口:

代码语言:javascript复制
1protected Resource getResourceByPath(String path) { 
2    if (path != null && path.startsWith("/")) { 
3        path = path.substring(1); 
4    } 
5    //这里使用文件系统资源对象来定义 bean 文件 
6    return new FileSystemResource(path); 
7} 

这样代码就回到了 FileSystemXmlApplicationContext 中来,他提供了 FileSystemResource 来完成从文件系统得到配置文件的资源定义。

这样,就可以从文件系统路径上对 IOC 配置文件进行加载 - 当然我们可以按照这个逻辑从任何地方加载,在 Spring 中我们看到它提供的各种资源抽象,比如 ClassPathResource, URLResource,FileSystemResource 等来供我们使用。上面我们看到的是定位 Resource 的一个过程,而这只是加载过程的一部分 - 我们回到AbstractBeanDefinitionReaderz 中的 loadDefinitions(resource)来看看得到代表 bean文件的资源定义以后的载入过程,默认的我们使用 XmlBeanDefinitionReader:

代码语言:javascript复制
 1public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { 
 2    ...
 3    try { 
 4        //这里通过 Resource 得到 InputStream 的 IO 流 
 5        InputStream inputStream = encodedResource.getResource().getInputStream(); 
 6        try { 
 7            //从 InputStream 中得到 XML 的解析源 
 8            InputSource inputSource = new InputSource(inputStream); 
 9            if (encodedResource.getEncoding() != null) { 
10                inputSource.setEncoding(encodedResource.getEncoding()); 
11            } 
12            //这里是具体的解析和注册过程 
13            return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); 
14        }finally { 
15            //关闭从 Resource 中得到的 IO 流 
16            inputStream.close(); 
17        } 
18    } 
19    ...
20} 
21
22protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { 
23    try { 
24        int validationMode = getValidationModeForResource(resource); 
25        //通过解析得到 DOM,然后完成 bean 在 IOC 容器中的注册 
26        Document doc = this.documentLoader.loadDocument( 
27        inputSource, this.entityResolver, this.errorHandler, validationMode, this.namespaceAware); 
28        return registerBeanDefinitions(doc, resource); 
29    } 
30    ...
31} 

我们看到先把定义文件解析为 DOM 对象,然后进行具体的注册过程:

代码语言:javascript复制
 1public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { 
 2    // 这里定义解析器,使用 XmlBeanDefinitionParser 来解析 xml 方式的 bean 定义文件 - 现在的版本不用这个解析器了,使用的是 XmlBeanDefinitionReader 
 3    if (this.parserClass != null) { 
 4        XmlBeanDefinitionParser parser = (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass); 
 5        return parser.registerBeanDefinitions(this, doc, resource); 
 6    } 
 7    // 具体的注册过程,首先得到 XmlBeanDefinitionReader,来处理 xml 的 bean 定义文件 
 8    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); 
 9    int countBefore = getBeanFactory().getBeanDefinitionCount(); 
10    documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 
11    return getBeanFactory().getBeanDefinitionCount() - countBefore; 
12} 

具体的在 BeanDefinitionDocumentReader 中完成对,下面是一个简要的注册过程来完成 bean 定义文件的解析和 IOC 容器中 bean 的初始化

代码语言:javascript复制
 1public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { 
 2    this.readerContext = readerContext; 
 3
 4    logger.debug("Loading bean definitions"); 
 5    Element root = doc.getDocumentElement(); 
 6
 7    BeanDefinitionParserDelegate delegate = createHelper(readerContext, root); 
 8
 9    preProcessXml(root); 
10    parseBeanDefinitions(root, delegate); 
11    postProcessXml(root); 
12} 
13
14protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { 
15    if (delegate.isDefaultNamespace(root.getNamespaceURI())) { 
16        //这里得到 xml 文件的子节点,比如各个 bean 节点 
17        NodeList nl = root.getChildNodes(); 
18
19        //这里对每个节点进行分析处理 
20        for (int i = 0; i < nl.getLength(); i  ) { 
21        Node node = nl.item(i); 
22        if (node instanceof Element) { 
23            Element ele = (Element) node; 
24            String namespaceUri = ele.getNamespaceURI(); 
25            if (delegate.isDefaultNamespace(namespaceUri)) { 
26                //这里是解析过程的调用,对缺省的元素进行分析比如 bean 元素 
27                parseDefaultElement(ele, delegate); 
28            }else { 
29                delegate.parseCustomElement(ele); 
30            } 
31        } 
32        } 
33    }else { 
34        delegate.parseCustomElement(root); 
35    } 
36} 
37
38private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { 
39    //这里对元素 Import 进行处理 
40    if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) { 
41        importBeanDefinitionResource(ele); 
42    }else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) { 
43        String name = ele.getAttribute(NAME_ATTRIBUTE); 
44        String alias = ele.getAttribute(ALIAS_ATTRIBUTE); 
45        getReaderContext().getReader().getBeanFactory().registerAlias(name, alias); 
46        getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); 
47    }else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) { //这里对我们最熟悉的 bean 元素进行处理 
48        //委托给 BeanDefinitionParserDelegate 来完成对 bean 元素的处理,这个类包含了具体的 bean 解析的过程。 
49        // 把解析bean文件得到的信息放到BeanDefinition里,他是bean信息的主要载体,也是IOC容器的管理对象。 
50        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); 
51        if (bdHolder != null) { 
52            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); 
53            // 这里是向 IOC 容器注册,实际上是放到 IOC 容器的一个 map 里 
54            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); 
55
56            // 这里向 IOC 容器发送事件,表示解析和注册完成。 
57            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); 
58        } 
59    } 
60} 

我们看到在 parseBeanDefinition 中对具体 bean 元素的解析式交给 BeanDefinitionParserDelegate 来完成的,下面我们看看解析完的bean 是怎样在 IOC 容器中注册的:

在 BeanDefinitionReaderUtils 调用的是:

代码语言:javascript复制
 1public static void registerBeanDefinition(BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory) throws BeansException { 
 2    // 这里得到需要注册 bean 的名字; 
 3    String beanName = bdHolder.getBeanName(); 
 4    //这是调用 IOC 来注册的 bean 的过程,需要得到 BeanDefinition 
 5    beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()); 
 6
 7    // 别名也是可以通过 IOC 容器和 bean 联系起来的进行注册 
 8    String[] aliases = bdHolder.getAliases(); 
 9    if (aliases != null) { 
10        for (int i = 0; i < aliases.length; i  ) { 
11            beanFactory.registerAlias(beanName, aliases[i]); 
12        } 
13    } 
14} 

我们看看 XmlBeanFactory 中的注册实现:

代码语言:javascript复制
 1//--------------------------------------------------------------------- 
 2// 这里是 IOC 容器对 BeanDefinitionRegistry 接口的实现 
 3//--------------------------------------------------------------------- 
 4public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { 
 5    ...
 6    //这里省略了对 BeanDefinition 的验证过程 
 7    //先看看在容器里是不是已经有了同名的 bean,如果有抛出异常。 
 8    Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); 
 9    if (oldBeanDefinition != null) { 
10        if (!this.allowBeanDefinitionOverriding) { 
11            ...
12        }
13    }else { 
14        //把 bean 的名字加到 IOC 容器中去 
15        this.beanDefinitionNames.add(beanName); 
16    } 
17    //这里把 bean 的名字和 Bean 定义联系起来放到一个 HashMap 中去,IOC 容器通过这个 Map 来维护容器里的 Bean 定义信息。 
18    this.beanDefinitionMap.put(beanName, beanDefinition); 
19    removeSingleton(beanName); 
20}

这样就完成了 Bean 定义在 IOC 容器中的注册,就可被 IOC 容器进行管理和使用了。

从上面的代码来看,我们总结一下 IOC 容器初始化的基本步骤:

  • 初始化的入口在容器实现中的 refresh()调用来完成
  • 对 bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition,其中的大致过程如下:通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader 是默认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统, URL 等方式来定为资源位置。如果是 XmlBeanFactory 作为 IOC 容器,那么需要为它指定 bean 定义的资源,也就是说 bean 定义文件时通过抽象成 Resource 来被 IOC 容器处理的,容器通过 BeanDefinitionReader 来完成定义信息的解析和 Bean 信息的注册,往往使用的是XmlBeanDefinitionReader 来解析 bean 的 xml 定义文件 - 实际的处理过程是委托给 BeanDefinitionParserDelegate 来完成的,从而得到 bean 的定义信息,这些信息在 Spring 中使用 BeanDefinition 对象来表示 - 这个名字可以让我们想到loadBeanDefinition,RegisterBeanDefinition 这些相关的方法 - 他们都是为处理 BeanDefinitin 服务的,IoC 容器解析得到 BeanDefinition以后,需要把它在 IOC 容器中注册,这由 IOC 实现 BeanDefinitionRegistry 接口来实现。注册过程就是在 IOC 容器内部维护的一个HashMap 来保存得到的 BeanDefinition 的过程。这个 HashMap 是 IoC 容器持有 bean 信息的场所,以后对 bean 的操作都是围绕这个HashMap 来实现的。
  • 然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了。

在使用 IOC 容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IoC 风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。 Spring 本身提供了对声明式载入 web 应用程序用法的应用程序上下文,并将其存储在ServletContext 中的框架实现。具体可以参见以后的文章。

在使用 Spring IOC 容器的时候我们还需要区别两个概念:

Beanfactory 和 Factory bean,其中 BeanFactory 指的是 IOC 容器的编程抽象,比如 ApplicationContext, XmlBeanFactory 等,这些都是 IOC 容器的具体表现,需要使用什么样的容器由客户决定但 Spring 为我们提供了丰富的选择。而 FactoryBean 只是一个可以在 IOC容器中被管理的一个 bean,是对各种处理过程和资源使用的抽象,Factory bean 在需要时产生另一个对象,而不返回 FactoryBean 本省,我们可以把它看成是一个抽象工厂,对它的调用返回的是工厂生产的产品。所有的 Factory bean 都实现特殊的org.springframework.beans.factory.FactoryBean 接口,当使用容器中 factory bean 的时候,该容器不会返回 factory bean 本身,而是返回其生成的对象。Spring 包括了大部分的通用资源和服务访问抽象的 Factory bean 的实现,其中包括:对 JNDI 查询的处理,对代理对象的处理,对事务性代理的处理,对 RMI 代理的处理等,这些我们都可以看成是具体的工厂,看成是SPRING 为我们建立好的工厂。也就是说 Spring 通过使用抽象工厂模式为我们准备了一系列工厂来生产一些特定的对象,免除我们手工重复的工作,我们要使用时只需要在 IOC 容器里配置好就能很方便的使用了。

现在我们来看看在 Spring 的事件机制,Spring 中有 3 个标准事件,ContextRefreshEvent, ContextCloseEvent,RequestHandledEvent 他们通过 ApplicationEvent 接口,同样的如果需要自定义时间也只需要实现 ApplicationEvent 接口,参照 ContextCloseEvent 的实现可以定制自己的事件实现:

代码语言:javascript复制
 1public class ContextClosedEvent extends ApplicationEvent { 
 2
 3    public ContextClosedEvent(ApplicationContext source) { 
 4        super(source); 
 5    } 
 6
 7    public ApplicationContext getApplicationContext() { 
 8        return (ApplicationContext) getSource(); 
 9    } 
10} 

可以通过显现 ApplicationEventPublishAware 接口,将事件发布器耦合到 ApplicationContext 这样可以使用 ApplicationContext 框架来传递和消费消息,然后在 ApplicationContext 中配置好 bean 就可以了,在消费消息的过程中,接受者通过实现 ApplicationListener 接收消息。

比如可以直接使用Spring的ScheduleTimerTask和TimerFactoryBean作为定时器定时产生消息,具体可以参见《Spring框架高级编程》。 TimerFactoryBean 是一个工厂 bean,对其中的 ScheduleTimerTask 进行处理后输出,参考 ScheduleTimerTask 的实现发现它最后调用的是 jre 的 TimerTask:

代码语言:javascript复制
1public void setRunnable(Runnable timerTask) { 
2    this.timerTask = new DelegatingTimerTask(timerTask); 
3}

在书中给出了一个定时发送消息的例子,当然可以可以通过定时器作其他的动作,有两种方法:

1.定义 MethodInvokingTimerTaskFactoryBean 定义要执行的特定 bean 的特定方法,对需要做什么进行封装定义; 2.定义 TimerTask 类,通过 extends TimerTask 来得到,同时对需要做什么进行自定义

然后需要定义具体的定时器参数,通过配置 ScheduledTimerTask 中的参数和 timerTask 来完成,以下是它需要定义的具体属性,timerTask 是在前面已经定义好的 bean

代码语言:javascript复制
1private TimerTask timerTask; 
2private long delay = 0; 
3private long period = 0; 
4private boolean fixedRate = false; 

最后,需要在 ApplicationContext 中注册,需要把 ScheduledTimerTask 配置到 FactoryBean - TimerFactoryBean,这样就由 IOC 容器来管理定时器了。参照 TimerFactoryBean 的属性,可以定制一组定时器。

代码语言:javascript复制
 1public class TimerFactoryBean implements FactoryBean, InitializingBean, DisposableBean { 
 2    protected final Log logger = LogFactory.getLog(getClass()); 
 3
 4    private ScheduledTimerTask[] scheduledTimerTasks; 
 5
 6    private boolean daemon = false; 
 7
 8    private Timer timer; 
 9
10    ...
11} 

如果要发送时间我们只需要在定义好的 ScheduledTimerTasks 中 publish 定义好的事件就可以了。具体可以参考书中例子的实现,这里只是结合 FactoryBean 的原理做一些解释。如果结合事件和定时器机制,我们可以很方便的实现 heartbeat(看门狗),书中给出了这个例子,这个例子实际上结合了 Spring 事件和定时机制的使用两个方面的知识 - 当然了还有 IOC 容器的知识(任何 Spring 应用我想都逃不掉 IOC 的魔爪)

0 人点赞