Spring3之IOC
Spring是一个轻量的控制反转和面向切面的容器框架。Spring框架所提供的众多功能之所以能成为一个整体,正是因为建立在IoC的基础之上。Spring是为了降低企业应用程序开发的复杂性而创建的,主要优势之一是分层架构,由7个定义良好的模块组成。这个7个模块如下:
组成spring框架的每个模块都可以单独存在,或者与其他一个或多个模块联合使用。整个Spring框架构建在Core核心模块之上,它是整个框架的基础。在该模块中,Spring为我们提供了了一个IoC容器实现,用于帮助我们以依赖注入方式管理对象之间的依赖关系。Spring的AOP框架采用Proxy模式构建,与IoC容器相结合,可以充分显示出Spring AOP的强大威力,Spring在Core核心模块和AOP模块的基础上,为我们提供了完备的数据访问和事务管理的抽象和集成服务。在数据访问支持方面,Spring对JDBCAPI的最佳实践极大地简化了该API的使用,除此之外,Spring框架为各种当前业界流行的ORM产品,比如Hibernate、ibatis、Toplink、JPA等提供了统一的集成支持。Spring框架中的事务管理抽象层是Spring AOP的最佳实践,它直接构建在SpringAOP上为我们提供了编程式事务管理和声明式事务管理的完备支持。Spring还有一套自己的Web MVC框架,职责分明的角色让这套框架看起来十分地“醒目”。spring的Porlet MVC构建在Spring Web MVC之上,延续了Spring Web MVC的一贯风格。整个模块一起就像是一棵树一样。
一、IoC介绍
IoC(Inversion Of Control)即控制反转,是由Martin Fowler在其一篇名为《Inversion of Control Container and the Dependency Injection pattern》的论文中提出的。IoC就是由容器来控制业务对象之间的依赖关系,而非传统方式中的由代码来直接操控。控制反转的本质,是控制权由应用代码中转到了外部容器,控制权的转移即是所谓的反转,其好处是降低了业务对象之间的依赖程度,即实现了解耦。
IoC的实现策略有两种,依赖查找:容器的受控对象通过容器的API来查找自己所依赖的资源和协作对象。这种方式虽然降低了对象间的依赖,但是同时也是用了容器的API,从而造成了我们无法在容器外使用和测试对象。依赖注入(Dependency Injection):对象只提供普通的方法给容器去让它决定依赖关系。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性中的getter和setter,或是构造函数传递给需要的对象。我们将通过属性注入依赖关系的做法称为设值方法注入,将构造子参数传入的做法称为构造子注入。显然依赖注入的好处:查询依赖操作和应用代码分离,受控对象不会使用到容器的特定的API。
二、IoC讲解
我们在开发一个应用系统时,需要开发大量的java类,系统将会通过这些java类之间的相互调用来产生作用。类与类之间的调用关系是系统类之间最直接的关系。我们可将其分为调用者和被调用者。类的调用方法有三种:自己创建、工厂模式、外部注入,可用new、get、set来说明这三个方式。new是自己创建,get是从别人那里取得,set是别人送进来。下面一个学生学习图书的例子,使用接口驱动编程方式,说明这个问题。
1)new-自己创建
2)get-工厂模式
3)set-外部注入
显然第一种方式依赖于被调用者对象,第二种方式依赖于工厂,都存在依赖性。第三种方式完全抛开了依赖关系的枷锁,可以自由地由外部注入。这就是IoC,将对象的创建和获取提取到外部,并由外部容器提供需要的组件。
在IoC模式中,为调用者设置被调用者对象包括三种类型:
type1--接口注入:服务需要实现专门的接口,通过接口,由对象提供这些服务。可以从对象查询依赖性,例如从JNDI或ServiceManager等获得被调用者。
type2--构造注入:使依赖性以构造函数的形式提供,并不以JavaBean属性的形式公开。
type3--设置注入:通过JavaBean的属性(例如setter方法)分配依赖性。
type1的接口注入模式具有侵入性,要求组件必须与特定的接口相关联,而type2和type3的依赖注入均具备无侵入性的特点。Spring 对type2和type3类型的依赖注入机制提供了良好的机制。
三种注入方式比较
接口注入:
接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。
Setter 注入:
对于习惯了传统 javabean 开发的程序员,通过 setter 方法设定依赖关系更加直观。
如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。
如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造子注入模式也不适用。
构造器注入:
在构造期间完成一个完整的、合法的对象。
所有依赖关系在构造函数中集中呈现。
依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对“不变”的稳定状态。
只有组件的创建者关心其内部依赖关系,对调用者而言,该依赖关系处于“黑盒”之中。
总结
Spring使用注入方式,为什么使用注入方式,这系列问题实际归结起来就是一句话,Spring的注入和IoC(本人关于IoC的阐述)反转控制是一回事。
理论上:第三种注入方式(构造函数注入)在符合java使用原则上更加合理,第二种注入方式(setter注入)作为补充。
实际上:我个人认为第二种注入方式(setter注入)可以取得更加直观的效果,在使用工作上有不可比拟的优势,所以setter注入依赖关系应用更加广泛。
三、IoC实现
在Spring中BeanFactory就是IoC容器的代表者,Spring容器中管理的对象就是Bean,这种Bean不同于javaBean,只是spring容器初始化、装配及管理的组件。 Spring IoC容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。一般使用基于xml配置文件进行配置元数据,而且Spring与配置文件是完全解耦的,可以使用其他任何可能的方式进行配置元数据,比如注解、基于java文件的、基于属性文件的配置都可以。
那Spring是如何实例化Bean,如何管理Bean以及Bean之间的依赖关系呢? 如下图:
从上图看,Spring IoC容器首先会通过某种途径加载Configuration MetaData,然后进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了Bean定义必要信息的BeanDefinition中,注册到相应的BeanDefinitionRegistry,当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,容器查看被请求对象是否初始化,并进行注入依赖,当对象装配完毕之后,容器就会返回请求方使用。
在Spring中,BeanFactory是IoC容器的核心接口,负责容纳XML文件所描述的Bean,并对Bean进行管理,包括实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。Spring为我们提供了许多易用的BeanFactory的实现,XmlBeanFactory就是最常用的一个。ApplicationContext接口扩展了BeanFactory,还提供了与Spring AOP集成、国际化处理、事件传播及提供不同层次的context实现 (如针对web应用的WebApplicationContext)。ApplicationContext 增加了更多支持企业级功能支持。其完全继承BeanFactory,因而BeanFactory所具有的语义也适用于ApplicationContext。使用Spring的BeanFactory,有三个步骤如下:
第一步:配置XML元数据;第二步:实例化容器;第三步:调用BeanFactory进行Bean操作。
其中实例化容器的方法有多种:
XmlBeanFactory:BeanFactory的实现,提供基本的IoC容器功能,可以从classpath或文件系统等获取资源;
(1) File file = new File("beans.xml");
Resource resource = new FileSystemResource(file);
BeanFactory beanFactory = new XmlBeanFactory(resource);
(2) Resource resource = new ClassPathResource("beans.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
ClassPathXmlApplicationContext:ApplicationContext的实现,从classpath获取配置文件;
BeanFactory beanFactory = new ClassPathXmlApplicationContext("beans.xml");
FileSystemXmlApplicationContext:ApplicationContext的实现,从文件系统获取配置文件。
BeanFactory beanFactory = new FileSystemXmlApplicationContext("beans.xml");
BeanFactory提供的方法及其简单,有6中方法供客户代码调用:
boolean containsBean(String) :判断是否含有Bean;
Object getBean(String):返回以给定名字注册的Bean实例;
Object getBean(String,Class):返回以给定名称注册的Bean实例,并转换为给定Class类型的实例;
Class getType(String name):返回给定名称的Bean的Class;
boolean isSingleton(String): 判断给定名称的Bean定义是否为Singleton模式,如果找不到则抛出异常;
String[] getAlias(String):返回给定Bean名称的所有别名。
四、IoC配置
1)配置bean元素,下面看bean的定义参数:
id属性,命名bean,是必选的;class属性,指定实例化的类,也是必选的。factory-method属性指定工厂方法,factory-bean属性指定工厂类,scope属性指定bean的作用域,depends-on属性指定依赖Bean,lazy-init属性指定是否延迟初始化bean,默认值是启用延迟初始化,init-method属性指定初始化回调方法,destroy-method属性指定析构回调方法,parent属性指定bean的父类。
其中id必须是唯一的,不可重复,scope的设定有五种选择,Singleton、Prototype、Request、Session、Global Session,分别代表单例模式只允许有一个对象实例、每次请求重新创建一个对象实例、每次http请求创建对象实例、在一个Http Session中,一个Bean定义对应一个实例、在一个全局的HttpSession中,一个Bean定义对应一个实例,后三种作用域都是在基于Web的Spring ApplicationContext情形下有效。depends-on指定在初始化之前必须初始化的bean。
2)依赖注入配置
在Spring中类的实例化有以下几种方式:
i.使用构造器实例化Bean
使用空构造器的情况下 <bean name=" " class=" " />
构造器有参数的情况下 <bean id="" class="" >
<constructor-arg index="0" value="" />
</bean>
其中value表示常量值,也可以指定引用,指定引用使用ref来引用另一个Bean定义。如果id和name同时指定的话,则name表示别名,id是标识符。还可以通过类型,名称指定参数。
ii.使用静态工厂方法实例化Bean
该种配置方法就是指定factory-method方法 ,如下:
<bean id="" class="" factory-method="">
<constructor-arg index="0" value="" />
</bean>
iii.使用实例工厂方法实例化Bean
该种配置下由于不可直接使用类调用方法,因此需要指定工厂类,如下:
<bean id="" class="" factory-bean="" factory-method="" >
<constructor-arg index="0" value="" />
</bean>
在Spring中属性的注入,需要配置的bean中有setter方法,在Spring中可以注入的属性可以是常量,引用,还可以是集合类等。
常量配置: <property name="constVar" value="constVariable" />
引用配置: <property name="refVar" ref="refVariable" /> ,还可以使用ref子标签,idref标签在启动文件时若条件不满足便会抛出异常。引用配置的标签还有<ref local="" />,<ref parent="" />,<ref bean="" />等。
空属性配置:<property name="password" > <null/> </property>
List集合配置: <property name=" " >
<list >
<value>xxx</value>
</list>
</property>
Set集合采用相似的配置。
Map集合配置: <property name="" >
<map>
<entry key="" value="" />
</map>
</property>
Property配置: <property name="" >
<props>
<prop key="" >yyy</prop>
</props>
</property>
3)依赖属性检查
在大规模的应用中,IoC容器中可能声明了几百个或者几千个Bean,这些Bean之间的依赖性往往非常复杂,设置方法注入的不足之一是无法确定一个属性将会被注入。检查所有必要的属性是否已经设置是非常困难的。Spring的依赖检查功能能够帮助检查一个Bean上的所有特定类型属性是否都已经设置。只需在bean的dependency-check属性中指定依赖检查模式就可以了。下面是Spring支持的所有依赖检查模式:
none:不执行依赖检查,任何属性都可以保持未设置状态。
simple:如果任何简单类型(原始和集合类型)的属性未设置,将抛出UnsatisfiedDependencyException异常。
object:如果任何对象类型属性没有设置,将抛出UnsatisfiedDependencyException异常。
all:如果任何类型的属性未设置,将抛出UnsatisfiedDependencyException异常。
默认模式为none,但是可以设置<beans>根元素的default-dependency-check属性来改变,这样将改变IoC容器中的所有Bean的默认依赖检查模式。
如果要对特定的属性进行检查,可使用@Required注解,Spring bean后处理器RequiredAnnotationBeanPostProcessor会检查带有@Required注解的所有Bean属性是否设置。
4)自动装配
Spring中Bean和Bean之间互相访问时,我们需要显示指定引用装配它,使用<ref>设置,然后一个大的项目spring配置文件会十分庞大,手动装配比较麻烦,我们可以使用Spring提供的自动装配功能。自动装配通过配置<bean>标签的“autowire”属性来改变自动装配方式。Spring支持的自动装配模式:
no: 不执行自动装配,必须显示地装配依赖。
byName:对于每个Bean属性,装配一个同名的Bean。
byType:对于每个Bean属性,装配类型与之兼容的一个Bean。如果找到超过一个Bean,将抛出UnsatisfiedDependencyException异常。
Constructor: 对于每个构造程序参数,首先寻找与参数兼容的Bean。然后,选择具有最多匹配参数的构造程序。对于存在歧义的情况,将抛出UnsatisfiedDependencyException异常。
autodetect: 如果找到一个没有参数的默认构造程序,依赖将按照类型自动装配。否则,将由构造程序自动装配。
默认模式是no,但是可以设置<beans>根元素的default-autowire属性修改,但是在实践中,我们建议仅将自动装配应用到组件依赖不复杂的应用程序中。
还可以使用注解方式自动装配Bean,@Autowired和@Resource,需要注册一个AutowiredAnnotationBeanPostProcessor实例,也可以简单地包含<context:annotation-config />元素,这将自动注册一个AutowiredAnnotationBeanPostProcessor实例。@Autowired方式是按类型装配,而@Reource方式是默认按名称装配,当找不到和名称匹配的bean才会按类型装配。@Qualifier(xxx)为autowired方式提供一个按名称装配的候选Bean。
5)加载资源和组件扫描
Spring能够从Classpath中自动检测组件,默认情况下,它能用特定的典型化注解检测所有组件。@Conponent指示Spring管理的基本组件 ,@Repository注解指示持久层的一个DAO组件,@Service注解指示服务层的一个业务组件,@Controller指示表现层的一个控制器组件。有了注解之后,我们需要声明一个xml元素<context:component-scan base-package=" " />要求Spring扫描这些注解。
在Spring中如果我们需要获取IoC容器的内部资源,Spring提供了提供了几种感知接口:
BeanNameAware:IoC容器中配置的实例的Bean名称。
BeanFactoryAware:当前的Bean工厂,通过它可以调用容器的服务。
ApplicationContextAware:当前应用上下文,通过它可以调用容器的服务。
MessageSourceAware:消息资源,通过它可以解析文本消息。
ApplicationEventPublisherAware: 应用事件发布者,通过它可以发布应用事件。
ResourceLoaderAware: 资源装载器,通过它可以加载外部资源。
感知接口中的设置方法在Bean属性设置之后、初始化回调方法调用之前调用,说明如下:
a)构造程序或者工厂方法创建bean实例
b)为Bean属性设置值和Bean引用。
c)调用感知接口中定义的设置方法。
d)调用初始化回调方法。
e) bean可以使用。
f) 容器关闭时,调用析构回调方法。
有时候我们需要从不同位置(例如文件系统、classpath、URL)读取外部资源(如文本文件、XML文件、属性文件或图像文件)。在Spring中资源装载器提供统一的getResource()方法,按照资源路径读取外部资源。从文件系统加载转使用file前缀,如file:c:/shop/banner.txt。从classpath加载资源则使用classpath前缀,如classpath:com/spring/example/banner.txt。从URL加载如 http://xyz/banner.txt。
如果我们想把配置在外部文件的参数配置到bean属性中,如配置数据库时的参数。Spring有一个名为PropertyPlaceholderConfigure的Bean工厂后处理器,用来将部分Bean配置外部化为一个属性文件。在Bean配置文件中使用 ${var} 形式的变量,PropertyPlaceholderConfigure将从属性文件中加载属性并且用它们替代变量。如
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
在使用时如下: <property name="driverClassName">
五、IoC使用
如上面的使用方法,下面提供简单的使用方法:
配置文件: <bean id="personService" class="org.spring.example.PersonService">
使用获取Bean:public class SpringTest {