Spring 高级编程(第五版)-第三章读书笔记

第三章-在Spring中引入IOC和DI

控制反转的概念:

最早自己对于控制反转的理解概括为一句话:IoC的核心以及本质其实是DI,同时IoC只是在倡导提供一种优质的方案来将bean的控制权移交给Spring容器,让开发人员着重关注业务逻辑。书中的原话精炼:IoC的核心是DI,旨在提供一种更简单的机制来设置组件的依赖项,并在整个生命周期中管理这些依赖项,需要某些依赖项的组件通常被称为依赖对象,或者在IOC的情况下被称为目标对象,通常IOC可以分解为两种子类型:依赖注入依赖查找,这些子类型被进一步分解为IoC的服务的具体实现,对比两个理解可以发现我原来理解的IoC比较片面,同时暴露出了自己的知识碎片,所以在这里整合一下期望能体系化的理解IoC;
  依赖注入,通常的表现为构造函数注入和setter注入,这也是推荐的一种IoC方式;
  依赖查找,在文中作者对其进行再次分类分别为依赖拉取(DL)上下文依赖查找(CDL),关于DL,适用于早起的JNDI以及在EJB2.1的版本中非常流行,DL是通过从注册表中提取依赖项的方式来进行进行DI的(即通过JNDI API来查找EJB组件),CDL在某些方面与DL类似,CDL的查找是针对管理资源的容器执行的,在Spring中一个典型的依赖查找的场景就是通过Sping的ApplicationContext接口来getBean。
   注入查找,选择使用哪种类型的IoC通常是由容器决定的,例如在EJB2.1或更早的版本中,则必须使用查找式的IoC通过JNDI从JEE容器中获取EJB,Spring中,除了初始bean的查找,组件及其依赖项始终是使用注入式的IoC连接在一起的,所以目前常用以及推荐的IoC方式一定是注入的方式。


    tips:IoC的核心其实是DI,IoC提供了一种简便的方式来对依赖对象的整个生命周期做管理,关注DI其实着重关注的点是依赖项的管理,所以演化出两种IoC的方式依赖注入和依赖查找,在原来的理解中没有意识到依赖管理这么一说,这里纠正一下

  • Spring中的控制反转:

控制反转一直以来都是Spring的重要组成部分,Spring的核心就是Di在Spring中除了Spring初始化的时候后一些必要的基础组件需要通过依赖查找的方式来进行对象管理的,其他任何对象包含其依赖项都是通过依赖注入的方式来进行对象管理的。
  Spring支持构造函数注入和setter注入两种基础注入方式,并且支持标准的IoC功能集,同时提供了大量的附加功能来简化我们的开发(其实还有提到spEL表达式注入,属性注入)。

  • Spring中的依赖注入:

BeanFactory And FactoryBean ?
  之前一直不太理解为什么Spring中会同时存在BeanFactoryFactoryBean这两个接口,不理解的地方就在于现有的水平下想不到当时设计这两个接口的人是基于什么样的场景和什么样的问题来进行设计的,所以来稍微探索求证一下:
首先是FactoryBean,按照命名规范来看这个接口应该标识了一类bean,那么我们先看看源码(此处源码版本为Spring5.x):

//Spring 3.0开始支持泛型
public interface FactoryBean<T> {

 @Nullable
 T getObject() throws Exception;

 @Nullable
 Class<?> getObjectType();

  default boolean isSingleton() {
     return true;
  }
}

可以看到该接口非常简单,仅仅只有三个方法,先简单介绍一下:

  • T getObject() 该方法需要返回一个T类型实例,并且我们通常在使用BeanFactory来进行getBean时,如果你get的bean类型是一个实现了factoryBean的类,那么Spring将会调用该类的getObject方法。
  • Class<?> getObjectType() 该方法返回一个getObject中实例的类类型。
  • boolean isSingleton() 该方法用来告知Spring通过factoryBean创建的实例作用域是Singleton还是prototype,也就是告诉容器该实例是否为单例bean,如果返回为true,那么该实例将被放到Spring的单实例缓存池中,否则在getObject时将新建一个实例。
    趁热打铁,我们再找一个实现了FactoryBean的子类来看看具体的应用,这里参考mybatis-spring:3.1 中的SqlSessionFactoryBean,由于该类涉及到了mybatis的源码,在这里我们只关注如上三个方法的具体实现即可,源码片段如下:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
   //.........此处省略一万字

  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
            "Property 'configuration' and 'configLocation' can not specified with together");
   
       this.sqlSessionFactory = buildSqlSessionFactory();
  }

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }
  
  @Override
  public Class<? extends SqlSessionFactory> getObjectType() {
    return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
  }
 
  @Override
  public boolean isSingleton() {
    return true;
  }
}

从上面我们可以看到在mybatis 的SqlSessionFactoryBean中,mybatis在getObject方法里面进行了sqlSessionFactory依赖的初始化,在getObjectType中进行了默认类型的返回,总结为一句话就是,FactoryBean类型的bean在Spring初始化时显得更加灵活,可以进行更多的自定义配置以及需求,这有效的减少了复杂bean的实例化代价,如果没有FactoryBean,那么我们在某些场景下将面临及其繁琐的依赖配置。
我们再回过头来看看BeanFactory,源码如下:

public interface BeanFactory {

    
 String FACTORY_BEAN_PREFIX = "&";


 Object getBean(String name) throws BeansException;

    
 <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

    
 Object getBean(String name, Object... args) throws BeansException;

    
 <T> T getBean(Class<T> requiredType) throws BeansException;

    
 <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;


 boolean containsBean(String name);

    
 boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    
 boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    
 boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

    
 boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    
 @Nullable
 Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    
 String[] getAliases(String name);

}

同样,我们先从规范的名字入手,BeanFactory以Factory结尾应该是一个工厂类,我们从官方提供的Api中找到下面一段描述:
 The root interface for accessing a Spring bean container. This is the basic client view of a bean container; further interfaces such as ListableBeanFactory and ConfigurableBeanFactory are available for specific purposes.
大概意思就说BeanFactory是Spring容器的一个基础组件,进一步的实现例如ListableBeanFactoryConfigurableBeanFactory可以对BeanFactory做进一步的拓展,总结就是BeanFactory是IoC的一个核心接口,它提供基础的bean管理以及创建功能。


   引用书中的原话为:Spring的依赖注入容器的核心是**BeanFactory**接口,BeanFactory负载管理组件,包括依赖项以及它们的生命周期并可以在BeanFactory中识别自己的bean,为每个bean分配一个ID、一个名称或者两者兼具,一个bean也可以在没有任何ID或名称(匿名bean)的情况下被实例化,或者作为另一个bean的内部bean被实例化,每个bean至少有一个名称,多个名称用逗号隔开,第一个名称后面的名称都被认为是同一个bean的别名,可以使用BeanFactory用beanID去检索一个bean,并建立依赖关系

简单描述一下BeanFactory的方法(部分方法重载太多,忽略参数):

  • T getBean() 根据bean的名称类型等获取bean实例。
  • boolean containsBean(String name) 根据bean的名称去判断工厂中是否存在相同的bean实例,存在则返回true
  • boolean isSingleton(String name) 该方法和FactoryBean功能不太相同,该方法返回的是getBean中返回bean的作用域信息,是否是单例的bean,如果是则返回true
  • boolean isPrototype(String name) 该方法和isSingleton类似,返回getBean方法返回bean的作用域信息,返回作用域是否是prototype的bean,如果是则返回true
  • boolean isTypeMatch() 该方法用来判断指定name的bean是否满足特定的类型,如果满足则返回true
  • Class<?> getType(String name) 该方法用来获取指定name的bean的类型
  • String[] getAliases(String name) 该方法用来获取指定name的bean在实例池中的所有别名

关于BeanFactory的默认实现我们可以先看一下Spring源码内的ListableBeanFactory接口源码:

public interface ListableBeanFactory extends BeanFactory {

 boolean containsBeanDefinition(String beanName);

 int getBeanDefinitionCount();

 String[] getBeanDefinitionNames();

 String[] getBeanNamesForType(ResolvableType type);

 String[] getBeanNamesForType(@Nullable Class<?> type);

 String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);

 <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;

 <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
   throws BeansException;

 String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);

 Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;
    
 @Nullable
 <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
   throws NoSuchBeanDefinitionException;
}

可以看到ListableBeanFactory接口继承了BeanFactory,同时ListableBeanFactoryBeanFactory进行了拓展,也就是说相对于FactoryBean来说BeanFactory更像一个标准化的IoC实现,而FactoryBean更像是对bean做了一层装饰和代理,通过FactoryBean可以在bean初始化的时候进行一些自定义的配置以及开发,更加的灵活。


用现在比较流行的话来说,BeanFactory提供了操作管理bean的能力,而BeanFactory则为bean提供了装饰以及增强的能力。

ApplicationContext嵌套

Spring支持ApplicationConext的层次结构,因此一个上下文(以及相关的BeanFactory)被认为是另一个上下文的父级,通过ApplicationContexts嵌套,能够将配置分割成不同的文件(是不是有种似曾相识的感觉),在嵌套ApplicationContext实例时,Spring允许子上下文中的bean引用父上下文中的bean,下面是用GenericXmlApplicationContext实现的一个简单的嵌套方法,(GenericXmlApplicationContext类实现了ApplicationContext接口,并提供了可以通过XML文件中定义的配置去启动Spring的ApplicationContext的能力),核心是子ApplicationContextsetParent()方法:

 public static void main(String[] args) {
            GenericXmlApplicationContext parent = new GenericXmlApplicationContext();
            parent.load("classpath:spring/applicationContext.xml");
            parent.refresh();

            GenericXmlApplicationContext child = new GenericXmlApplicationContext();
            child.load("classpath:spring/applicationContext-dao.xml");
            child.setParent(parent);
            child.refresh();

            child.close();
            parent.close();
}

上面模拟了一个在项目中经常会遇到的情况,通常会有一个基础的applicationContext.xml文件然后更细粒度的可能会划分出applicationContext-dao.xml ,applicationContext-service.xml等等配置文件,其实Spring也是通过ApplicationContext嵌套来处理的。


tips:看完Spring关于ApplicationContext嵌套的处理方式,是不是突然回答了为什么有的Spring bean配置只需要挪一下位置就可以生效,换个位置就会报错的问题了?

实例bean作用域(Spring5.x)

  • 单例作用域:默认为单例作用域,每个Spring IoC容器只会创建一个对象。(其实所谓的Spring的单实例缓存池本质上是一个Map,具体分析后面讲DefaultListableBeanFactory的时候再回头看)
  • 原型作用域:当应用程序请求时,Spring将创建一个新的实例
  • 请求作用域:用于Web应用程序,当为Web应用程序使用SpringMVC时,首先针对每个HTTP请求实例化带有请求作用域的bean,然后在请求完成时销毁
  • 会话作用域:用于Web应用程序,当为Web应用程序使用SpringMVC时,首先针对每个HTTP会话实例化带有会话作用域的bean,然后在会话结束时销毁
  • 全局会话作用域:用于基于Portlet的Web应用程序,带有全局会话作用域的bean可以在同一个SpringMVC驱动的门户应用程序的所有Porlet之间共享
  • 线程作用域:当一个新线程请求bean实例时,Spring将创建一个新的bean实例,而对于同一个线程,返回相同的bean实例,线程作用域默认情况下未注册(此处应该大量使用到了ThreadLocal 后面求证)
  • 自定义作用域:可以通过实现org.springframework.beans.factory.config.Scope接口创建自定义作用域,并在Spring配置中注册自定义作用域(如果是XML配置,可以使用org.springframework.bean.factory.config.CustomScopeConfigurer类)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容