BeanFactory和ApplicationContext

概述

BeanFatory作为Spring的IOC容器主要提供了注册Bean和管理依赖这两个功能,它的接口包括如下函数:

BeanFactory.png

ApplicationContext在间接继承了BeanFactory的基础上,还多继承了四个接口,提供了更多的功能,如事件发布,资源载入,消息等。
BeanFactory and ApplicationContext

实现

BeanFactory接口的实现为了实现注册Bean的功能,需要使用BeanDefinitionRegistry这个接口,这三者的关系如图:

BeanFactoryAndBeanDefinitionRegistry.png

可以看到,BeanFactory的实现同时实现了这两个接口,而它的内部,正是使用BeanDefinition来管理Bean:

DefaultListableBeanFactory.png

无论是通过代码还是配置文件或者注解,本质上都是通过BeanDefinitionReader先构造BeanDefinition,然后将BeanDefinition注册到BeanDefinitionRegistry,通常也就是BeanFactory的实现。

IoC容器的两个阶段

容器启动阶段
  • 加载配置
  • 分析配置信息
  • 装配到BeanDefinition
  • PostProcess

BeanFactoryPostProcessor是容器的扩展机制,可在实例化阶段之前对BeanDefinition中的信息进行修改
如PropertyPlaceHolderConfigurer,把它声明为Bean,ApplicationContext会注册并应用它,将bean配置中的占位符用属性文件中的值替换

Bean实例化阶段
  • 实例化对象
  • 装配依赖
  • 生命周期回调
  • 对象的其他处理
  • 注册回调接口
Bean生命周期
  1. 实例化
    BeanFactory根据BeanDefinition来进行实例化,使用策略模式有反射和cglib两种方式来实例化bean,返回一个包裹了bean的BeanWrapper,用于第二步设置属性
  2. 设置属性
    BeanWrapperImpl同时也是继承了PropertyEditorRegistry,使用PropertyEditor来转换类型,设置属性
  3. Aware接口
    BeanFactory: BeanNameAware BeanClassLoaderAware BeanFactoryAware
    ApplicationContext: ResourceLoaderAware ApplicationEventPublisherAware MessageSourceAware ApplicationContextAware(都是ApplicationContext)
  4. BeanPostProcessor
public interface BeanPostProcessor {

    /**
     * Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean
     * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
     * or a custom init-method). The bean will already be populated with property values.
     * The returned bean instance may be a wrapper around the original.
     * @param bean the new bean instance
     * @param beanName the name of the bean
     * @return the bean instance to use, either the original or a wrapped one; if
     * {@code null}, no subsequent BeanPostProcessors will be invoked
     * @throws org.springframework.beans.BeansException in case of errors
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
     */
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

    /**
     * Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean
     * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
     * or a custom init-method). The bean will already be populated with property values.
     * The returned bean instance may be a wrapper around the original.
     * <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
     * instance and the objects created by the FactoryBean (as of Spring 2.0). The
     * post-processor can decide whether to apply to either the FactoryBean or created
     * objects or both through corresponding {@code bean instanceof FactoryBean} checks.
     * <p>This callback will also be invoked after a short-circuiting triggered by a
     * {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
     * in contrast to all other BeanPostProcessor callbacks.
     * @param bean the new bean instance
     * @param beanName the name of the bean
     * @return the bean instance to use, either the original or a wrapped one; if
     * {@code null}, no subsequent BeanPostProcessors will be invoked
     * @throws org.springframework.beans.BeansException in case of errors
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
     * @see org.springframework.beans.factory.FactoryBean
     */
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

常用场景:1.处理Aware接口 2.生成当前对象的代理(AOP)
ApplicationContextAwareProcessor处理ApplicationContext相关的各个Aware接口。

用BeanPostProcessor处理Marker接口时(如Aware接口),其实这个接口相当于为这个Bean添加了新的属性,接口一般包括的函数只有设置属性和获取属性,获取属性由Bean的Client调用,设置属性的函数则由BeanPostProcessor调用来进行设置,所以这种Marker接口一般是在BeanPostProcessor的Before函数中处理,紧邻着Bean实例化过程中的设置属性(因为这也是设置属性的一种,只不过是所有Bean统一设置)。

相对的,使用BeanPostProcessor来生成proxy时会在after函数中处理,这时候bean的初始化已经完成,可以被代理了。

  1. InitializingBean和init-method
    Bean的生命周期接口和生命周期方法

  2. 注册销毁回调
    以上的设置都完成之后,如果Bean实现了DisposableBean接口或者声明了destroy-method属性,那么这时候容器会为这个Bean注册一个用于销毁的回调,当关闭容器的时候,主动调用一个函数,来执行bean的销毁工作。

ConfigurableBeanFactory
/**
     * Destroy all singleton beans in this factory, including inner beans that have
     * been registered as disposable. To be called on shutdown of a factory.
     * <p>Any exception that arises during destruction should be caught
     * and logged instead of propagated to the caller of this method.
     */
    void destroySingletons();
ConfigurableApplicationContext
    /**
     * Register a shutdown hook with the JVM runtime, closing this context
     * on JVM shutdown unless it has already been closed at that time.
     * <p>This method can be called multiple times. Only one shutdown hook
     * (at max) will be registered for each context instance.
     * @see java.lang.Runtime#addShutdownHook
     * @see #close()
     */
    void registerShutdownHook();
  1. 使用

ApplicationContext

  • BeanFactoryPostProcessor,BeabPostProcessor等特殊bean的自动识别
  • bean实例的自动初始化
  • 统一的资源加载策略
  • 国际化的信息支持
  • 容器内事件发布
资源加载

问题:

  1. java.net.URL局限于http,ftp,file等协议,而资源可以以二进制、字节流、文件等形式存在于文件系统、classpath、URL定位等地方
  2. java.net.URL对资源的查找和定位没有清晰的界限,返回的形式多种多样,没有统一的抽象

解决:
Resource与ResourceLoader

ClassPathResource class|classLoader#getResourceAsStream
FileSystemResource
UrlResource   UrlConnection#getInputStream
ByteArrayResource
InputStreamResource 

public interface Resource extends InputStreamSource {

    /**
     * Return whether this resource actually exists in physical form.
     * <p>This method performs a definitive existence check, whereas the
     * existence of a {@code Resource} handle only guarantees a
     * valid descriptor handle.
     */
    boolean exists();

    /**
     * Return whether the contents of this resource can be read,
     * e.g. via {@link #getInputStream()} or {@link #getFile()}.
     * <p>Will be {@code true} for typical resource descriptors;
     * note that actual content reading may still fail when attempted.
     * However, a value of {@code false} is a definitive indication
     * that the resource content cannot be read.
     * @see #getInputStream()
     */
    boolean isReadable();

    /**
     * Return whether this resource represents a handle with an open
     * stream. If true, the InputStream cannot be read multiple times,
     * and must be read and closed to avoid resource leaks.
     * <p>Will be {@code false} for typical resource descriptors.
     */
    boolean isOpen();

    /**
     * Return a URL handle for this resource.
     * @throws IOException if the resource cannot be resolved as URL,
     * i.e. if the resource is not available as descriptor
     */
    URL getURL() throws IOException;

    /**
     * Return a URI handle for this resource.
     * @throws IOException if the resource cannot be resolved as URI,
     * i.e. if the resource is not available as descriptor
     */
    URI getURI() throws IOException;

    /**
     * Return a File handle for this resource.
     * @throws IOException if the resource cannot be resolved as absolute
     * file path, i.e. if the resource is not available in a file system
     */
    File getFile() throws IOException;

    /**
     * Determine the content length for this resource.
     * @throws IOException if the resource cannot be resolved
     * (in the file system or as some other known physical resource type)
     */
    long contentLength() throws IOException;

    /**
     * Determine the last-modified timestamp for this resource.
     * @throws IOException if the resource cannot be resolved
     * (in the file system or as some other known physical resource type)
     */
    long lastModified() throws IOException;

    /**
     * Create a resource relative to this resource.
     * @param relativePath the relative path (relative to this resource)
     * @return the resource handle for the relative resource
     * @throws IOException if the relative resource cannot be determined
     */
    Resource createRelative(String relativePath) throws IOException;

    /**
     * Determine a filename for this resource, i.e. typically the last
     * part of the path: for example, "myfile.txt".
     * <p>Returns {@code null} if this type of resource does not
     * have a filename.
     */
    String getFilename();

    /**
     * Return a description for this resource,
     * to be used for error output when working with the resource.
     * <p>Implementations are also encouraged to return this value
     * from their {@code toString} method.
     * @see Object#toString()
     */
    String getDescription();

}
public interface InputStreamSource {

    /**
     * Return an {@link InputStream}.
     * <p>It is expected that each call creates a <i>fresh</i> stream.
     * <p>This requirement is particularly important when you consider an API such
     * as JavaMail, which needs to be able to read the stream multiple times when
     * creating mail attachments. For such a use case, it is <i>required</i>
     * that each {@code getInputStream()} call returns a fresh stream.
     * @return the input stream for the underlying resource (must not be {@code null})
     * @throws IOException if the stream could not be opened
     * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource)
     */
    InputStream getInputStream() throws IOException;
}
public interface ResourceLoader {

    /** Pseudo URL prefix for loading from the class path: "classpath:" */
    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;


    /**
     * Return a Resource handle for the specified resource.
     * The handle should always be a reusable resource descriptor,
     * allowing for multiple {@link Resource#getInputStream()} calls.
     * <p><ul>
     * <li>Must support fully qualified URLs, e.g. "file:C:/test.dat".
     * <li>Must support classpath pseudo-URLs, e.g. "classpath:test.dat".
     * <li>Should support relative file paths, e.g. "WEB-INF/test.dat".
     * (This will be implementation-specific, typically provided by an
     * ApplicationContext implementation.)
     * </ul>
     * <p>Note that a Resource handle does not imply an existing resource;
     * you need to invoke {@link Resource#exists} to check for existence.
     * @param location the resource location
     * @return a corresponding Resource handle
     * @see #CLASSPATH_URL_PREFIX
     * @see org.springframework.core.io.Resource#exists
     * @see org.springframework.core.io.Resource#getInputStream
     */
    Resource getResource(String location);

    /**
     * Expose the ClassLoader used by this ResourceLoader.
     * <p>Clients which need to access the ClassLoader directly can do so
     * in a uniform manner with the ResourceLoader, rather than relying
     * on the thread context ClassLoader.
     * @return the ClassLoader (only {@code null} if even the system
     * ClassLoader isn't accessible)
     * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
     */
    ClassLoader getClassLoader();

}
DefaultResourceLoader#getResource

(1) 检查路径是否以classpath:开头->ClassPathResource
(2) a.通过Url来定位资源,构造UrlResource;b.构造ClassPathResource

FileSystemResourceLoader

修改了(2)b构造FileSystemResource

ResourcePatternResolver

ResourceLoader的扩展,添加了getResources
它的实现会委派一个ResouceLoader来加载资源

ApplicationContext

继承了ResourcePatternResolver,它的实现一般会继承一个AbstractApplicationContext,这个类会继承DefaultReosurceLoader,而且有一个ResourcePatternResolver的对象,通过自己构造。详见Spring揭秘p91。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • 文章作者:Tyan博客:noahsnail.com | CSDN | 简书 3.8 Container Exten...
    SnailTyan阅读 1,222评论 0 6
  • Spring容器高层视图 Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相...
    Theriseof阅读 2,789评论 1 24
  • 注意LifecycleProcessor接口继承了Lifcycle接口。同时,增加了2个方法,用于处理容器的ref...
    google666s阅读 1,095评论 0 51
  • 出现这种错误的原因是 边遍历数组 边修改数组中的内容 解决办法 1. 亲测有效 NSMutableArray* a...
    骑驴去旅行阅读 6,087评论 0 1
  • 时间:2016年5月17日 在昨天学习了边框和尺寸的情况下,今天我们主要学习的就是盒模型。盒模型是HTML中最重要...
    旭先生阅读 598评论 0 2