第三章-在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中会同时存在BeanFactory和FactoryBean这两个接口,不理解的地方就在于现有的水平下想不到当时设计这两个接口的人是基于什么样的场景和什么样的问题来进行设计的,所以来稍微探索求证一下:
首先是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容器的一个基础组件,进一步的实现例如ListableBeanFactory,ConfigurableBeanFactory可以对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,同时ListableBeanFactory对BeanFactory进行了拓展,也就是说相对于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的能力),核心是子ApplicationContext的setParent()方法:
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类)
网友评论