Spring:高级依赖关系配置&XML简化配置方式

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>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值