Spring学习笔记(二)

本文详细介绍Spring框架中Bean的配置方法,包括使用XML和Java类进行配置的区别及使用场景,探讨Bean的生命周期管理、作用域配置、依赖注入等核心概念。

常用注解整理:

  • @Configuration:用于修饰一个Java配置类
  • @Bean:用于修饰一个方法,将该方法的返回值定义成容器中的一个Bean
  • @Value:用于修饰一个Field,用于为该Field配置一个值,相当于配置一个变量
  • @Import:修饰一个Java配置类,用于向当前Java配置类中导入其他Java配置类
  • @Scope:用于修饰一个方法,指定该方法对应的Bean的生命域
  • @Lazy:用于修饰一个方法,指定该方法对应的Bean是否需要延迟初始化
  • @DependsOn:用于修饰一个方法,指定在初始化该方法对应的Bean之前初始化指定的Bean
  • @ImportResource注:用于修饰Java配置类,用于导入指定的XML配置文件

一、Spring 3.0提供的Java配置管理

  在前面已经提及到使用XML配置文件来配置Spring,Spring 3.0提供了另外一种配置方式,那就是使用Java类进行配置。

使用XML进行配置如下:

<!-- 配置chinese实例,其实现类是Chinese -->
<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
	<!-- 驱动Spring执行setAxe()方法,以容器中id为stoneAxe的Bean为参数 -->
	<property name="axe" ref="stoneAxe" />
	<!-- 驱动Spring执行setName()方法,以字符串"孙悟空"为参数 -->
	<property name="name" value="孙悟空" />
</bean>
<!-- 配置stoneAxe实例,其实现类是StoneAxe -->
<bean id="stoneAxe" class="org.crazyit.app.service.impl.StoneAxe" />
<!-- 配置steelAxe实例,其实现类是SteelAxe -->
<bean id="steelAxe" class="org.crazyit.app.service.impl.SteelAxe" />

同样我们可以使用Java类(引入注解的方式)进行配置如下:

@Configuration
public class AppConfig {
	// 相当于定义一个名为personName的变量,其值为"孙悟空"
	@Value("孙悟空")
	String personName;
	// 配置一个Bean:chinese
	@Bean(name = "chinese")
	public Person person() {
		Chinese p = new Chinese();
		p.setAxe(stoneAxe());
		p.setName(personName);
		return p;
	}
	// 配置Bean:stoneAxe
	@Bean(name = "stoneAxe")
	public Axe stoneAxe() {
		return new StoneAxe();
	}
	// 配置Bean:steelAxe
	@Bean(name = "steelAxe")
	public Axe steelAxe() {
		return new SteelAxe();
	}
}

  以上两种配置方式的效果是相同的,上面的配置文件中使用了Java配置类的三个常用Annotation:

  • @Configuration:用于修饰一个Java配置类
  • @Bean:用于修饰一个方法,将该方法的返回值定义成容器中的一个Bean
  • @Value:用于修饰一个Field,用于为该Field配置一个值,相当于配置一个变量

  一旦使用了Java配置类来管理Spring容器中的Bean及其依赖关系,此时就需要使用如下方式来创建Spring容器:

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

  使用Java配置类时,还有如下常用的Annotation:

  • @Import:修饰一个Java配置类,用于向当前Java配置类中导入其他Java配置类
  • @Scope:用于修饰一个方法,指定该方法对应的Bean的生命域
  • @Lazy:用于修饰一个方法,指定该方法对应的Bean是否需要延迟初始化
  • @DependsOn:用于修饰一个方法,指定在初始化该方法对应的Bean之前初始化指定的Bean

  实际上,Spring提供@Configuration和@Bean并不是为了完全取代XML配置,只是希望它作为XML配置的一种补充。对于Spring框架的使用者来说,Spring配置文件的“急剧膨胀”是一个让人头痛的点,因此Spring框架从2.0开始就不断地寻找各种对配置文件“减肥”的方法。

  由于Annotation出现的比较晚,而XML已经相当成熟了。因此,在目前的多数项目中要么完全使用XML配置方式管理Bean的配置,要么使用以Annotation为主、XML为辅的配置方式管理Bean的配置,想要完全放弃XML配置还是比较困难的。毕竟对于复杂的配置,注解有可能还不足以独当一面,从而仍需要使用XML进行配置。

  分析:通常来说,大部分还是使用XML配置文件管理Bean及其依赖关系更为方便——毕竟使用XML文件来管理Bean及其依赖关系是为了解耦。而Java配置类的方式又退回到Java代码耦合层次,只是将这种耦合几种到一个或多个Java配置类中,但是降低了XML配置文件的臃肿。

  因此,在实际项目中可能会混合使用XML配置和Java类配置,在这种混合下存在一个问题:项目到底以XML配置为主,还是以Java类配置为主?

<1> 以XML配置为主

  如果以XML配置为主,就需要让XML配置能加载Java类配置。这并不难,只要在XML配置中增加如下代码即可:

<bean class="org.crazyit.app.config.AppConfig"/>

  由于以XML配置为主,因此应用创建Spring容器时,还是以这份XML配置文件为参数来创建ApplicationContext对象。那么Spring会先加载这份XML配置文件,再根据这份XML配置文件的指示去加载指定的Java配置类。

<2> 以Java类配置为主

  如果以Java类配置为主,就需要让Java配置类能加载XML配置。这就需要借助于@ImportResource注解,这个注解可修饰Java配置类,用于导入指定的XML配置文件。也就是在Java配置类上增加如下注解:

@Configuration
//导入XML配置
@ImportResource("classpath:/beans.xml")
public class MyConfig{
...
}

  由于以Java类配置为主,因此应用创建Spring容器时,应以Java配置类为参数,通过创建AnnotationConfigApplicationContext对象作为Spring容器。那么Spring会先加载这个Java配置类,再根据这个Java配置类的指示去加载指定的XML配置文件。

二、创建Bean的3种方式

Spring支持使用如下方法来创建Bean:

  • 调用构造器创建Bean
  • 调用静态工厂方法创建Bean
  • 调用实例工厂方法创建Bean

2.1 使用构造器创建Bean实例

  这种用方式也就是我们先前一直使用的,也就是前面所提及的设置注入和构造注入的方式。其中Bean元素的class属性时必须的(除非采用继承),class属性的值就是Bean实例的实现类。如果实例的一些属性没有注入值,则会按照默认值的方式进行初始化。

2.2 使用静态工厂方法创建Bean

  使用静态工厂方法创建bean实例时,class属性也必须指定,但此时属性并不是指定Bean实例的实现类,而是静态工厂类,Spring通过该属性知道那个工厂类来创建Bean实例。除此之外,还需要使用factor-method属性来指定静态工厂方法,Spring将调用静态工厂方法(可能包含一组参数)返回一个Bean实例,一旦获得了指定Bean实例,Spring后面的处理步骤与采用普通方法创建Bean实例完全一样。

  采用静态工厂方法创建Bean实例时,<bean.../>元素需要指定如下两个属性:

  • class:该属性的值为静态工厂类的类名
  • factory-method:该属性指定静态工厂方法来生产Bean实例

配置实例如下:

<!-- 下面配置驱动Spring调用BeingFactory的静态getBeing()方法来创建Bean
该bean元素包含的constructor-arg元素用于为静态工厂方法指定参数,
因此这段配置会驱动Spring以反射方式来执行如下代码:
dog = org.crazyit.app.factory.BeingFactory.getBeing("dog"); -->
<bean id="dog" class="org.crazyit.app.factory.BeingFactory"
	factory-method="getBeing">
	<!-- 配置静态工厂方法的参数 -->
	<constructor-arg value="dog"/>
	<!-- 驱动Spring以"我是狗"为参数来执行dog的setMsg()方法 -->
	<property name="msg" value="我是狗"/>
</bean>

注意:<constructor-arg value="..."/>指定的是静态工厂方法的参数,而<property name="..." value="..."/>针对这个工厂类方法返回的实例进行注入。

示例代码详见:codes\07\7.7\staticFactory

  一旦指定factory-method属性,Spring就不再调用构造器来创建Bean实例,而是调用工厂方法来创建Bean实例。

2.3 调用实例工厂方法创建Bean

  实例工厂方法与静态工厂方法只有一点不同:调用静态工厂方法只需使用工厂类即可,而调用实例工厂方法则需要工厂实例。因此这二者的配置基本相同,只有一个区别:配置静态工厂方法使用class指定静态工厂类,而配置实例工厂方法则使用factory-bean指定工厂实例。

  使用实例工厂方法时,配置Bean实例的<bean.../>元素无须class属性,因为Spring容器不再直接实例化该Bean,Spring容器仅仅调用实例工厂的工厂方法,工厂方法负责创建Bean实例。

  采用实例工厂方法创建Bean的<bean.../>元素时需要指定如下两个属性:

  • factory-bean:该属性的值为工厂Bean的id
  • factory-method:该属性指定实例工厂的工厂方法

  与静态工厂方法相似,如果需要在调用实例工厂方法时传入参数,则使用<constructor-arg.../>元素确定参数值。

示例代码详见:codes\07\7.7\instanceFactory

配置实例如下:

<!-- 配置工厂Bean,该Bean负责产生其他Bean实例 -->
<bean id="personFactory" class="org.crazyit.app.factory.PersonFactory"/>
<!-- 下面配置驱动Spring调用personFactory Bean的getPerson()方法来创建Bean
该bean元素包含的constructor-arg元素用于为工厂方法指定参数,
因此这段配置会驱动Spring以反射方式来执行如下代码:
PersonFactory pf = container.get("personFactory"); // container代表Spring容器
chinese = pf.getPerson("chin"); -->
<bean id="chinese" factory-bean="personFactory" 
	factory-method="getPerson">
	<!-- 配置实例工厂方法的参数 -->
	<constructor-arg value="chin"/>
</bean>

调用实例工厂方法创建Bean,与调用静态工厂方法创建Bean的用法基本相似。区别如下:

  • 配置实例工厂方法创建Bean,必须将实例工厂配置成Bean实例;而配置静态工厂方法创建Bean,则无须配置工厂Bean
  • 配置实例工厂方法创建Bean,必须使用factory-bean属性确定工厂Bean;而配置静态工厂方法相同之处如下

相同之处如下:

  • 都需要使用factory-method属性指定产生Bean实例的工厂方法
  • 工厂方法如果需要参数,都使用<constructor-arg.../>元素指定参数值
  • 普通的设值注入,都使用<property.../>元素确定参数值

三、深入理解容器中的Bean

3.1 抽象Bean与子Bean

  在实际开发过程中,有可能Bean中配置具有大致相同的配置信息,如果项目比较大可能导致配置文件臃肿以及后期难以修改、维护。针对这种问题我们可以把公共配置提取出来,集中成配置模板,这个就是抽象Bean。抽象Bean不能被实例化,Spring容器不会创建抽象Bean实例。抽象Bean的价值在于被继承,抽象Bean通常作为父Bean被继承。抽象Bean只是配置信息的模板,指定abstract=“true”即可阻止Spring实例化该Bean,因此抽象Bean可以不指定class属性。

  通过为一个<bean.../>元素指定parent属性即可指定该Bean是一个子Bean,parent属性指定该Bean所继承的父Bean的id。

  子Bean无法从父Bean继承如下属性:depends-on、autowire、singleton、scope、lazy-init,这些属性将总是从子Bean定义中获得,或采用默认值。

3.2 Bean继承与Java继承的区别

  Spring中的Bean继承与Java中的继承截然不同。前者是实例与实例之间参数值的延续,后者则是一般到特殊的细化;前者是对象与对象之间的关系,后者则是类与类之间的关系。详细区别如下:

  • Spring中的子Bean和父Bean可以是不同类型,但Java中的继承则可保证子类是一种特殊的父类
  • Spring中Bean的继承是实例之间的关系,因此主要表现为参数值的延续;而Java中的继承是类之间的关系,主要表现为方法、属性的延续
  • Spring中的子Bean不可作为父Bean使用,不具备多态性;Java中的子类实例完全可当成父类实例使用

3.3 容器中的工厂Bean

  此处的工厂Bean,与前面介绍的实例工厂和静态工厂方法创建Bean的工厂有所区别:前面那些工厂的标准的工厂模式,Spring只是负责调用工厂方法来创建Bean实例;此处的工厂Bean是Spring的一种特殊Bean,这种工厂Ben啊必须实现FactoryBean接口。该接口是工厂Bean的标准接口,把工厂Bean部署在容器之后,如果程序通过getBean方法来获取它时,容器返回的不是FactoryBean实现类的实例,而是返回FactoryBean的产品,FactoryBean接口提供了如下三个方法:

  • T getObject():实现该方法负责返回该工厂Bean生成的Java实例
  • Class<?> getObjectType():实现该方法返回该工厂Bean生成的Java实例的实现类
  • boolean isSingleton():实现该方法表示该工厂Bean生成的Java实例是否为单例模式

  配置FactoryBean与配置普通Bean的定义没有区别,但当程序向S任凭容器请求获取该Bean时,容器返回该FactoryBean的产品,而不是返回该FactoryBean本身。

  实现FactoryBean接口的最大作用在于:Spring容器并不是简单返回该Bean的实例,而是返回该Bean实例的getObject()方法的返回值,而getObject()方法有开发者负责实现,这样相当于我们想让它返回什么就返回什么。如果想要得到该FactoryBean本身时,我们就不能直接请求Bean id,而是在Bean id前增加&符号,这样就会返回FactoryBean本身了。实际上FactoryBean是Spring中非常有用的一个接口,Spring内置提供了很多实用的工厂Bean,例如TransactionProxyFactoryBean等,这个工厂Bean专门用于为目标Bean创建事务代理。

注意:Spring提供的工厂Bean,大多以FactoryBean后缀结尾,并且大多用于生产一批具有某种特征的Bean实例,工厂Bean是Spring的一个重要工具类。

3.4 获得Bean本身的id

  如果在开发Bean类时需要预先知道该Bean的配置id,此时可借助Spring提供的BeanNameAware接口,通过该接口即可提前预知该Bean的配置id。

  BeanNameAware接口提供了一个方法:setBeanName(String name),该方法的name参数就是Bean的id,实现该方法的Bean类就可通过该方法来获得部署该Bean的id了。示例配置如下:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
	<!-- Spring容器会检测容器中所有Bean,如果发现某个Bean实现了BeanNameAware接口,
	Spring容器会在创建该Bean之后,自动调用该Bean的setBeanName()方法,
	调用该方法时,会将该Bean的配置id作为参数传给该方法
	该方法的实现部分将Spring传入的参数(Bean的配置id)赋为给该Chinese对象的
	beanName实例变量,因此接下来即可通过该beanName实例变量来访问Bean的配置id。-->
	<bean id="chinese" class="org.crazyit.app.service.Chinese"/>
</beans>

 3.5 强制初始化Bean

  在大多数情况下,Bean之间的依赖非常直接,Spring容器在返回Bean实例之前,先要完成Bean依赖关系的注入。假如Bean A依赖于Bean B,程序请求A时,Spring容器会自动先初始化B,再将B注入A,最后将具备完整依赖的A返回给程序。

  在极端的情况下,Bean之间的依赖不够直接,比如,某个类的初始化块中使用其他Bean,Spring总是先初始化主调Bean,当执行初始化块时,被依赖Bean可能还没有实例化,此时将引发异常。

  为了显示指定被依赖Bean在目标Bean之前初始化,可以使用depends-on属性,该属性可以在初始化主调Bean之前,强制初始化一个或多个Ben。配置片段如下:

<!-- 配置beanOne,使用depends-on强制在初始化beanOne之前初始化manager Bean -->
<bean id="beanOne" class="..." depends-on="manager"/>
<bean id="manager" class="..." />

四、容器中Bean的生命周期

  Spring可以管理singleton作用域的Bean的生命周期,Spring可以精确地知道该Bean何时被创建、何时被初始化完成、容器何时准备销毁该Bean实例。

  对于prototype作用域的Bean,Spring容器仅仅负责创建,当容器创建了Bean实例之后,Bean实例完成交给客户端代码管理,容器不再跟踪其生命周期。因此Spring无法管理prototype作用域的Bean。
  对于singleton作用域的Bean,Spring容器知道Bean何时实例化结束、何时销毁,Spring可以管理实例化结束之后和销毁之前的行为。管理Bean的生命周期行为主要有如下两个时机:

  • 注入依赖关系之后
  • 即将销毁Bean之前

Spring通过<bean.../>的scope属性指定Bean的作用域,其包含五种:

  • singleton:单例模式,在整个Spring IoC容器中,singleton作用域的Bean将只生成一个实例,配置中默认为singleton
  • prototype:每次通过容器的getBean()方法获取prototype作用域的Bean时,都将产生一个新的Bean实例
  • request:对于一次HTTP请求,request作用域的Bean将只生成一个实例,这意味着,在同一次HTTP请求内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效
  • session:对于一次HTTP会话,session作用域的Bean将只生成一个实例,这意味着,在同一次HTTP会话内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效
  • global session:每个全局的HTTP Session对应一个Bean实例。在典型的情况下,仅在使用portlet context的时候有效。只有在Web应用中使用Spring时,该作用域才真正有效

4.1 依赖关系注入之后的行为

Spring提供两种方式在Bean全部属性设置成功后执行特定行为:

  • 使用init-method属性。使用该属性指定某个方法应在Bean全部依赖关系设置结束后自动执行。使用这种方式不需要将代码与Spring的接口耦合在一起,代码污染小
  • 实现InitializingBean接口。可以达到同样的效果,就是让Bean类实现InitializingBean接口,该接口提供一个方法:
    • void afterPropertiesSet() throws Exception:Spring容器会在为该Bean注入依赖关系之后,调用该Bean所实现的afterPropertiesSet()方法

注意:Spring执行完注入操作后会先调用该Bean的afterPropertieSet()方法进行初始化,在调用init-method属性所指定的方法进行初始化。实现InitializingBean接口是一种侵入式设计,因此不推荐采用。

示例代码详见:codes\07\7.9\lifecycle-init

4.2 Bean销毁之前的行为

  与定制初始化行为相似,Spring也提供两种方式定制Bean实例销毁之前的特定行为,这两种方式如下:

  • 使用destroy-method属性。使用该属性指定某个方法在Bean销毁之前被自动执行。使用这种方式,不需要讲代码与Spring的接口耦合在一起,代码污染小。
  • 实现DisposableBean接口。可以达到同样的效果,就是让Bean类实现DisposableBean接口,该接口提供一个方法:
    • void destroy() throws Exception:实现该接口必须实现该方法,该方法就是Bean实例销毁之前应该执行的方法

4.3 协调作用域不同步的Bean

  当两个singleton作用域的Bean存在依赖关系时,或者当prototype作用域的Bean依赖singleton作用域的Bean时,使用Spring提供的依赖注入进行管理即可。

  singleton作用域的Bean只有一次初始化的机会,它的依赖关系也只有在初始化阶段被设置,当singleton作用域的Bean依赖prototype作用域的Bean时,Spring容器会在初始化singleton作用域的Bean之前,先创建被依赖的prototype Bean,然后才初始化singleton Bean,并将prototype Bean注入singleton Bean,这会导致以后无论何时通过singleton Bean去访问prototype Bean时,得到的永远是最初那个prototype Bean——这样就相当于singleton Bean把它所依赖的prototype Bean变成了singleton行为。

  由于singleton Bean具有单例行为,当客户端多次请求该Bean时,Spring返回给客户端的将是同一个singleton Bean实例,这不存在任何问题。但是通过该singleton Bean去调用prototype Bean的方法时——始终都是调用同一个prototype Bean实例,这样本来它具有prototype行为,但实际上他却表现出singleton行为。针对这个问题的解决思路:

  • 放弃依赖注入:singleton作用域的Bean每次需要prototype作用域的Bean时,主动向容器请求新的Bean实例,即可保证每次注入prototype Bean实例都是最新的实例
  • 利用方法注入

  第一种方法显然不是一个好的做法,代码主动请求新的Bean实例,必然导致程序代码与Spring API解耦,造成代码污染。在通常情况下,建议采用第二种方法,使用方法注入。

4.3.1 方法注入

  方法注入通常使用lookup方法注入,使用lookup方法注入可以让Spring容器重写容器中Bean的抽象或具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个non-singleton Bean(尽管也可以是一个singleton)。Spring通过使用JDK动态代理或cglib库修改客户端的二进制码,从而实现上述要求。为了使用lookup方法注入,大致需要如下两步:

  1. 将调用者Bean的实现类定义为抽象类,并定义一个抽象方法来获取被依赖的Bean
  2. 在<bean.../>元素中添加<lookup-method.../>子元素让Spring为调用者Bean的实现类实现指定的抽象方法。<lookup-method.../>子元素告诉Spring需要实现哪个抽象方法。Spring为抽象方法提供实现体之后,这个方法就会变成具体方法,这个类也就变成了具体类,接下来Spring就可以创建该Bean的实例了。

使用<lookup-method.../>元素需要指定如下两个属性:

  • name:指定需要让Spring实现的方法
  • bean:指定Spring实现该方法的返回值

注意:Spring4.0的spring-core-*.jar包中已经集成了cglib类库,因此无须额外添加cglib的JAR包了。

示例代码详见:codes\07\7.9\lookup-method

调用者的抽象方法定义如下:

public abstract class Chinese implements Person {
	private Dog dog;
	// 定义抽象方法,该方法用于获取被依赖Bean
	public abstract Dog getDog();
	public void hunt() {
		System.out.println("我带着:" + getDog() + "出去打猎");
		System.out.println(getDog().run());
	}
}

配置的beans.xml文件如下:

<bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
	<!-- Spring只要检测到lookup-method元素,
	Spring会自动为该元素的name属性所指定的方法提供实现体。-->
	<lookup-method name="getDog" bean="gunDog"/>
</bean>
<!-- 指定gunDog Bean的作用域为prototype,
希望程序每次使用该Bean时用到的总是不同的实例 -->
<bean id="gunDog" class="org.crazyit.app.service.impl.GunDog"
	scope="prototype">
	<property name="name" value="旺财"/>
</bean>

五、高级依赖关系配置

  如果将基本类型的成员变量值也通过配置文件指定,虽然提供了很好的解耦,但大大降低了程序的可读性,因此滥用依赖注入也会引起一些问题。通常建议:组件与组件之间的耦合关系,采用依赖注入管理;但基本类型的成员变量值,应直接在代码中设置。对于组件之间的耦合关系,通过使用控制反转,代码变得非常清晰。
  前面所讲述的依赖关系,要么是基本类型的值,要么直接依赖于其他Bean。在实际的应用中,某个Bean实例的属性值可能是某个方法的返回值,或者类的Field值,或者另一个对象的getter方法返回值,Spring同样可以支持这种非常规的注入方式。Spring甚至支持将任意方法的返回值、类或对象的Field值、其他Bean的getter方法返回值,直接定义成容器中的一个Bean。
  Spring框架的本质是,开发者在Spring配置文件中使用XML元素进行配置,实际驱动Spring执行相应的代码:

  • 使用<bean.../>元素,实际启动S赔给你执行无参数或有参数的构造器,或者调用工厂方法创建Bean
  • 使用<property.../>元素,实际驱动Spring执行一次setter方法

  如果调用getter方法、调用普通方法、访问类或对象的Field,而Spring也为这种语句提供了对应的配置语法

  • 调用getter方法:使用PropertyPathFactoryBean
  • 访问类或对象的Field值:使用FieldRetrievingFactoryBean
  • 调用普通方法:使用MethodInvokingFactoryBean

Filed值:

  • 若类的变量用static关键字修饰,则该变量是该类的field值
  • 若类的变量没有用static关键字修饰,则该变量是该类实例化对象的field值

  Spring框架可以让开发者无须书写Java代码就可进行Java编程,当开发者XML采用合适语法进行配置之后,Spring就可通过反射在底层执行任意的Java代码。如果Spring框架真正用的熟练,至少能达到的程度是时,别人给你任何一段Java代码,你都应该能用Spring配置文件将它配置出来。

5.1 获取其他Bean的属性值

  PropertyPathFactoryBean用来获取目标Bean的属性值(实际上就是它的getter方法的返回值),获得的值可注入给其他Bean,也可直接定义成新的Bean。使用PropertyPathFactoryBean来调用其他Bean的getter方法需要指定如下信息:

  • 调用哪个对象。由PropertyPathFactoryBean的setTargetObject(ObjecttargetObject)方法指定,传参是Bean实例
  • 调用哪个对象。由PropertyPathFactoryBean的setTargetBeanName(StringtargetBeanName)方法指定,传参是Bean的ID
  • 调用哪个getter方法。由PropertyPathFactoryBean的setPropertyPath(String propertyPath)方法指定

  <util:property-path.../>元素可作为PropertyPathFactoryBean的简化配置,使用该元素时可指定如下两个属性:

  • id:该属性指定将getter方法的返回值定义成名为id的Bean实例
  • path:该属性指定将哪个Bean实例、哪个属性(支持符合属性)暴露出来

注意:此处用到了util命名空间,关于如何导入命名空间将在下面小节详细描述。

Java中的属性和字段有什么区别:Java中的属性,通常可以理解为get和set方法。而字段,通常叫做“类成员”。这两个概念是完全不同的。

示例代码详见:codes\07\7.10\PropertyPathFactoryBean

注:工厂Bean专门返回某个类型的值,并不是返回该Bean的实例。在这种配置方式下,配置PropertyPathFactory工厂Bean的id属性,并不是该Bean的唯一标识,而是用于指定属性表达式的值。

示例(只展现配置文件):

<1> 获取Bean的某个属性

<!-- 将指定Bean实例的getter方法返回值定义成son1 Bean -->
<bean id="son1" class=
	"org.springframework.beans.factory.config.PropertyPathFactoryBean">
	<!-- 确定目标Bean,指定son1 Bean来自哪个Bean的getter方法 -->
	<property name="targetBeanName" value="person"/>
	<!-- 指定son1 Bean来自目标bean的哪个getter方法,son代表getSon() -->
	<property name="propertyPath" value="son"/>
</bean>
<!-- 简化配置
	<util:property-path id="son1" path="person.son"/> -->

  其实上面的配置相当于一个属性表达式:son1=person.son其实就是将person这个Bean的son属性赋值给son1。下面的方式可以达到同样的效果:

<bean id="person.son"
		class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
	</bean>

  其实这个配置相当于一个属性表达式person.son,因为其本身就是一个属性值,就无须赋值了,本身就是一个值了。而且id为person.son的这个Bean完全就可以作为一个参数注入到另一个Bean中。
<2> 将获取Bean的属性注入到另一个Bean中

<bean id="son2" class="org.crazyit.app.service.Son">
	<property name="age">
		<!-- 使用嵌套Bean为调用setAge()方法指定参数值 -->
		<!-- 以下是访问指定Bean的getter方法的简单方式, 
		person.son.age代表获取person.getSon().getAge()-->
		<bean id="person.son.age" class=
			"org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
	</property>
</bean>
<!-- 简化配置
<bean id="son2" class="org.crazyit.app.service.Son">
	<property name="age">
		<util:property-path path="person.son.age"/>
	</property>
</bean> -->

<3> 获取Bean属性的字段值

  其实我们在获取Bean字段的时候,如果该属性是一个对象,我们也可以指定该对象的字段,就像平常调用方式类同,用点进行连接就可以了。示例如下:

<!-- 将基本数据类型的属性值定义成Bean实例 -->
<bean id="theAge" class=
	"org.springframework.beans.factory.config.PropertyPathFactoryBean">
	<!-- 确定目标Bean,表明theAge Bean来自哪个Bean的getter方法的返回值 -->
	<property name="targetBeanName" value="person"/>
	<!-- 使用复合属性来指定getter方法。son.age代表getSon().getAge() -->
	<property name="propertyPath" value="son.age"/>
</bean>
<!-- 简化配置
<util:property-path id="theAge" path="person.son.age"/> -->

<4>目标Bean是容器中没有的Bean(嵌套Bean实例)

<!-- 将基本数据类型的属性值定义成Bean实例 -->
<bean id="theAge2" class=
	"org.springframework.beans.factory.config.PropertyPathFactoryBean">
	<!-- 确定目标Bean,表明theAge2 Bean来自哪个Bean的属性。
		此处采用嵌套Bean定义目标Bean -->
	<property name="targetObject">
		<!-- 目标Bean不是容器中已经存在的Bean, 而是如下的嵌套Bean-->
		<bean class="org.crazyit.app.service.Person">
			<property name="age" value="30"/>
		</bean>
	</property>
	<!-- 指定theAge2 Bean来自目标bean的哪个getter方法,age代表getAge() -->
	<property name="propertyPath" value="age"/>
</bean>

5.2 获取field值

  通过FieldRetrievingFactoryBean类,可以访问类的静态Field或对象的实例Field值。FieldRetrievingFactoryBean获得指定Field的值之后,即可将获取的值注入其他Bean,也可直接定义成新的Bean。
  使用FieldRetrievingFactoryBean访问Field值可分为两种情形:

  1. 如果要访问的Field是静态Field,则需要指定:
    • 调用哪个类。由FieldRetrievingFactoryBean的setTargetClass(Class<?> targetClass)方法指定
    • 访问哪个Field。由FieldRetrievingFactoryBean的setTargetField(String targetField)方法指定
  2. 如果要访问的Field是实例Field,则需要指定:
    • 访问哪个对象。由FieldRetrievingFactoryBean的setTargetObject(Object targetObject)方法指定
    • 访问哪个Field。由FieldRetrievingFactoryBean的setTargetField(String targetField)方法指定

注:我不知道为什么书中没有说明,但是还有一种获得静态Field的方式是通过setStaticField(String staticField)方法来指定,其中staticField参数是这个静态Field的全路径名,示例如下:

<!-- 将指定类的静态Field值定义成容器中的Bean实例 -->
<bean id="theAge2" class=
	"org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
	<!-- staticField指定访问哪个类的哪个静态Field -->
	<property name="staticField" 
		value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>

  <util:constant.../>元素可作为FieldRetrievingFactoryBean访问静态Field的简化配置,使用该元素可指定如下两个属性:

  • id:该属性指定将Field的值定义成名为id的Bean实例
  • static-field:该属性指定访问哪个类的哪个静态Field

  对于FieldRetrievingFactoryBean的第一种用法,与介绍FactoryBean时开发的GetFieldFactoryBean基本相同。对于FieldRetrievingFactoryBean第二种用法,在实际编程中几乎没多大用处,原因是根据良好封装原则,Java类的实例Field应用private修饰,并使用getter和setter来访问和修改。FieldRetrievingFactoryBean则要求实例Field以public修饰。
使用方式与上面的获取属性值类同就不详细介绍了,只贴出示例配置,如下:

<!-- 将指定类的静态Field值定义成容器中的Bean实例-->
<bean id="theAge1" class=
	"org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
	<!-- targetClass指定访问哪个目标类 -->
	<property name="targetClass" value="java.sql.Connection"/>
	<!-- targetField指定要访问的Field名 -->
	<property name="targetField" value="TRANSACTION_SERIALIZABLE"/>
</bean>

<!-- 简化配置
<util:constant id="theAge1" 
static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/> -->

<!-- 将指定类的静态Field值定义成容器中的Bean实例 -->
<bean id="theAge2" class=
	"org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
	<!-- staticField指定访问哪个类的哪个静态Field -->
	<property name="staticField" 
		value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>

<bean id="son" class="org.crazyit.app.service.Son">
	<property name="age">
		<!-- 指定java.sql.Connection.TRANSACTION_SERIALIZABLE
			作为调用setAge()方法的参数值 -->
		<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE" class=
		"org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
	</property>
</bean>

<!-- 简化配置
<bean id="son" class="org.crazyit.app.service.Son">
	<property name="age">
		<util:constant
		static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
	</property>
</bean> -->

5.3 获取方法返回值

  通过MethodInvokingFactoryBean工厂Bean,可调用任意类的类方法,也可调用任意对象的实例方法,如果调用的方法有返回值,则即可将该指定的方法的返回值定义成容器中的Bean,也可将指定方法的返回值注入给其他Bean。
  使用MethodInvokingFactoryBean来调用任意方法时,可分为两种情形:

  1. 如果希望调用的方法时静态方法,则需要指定:
    • 调用哪个类。通过MethodInvokingFactoryBean的setTargetClass(String targetClass)方法指定
    • 调用哪个方法。通过MethodInvokingFactoryBean的setTargetMethod(String targetMethod)方法指定
    • 调用方法的参数。通过MethodInvokingFactoryBean的setArguments(Object[] arguments)方法指定。如果希望代用的方法无须参数,则可以省略该配置
  2. 如果希望调用的方法是实例方法,则需要指定:
    • 调用哪个对象。通过MethodInvokingFactoryBean的setTargetObject(Object targetObject)方法指定
    • 调用哪个方法。通过MethodInvokingFactoryBean的setTargetMethod(String targetMethod)方法指定
    • 调用方法的参数。通过MethodInvokingFactoryBean的setArguments(Object[] arguments)方法指定。如果希望代用的方法无须参数,则可以省略该配置

下面将一段Java代码使用XML 进行配置出来:

JFrame win=new JFrame("我的窗口");
JTextArea jta= new JTextArea(7,40);
win.add(new JScrollPane(jta));
JPanel jp=new JPanel();
win.add(jp,BorderLayout.SOUTH);
JButton jb1=new JButton("确定");
jp.add(jb1);
JButton jb2=new JButton("确定");
jp.add(jb2);
win.pack();
win.setVisible(true);

使用XML配置文件将上面这段Java代码配置出来如下:

<?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>

详细源代码参见:codes\07\7.10\MethodInvokingFactoryBean

5.4 总结

  上面示例证实了一点:几乎所有的Java代码都可以通过Spring XML配置文件配置出来。不难发现:Spring框架的本质其实就是通过XML配置来执行Java代码,因此几乎可以把所有Java代码放到Spring配置文件中管理。归纳一下:

  • 调用构造器创建对象(包括使用工厂方法创建对象),用<bean.../>元素
  • 调用setter方法,用<property.../>
  • 调用getter方法用PropertyPathFactory或<util:property-path.../>元素
  • 调用普通方法,用MethodInvokingFactoryBean工厂Bean
  • 获取Field的值,用FieldRetrievingFactoryBean或<util:constant.../>元素

  虽然所有的Java代码都可以放在Spring配置文件中管理,但是过度使用XML配置文件不仅使得配置文件更加臃肿,难以维护,而且导致程序的可读性严重下降。
  一般来说,应该将如下两类信息放到XML配置文件中管理

  • 项目升级、维护时经常需要改动的信息
  • 控制项目内各组件耦合关系的代码

  这样就体现了Spring IoC容器的作用:将原来使用Java代码管理的耦合关系,提取到XML中进行管理,从而降低了各组件之间的耦合,提高了软件系统的可维护性。
参考资料:

赞赏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值