Spring源码阅读(一)——浅析ApplicationContext

一、IOC与DI

很多人学习Spring框架都是从IOC入手的, IOC(Inversion of Control)译为“控制反转”,基于这一概念,可以衍生出下面几个问题:

  1. 谁控制了谁?

  2. 控制了什么?

  3. 为什么是反转?

首先,我们来回答第一个问题:传统模式下,我们通常使用new来创建对象。而使用Spring,我们调用getBean(String name, Class<?> type)就可以直接获得对象。因此,IOC容器控制了对象。

那么,Spring容器控制了对象的什么呢?要回答这个问题,我们可以直接把IOC的定义搬过来:

所谓 IOC ,就是由 Spring IOC 容器来负责对象的生命周期和对象之间的关系。

为什么是反转?通过new来创建对象,对象的生命周期以及对象间的依赖都由程序员自己控制,这是正转。而反转刚好倒过来,由容器来对对象进行管理,我们可以直接从容器中获得对象及其依赖。

DI(Dependency Injection),即“依赖注入”,是指容器在运行时决定组件之间的依赖,这是容器管理对象的另一个说法。它并非是为系统添加新的功能,而是提升了组件的重用性。

二、ApplicationContext

也许大家从零零星星的文章中了解到,ApplicationContext是一个高级的BeanFactory,它在BeanFactory的基础上扩展了很多功能。至于它有哪些功能,我们接下来细细道来。

ApplicationContext.png

从ApplicationContext的类图中可以看到,它除了简接地继承了BeanFactory之外,还继承了MessageSource、ResourceLoader、ApplicationEventPublisher和EnvironmentCapable接口,言外之意是它包含了这几个接口定义的所有功能。

2.1 MessageSource

MessageSource是一个解析消息的策略接口,它支持参数化与国际化。也许大家并不是很理解这句话,下面我们一步步地解析这句话的含义。

/**
 * 用于解析消息的策略接口,支持此类消息的参数化和国际化
 */
public interface MessageSource {

    /**
     * 解析消息,如果没找到code对应的消息就返回defaultMessage
     */
    @Nullable
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

    /**
     * 解析消息,如果没找到code对应的消息就抛异常
     
     */
    String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

    /**
     * 使用MessageSourceResolvable中的所有属性解析消息
     * MessageSourceResolvable用code[]是啥意思呢???
     */
    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}

从MessageSource的源码可以看到,它定义了三个getMessage方法,根据Locale指定的地区解析code对应的消息,并用args参数替换消息中的占位符,最终返回解析后的消息。

为了帮助大家理解,我举个简单的例子:

  1. 在Spring配置文件spring.xml中定义properties文件的前缀
<bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>messages</value>
        </list>
    </property>
</bean>
  1. 新建几个以messages为前缀的properties文件,内容和文件名如下:
message=我只是个{0}   #messages_zh_CN.properties
message=I am just a {0}  #messages_en.properties
  1. 解析并获取解析后的消息
ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml");
String message1=context.getMessage("message", new String[]{"小孩"}, Locale.CHINA);
String message2=context.getMessage("message", new String[]{"child"}, Locale.ENGLISH);
        
System.out.println("message1=" + message1);
System.out.println("message2=" + message2);
  1. 打印输出
message1=我只是个小孩
message2=I am just a child

通过这个例子,大家应该理解参数化和国际化的意思了吧~~
我们继续回到ApplicationContext,它继承了MessageSource接口,说明它具有解析参数化和国际化消息的功能。

2.2 BeanFactory

要实现IOC容器,BeanFactory是不可或缺的一环,它负责bean的创建和管理。从ApplicationContext的类图中可以看出,ApplicationContext并不是直接继承BeanFactory接口,而是继承了BeanFactory的子接口:ListableBeanFactory和HierarchicalBeanFactory。

2.2.1 BeanFactory
/**
 * 创建、获取bean以及一些bean相关的其他操作
 */
public interface BeanFactory {

    /**
     * FactoryBean的前缀
     */
    String FACTORY_BEAN_PREFIX = "&";

    /**
     * 返回name对应的bean对象
     */
    Object getBean(String name) throws BeansException;

    /**
     * 返回指定type和name的bean对象
     */
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    /**
     * 返回现有对象或者用传入的args创建对象,非原型模式传入args会报错
     */
    Object getBean(String name, Object... args) throws BeansException;

    /**
     * 只能返回指定类型的唯一对象,大于小于一个都报错
     */
    <T> T getBean(Class<T> requiredType) throws BeansException;

    /**
     * 返回requiredType对应的bean对象或者用传入的args创建对象,非原型模式传入args会报错
     */
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    /**
     * 获取requiredType类型的ObjectProvider
     */
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

    /**
     * 获取requiredType类型的ObjectProvider
     */
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    /**
     * 是否存在名字为name的bean
     */
    boolean containsBean(String name);

    /**
     * 名字为name的bean是否单例bean
     */
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    /**
     * 是否原型bean
     */
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    /**
     * 指定bean是否该类型
     */
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

    /**
     * 指定bean是否该类型
     */
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    /**
     * 获取指定bean的类型
     */
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    /**
     * 获取指定bean的别名
     */
    String[] getAliases(String name);
}

从BeanFactory的源码中可以看出,BeanFactory定义了一些对bean的基本操作。继承BeanFactory,ApplicationContext就有了对bean进行操作的行为。

2.2.2 ListableBeanFactory

上面BeanFactory定义的都是对单个bean进行的操作,而ListableBeanFactory定义的操作大多数都是返回多个bean或者bean相关元素。从“Listable”就可以看出来,它定义了一些对bean进行列表化的操作。因此,ApplicationContext也可以对bean进行一些列表化操作。

/**
 * 可以枚举所有bean实例,而不是按客户端的请求逐个尝试按名称查找bean
 */
public interface ListableBeanFactory extends BeanFactory {

    /**
     * 是否存在指定name的BeanDefinition
     */
    boolean containsBeanDefinition(String beanName);

    /**
     * 获取BeanFactory中BeanDefinition的数量
     */
    int getBeanDefinitionCount();

    /**
     * 获取所有BeanDefinition的名字
     */
    String[] getBeanDefinitionNames();

    /**
     * 获取指定类型的beanName数组
     */
    String[] getBeanNamesForType(ResolvableType type);

    /**
     * 获取指定类型的beanName数组
     */
    String[] getBeanNamesForType(@Nullable Class<?> type);

    /**
     * 获取指定类型的beanName数组,加了是否包含单例和非懒加载这几个限制
     */
    String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);

    /**
     * 获取指定类型的所有bean
     */
    <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;

    /**
     * 获取指定类型的所有bean,加了是否包含单例和非懒加载这几个限制
     */
    <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
            throws BeansException;

    /**
     * 获取指定注解类型的beanNames
     */
    String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);

    /**
     * 获取指定类型的bean对象
     */
    Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;

    /**
     * 根据beanName和指定注解类型获取注解bean对象
     */
    @Nullable
    <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
            throws NoSuchBeanDefinitionException;
}
2.2.3 HierarchicalBeanFactory

HierarchicalBeanFactory定义的方法相对少点,它定义了对BeanFactory分级的一些操作,比如返回父BeanFactory。

/**
 * 定义了对BeanFactory层次结构的操作
 */
public interface HierarchicalBeanFactory extends BeanFactory {

    /**
     * Return the parent bean factory, or {@code null} if there is none.
     */
    @Nullable
    BeanFactory getParentBeanFactory();

    /**
     * 当前context(不包含祖先context)是否含有指定name的bean
     */
    boolean containsLocalBean(String name);
}

2.3 ResourceLoader

ResourceLoader是一个资源加载的策略接口,继承这个接口说明ApplicationContext有资源加载的功能,这也是Spring容器的第一步操作——加载配置文件。因为配置文件定义了bean以及bean之间的关系,所以只有把配置文件加载进来才能创建、管理bean。

/**
 * 资源加载的策略接口
 */
public interface ResourceLoader {

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


    /**
     * 返回指定路径的资源,这里只返回一个,说明不支持模式匹配
     */
    Resource getResource(String location);

    /**
     * 返回统一的ClassLoader,而不是依赖于线程上下文ClassLoader
     */
    @Nullable
    ClassLoader getClassLoader();
}
2.3.1 ResourcePatternResolver

从名字就可以看出来,ResourcePatternResolver对ResourceLoader进行了扩展,它支持解析模式匹配的路径。

/**
  * 解析模式匹配的路径,加载资源
  */
public interface ResourcePatternResolver extends ResourceLoader {

    /**
     * classpath url的前缀
     */
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    /**
     * 解析模式匹配的路径,返回多个Resource
     */
    Resource[] getResources(String locationPattern) throws IOException;

}

2.4 ApplicationEventPublisher

ApplicationEventPublisher定义了事件发布的功能,可以将事件发布给注册了此应用所有匹配的监听器。由此可见,ApplicationContet也可以发布事件。

/**
 * 定义了事件发布功能
 */
@FunctionalInterface
public interface ApplicationEventPublisher {

    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    /**
     * 通知在此应用程序中注册的所有匹配的listeners
     */
    void publishEvent(Object event);
}

2.5 EnvironmentCapable

EnvironmentCapable中只定义了getEnvironment的方法,向外界暴露了Environment接口。Environment是Spring运行时的环境,它包含了profiles和properties。
在一个软件的开发过程中,我们往往要经过很多步骤,而这些执行步骤往往要将项目部署在不同的环境,比如测试时部署在测试环境、上线时部署在生产环境。profile正是起到了区分环境的作用,容器只会加载当前active的profile环境所定义的bean及其他配置。
properties是指当前应用的属性,它可以在System环境变量、properties文件等多个地方进行配置。

三、小结

ApplicationContext是一个BeanFactory,它包含了创建bean以及其他管理bean的功能,除此之外它还有下列功能:

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

推荐阅读更多精彩内容