1,高级依赖关系配置
Spring允许将Bean实例的所有成员变量,甚至基本类型的成员变量都通过配置文件来指定值,这种方式提供了很好的解耦。但是否真的值得呢?如果将基本类型的成员变量也通过配置文件指定,虽然提供了很好的解耦,但大大降低了程序的可读性(必须同时参照配置文件才可以知道程序中各成员变量的值)。因此,滥用依赖注入会产生严重问题!
最好的做法:组件与组件的耦合,采用依赖注入管理;但基本类型的成员的变量值,应直接在代码中设置。对于组件之间的耦合关系,通过使用控制反转,使代码变得清晰。因此,Bean无须管理依赖关系,而是由容器提供注入,Bean无须知道这些实例在哪里,以及它们具体的实现。
前面介绍的依赖关系,要么事基本类型的值,要么直接依赖于其他Bean。在实际的应用中,某个Bean实例的属性值可能是某个方法的返回值,或者类的Field值,或者另一个对象的getter方法返回值,Spring同样可以支持这种非常规的注入方式。Spring甚至支持将任意方法的返回值、类或对象的Field值、其他Bean的getter方法返回值,直接定义成容器中的一个Bean。
Spring框架的本质是:开发者在Spring配置文件中使用XML元素进行管理,实际驱动Spring执行相应的代码。
- 使用<bean.../>元素,实际启动Spring执行无参数或有参数的构造器,或者调用工厂方法创建Bean。
- 使用<property.../>元素,实际驱动Spring执行一次setter方法。
但Java程序还可能有其他类型的语句,如调用getter方法、调用普通方法、访问类或对象的Field,而Spring也为这种语句提供了对相应的配置语法。
- 调用getter方法:使用PropertyPathFactoryBean。
- 访问类或对象的Field对象:使用FieldRetrievingFactoryBean。
- 调用普通方法:使用MethodInvokingFactoryBean。
可以换一个角度来看Spring框架:Spring框架的功能是什么?它可以让开发者无须书写Java代码就可以进行Java编程,当开发者XML采用合适语法进行配置后,Spring就可通过反射在底层执行任意的Java代码。
1.1,获取其他Bean的属性值
PropertyPathFactoryBean用来获取目标Bean的属性值(实际上就是它的getter方法的返回值),获得的值可注入给其他Bean,也可直接定义成新的Bean。
使用PropertyPathFactoryBean来调用其他Bean的getter方法需要指定如下信息:
- 调用那个对象:由PropertyFactoryBean的setTargetObject(Object targetObject)方法指定。
- 调用那个getter方法:由PropertyPathFactoryBean的setPropertyPath(String propertyPath)方法指定。
package Bean; public class Person { private String name; private Son son; public void setName(String name) { this.name = name; } public void setSon(Son son) { this.son = son; } public String getName() { return name; } public Son getSon() { return son; } } --------------------------------- package Bean; public class Son { private String age; public void setAge(String age) { this.age = age; } public String getAge() { return age; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="person" class="Bean.Person"> <property name="name" value="30"/> <property name="son"> <bean class="Bean.Son"> <property name="age" value="11"/> </bean> </property> </bean> <bean id="son1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"> <property name="targetBeanName" value="person"/> <property name="propertyPath" value="son"/> </bean> </beans>
上面配置文件使用PropertyPathFactoryBean来获取指定Bean的、指定getter的返回值,其中粗体代码指定了获取person的getSon()方法的返回值,该返回值将直接定义成容器中的son1。
PropertyPathFactoryBean就是工厂Bean,工厂Bean专门返回某个类型的值,并不是返回该Bean的实例。在这种配置方式下,配置PropertyPathFactoryBean工厂Bean时指定id属性,并不是该Bean的唯一标识,而是用于指定属性表达式的值。
AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(((Son)context.getBean("son1")).getAge());
Spring获取指定Bean的getter方法的返回值之后,该返回值不仅可直接定义成容器中的Bean,还可注入另一个Bean。
<bean id="son2" class="Bean.Son"> <property name="age"> <bean id="person.son.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/> </property> </bean>
上面的代码中,程序调用son2实例setAget()方法时的参数并不是直接指定的,而是将容器中另一个Bean实例的属性值(getter方法的返回值)作为setAge()方法的参数,PropertyPathFactoryBean工厂Bean负责获取容器中另一个Bean的属性值(getter()方法的返回值)。
public static void main(String[] args) throws BeansException { AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(((Son)context.getBean("son2")).getAge()); }
为PropertyPathFactoryBean的setPropertyPath()方法指定属性表达式时,还支持使用复合属性的形式。
<bean id="son1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"> <property name="targetBeanName" value="person"/> <property name="propertyPath" value="son.age"/> </bean> ------------------------------------------ public static void main(String[] args) throws BeansException { AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(context.getBean("son1")); }
1.2,获取Field值
通过FieldRetrievingFavtoryBean类,可访问类的静态Field或对象的实例Field值。FieldRetrievingFactoryBean获得指定Field的值之后,即可将获得的值注入其他Bean,也可直接定义成新的Bean。
使用FieldRetrievingFactoryBean访问Field值可分为两种情形:
(1)如果要访问的Field是静态Field,则需要指定:
- 调用哪个类:由FieldRetrievingFactoryBean的setTargetClass(String targetClass)方法指定。
- 访问哪个Field:由FieldRetrievingFactoryBean的setTargetField(String targetClass)方法指定。
(2)如果要访问的Field是实例Field,则需要指定:
- 调用哪个对象:由FieldRetrievingFactoryBean的setTargetObject(Object targetObject)方法指定。
- 访问哪个Field:由FieldRetrievingFactoryBean的setTargetField(String targetField)方法指定。
对于FieldRetrievingFactoryBean的第一种用法,与前面介绍FactoryBean时开发的GetFieldFactoryBean基本相同。对于FieldRetrievingFactoryBean的第二种用法,在实际编程机会没有太大作用,原因是根据良好的封装原则,Java类的实例Field应该用private修饰,并使用getter和setter来访问和修改。FieldRetrievingFactoryBean则要求实例Field以public修饰。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="theAge1" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"> <property name="targetClass" value="java.sql.Connection"/> <property name="targetField" value="TRANSACTION_SERIALIZABLE"/> </bean> </beans>
public static void main(String[] args) throws BeansException { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(context.getBean("theAge1")); } ================================= 8
FieldRetrievingFactoryBean还提供了一个setStaticField(String staticField)方法,该方法可同时指定获取哪个类的哪个静态Field值。
<bean id="theAge1" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"> <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/> </bean>
从程序输出可以看出,son的age成员变量的值,等于java.sql.Connection接口中TRANSACTION_SERIALIZABLE的值。在上面定义中,定义FieldRetrievingFactoryBean工厂Bean时指定的id属性,并不是该Bean实例的唯一标识,而是指定Field表达式。
1.3,获取方法返回值
通过MethodInvokingFactoryBean工厂Bean,可调用任意类的类方法,也可调用任意对象的实例方法,如果调用的方法有返回值,则即可将该指定方法的返回值定义成容器中的Bean,也可将指定方法的方法的返回值会注入给其他Bean。
使用MethodInvokingFactoryBean来调用任意方法时,可分为两种情形:
(1)如果希望调用的方法是静态方法,则需要指定:
- 调用哪个类:通过MethodInvokingFactoryBean的setTargetClass(String targetClass)方法指定。
- 调用哪个方法:通过MethodInvokingFactoryBean的setTargetMethod(String targetMethod)方法指定。
- 调用方法的参数:通过MethodInvokingFactoryBean的setAtrguments(Object[] arguments)方法指定。
(2)如果希望调用的方法无须参数,则可以省略该配置:
- 调用哪个对象:通过MethodInvokingFactoryBean的setTargetObject(Object targetObject)方法指定。
- 调用哪个方法:通过MethodInvokingFactoryBean的setTargetMethod(String targetMethod)方法指定。
- 调用方法的参数:通过MethodInvokingFactoryBean的setArguments(Object[] arguments)方法指定。
<?xml version="1.0" encoding="GBK"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"> <!-- 下面配置相当于如下Java代码: JFrame win = new JFrame("我的窗口"); win.setVisible(true); --> <bean id="win" class="javax.swing.JFrame"> <constructor-arg value="我的窗口" type="java.lang.String"/> <property name="visible" value="true"/> </bean> <!-- 下面配置相当于如下Java代码: JTextArea jta = JTextArea(7, 40); --> <bean id="jta" class="javax.swing.JTextArea"> <constructor-arg value="7" type="int"/> <constructor-arg value="40" type="int"/> </bean> <!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法 下面配置相当于如下Java代码: win.add(new JScrollPane(jta)); --> <bean class= "org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="win"/> <property name="targetMethod" value="add"/> <property name="arguments"> <list> <bean class="javax.swing.JScrollPane"> <constructor-arg ref="jta"/> </bean> </list> </property> </bean> <!-- 下面配置相当于如下Java代码: JPanel jp = new JPanel(); --> <bean id="jp" class="javax.swing.JPanel"/> <!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法 下面配置相当于如下Java代码: win.add(jp , BorderLayout.SOUTH); --> <bean class= "org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="win"/> <property name="targetMethod" value="add"/> <property name="arguments"> <list> <ref bean="jp"/> <util:constant static-field="java.awt.BorderLayout.SOUTH"/> </list> </property> </bean> <!-- 下面配置相当于如下Java代码: JButton jb1 = new JButton("确定"); --> <bean id="jb1" class="javax.swing.JButton"> <constructor-arg value="确定" type="java.lang.String"/> </bean> <!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法 下面配置相当于如下Java代码: jp.add(jb1); --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="jp"/> <property name="targetMethod" value="add"/> <property name="arguments"> <list> <ref bean="jb1"/> </list> </property> </bean> <!-- 下面配置相当于如下Java代码: JButton jb2 = new JButton("取消"); --> <bean id="jb2" class="javax.swing.JButton"> <constructor-arg value="取消" type="java.lang.String"/> </bean> <!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法 下面配置相当于如下Java代码: jp.add(jb2); --> <bean class= "org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="jp"/> <property name="targetMethod" value="add"/> <property name="arguments"> <list> <ref bean="jb2"/> </list> </property> </bean> <!-- 使用MethodInvokingFactoryBean驱动Spring调用普通方法 下面配置相当于如下Java代码: win.pack(); --> <bean class= "org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="win"/> <property name="targetMethod" value="pack"/> </bean> </beans>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
几乎所有的Java代码都可以通过Spring XML配置文件配置出来——连上吗的Swing编程都可以使用Spring XML配置文件来驱动。
Spring框架的本质:通过XML配置来执行Java代码,因此几乎可以把所有的Java代码放入到Spring配置文件中管理。
- 调用构造器创建对象(包括使用工程方法创建对象):用<bean.../>元素。
- 调用setter方法:用<property.../>元素。
- 调用getter方法:用PropertyPathFactoryBean。
- 调用普通方法:用MethodInvokingFactoryBean。
- 获取Field的值:用FieldRetrievingFactoryBean。
但是,过度使用XML配置文件不仅使得配置文件更加臃肿,难以维护,而且导致程序可读性严重降低。一般来说,应该将:项目升级、维护时需要改动的信息。控制项目内各组件的耦合关系的代码。放到XML配置文件。这就体现了Spring IoC容器的作用:将原来使用Java代码管理的耦合关系,提取到XML中进行管理,从而降低了各组件之间的耦合,提高了软件系统的可维护性。
2,基于XML Schema的简化配置方式
从Spring2.0开始,Spring允许使用基于XML Schema的配置方式来简化Spring配置文件。
早期Spring用一种<bean.../>元素即可配置所有的Bean实例,而每个设值注入再用一个<property.../>元素即可。这种配置方式简单、直观,而且能以相同风格处理所有Bean的配置——唯一的缺点是配置烦琐,当Bean实例足够多的时候,且类型复杂(大多数是集合注入)时,基于DTD的配置文件将变得更加烦琐。
这种情况下,Spring提出了使用基于XML Schema的配置方式。这种配置方式更加简洁,可以对Spring配置文件进行“减肥”。
2.1,使用p:命名空间简化配置
p:命名空间甚至不需要特定的Schema定义,它直接存在于Spring内核中。与前面采用<property.../>元素定义Bean的属性不同的是,当导入p:命名空间之后,就可直接在<bean.../>元素中使用属性来驱动执行setter方法。
xmlns:p="http://www.springframework.org/schema/p"
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="son" class="Bean.Son" p:age="22"></bean> </beans>
2.2,使用c:命名空间简化配置
c:用于简化构造注入。
xmlns:c="http://www.springframework.org/schema/c"
<?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:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="son" class="Bean.Son" c:age="22"></bean> </beans>
使用c:指定构造器参数的格式为:c:构造器参数名=“值”或c:构造器参数名-ref=“其他Bean的id”。
另外,Spring还支持一种通过索引来配置构造参数的方式。
<bean id="son" class="Bean.Son" c:_0="22"></bean>
2.3,使用util:命名空间简化配置
在Spring框架压缩包的schema\util\路径下包含有util:命名空间的XML Schema文件,为了使用util:命令空间的元素,必须先在Spring配置文件中导入最新的spring-util-4.3.xsd。
<?xml version="1.0" encoding="GBK"?> <!-- 指定Spring配置文件的根元素和Schema 导入p:命名空间和util:命名空间的元素 --> <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" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd"> <!-- 配置chinese实例,其实现类是Chinese --> <bean id="chinese" class="Bean.Chinese" p:age-ref="chin.age" p:axe-ref="stoneAxe" p:schools-ref="chin.schools" p:axes-ref="chin.axes" p:scores-ref="chin.scores"/> <!-- 使用util:constant将指定类的静态Field定义成容器中的Bean --> <util:constant id="chin.age" static-field= "java.sql.Connection.TRANSACTION_SERIALIZABLE"/> <!-- 使用util.properties加载指定资源文件 --> <util:properties id="confTest" location="classpath:test_zh_CN.properties"/> <!-- 使用util:list定义一个List集合,指定使用LinkedList作为实现类, 如果不指定默认使用ArrayList作为实现类 --> <util:list id="chin.schools" list-class="java.util.LinkedList"> <!-- 每个value、ref、bean...配置一个List元素 --> <value>小学</value> <value>中学</value> <value>大学</value> </util:list> <!-- 使用util:set定义一个Set集合,指定使用HashSet作为实现类, 如果不指定默认使用HashSet作为实现类--> <util:set id="chin.axes" set-class="java.util.HashSet"> <!-- 每个value、ref、bean...配置一个Set元素 --> <value>字符串</value> <bean class="Bean.SteelAxe"/> <ref bean="stoneAxe"/> </util:set> <!-- 使用util:map定义一个Map集合,指定使用TreeMap作为实现类, 如果不指定默认使用HashMap作为实现类 --> <util:map id="chin.scores" map-class="java.util.TreeMap"> <entry key="数学" value="87"/> <entry key="英语" value="89"/> <entry key="语文" value="82"/> </util:map> <!-- 配置steelAxe实例,其实现类是SteelAxe --> <bean id="steelAxe" class="Bean.SteelAxe"/> <!-- 配置stoneAxe实例,其实现类是StoneAxe --> <bean id="stoneAxe" class="Bean.StoneAxe"/> </beans>