典型的企业应用程序不可能由单个对象(在spring中,也可称之bean)组成,再简单的应用也是由几个对象相互配合工作的,这一章主要介绍bean的定义以及bean之间的相互协作。
spring中的依赖注入(Dependency injection (DI))主要有两种形式:构造器注入和setter方法注入。
基于构造函数的方式有其自己的优势,它能够明确地创建出带有特定构造参数的对象,另外它对于创建一些在类中不需要经常变化的域有明显的优势。如果用setter方法来做这种事情会显得很不协调,但通常可以采用init的方法在创建时就将其初始化。当然对于某些类可能有很多的域,构造函数不可能包含所有的情况,而且其中能够包含的构造参数也是有限的,此时Setter方法注入即可以发挥其余地。
以下示例是一个只能通过构造器注入的类:
public class SimpleMovieLister { // the SimpleMovieLister has a dependency on a MovieFinder private MovieFinder movieFinder; // a constructor so that the Spring container can 'inject' a MovieFinder public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually 'uses' the injected MovieFinder is omitted... }
package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } }
没有潜在的歧义存在,假设Bar和Baz两个类不想关
<span style="color:#333333;"><beans> <bean id="foo" class="x.y.Foo"> <</span><span style="color:#ff0000;">constructor-arg</span><span style="color:#333333;"> ref="bar"/> <constructor-arg ref="baz"/> </bean> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> </beans></span>
package examples; public class ExampleBean { // No. of years to the calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
在前面这个示例中,使用type属性,容器可以进行简单的类型匹配:
<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>
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateanswer" value="42"/> </bean>
注意:Keep in mind that to make this work out of the box your code must be compiled with the debug flag enabled so that Spring can look up the parameter name from the constructor. If you can't compile your code with debug flag (or don't want to) you can use @ConstructorProperties
JDK annotation to explicitly name your constructor arguments. The sample class would then have to look as follows:
package examples; public class ExampleBean { // Fields omitted @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
以下是一个简单的只能用setter方法进行注入的例子:
public class SimpleMovieLister { // the SimpleMovieLister has a dependency on the MovieFinder private MovieFinder movieFinder; // a setter method so that the Spring container can 'inject' a MovieFinder public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually 'uses' the injected MovieFinder is omitted... }
ApplicationContext
支持setter注入和构造器注入,也支持在已存在构造器注入的情况下继续进行setter注入。
以下例子为setter注入的xml配置文件:
<bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested <ref/> element --> <property name="beanOne"><ref bean="anotherExampleBean"/></property> <!-- setter injection using the neater 'ref' attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; } }
<bean id="exampleBean" class="examples.ExampleBean"> <!-- constructor injection using the nested <ref/> element --> <constructor-arg> <ref bean="anotherExampleBean"/> </constructor-arg> <!-- constructor injection using the neater 'ref' attribute --> <constructor-arg ref="yetAnotherBean"/> <constructor-arg type="int" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public ExampleBean( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; } }
<bean id="exampleBean" class="examples.ExampleBean" <span style="color:#ff0000;">factory-method</span>="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"/>
public class ExampleBean { // a private constructor private ExampleBean(...) { ... } // a static factory method; the arguments to this method can be // considered the dependencies of the bean that is returned, // regardless of how those arguments are actually used. public static ExampleBean createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; } }创建具体Bean实例的是静态方法,将其纳入Spring容器来管理,需要通过factory-method方法指定静态方法名称。
在增加一个,使用工厂方法实例化一个bean。
与静态方法不同之处在于,工厂方法不需要是静态的,但是在spring的配置文件中需要配置更多的内容。如下:
<bean id = "personServiceBeanFactory" class = "factory.PersonServiceBeanFactory"></bean> <bean id = "factory" factory-bean="personServiceBeanFactory" factory-method="createPersonServiceBean"></bean>
package factory; public interface PersonService { public void save(); }
package factory; public class PersonServiceBean implements PersonService{ public void save() { System.out.println("my name is zfq"); } }
package factory; public class PersonServiceBeanFactory { public PersonServiceBean createPersonServiceBean() { return new PersonServiceBean(); } }
package factory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Realize { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("com/springinaction/springidol/factory.xml"); PersonServiceBean factory = (PersonServiceBean) context.getBean("factory"); System.out.println(factory); } }
在上面的内容中,我们可以通过引用其他的bean来定义bean的properties和constructor参数,或直接使用values。
Strings
, and so on)在<property/>中的value属性作为properties或constructor的具体参数值,使人较易读懂,如下:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <!-- results in a setDriverClassName(String) call --> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mydb"/> <property name="username" value="root"/> <property name="password" value="masterkaoli"/> </bean>在xml文件中使用p-namespace使得配置更加简洁明了:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/mydb" p:username="root" p:password="masterkaoli"/> </beans>选择<property> 还是命名空间p取决与你自己,两者是等价的。然而,错误会在运行时被发现而不是在编译时,除非使用类似intelliJ IDEA或SpringSource Tool Suite这写工具。
idref元素用来讲容器中其他bean的id传给<constructor-arg/> 或<property/>元素,同时提供错误验证功能。
<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean" /> </property> </bean>
<bean id="theTargetBean" class="..." /> <bean id="client" class="..."> <property name="targetName" value="theTargetBean" /> </bean>
在<constructor-arg>或<property>中,ref属性可以设置当前的bean使用引用的bean,当所有的参数最终都是其他对象的引用,范围和验证取决于你应用其他对象时是使用bean,regardless或是parent属性。
bean属性的值可能与目标bean的id属性值相同,或者与目标bean中的某个name属性值相同
<ref bean="someBean"/>
<ref local="someBean"/>parent只能定位于当前容器的父容器中定义的对象的引用。parent属性的值可能与目标bean的id属性的值相同,或者与目标bean的name属性中的某个值相同
<!-- in the parent context --> <bean id="accountService" class="com.foo.SimpleAccountService"> <!-- insert dependencies as required as here --> </bean>
<!-- in the child (descendant) context --> <bean id="accountService" <-- bean name is the same as the parent bean --> class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref parent="accountService"/> <!-- notice how we refer to the parent bean --> </property> <!-- insert other configuration and dependencies as required here --> </bean>
内部bean是把<bean/>直接写在<constructor-arg/>或<property/>下。
<bean id="outer" class="..."> <!-- instead of using a reference to a target bean, simply define the target bean inline --> <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的定义不需要定义id或name属性,容器会自动忽略这些值,他也会忽略scope标记,内部bean通常是匿名的,且他们的scope通常是prototypes类型, It is not possible to inject inner beans into collaborating beans other than into the enclosing bean.(??)
java自带了很多集合类,spring也提供了相应的集合装配元素:<list/>,<map/>,<set/>,<props>
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setAdminEmails(java.util.Properties) call --> <property name="adminEmails"> <props> <prop key="administrator">[email protected]</prop> <prop key="support">[email protected]</prop> <prop key="development">[email protected]</prop> </props> </property> <!-- results in a setSomeList(java.util.List) call --> <property name="someList"> <list> <value>a list element followed by a reference</value> <ref bean="myDataSource" /> </list> </property> <!-- results in a setSomeMap(java.util.Map) call --> <property name="someMap"> <map> <entry key="an entry" value="just some string"/> <entry key ="a ref" value-ref="myDataSource"/> </map> </property> <!-- results in a setSomeSet(java.util.Set) call --> <property name="someSet"> <set> <value>just some string</value> <ref bean="myDataSource" /> </set> </property> </bean>
bean | ref | idref | list | set | map | props | value | null
集合的合并:以下例子实现的集合的合并:
<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 <span style="color:#ff0000;">merge="true"</span>> <prop key="sales">[email protected]</prop> <prop key="support">[email protected]</prop> </props> </property> </bean> <beans>注意到在adminEmails中的<props/>元素中,我们使用了merge= true,最终的adminEmails将会合并父类的adminEmails中的值,结果为:
[email protected] [email protected] [email protected]
合并的限制:不能合并不同类型集合(例如map和list则不能相互合并),合并的属性必须是底层的,继承的,子类的定义,,想象一个在父类中定义merge是多余的
强类型集合:(Strongly-typed collection (Java 5+ only))
public class Foo { private Map<String, Float> accounts; public void setAccounts(Map<String, Float> accounts) { this.accounts = accounts; } }
<beans> <bean id="foo" class="x.y.Foo"> <property name="accounts"> <map> <entry key="one" value="9.99"/> <entry key="two" value="2.75"/> <entry key="six" value="3.99"/> </map> </property> </bean> </beans>
spring将属性empty参数类型类似Stirngs的empty类型输出,以下xml配置中的emal属性为空
<bean class="ExampleBean"> <property name="email" value=""/> </bean>
<bean class="ExampleBean"> <property name="email"><null/></property> </bean>
在bean元素属性中,p命名空间可以替代<property/>元素。以下例子说明了p命名空间的用法,第一个bean用的是property,第二个用的是p-namespace
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="[email protected]"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="[email protected]"/> </beans>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean name="john-classic" class="com.example.Person"> <property name="name" value="John Doe"/> <property name="spouse" <span style="color:#ff0000;">ref="jane"</span>/> </bean> <bean name="john-modern" class="com.example.Person" p:name="John Doe" <span style="color:#ff0000;">p:spouse-ref</span>="jane"/> <bean name="jane" class="com.example.Person"> <property name="name" value="Jane Doe"/> </bean> </beans>值得注意的是,p-namespace没有标准的xml形式来的灵活,在定义一个property引用时,p-namespace在ref处结束,而标准的xml格式并没有这个限制,根据具体情况选择合适的写法。
混合属性命名:
当设置bean的properties属性时你也可以混合或嵌套属性命名,只要使得路径中除了最后一个属性外其他属性名都不为null。如下所示:
<bean id="foo" class="foo.Bar"> <property name="fred.bob.sammy" value="123" /> </bean>foo bean有一个fred 属性,fred又有bob属性,bob又有sammy属性,最终sammy属性被设置为123,为了使这个bean可以工作,fred和bob都不能为空。
如果一个bean是另一个bean的依赖,一般来说,一个bean会被设置成另一个bean的引用,典型的就是使用<ref> 元素,但是,有时候两个bean之间并没有直接的关系,例如在某些bean实例化之前需要数据库启动,则应当设置depends-on属性使相应的bean先启动。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />如果是depends-on多个bean
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"> <property name="manager" ref="manager" /> </bean> <bean id="manager" class="ManagerBean" /> <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
ApplicationContext默认会实例化singleton的bean,如果想延迟初始化它则在bean标签中用lazy-init=‘true’,同时也可以再beans标签中指定default-lazy-init=”treu“来讲所有的延迟初始化:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.foo.AnotherBean"/>
<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>
当涉及自动装配bean的依赖关系时,spring有多种处理方式:
Table 3.2. Autowiring modes
Mode | Explanation |
---|---|
no | (Default) No autowiring. Bean references must be defined via a |
byName | Autowiring by property name. Spring looks for a bean with the same name as the property that needs to be autowired. For example, if a bean definition is set to autowire by name, and it contains a master property (that is, it has a setMaster(..) method), Spring looks for a bean definition named |
byType | Allows a property to be autowired if exactly one bean of the property type exists in the container. If more than one exists, a fatal exception is thrown, which indicates that you may not usebyType autowiring for that bean. If there are no matching beans, nothing happens; the property is not set. |
constructor | Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised. |
byName——把与bean的属性具有相同名字或ID的其他bean自动装配到bean对应的属性中。如果没有跟属性的名字相匹配的bean,该属性不进行装配。
byType——把与bean的属性具有相同类型的其他bean自动装配到bean对应的属性中。如果没有跟属性类型相匹配的bean,则该属性不进行装配。(应用只允许存在一个Bean与需要自动装配的属性类型相匹配,为自动识别一个普通bean,可以使用bean元素的primary属性,如果只有一个自动装配的候选Bean的primary属性设置为true,那么该bean优先选择。)
constructor——把与bean的构造器入参具有相同类型的其他bean自动装配到bean构造器的对应入参中。
1.在property和constructor-arg中的明确指定的依赖会覆盖掉autowiring,你也不能自动装配简单属性,例如Strings,Classes等,这是设计的不足引起的。
2.Autowiring比明确的注入写法正确率来的低,spring不会去猜测不明确的关系,可能不能拿到想要的结果。
3.对已setter或构造器注入的具体参数中,可能存在多个bean匹配参数,这样是不允许的,因为不明确结果。
在大部分情况下,容器中的bean都是singleton类型的。如果一个singleton bean要引用另外一个singleton bean,或者一个非singleton bean要引用另外一个非singleton bean时,通常情况下将一个bean定义为另一个bean的property值就可以了。不过对于具有不同生命周期的bean来说这样做就会有问题了,比如在调用一个singleton类型bean A的某个方法时,需要引用另一个非singleton(prototype)类型的bean B,对于bean A来说,容器只会创建一次,这样就没法在需要的时候每次让容器为bean A提供一个新的的bean B实例。
对于上面的问题,spring提供了三种解决方案:
ApplicationContextAware和BeanFactoryAware差不多,用法也差不多,实现了ApplicationContextAware接口的对象会拥有一个ApplicationContext的引用,这样我们就可以已编程的方式操作ApplicationContext。看下面的例子。
// a class that uses a stateful Command-style class to perform some processing package fiona.apple; // Spring-API imports import org.springframework.beans.BeansException; import org.springframework.context.Applicationcontext; import org.springframework.context.ApplicationContextAware; public class CommandManager implements ApplicationContextAware { private ApplicationContext applicationContext; public Object process() { // grab a new instance of the appropriate Command Command command = createCommand(); return command.execute(); } protected Command createCommand() { // notice the Spring API dependency! return this.applicationContext.getBean("command", Command.class); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }下面定义Command接口和其实现类AsyncCommand
package example; public interface Command { public Object execute(); }
package example; public class AsyncCommand implements Command{ public Object execute() { return this; } }xml配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean id = "asyncCommand" class = "example.AsyncCommand" scope = "prototype"></bean> <bean id = "commandManager" class = "example.CommandManager"></bean> </beans>以上主要是单例bean commandManager的process()方法需要引用一个prototype(非单例)的bean,所以在调用process的时候先通过createCommand方法从容易中取出一个Command,然后再执行业务计算。
public class Realize { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("com/springinaction/springidol/example.xml"); CommandManager commandManager = (CommandManager) applicationContext.getBean("commandManager"); System.out.println("first = " + commandManager.process()); System.out.println("second = " + commandManager.process()); } }可以通过控制台输出看到两次的输出借中的Command的地址是不一样的,因为我们为asyncCommand配置了scope="prototype"属性,这种方式就是使得每次从容器中取得的bean实例都不一样。通过这样方式我们实现了单例bean(commandManager)中的方法(process方法)引用非单例的bean(asyncCommand)。虽然我们实现了,但是这不是一种好的方法,因为我们的业务代码和Spring Framework产生了耦合。下面介绍Spring提供的另外一种干净的实现方式,就是Lookup方法注入。
使用这种方式很简单,因为Spring已经为我们做了很大一部分工作,我们要做的就是bean配置和业务类。
package example; public abstract class CommandManager { //模拟业务处理的方法 public Object process(){ Command command=createCommand(); return command.execute(); } //获取一个命令 protected abstract Command createCommand(); }xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean id = "asyncCommand" class = "example.AsyncCommand" scope = "prototype"></bean> <bean id = "commandManager" class = "example.CommandManager"> <lookup-method name="createCommand" bean="asyncCommand"/> </bean> </beans>
<public|protected> [abstract] <return-type> theMethodName(no-arguments);被注入方法不一定是抽象的,如果被注入方法是抽象的,动态生成的子类(这里就是动态生成的CommandManager的子类)会实现该方法。否则,动态生成的子类会覆盖类里的具体方法。为了让这个动态子类得以正常工作,需要把CGLIB的jar文件放在classpath里,这就是我们引用cglib包的原因。还有,Spring容器要子类化的类(CommandManager)不能是final的,要覆盖的方法(createCommand)也不能是final的。
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency>