Sping IOC 学习笔记
IOC容器
org.springframework.beans和org.springframework.context包是springIOC容器的基础。
BeanFactory和 ApplicationContext:BeanFactory提供的高级配置机制,使得管理各种对象成为可能。而ApplicationContext是BeanFactory的扩展,功能进一步增强。简单而言,BeanFactory提供了
配置框架以及基本功能,而ApplicationContext则增加了支持企业核心内容的功能,如事物处理和AOP、国际化等。ApplicationContext完全由BeanFactory扩展而来,因而具备BeanFactory的所有功能。
容器和bean
在spring中,那些组成应用程序的主体以及由SpringICO容器所管理的对象,称为bean。bean的定义和bean之间的依赖关系则通过配置元数据来描述。
SpringIOC容器的实际代表者和核心接口:BeanFactory,它的职责包括:实例化、定位、配置应用程序中的对象以及建立这些对象间的依赖。
最常用的BeanFactory的实现:XmlBeanFactory.
SpringIOC容器读取配置元数据,并通过它对应用中各个对象进行实例化、配置以及组装。
配置元数据的格式:基于xml和基于注解
SpringIOC容器实例化:
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"services.xml","daos.xml"}); BeanFactory factory = context;
Spring配置文件分拆:
<beans> <import resource="services.xml"/> <import resource="resources/messageSource.xml"/> <import resource="/resources/themeSource.xml"/> <bean id="bean1" class="..."/> <bean id="bean2" class="..."/> </beans>所有的import元素必须在<bean/>元素之前完成bean定义的导入。
bean命名:
每个bean都有一个或多个id,这些id在当前IOC容器中必须唯一。如果一个bean有多个id,那么其他的id在本质上将被认为是别名。bean的name并不是必须的,如果没有指定,
那么容器将为其生成一个唯一的name。
别名:
<aliasname="fromName"alias="toName"/>
如果在容器中存在名为fromName的bean定义,在增加别名定义后,也可以用toName来引用。
Bean实例化:
1、构造器实例化;
采用构造器实例化bean时,Spring对class并没有特殊要求,只需要bean的class有默认的空构造器。
<bean id="exampleBean" class="examples.ExampleBean"/>2、静态工厂方法实例化;
采用静态工厂方法实例化时,除了指定class属性外,还需要通过factory-method属性来指定创建bean实例的工厂方法。Spring将调用此方法返回实例化对象。
<bean id="exampleBean" class="examples.ExampleBean2" factory-method="createInstance"/>createInstance()必须是一个static方法。
3、实例工厂方法实例化;
用来实例化的非静态实例工厂方法位于另外一个bean中,容器将调用该bean的工厂方法来创建一个新的bean实例。
<bean id="serviceLocator" class="com.foo.DefaultServiceLocator"></bean> <bean id="exampleBean" factory-bean="serviceLocator" factory-method="createInstance"/>class属性必须为空,factory-bean指定为当前容器中包含的工厂方法的bean的名称,factory-method指向工厂bean中的工厂方法。
依赖注入
依赖注入背后的原理是对象之间的依赖关系。
两种方式:构造器注入和setter注入
构造器注入:通过调用带参数的构造器实现
package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } } <beans> <bean name="foo" class="x.y.Foo"> <constructor-arg> <bean class="x.y.Bar"/> </constructor-arg> <constructor-arg> <bean class="x.y.Baz"/> </constructor-arg> </bean> </beans>
因为此实例中参数类型是非常明确的,所有在配置中没有指定构造参数顺序和类型也会工作,但是当参数类型相同或者使用简单的参数类型时,Spring将无法根据参数类型进行匹配。
此时可以使用构造器参数类型匹配或构造器参数索引。
构造器参数类型匹配:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
构造器参数索引
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
注意,index是从0开始的。
setter方法注入:
<beans> <bean name="foo" class="x.y.Foo"> <property name="age" value="24" /> <property name="anotherBean" ref="anotherBeanName" /> </bean> </beans>
Lookup方法注入:
此方法注入是用于依赖bean和协作bean的作用域不同的情况,比如一个singletonbean需要引用一个prototypebean。
public abstract class CommandManager { public Object process(Object commandState) { // grab a new instance of the appropriate Command interface Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } // okay... but where is the implementation of this method? protected abstract Command createCommand(); }
此实例中,CommandManager是singleton,在process中需要使用一个prototype的Commandbean。我们将CommandManager声明为abstract,并创建一个abstract方法用于创建prototype的Command,但是不实现;
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"> <lookup-method name="createCommand" bean="command"/> </bean>
bean依赖关系处理:
一、根据定义bean的配置文件创建并初始化BeanFacotry实例;
二、当这些bean被实际创建时,他相关的依赖也将提供给该bean;
三、每个属性或构造器参数既可以是一个实际的值,也可以是对容器中另外一个bean的引用。
四。每个指定的属性或者构造器参数值必须能够被转换成特定格式或者构造器参数所需的类型
在默认情况下,ApplicationContext实现中的bean采用的是提前实例化的singleton模式,这样做将带来时间和内存的开销,但是好处是ApplicationContext被加载的时候可以尽早的发现一些配置问题。不过用户也可以根据
需要采用延迟实例化的方式。
循环依赖:当一个类A,需要通过构造器注入类B,而类B又需要通过构造器注入类A,这样springioc容器将检测出循环引用,并抛出BeanCurrentlyInCreationException异常。
静态工厂方法注入:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> <constructor-arg ref="anotherExampleBean"/> <constructor-arg ref="yetAnotherBean"/> <constructor-arg value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
传给static工厂方法的参数由constructor-arg元素提供,这与使用构造器注入时完全一样
idref元素
将容器内其他bean的id传给<constructor-arg/>或<prperty/>元素,同时提供错误验证功能。
<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean" /> </property> </bean>
只是传了bean的id值,并不是bean的引用,这里等同于:
<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <value>theTargetBean</value> </property> </bean>
但是这里没有错误验证功能,也就是说采用idref的时候,会验证theTargetBean是否存在。
使用idref时,如果引用的bean在同一个xml配置文件中,也可以用
<idreflocal=""/>
引用其他bean
ref元素
<refbean="someBean"/>
引用同一容器或者父容器中的任何bean,无论是否在同一个xml配置文件中。bean的值既可以是bean的id值,也可以是name的值。
<reflocal="someBean"/>
如果所引用的bean在同一个xml配置文件中,可以用这种方式,但是local的属性值必须是目标bean的id值。
<refparent="someBean"/>
如果引用的目标bean在父容器中,可以使用此方式,parent属性值既可以是目标bean的id值,也可以是name属性值,而起目标bean必须在当前容器的父容器中。
内部bean
指在一个bean的<property/>或<constructor-arg/>元素中使用<bean/>元素定义的bean。内部bean定义不需要id和name属性,即使指定也会被容器忽略。。
<bean id="outer" class="..."> <property name="target"> <bean class="com.example.Person"> <!-- this is the inner bean --> <property name="name" value="Fiona Apple"/> <property name="age" value="25"/> </bean> </property> </bean>
内部bean总是匿名的且他们总是prototype模式的,同时将内部bean注入到包含该内部bean之外的bean是不可能的
集合的合并:
从2.0开始,SpringIOC容器将支持集合的合并
<beans> <bean id="parent" abstract="true" class="example.ComplexObject"> <property name="adminEmails"> <props> <prop key="administrator">[email protected]</prop> <prop key="support">[email protected]</prop> </props> </property> </bean> <bean id="child" parent="parent"> <property name="adminEmails"> <!-- the merge is specified on the *child* collection definition --> <props merge="true"> <prop key="sales">[email protected]</prop> <prop key="support">[email protected]</prop> </props> </property> </bean> <beans>
此实例中,childbean的adminEmails属性的<props/>元素使用了merge=true属性,当childbean被容器实际解析以及实例化时,其adminEmails将与父集合的adminEmails属性进行合并.
合并功能仅在spring2.0以及后版本中可用,不同的集合类型是不能合并的,比如Map类型的不能和list合并。
<null/>元素
此元素用于处理null值,Spring会把属性的空参数当做空字符串处理;
<bean class="ExampleBean"> <property name="email"> <value/> </property> </bean>
这等同于java代码:example.setEmail("");
<bean class="ExampleBean"> <property name="email"> <null /> </property> </bean>
这等同于java代码:example.setEmail(null);
depends-on:
多数情况下,一个bean对另一个bean的依赖最简单的做法就是将一个bean设置为另外一个bean的属性。在xml配置文件中最常用的就是使用<ref/>元素,但是
在少数情况下,有时候bean之间的依赖关系并不是那么直接。depends-on属性可以用于当前bean初始化之前显示的强制一个或者多个bean初始化。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />
如果要表达多个bean的依赖,可以再'depends-on'中指定多个bean名字,用逗号、空格、分号等隔开。
它同时也可以用来指定相应的销毁时的依赖(针对singletonbean),指定的bean会在当前bean销毁之前被销毁。
延迟初始化bean
如果你不想让一个singletonbean在ApplicationContext初始化时提前实例化,那么可以将bean设置为延迟实例化
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
如果一个bean设置为延迟实例化,而另一个非延迟实例化的singletonbean依赖它,那当IOC容器实例化singletonbean时,会确保它依赖的延迟bean也实例化
在容器层次上控制延迟初始化:
<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>
autowire自动装配协作者:
SpringIOC可以自动装配相互协作bean之间的关联关系
byName根据属性的名字自动装配,此选项检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配;
byType如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配。如果存在多个该类型的bean,将抛出异常;如果没有找到匹配的bean,
则什么事都不发生,如果希望抛出异常,可以设置dependency-check="objects";
constructor与byType类似,但是它应用于构造器参数,如果没有找到匹配的bean,那么会抛出异常;
autodetect通过bean的自省机制来决定使用construtor还是byType,如果发现默认的构造器,则使用byType;
如果使用setter和构造器注入依赖的话,那么将总是覆盖自动装配,而起目前也不支持简单类型的自动装配,简单类型包括基本类型、String,Class以及简单类型的数组。
优点:
减少配置的数量;
使配置与Java代码同步更新,比如,如果你需要给一个java类增加一个依赖,那么该依赖将会被自动实现而不需要修改配置;
缺点:
如果装配不明确,将出现难以预料的结果;
如果需要根据配置文件生成文档的话,自动装配将会没法生成依赖信息,所以spring所管理的对象之间的关联关系不能清晰文档化;
bean定义中设置primary属性为true,将该bean设置为首选自动装配bean;
bean定义中设置autowire-candidate属性设置为false,此bean将排除在自动装配之外,容器在查找自动装配对象时将不考虑此bean;
<beans/>元素的default-autowire-candidates属性用来对bean名字进行模式匹配,如自动匹配限制在名字以Respository结尾的bean,则可以设置其值为*Repository,
但是如果某个bean满足名字匹配模式,而它的autowire-candidate属性设置成了false,则不会被装配。
bean作用域
Singleton作用域:
当一个bean的作用域为singleton,那么SpringIOC容器中只存在一个共享的bean实例。
和GOF模式中的Singleton模式完全不同,GOF模式中的singleton对象范围是指在一个ClassLoader中指定class创建的实例有且仅有一个。
Singleton作用域是Spring中的缺省作用域;
Prototype作用域:
prototype作用域的bean会导致在每次对该bean请求时,都会创建一个新的bean实例;
一般来说,对有状态的bean应该使用prototype作用域,而无状态的bean则应该使用singleton作用域;
配置实例:<beanid="accountService"class="com.foo.DefaultAccountService"scope="prototype"/>
Spring不能对一个prototypebean的整个生命周期负责,容器在初始化、配置、装配完一个prototype实例后,将它交给客户端,虽然就对该bean不闻不问了。
不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法,但是prototype例外。
如果一个singleton类型的beanA需要引用另一个prototype类型的beanB,对于beanA来说,容器只会创建一次,这样就没法在需要的时候每次让容器为beanA提供一个新的beanB实例,这个时候需要采用方法注入;
Request作用域:
<beanid="loginAction"class="com.foo.LoginAction"scope="request"/>
针对每次HTTP请求,Spring容器会根据loginActionbean定义创建一个全新的LoginAction实例,且该实例仅在当前HTTPrequest内有效。
Session作用域:
<beanid="userPreferences"class="com.foo.UserPreferences"scope="session"/>
针对某个HTTPSession,Spring容器会根据userPreferencesbean定义创建一个全新的实例,且该实例仅在当前HTTPSession内有效。
GlobalSession作用域:
<beanid="userPreferences"class="com.foo.UserPreferences"scope="globalSession"/>
该作用越类似于HTTPSession作用域,但是它仅仅在基于portlet的web应用中才有意义。
作用域bean与依赖:
request、session、globalSession和自定义作用域需要<aop:scoped-proxy/>元素;
<beanid="userPreferences"class="com.foo.UserPreferences"scope="session"/>
<beanid="userManager"class="com.foo.UserManager">
<propertyname="userPreferences"ref="userPreferences"/>
</bean>
此实例中,singletonbeanuserManager中注入了一个session作用域的userPreferencesbean,这意味着userManager在理论上只会操作同一个userPreferences对象。
所以此时需要使用AOP代理
<beanid="userPreferences"class="com.foo.UserPreferences"scope="session">
<aop:scoped-proxy/>
</bean>
<beanid="userManager"class="com.foo.UserManager">
<propertyname="userPreferences"ref="userPreferences"/>
</bean>
此时注入的对象其实是实现了和UserPreferences类一样的公共接口,即就是代理对象,并且不论底层选择了何种作用域,容器都能获取到真正的UserPreferences对象
但是<aop:scoped-proxy/>不能和singleton或prototype的bean一起使用,所以,如果将一个prototype类型的bean注入到singleton中时需要使用lookup方法注入法
默认情况下,当一个bean有<aop:scope-proxy/>标记时,Spring容器将为它创建一个基于CGLIB的类代理,此时需要将CGLIB库加到classpath中。CGLIB代理仅仅拦截public方法的调用;
如果想选择JDK代理,则需要将<aop:scoped-proxy/>的属性proxy-target-class设置为false,但是此时类必须要实现至少一个接口。
自定义作用域:
自动以作用域需要实现org.springframework.beans.factory.config.Scope接口
此接口提供四个方法来处理获取对象,移除对象和必要的时候销毁对象:
一、Objectget(Stringname,ObjectFactoryobjectFactory)从作用域中获取对象;
二、remove(Stringname)从作用域中移除对象;
三、registerDestructionCallback(Stringname,RunnabledestructionCallback)注册作用域析构的回调方法,当作用域销毁或作用域中的某个对象销毁时会执行;
四、getConversationId()处理作用域的会话标识。