Fork me on GitHub

Spring概述与IoC容器

Spring框架概述

Spring是于2003年兴起的一个轻量级的Java开发框架
Spring框架理念:基于POJO(Plain Old Java Object,简单Java对象)的轻量级开发理念
Spring框架总体结构:

组成整个Spring框架的各种服务实现被划分到了多个相互独立却又相互依赖的模块当中。这些模块组成了Spring框架的核心骨架
ORM(Object Relational Mapping对象关系映射)产品:Hibernate/iBATIS/Toplink/JPA

Core模块

整个Spring框架构建在Core核心模块之上,它是整个框架的基础。在该模块中,Spring为我们提供了一个IoC容器实现,用于帮助我们以依赖注入的方式管理对象之间的依赖关系。Core核心模块还包括框架内部使用的各种工具类

Spring的IoC容器

SpringIoC容器是整个Spring框架的核心和基础

IoC的基本概念

IOC(Inversion of Control控制反转)概念:程序中对象的获取方式发生反转,由最初的new方式创建,转变为由第三方框架创建、注入。
IOC控制由DI(依赖注入)方式实现。DI是实现IOC的主要技术途径。

三种依赖注入的方式

构造方法(构造器)注入

构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象。
IoC Service Provider会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。

1
2
3
4
<!-- 构造器注入 -->
<bean id="userDAO" class="dao.OracleDriver">
<constructor-argindex="0" ref="dataSource"/>
</bean>

setter 方法注入

对于JavaBean对象来说,通常会通过setXXX()getXXX()方法来访问对应属性。这些setXXX()方法统称为setter方法,getXXX()称为getter方法。
当前对象只要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。

1
2
3
4
5
6
7
8
9
<!-- setter注入 -->
<!-- 使用<property>标签为基于setter注入的<bean>元素内 -->
<bean id="dataSource" class="dao.JDBCDataSource">
<!-- 注入属性 -->
<property name="driver" value="oracle.jdbc.OracleDriver"/>
<!-- 注入Bean对象 -->
<property name="userDAO"><bean class="com.dao.OracleYserDao"></bean></property>
<property name="pwd" value="123456"/>
</bean>

接口注入

被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。

三种注入方式的比较

  • 接口注入:不提倡,它强制被注入对象实现不必要的接口,带有侵入性。
  • 构造方法注入:优点是构造完成后就可以马上进入就绪状态;缺点是依赖对象较多时,构造方法的参数列表比较长,通过反射构造对象时,对相同类型的参数处理比较困难,维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。
  • setter方法注入:优点是setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。缺点是对象无法在构造完成后马上进入就绪状态。

经过对比,setter方法注入是相对最好的。

IoC的好处

不会对业务对象构成很强的侵入性,使用IoC后对象具有更好的可测试性、可重用性和可扩展性等。
IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式!

IoC Service Provider

IoC Service Provider是一个抽象的概念,可以指代任何将IoC场景中的业务对象绑定到一起的实现方式。它可以是一段代码,也可以是一组相关的类,甚至可以是比较通用的IoC框架或者IoC容器实现。
SpringIoC容器就是一个提供依赖注入服务的IoC Service Provider

IoC Service Provider 的职责

业务对象的构建管理

IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做。所以,IoC Service Provider需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。
客户端对象:这里指代使用某个对象或者某种服务的对象。如果对象A需要引用对象B,那么A就是B的客户端对象,而不管A处于Service层还是数据访问层。

业务对象间的依赖绑定

最艰巨也是最重要的职责;
IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

对象间依赖关系管理

直接编码方式

当前大部分的IoC容器都应该支持直接编码方式,比如PicoContainer、Spring、Avalon等

1
2
3
4
5
6
IoContainer container = ... ;
container.register(FXNewsProvider.class,new FXNewsProvider());
container.register(IFXNewsListener.class,new DowJonesNewsListener());
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newProvider.getAndPersistNews();

配置文件方式

1
2
3
4
5
6
7
8
9
<bean id="newsProvider" class="..FXNewsProvider">
<property name="newsListener"><ref bean="djNewsListener"/></property>
<property name="newPersistener"><ref bean="djNewsPersister"/></property>
</bean>
<bean id="djNewsListener" class="..impl.DowJonesNewsListener">
</bean>
<bean id="djNewsPersister" class="..impl.DowJonesNewsPersister">
</bean>

元数据(注解)方式

元数据是关于数据的数据。在编程语言上下文中,元数据是添加到程序元素如方法、字段、类和包上的额外信息。
注解最终也要通过代码处理来确定最终的注入关系,从这点儿来说,注解方式可以算作编码方式的一种特殊情况。

Spring的IoC容器类型

Spring提供了两种容器类型:BeanFactoryApplicationContext

BeanFactory

基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以相对来说,容器启动初期速度较快。

ApplicationContext

ApplicationContextBeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。因为在启动时就完成所有初始化,容器启动时间较BeanFactory更长一些。

ApplicationContext间接继承自BeanFactory,所以说它是构建于BeanFactory之上的IoC容器。

BeanFactory

Spring 的 IoC 容器之 BeanFactory

BeanFactory 作用

BeanFactory,顾名思义,就是生产Bean的工厂。Spring框架提倡使用POJO(简单Java对象,就是普通的JavaBeans),把每个业务对象看做一个JavaBean对象。
BeanFactory可以完成作为IoC Service Provider的所有职责,包括业务对象的注册对象间依赖关系的绑定

BeanFactory 的对象注册与依赖绑定方式

直接编码方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void main(String[] args){
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = (BeanFactory)bindViaCode(beanRegistry);
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaCode(BeanDefinitionRegistry registry){
AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class,true);
AbstractBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class,true);
AbstractBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class,true);
//将bean定义注册到容器中
registry.registerBeanDefinition("djNewsProvider",newsProvider);
registry.registerBeanDefinition("djListener",newsListener);
registry.registerBeanDefinition("djPersister",newsPersister);
//指定依赖关系
// 1.可以通过构造方法注入方式
ConstructorArgumentValues argValues = new ConstructorArgumentValues();
argValues.addIndexedArgumentValue(0,newsListener);
argValues.addIndexedArgumentValue(1,newsPersister);
newsProvider.setConstructorArgumentValues(argValues);
// 2.或者通过setter方法注入方式
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue(new ropertyValue("newsListener",newsListener));
propertyValues.addPropertyValue(new ropertyValue("newsPersistener",newsPersister));
newsProvider.setPropertyValues(propertyValues);
//绑定完成
return (BeanFactory)registry;
  • main方法中,首先构造一个DefaultListableBeanFactory作为BeanDefinitionRegistry,然后将其交给bindViaCode方法进行具体的对象注册和相关依赖管理,然后通过bindViaCode返回的 BeanFactory取得需要的对象,最后执行相应逻辑。
  • bindViaCode方法中,首先针对相应的业务对象构造与其相对应的BeanDefinition,使用了RootBeanDefinition作为BeanDefinition的实现类。构造完成后,将这些BeanDefinition注册到通过方法参数传进来的BeanDefinitionRegistry中。因为我们的FXNewsProvider是采用的构造方法注入,所以,需要通过ConstructorArgumentValues为其注入相关依赖。在这里为了同时说明setter方法注入,也同时展示了在Spring中如何使用代码实现setter方法注入。如果要运行这段代码,需要把setter方法注入的4行代码注释掉。最后,以BeanFactory的形式返回已经注册并绑定了所有相关业务对象的BeanDefinitionRegistry实例。

外部配置文件方式

SpringIoC容器支持两种配置文件格式:

  • Properties文件格式
  • XML文件格式
Properties配置格式的加载

Spring提供了org.springframework.beans.factory.support.PropertiesBeanDefinitionReader类用于Properties格式配置文件的加载,所以,我们不用自己去实现BeanDefinitionReader,只要根据该类的读取规则,提供相应的配置文件即可。

Properties格式表达的依赖注入配置内容

1
2
3
4
5
6
7
8
9
10
11
djNewsProvider.(class)=..FXNewsProvider
#---------------通过构造方法注入的时候--------
djNewsProvider.$0(ref)=djListener
djNewsProvider.$1(ref)=djPersister
#---------------通过setter方法注入的时候------
# djNewsProvider.newsListener(ref)=djListener
# djNewsProvider.newPersistener(ref)=djPersister
djListener.(class)=..impl.DowJonesNewsListener
djPersister.(class)=..impl.DowJonesNewsPersister

  • djNewsProvider作为beanName,后面通过.(class)表明对应的实现类是什么,实际上使用djNewsProvider.class=…的形式也是可以的,但是Spring 1.2.6之后不再提倡使用,而提倡使用.(class)的形式。其他两个类的注册,djListenerdjPersister,也是相同的道理。
  • 通过在表示beanName的名称后添加.$[number]后缀的形式,来表示当前beanName对应的对象需要通过构造方法注入的方式注入相应依赖对象。在这里,我们分别将构造方法的第一个参数和第二个参数对应到djListenerdjPersister。需要注意的一点,就是$0和$1后面的(ref),(ref)用来表示所依赖的是引用对象,而不是普通的类型。如果不加(ref),PropertiesBeanDefinitionReader会将djListenerdjPersister作为简单的String类型进行注入,异常自然不可避免。
  • setter方法注入使用相应的属性名称来指定注入。newsListenernewPersistener恰好就是我们的FXNewsProvider类中所声明的属性名称。同时不要忘了(ref)的作用。
  • 构造方法注入和setter方法注入差异:构造方法注入无法通过参数名称来标识注入的确切位置,而setter方法注入则可以通过属性名称来明确标识注入。

加载Properties配置的BeanFactory的使用演示

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args){
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = (BeanFactory)bindViaPropertiesFile(beanRegistry);
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("jdNewsProvider");
newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry){
PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(registry);
reader.loadBeanDefinitions("classpath:.../../binding-config.properties");
return (BeanFactory)registry;
}

XML 配置格式的加载

XML配置格式是Spring支持最完整,功能最强大的表达方式。
XML配置文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml veersion="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="djNewsProvider" class="..FXNewsProvider">
<constructor-arg index="0">
<ref bean="djNewsListener"/>
</constructor-arg>
<constructor-arg index="1">
<ref bean="djNewsPersister"/>
</constructor-arg>
</bean>
<bean id="djNewsListener" class="..impl.DowJonesNewsListener">
</bean>
<bean id="djNewsPersister" class="..impl.DowJonesNewsPersister">
</bean>
</beans>

加载XML配置文件的BeanFactory的使用演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args){
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = (BeanFactory)bindViaXMLFile(beanRegistry);
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaXMLFile(BeanDefinitionRegistry registry){
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
reader.loadBeanDefinitions("classpath:../news-config.xml");
return (BeanFactory)registry;
//或者直接
//return new XmlBeanFactory(new ClassPathResource("../news-config.xml"));
}

注解方式
  • Spring 2.5发布之前,Spring框架并没有正式支持基于注解方式的依赖注入;
  • Spring 2.5发布的基于注解的依赖注入方式,如果不使用classpath-scanning功能的话,依然部分依赖于”基于XML配置文件”的依赖注入方式。
  • 注解是Java5之后才引入的,所以,一下内容只适用于引用程序使用了Spring 2.5以及Java 5或者更高版本情况下。
    使用指定注解标注的相关类
    1
    2
    3
    4
    5
    @Component
    public class FXNewsProvider{
    @Autowired
    private IFXNewsListener newsListener;
    }
1
2
//Spring.xml里面需要配置 组件扫描
<context:component-scan base-package="cn.base.package"/>

会到指定的包(package)下面扫描标注有 @Component 的类,如果找到,则将他们添加到容器进行管理,并根据它们所标注的 @Autowired 为这些类注入符合条件的依赖对象。
注解完成后,加载配置并演示

1
2
3
4
public static void main(String[] args){
ApplicationContext ctx = new ClassPathXmlApplicationContext("配置文件路径");
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("FXNewsProvider");
newsProvider.getAndPersistNews();

BeanFactory的XML配置文件

每一个对象在XML中的映射自然而然地对应一个叫做的元素

是XML配置文件中最顶层的元素,它下面可以包含0或者1个和多个以及或者

拥有相应的属性(attribute)对所辖的进行统一的默认行为设置,包括:

  • default-lazy-init 其值可以指定为true或者false,默认为false。用来标志是否对所有的进行延迟初始化。
  • default-autowire 可以取值为no、byName、byType、constructor以及autodetect。默认值为no,如果使用自动绑定的话,用来标志全体bean使用哪一种默认绑定方式。


可以通过在配置的文件中指定一些描述性的信息。通常情况下,该元素是省略的。


在主要的配置文件中通过元素对其所依赖的配置文件进行引用。比如:如果A.xml中的定义可能依赖B.xml中的某些定义,那么就可以在A.xml中使用将B.xml引入到A.xml,以类似于的形式。


为某些起一些”外号”(别名),两个名称都可以引用这个

scope(作用域)

singleton

  • 对象实例数量:singleton类型的bean定义,在一个容器中只存在一个共享实例。所有对该类型bean的依赖都引用这一单一实例。
  • 对象存活时间:singleton类型bean定义,从容器启动,到它第一次被请求而实例化开始,只要容器不销毁或者退出,该类型bean的单一实例就会一直存活。

通常如果不指定bean的scope,singleton便是容器默认的scope;下面三种配置达到的效果是一样的:

1
2
3
<bean id="mockObject" class="...MockBusinessObject"/>
<bean id="mockObject" class="...MockBusinessObject" singleton="true"/>
<bean id="mockObject" class="...MockBusinessObject" scope="singleton"/>

prototype

针对声明为拥有prototype scope的bean定义,容器在接到该类型对象的请求的时候,会每次都重新生成一个新的对象实例给请求方。

IoC容器实现两个阶段


Spring的IoC容器的实现可以划分为两个阶段:

  • 容器启动阶段
  • Bean实例化阶段

容器启动阶段

容器启动伊始,首先会通过某种途径加载Configuration MetaData。除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。

Bean实例化阶段

当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。

容器启动之后,并不会马上就实例化相应的bean定义。容器现在仅仅拥有所有对象的BeanDefinition来保存实例化阶段将要用的必要信息。只有当请求方通过BeanFactory的getBean()方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。

  • 对于BeanFactory来说,对象实例化默认采用延迟初始化。通常情况下,当对象A被请求而需要第一次实例化的时候,如果它所依赖的对象B之前同样没有被实例化,那么容器会先实例化对象A所依赖的对象。这种情况是容器内部调用getBean(),对于本次请求的请求方是隐式的。
  • ApplicationContext启动后会实例化所有的bean定义

第二次被调用则会直接返回容器缓存的第一次实例化完的对象实例(prototype类型bean除外)。

ApplicationContext

基于注解的依赖注入

@Autowired

将自动绑定的标志用注解来表示时,也就得到了基于注解的依赖注入,或者更确切地称为基于注解的自动绑定。
@Autowired 是基于注解的依赖注入的核心注解,它的存在可以让容器知道需要为当前类注入哪些依赖。
与原有的byType类型的自动绑定方式类似, @Autowired 也是按照类型匹配进行依赖注入的,只不过,它要比byType更加灵活,也更加强大。 @Autowired 可以标注于类定义的多个位置,包括如下几个:

  • 域(Filed)或者说属性(Property):不管它们声明的访问限制符是private、protected还是public,只要标注了 @Autowired ,它们所需要的依赖注入需求就都能够被满足。
  • 构造方法定义(Constructor):标注于类的构造方法之上的 @Autowired,相当于抢夺了原有自动绑定功能中”constructor”方式的权利,它将根据构造方法参数类型,来决定将什么样的依赖对象注入给当前对象。
  • 方法定义(Method): @Autowired 不仅可以标注于传统的setter方法之上,而且还可以标注于任意名称的方法定义之上,只要该方法定义了需要被注入的参数。
1
2
3
4
5
6
7
8
9
//属性使用 @Autowired
public class FXNewsProvider
{
@Autowired
private IFXNewsListener newsListener;
@Autowired
private IFXNewsPersister newPersistener;
...
}
1
2
3
4
5
6
7
//构造方法使用 @Autowired
@Autowired
public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister)
{
this.newsListener = newsListner;
this.newPersistener = newsPersister;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 标注于方法之上的 @Autowired 代码示例
public class FXNewsProvider
{
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
@Autowired
public void setUp(IFXNewsListener newsListener,IFXNewsPersister newPersistener)
{
this.newsListener = newsListener;
this.newPersistener = newPersistener;
}
...
}

classpath-scanning

classpath-scanning 功能可以从某一顶层包(base package) 开始扫描。当扫描到某个类标注了相应的注解之后,就会提取该类的相关信息,构建对应的BeanDefinition,然后把构建完的BeanDefinition注册到容器中。

1
2
3
<context:component-scan base-package="org.yhpackage"/>
//如果要扫描的类定义存在于不同的源码包下面,也可以为base-package指定多个以逗号分隔的扫描路径。
<context:component-scan base-package="org.yhpackage,org.yhpackage1,org.yhpackage2"/>

默认扫描的注解类型是 @Component。不过,在 @Component语义基础上细化后的 @Repository、 @Service和 @Controller也同样可以获得的青睐。 @Component的语义更广、更宽泛,而 @Repository、 @Service和 @Controller的语义则更具体。所以,同样对于服务层的类定义来说,使用 @Service 标注它,要比使用 @Component更为确切,对于其他两种注解也是同样的道理。

在扫描相关类定义并将它们添加到容器的时候,会通过Bean命名规范,来生成那些添加到容器的bean定义的名称(beanName)。

「真诚赞赏,手留余香」