第三部分 核心技术
sunRainAmazing
参考文档的这一部分涵盖了所有与Spring框架完全一致的技术。
其中最重要的是Spring框架的反向控制(IoC)容器。Spring框架的IoC容器的彻底处理紧随其后,全面覆盖了Spring面向方面编程(AOP)技术。Spring框架有自己的AOP框架,它在概念上易于理解,并且成功地解决了Java企业编程中80%的AOP要求的最佳点。
Spring提供了与AspectJ的集成(目前是最丰富的 - 在功能方面 - 当然也是Java企业领域最成熟的AOP实现)。
第7章 IoC容器
第8章 资源
第9章 验证,数据绑定和类型转换
第10章 Spring表达语言(Spel)
第11章 面向方面编程与Spring
第12章 Spring AOP API
7 IoC 容器
7.1 Spring IoC容器和bean概述
本章介绍了Spring框架实现控制反转(IoC) [1] 的原理。IoC也被称作 依赖注入 (DI)。
它是一个处理对象依赖项的过程,也就是说,和他们一起工作的其他的对象,只有通过构造参数、工厂方法参数或者(属性注入)通过构造参数实例化或通过工厂方法返回对象后再设置属性。
当创建bean后,IoC容器再将这些依赖项注入进去。这个过程基本上是反转的,因此得名 控制反转 (IoC), 它通过使用直接构造类来控制其依赖关系的实例化或位置,或者诸如服务定位器模式之类的机制。
org.springframework.beans和org.springframework.context包是Spring框架IoC容器的基础。 beanFactory接口提供了一个先进的配置机制能够管理任何类型的对象。
总之,beanFactory提供了配置框架和基本功能,ApplicationContext则添加了更多的企业特定的功能。
ApplicationContext是beanFactory的一个完整的超集,并且在本章专门用于指代Spring容器。
关于更多使用beanFactory替代ApplicationContext的信息,参考Section 5.16,”The beanFactory”。
在Spring中,被Spring IoC 容器 管理的这些来自于应用主干的这些对象称作 beans 。
bean是一个由Spring IoC容器进行实例化、装配和管理的对象。此外,bean只是你应用中许多对象中的一个。
beans以及他们之间的 依赖关系 是通过容器使用 配置元数据 反应出来。
7.2 容器概述
org.springframework.context.ApplicationContext接口代表了Spring IoC容器,并且负责上面提到的beans的实例化、配置和装配。
容器通过读取配置元数据获取对象如何实例化、配置和装配的指示。配置元数据可以用XML、Java注解或Java代码来描述。
它允许你表示组成你应用的对象,以及对象间丰富的依赖关系。
Spring提供了几个开箱即用的ApplicationContext接口的实现。
在独立的应用程序中,通常创建 ClassPathXmlApplicationContext 或FileSystemXmlApplicationContext的实例。
虽然XML是定义配置元数据的传统格式,但是你可以指示容器使用Java注解或者代码作为元数据格式,你需要通过提供少量XML配置声明支持这些额外的元数据格式。
在大多数的应用场景,不需要显式的代码来实例化一个或多个Spring IoC容器。
例子,在wei应用中,在应用的web.xml文件中,简单的8行样板式的xml配置文件就足够了。
如果你使用 Spring Tool Suite 的Eclipse开发环境,你只需要点几下鼠标或者键盘就可以轻松的创建这个配置。
下面的图表是一个Spring工作的高级别视图。
你的应用程序类都通过配置元数据进行关联,所以在ApplicationContext创建和初始化后,你就有了一个完全配置和可执行的系统或应用程序。
Figure 5.1. Spring IoC容器
7.2.1 配置元数据
如上图所示,Spring IoC容器使用了一种 配置元数据 的形式,这些配置元数据代表了你作为一个应用开发者告诉Spring容器如何去实例化、配置和装备你应用中的对象。
配置元数据通常使用一个简单和直观的XML格式,本章大部分都使用这种格式来表达Spring IoC容器概念和特性。
【注】基于XML配置的元数据 不是 唯一允许用来配置元数据的一种形式。Spring IoC容器本身是 完全 和元数据配置书写的形式解耦的。这些天,许多开发者在他们的Spring应用中选择使用基于Java的配置的元数据形式。
更多有关在Spring容器中使用其他形式的元数据的内容,请查阅:
1. 基于注解的配置: Spring 2.5引入基于注解的配置元数据。
2. 基于Java的配置: 从Spring 3.0开始,由Spring JavaConfig提供的许多功能已经成为Spring框架的一部分。因此,你可以通过Java而不是XML文件来定义外部应用程序的bean类。使用这些新的功能,请看@Configuration、@bean、@Import和@DependsOn 注解。
Spring配置包括至少一个且通常多个由容器管理的bean定义。在基于XML配置的元数据中,这些beans配置成一个<bean/>
元素,这些<bean/>
元素定义在顶级元素<beans/>
的里面。在Java配置中通常在一个@Configuration注解的类中,在方法上使用@bean注解。
这些bean定义对应的实际对象组成了你的应用。通常你会定义服务层对象、数据访问层对象(DAO),展现层对象如Struts的Action实例,底层对象如Hibernate的SessionFactories,JMS的Queues等等。一般很少会在容器中配置细粒度的领域对象,因为通常是DAO和业务逻辑负责创建和加载领域对象。但是你可以使用Spring集成AspectJ来配置IoC容器之外创建的对象。查看在Spring中使用AspectJ依赖注入领域对象。
下面的例子演示了基于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"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id = "..." class = "...">
<!-- 在这里写 bean 的配置和相关引用 -->
</bean>
<bean id = "..." class = "...">
<!-- 在这里写 bean 的配置和相关引用 -->
</bean>
<!-- 更多bean的定义写在这里 -->
</beans>
id属性是一个用来识别每一个独立bean定义的字符串。class属性定义了bean的类型,这个属性需要使用bean类的全限定名称。id属性的值可以被其他的bean对象引用。这个例子中没有引用其他bean,查看bean依赖获取更多信息。
7.2.2 实例化容器
实例化Spring IoC容器很容易。将一个或多个位置路径提供给ApplicationContext的构造方法就可以让容器加载配制元数据,可以从多种外部资源进行获取,例如文件系统、Java的CLASSPATH等等。
ApplicationContext context =
new ClassPathXmlApplicationContext( new String({"services.xml","daos.xml"});
【注】在你了解Spring的IoC容器之后,若想知道更多有关Spring的Resource的更多内容,它的介绍在Chapter 6, Resources,它提供了一个方便的机制来读取URI中的InputStream。Resource路径是用来构建应用程序上下文的,详细内容请看Section 6.7,”应用上下文和资源路径”。
下面的例子是服务层对象(services.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"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd" >
<! - 服务 - >
<bean id = "petStore" class = "org.springframework.samples.jpetstore.services.PetStoreServiceImpl" >
<property name = "accountDao" ref = "accountDao" />
<property name = "itemDao" ref = "itemDao" />
<! - 这个bean的附加协作者和配置就在这里 - >
</bean>
<! - 服务的更多bean定义到这里 - >
</beans>
下面的例子是数据访问层daos.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"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd" >
<bean id = "accountDao"
class = "org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao" >
<! - 这个bean的其他协作者和配置去这里 - >
</bean>
<bean id= "itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- 这个bean的附加协作者和配置去这里 -- >
</bean>
<!-- 数据访问对象的更多bean定义到这里 -- >
</beans>
在上面的例子中,服务层包含了PetStoreServiceImpl类和两个类型为JpaAccountDao和JpaItemDao(基于JPA对象/关系映射标准)的数据访问对象。property name元素指代Javabean属性的名称,ref元素引用了另一个bean定义的名称。id和ref直接的这种关系表达出了这两个合作对象间的依赖关系。配置对象直接依赖关系的详细信息,请参见依赖关系。
编写基于XML的配置元数据
bean定义可以跨越多个XML文件是非常有用的。通常每个独立的XML配置文件表示一个逻辑层或者是你架构中的一个模块。
你可以使用应用上下文的构造方法从多个XML片段中加载bean的定义。像上面例子中出现过的一样,构造方法可以接收多个Resource位置。或者可以在bean定义中使用一个或多个从其他的配置文件引入bean定义。例如:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
上面的例子中,外部的bean定义从services.xml、messageSource.xml和themeSource.xml这三个文件中加载。所有的位置路径都是相对于定义执行导入的文件,所以 services.xml必须和当前定义导入的文件在相同的路径下。而messageSource.xml和themeSource.xml必须在当前定义导入的文件路径下的resources路径下。你可以看到,这里忽略了反斜杠,由于这里的路径是相对的,因此建议 不使用反斜杠。这些被引入文件的内容会被导入进来,包含顶层的<beans/>
元素,它必须是一个符合Spring架构的有效的XML bean定义。
使用一个相对”../”路径引用父目录中的配置是允许的,但是不推荐这么做。如果这么做就产生了一个当前应用外的引用依赖。特别不推荐在使用”classpath:”路径的时候,在运行的时候解析选择”最近”的classpath跟路径,然后在找父目录。Classpath配置的更改可能会导致选择一个不同的、错误的目录。
通常情况下,你可以使用完全限定的资源位置来代替相对路径,例如:”file:C:/config/services.xml”或”classpath:/config/services.xml”。但是请注意,你的应用可能和一个特定的绝对路径耦合了。通常更合适的方式是通过间接的方式来使用绝对路径,例如通过”${…}”占位符,在运行时解析JVM的系统属性。
import指令是由beans命名空间本身提供的一个功能。Spring提供的一些XML命名空间,例如”上下文”和”util”命名空间,可以使用超出纯bean定义的进一步配置功能。
Groovy bean定义DSL
作为外部配置元数据的另一个例子,bean定义也可以在Spring的Groovy bean Definition DSL中表示,如Grails框架所知。通常,这种配置将生活在一个”.groovy”文件中,结构如下:
bean {
dataSource(BasicDataSource){
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting" ]
}
sessionFactory(SessionFactory){
dataSource = dataSource
}
myService(MyService){
nestedbean = {Anotherbean bean - >
dataSource = dataSource
}
}
}
这种配置风格在很大程度上等同于XML bean定义,甚至支持Spring的XML配置命名空间。它还允许通过”importbeans”指令导入XML bean定义文件。
7.2.3 使用容器
这ApplicationContext是一个能够维护不同bean及其依赖关系的注册表的高级工厂的界面。使用该方法T getbean(String name, Class requiredType)可以检索bean的实例。
这ApplicationContext使您能够读取bean定义并访问它们,如下所示:
//创建和配置bean
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml","daos.xml");
//检索配置实例
PetStoreService service= context.getbean( " PetStore",PetStoreService.class);//使用配置的实例
List <String> userList = service.getUsernameList();
使用Groovy配置,引导看起来非常相似,只是一个不同的上下文实现类,它是Groovy感知的(但也可以理解XML bean定义):
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy","daos.groovy");
最灵活的变体GenericApplicationContext与读者代表结合使用,例如与XmlbeanDefinitionReaderXML文件:
GenericApplicationContext context = new GenericApplicationContext();
new XmlbeanDefinitionReader(context).loadbeanDefinitions("services.xml","daos.xml");
context.refresh();
或与GroovybeanDefinitionReaderGroovy文件:
GenericApplicationContext context = new GenericApplicationContext();
new GroovybeanDefinitionReader(context).loadbeanDefinitions("services.groovy","daos.groovy");
context.refresh();
如果需要,这样的用户可以在相同的情况下进行混合和匹配ApplicationContext,从不同的配置源读取bean定义。
然后,您可以使用它getbean来检索您的bean的实例。该ApplicationContext 界面还有其他一些检索bean的方法,但理想情况下,您的应用程序代码不应该使用它们。实际上,您的应用程序代码根本不应该调用该 getbean()方法,因此根本不需要依赖Spring API。例如,Spring与Web框架的集成为各种Web框架组件(如控制器和JSF管理的bean)提供依赖注入,允许您通过元数据(例如自动连线注释)声明对特定bean的依赖。
7.3 bean概述
Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的,例如,以XML 定义的形式 。
在容器本身中,这些bean定义被表示为beanDefinition 对象,其中包含以下元数据(以及其他信息):
一个包合格的类名称:通常是要定义的bean的实际实现类。
bean行为配置元素,它说明bean应该在容器(作用域,生命周期回调等)中的行为。
引用bean来完成工作所需的其他bean; 这些引用也称为协作者或依赖关系。
在新创建的对象中设置的其他配置设置,例如,在管理连接池的bean中使用的连接数,或池的大小限制。
该元数据转换为组成每个bean定义的一组属性。
表 7.1. bean定义
7.3.1 bean命名约定
约定是在命名bean时使用标准Java约定来实例字段名称。也就是说,bean名称以小写字母开头,并从那时起就开始绘制。这种名称的例子将是(不带引号)’accountManager’, ‘accountService’,’userDao’,’loginController’,等等。
命名bean一直使您的配置更容易阅读和理解,如果您使用的是Spring AOP,则可以通过名称相关的一组bean应用建议来帮助您。
通过类路径中的组件扫描,Spring会根据以上规则为未命名组件生成bean名称:基本上,使用简单的类名称并将其初始字符转换为小写。然而,在(异常)特殊情况下,如果有多个字符,并且第一个和第二个字符都是大写字母,则原始外壳将被保留。这些是与java.beans.Introspector.decapitalize(Spring在这里使用的)定义的规则相同的规则。
在bean定义之外混用一个bean
在bean定义本身中,可以通过使用由id属性指定的多达一个名称和属性中任意数量的其他名称组合为bean提供多个名称name。这些名称可以是同一个bean的等效别名,并且对于某些情况是有用的,例如允许应用程序中的每个组件通过使用特定于该组件本身的bean名称来引用常见的依赖关系。
但是,指定bean实际定义的所有别名并不总是足够的。有时候,为其他地方定义的bean引入别名。这在大型系统中通常是这样,其中配置在每个子系统之间分配,每个子系统具有其自己的一组对象定义。在基于XML的配置元数据中,您可以使用该<alias/>
元素来完成此操作。
<alias name = "fromName" alias = "toName" />
在这种情况下,同名容器中的一个bean fromName,在使用此别名定义之后,也可能被称为toName。
例如,子系统A的配置元数据可以通过名称来引用DataSource subsystemA-dataSource。子系统B的配置元数据可以通过名称引用DataSourcesubsystemB-dataSource。当组合使用这两个子系统的主应用程序时,主应用程序通过名称引用DataSource myApp-dataSource。要使所有三个名称都引用相同的对象,您将添加到MyApp配置元数据中以下别名定义:
<alias name = "subsystemA-dataSource" alias = "subsystemB-dataSource" />
<alias name = "subsystemA-dataSource" alias = "myApp-dataSource" />
现在,每个组件和主应用程序可以通过唯一的名称来引用dataSource,并保证不会与任何其他定义(有效创建命名空间)冲突,但它们引用同一个bean。
Java的配置
如果您使用Java配置,则@bean可以使用注释来提供别名,详见第7.12.3节”使用@bean注释”。
7.3.2实例化bean
bean定义本质上是创建一个或多个对象的配方。当询问时,容器会查看命名bean的配方,并使用由该bean定义封装的配置元数据创建(或获取)实际对象。
如果使用基于XML的配置元数据,则可以在元素的class属性中指定要实例化的对象的类型(或类)<bean/>
。该 class属性内部是 实例Class上的属性beanDefinition,通常是必需的。(有关异常,请参见 “使用实例工厂方法进行实例化”一节以及第7.7节”bean定义继承”一节。)Class以下两种方式之一使用该属性:
通常,要指定要在容器本身通过反复调用其构造函数直接创建bean的情况下构造的bean类,这与使用new运算符的Java代码相当。
要指定包含static将被调用以创建对象的工厂方法的实际类,在容器调用类创建bean 的static 工厂方法的情况较少的情况下 。从调用staticfactory方法返回的对象类可能是完全相同的类或另一个类。
内部类名 如果要为static嵌套类配置bean定义,则必须使用嵌套类的二进制名称。
例如,如果您Foo在com.example程序包中有一个类,并且 Foo该类有一个static被调用的嵌套类Bar,则’class’ bean定义上的属性值将为…
com.example.Foo
实例化与构造函数
当通过构造函数方法创建一个bean时,所有普通类都可以使用并与Spring兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定方式编码。只需指定bean类即可。但是,根据特定bean使用的IoC类型,您可能需要一个默认(空)构造函数。
Spring IoC容器几乎可以管理您想要管理的任何类; 它不限于管理真正的Javabeans。大多数Spring用户喜欢实际的Javabean,只有默认(无参数)构造函数和容器中的属性之后建立适当的setter和getter。您还可以在容器中拥有更多异国情调的非bean风格的类。例如,如果您需要使用绝对不遵守Javabean规范的旧版连接池,那么Spring也可以管理它。
使用基于XML的配置元数据,您可以如下指定您的bean类:
<bean id = "examplebean" class = "examples.Examplebean" /><bean name = "anotherExample" class = "examples.ExamplebeanTwo" />
有关在构造函数(如果需要)提供参数的机制以及在构造对象之后设置对象实例属性的机制的详细信息,请参阅 注入依赖关系。
使用静态工厂方法实例化
定义使用静态工厂方法创建的bean时,您可以使用该class 属性来指定包含static工厂方法的类和一个factory-method名称为的属性,以指定工厂方法本身的名称。您应该可以调用此方法(随后可以使用可选参数),并返回一个活动对象,随后将其视为通过构造函数创建的对象。这种bean定义的一个用途是static在旧代码中调用工厂。
以下bean定义指定将通过调用工厂方法创建该bean。定义没有指定返回对象的类型(class),只指定了包含工厂方法的类。在这个例子中,该createInstance() 方法必须是静态方法。
<bean id = "clientService" class = "examples.ClientService" factory-method = "createInstance" />
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService(){}
public static ClientService createInstance(){
return clientService;
}
}
有关从工厂方法提供(可选)参数的机制以及从工厂返回对象后设置对象实例属性的详细信息,请参阅依赖关系和配置。
实例化使用实例工厂方法
与通过静态工厂方法的实例化类似,使用实例工厂方法实例化从容器调用现有bean的非静态方法来创建新bean。要使用此机制,请将该class属性保留为空,并在factory-bean属性中指定包含要调用以创建对象的实例方法的当前(或父/祖先)容器中的bean的名称。使用factory-method属性设置工厂方法本身的名称。
<! - 工厂bean,它包含一个名为createInstance()的方法 - >
<bean id = "serviceLocator" class = "examples.DefaultServiceLocator" >
<! - 注入此定位器bean所需的任何依赖关系 - >
< /bean>
<! - 要通过工厂bean创建的bean - >
<bean id = "clientService" factory-bean = "serviceLocator" factory-method = "createClientServiceInstance" />
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private DefaultServiceLocator(){}
public ClientService createClientServiceInstance(){
return clientService;
}
}
一个工厂类也可以持有多种工厂方法,如下所示:
<bean id = "serviceLocator" class = "examples.DefaultServiceLocator" >
<! - 注入此定位器bean所需的任何依赖关系 - >
</bean>
<bean id = "clientService" factory-bean = "serviceLocator"
factory-method = "createClientServiceInstance" />
<bean id = "accountService" factory-bean = "serviceLocator"
factory-method = "createAccountServiceInstance" />
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
private DefaultServiceLocator(){}
public ClientService createClientServiceInstance(){
return clientService;
}
public AccountService createAccountServiceInstance(){
return accountService;
}
}
这种方法表明,工厂bean本身可以通过依赖注入(DI)进行管理和配置。详细信息请参见依赖关系和配置。
在Spring文档中,工厂bean是指通过实例或 静态工厂方法创建对象的Spring容器中配置的bean 。相反, Factorybean(注意大写)是指Spring特定的Factorybean。
7.4 依赖
典型的企业应用不会单一得由一个对象组成(或者说Spring术语中的bean)。即便是最简单的系统也需要多个对象共同协作来展示给终端用户一个条理分明的应用。接下来的这一节内容会阐述如何定义多个独立于应用程序的bean一起协同工作完成目标。
7.4.1 依赖注入
依赖注入 (DI) 是指对象之间的依赖关系,也就是说,一起协作的其他对象只通过构造器的参数、工厂方法的参数或者由构造函数或者工厂方法创建的对象设置属性。因此容器的工作就是创建bean并注入那些依赖关系。这个过程实质通过直接使用类的构造函数或者服务定位模式来反转控制bean的实例或者其依赖关系的位置,因此它有另外一个名字叫控制反转 (IoC)。
运用了DI原理代码会更加清晰并且由依赖关系提供对象也将使各层次的松耦合变得更加容易。对象不需要知道其依赖关系,也不需要知道它的位置或者类之间的依赖。因此,你的类会更容易测试,尤其是当依赖关系是在接口或者抽象基本类,
DI主要有两种注入方式,即构造器注入和Setter注入。
构造器注入
基于构造器注入 DI通过调用带参数的构造器来实现,每个参数代表着一个依赖关系。此外,还可通过给 静态 工厂方法传参数来构造bean。接下来的介绍将认为给构造器传参数和给静态工厂方法传参数是类似的。下面展示了只能使用构造器来注入依赖关系的例子。请注意这个类并没有什么 特别 之处,它只是个普通的POJO,不依赖于特殊的接口,抽象类或者注解。
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...
}
构造器参数解析
构造器参数通过参数类型进行匹配。如果构造器参数的类型定义没有潜在的歧义,那么bean被实例化的时候,bean定义中构造器参数的定义顺序就是这些参数的顺序并依次进行匹配。看下面的代码:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
//...
}
}
上述代码中参数类型定义不存在潜在的歧义,我们假设Bar和Baz之间不存在继承关系。因此,下面代码中在元素<constructor-arg/>
的配置即使没有明确指定构造参数顺序或者类型也会起作用。
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
当另一个bean被引用,它的类型是已知的,并且匹配也没问题(跟前面的例子一样)。当我们使用简单类型,比如<value>true</value>
。Spring并不能知道该值的类型,不借助其他帮助Spring将不能通过类型进行匹配。看下面的类:
package examples;
public class Examplebean {
//Number of years to 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>
使用index属性来显式指定构造参数的索引,比如:
<bean id="examplebean" class="examples.Examplebean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/></bean>
使用索引可以解决多个简单值的混淆,还能解决构造方法有两个相同类型的参数的混淆问题,注意index是从0开始的。
你也可以使用构造器参数命名来指定值的类型:
<bean id="examplebean" class="examples.Examplebean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/></bean>
请记住为了使这个起作用,你的代码编译时要打开编译模式,这样Spring可以检查构造方法的参数。如果你不打开调试模式(或者不想打开),也可以使用@ConstructorProperties JDK注解明确指出构造函数的参数。下面是简单的例子:
package examples;
public class Examplebean {
//Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public Examplebean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
Setter注入
在调用了无参的构造方法或者无参的静态工厂方法实例化bean之后,容器通过回调bean的setter方法来完成setter注入。接下来的例子将展示只使用setter注入依赖。这个类是个普通的Java类
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所管理的beans支持构造函数注入和setter注入,在一些依赖已经使用构造器注入之后它还支持setter注入。你可以以beanDefinition的形式配置依赖,它能根据指定的PropertyEditor实现将属性从一种格式转化为另外一种格式。但是,大多数Spring的使用者不会直接使用这些类(也就是通过编程的形式),而是采用XML配置这些bean,注解的组件(即用@Component,@Controller等注解类),或者基于@Configuration类的@bean方法。本质上这些资源会转换成beanDefinition的实例并且用于加载整个Spring IoC容器实例。
构造器注入还是Setter注入
因为你可以混合使用构造器注入和setter注入, 强制性依赖关系 时使用构造器注入, 可选的依赖关系 时使用setter方法或者配置方法是比较好的经验法则。请注意@Required注解在setter方法上可以注入所需要的依赖。
Spring开发团队一般主张当实现的应用组件是不可变对象时使用构造器注入并且要保证所需的依赖不是null。此外构造器注入的组件总是返回给客户端(或调用)完整的初始化状态。另一方面,大量的构造器参数造成糟糕的代码异味,这表明类可能承担了太多的职责应该需要重构以便更好的适当分离要解决的问题。
setter注入主要只用作可选的依赖,这些依赖分配合理的缺省值。否则,当代码使用依赖时必须进行非空检查。setter注入的一个好处是setter方法使得这个类的对象在以后的某个时候还可合理的重新配置或者重新注入。JMX Mbeans的管理就是一个很好的setter注入例子。
使用DI的风格可以使特定的类更有意义。有时,使用第三方类时你并没有资源,那么这个选择很适合你。举个例子,如果第三方类并不公开任何setter方法,那么构造器注入可能是唯一可用的依赖注入方式。
依赖解决步骤
容器解决依赖问题通常有以下几个步骤:
描述所有bean的ApplicationContext创建并根据配置的元数据初始化。配置的元数据可以通过置顶的XML,Java代码或者注解。
每个bean的依赖将以属性,构造器参数或者静态工厂方法的形式出现。当这些bean被创建时,这些依赖将会提供给该bean。
每个属性或者构造器参数既可以是一个实际的值也可以是容器中的另一个引用。
每个指定的属性或者构造器参数值必须能够被转换成特定的格式或构造参数所需的类型。默认情况下Spring会以String类型转换为各种内置类型,比如int,long,String, boolean 等。
当Spring容器创建时容器会校验每个bean的配置。但是在bean被实际创建 前,bean的值并不会被设置。那些单例类型的bean和被设置为预安装(默认)的bean会在容器创建时与容器同时创建。Scopes是在Section 5.5, “bean scopes”中定义的。同时,另外的bean只会在被需要时创建。伴随着bean被实际创建,作为该bean的依赖和它的依赖的依赖(以此类推)会被创建和分配。注意 这些依赖之间的解决会显示迟一些。
循环依赖
如果你主要使用构造器注入,很有可能会产生无法解决的循环依赖问题。 举个例子:A类需要通过构造器注入B类的实例,并且B类又需要通过构造器注入A类的实例。如果为类A和类B配置的bean被相互注入的话,Spring IoC容器在运行时会检测到这个循环依赖并且抛出一个beanCurrentlyInCreationException异常。
一个可能的解决方法是修改类的源代码,将构造器注入改为setter注入。或者只使用setter注入避免使用构造器注入。换句话说,虽然这并不被推荐使用,你可以使用setter注入配置循环依赖。 和通常的情况不同(没有循环依赖),bean A 和bean B之间的循环依赖将会导致其中一个bean在被完全初始化的之前被注入到另一个bean里(先有鸡先有蛋的问题)
通常你可以信赖Spring。在容器加载时Spring会检查配置,比如不存在的bean和循环依赖。当bean创建时,Spring尽可能迟得设置属性和依赖关系。这意味着即使Spring正常加载,在你需要一个存在问题或者它的依赖存在问题的对象时,Spring会报出异常。举个例子,bean因设置缺少或者无效的属性会抛出一个异常。因为一些配置问题存在将会导致潜在的可见性被延迟,所以默认ApplicationContext的实现bean采用提前实例化的单例模式。在实际需要之前创建这些bean会带来时间和内存的开销,当ApplicationContext创建完成时你会发现配置问题,而不是之后。你也可以重写默认的行为使得单例bean延迟实例化而不是提前实例化。
如果不存在循环依赖,当一个或者多个协助bean会被注入依赖bean时,每个协助bean必须在注入依赖bean之前 完全 配置好。这意味着如果bean A对bean B存在依赖关系, 那么Spring Ioc容器在调用bean A的setter方法之前会完全配置bean B。换句话说,bean会被实例化(如果不是采用提前实例化的单例模式),相关的依赖会被设置好,相关的lifecycle方法(比如configured init 方法或者Initializingbean callback 方法)会被调用
一些依赖注入的例子
接下来的Setter注入例子使用基于XML的配置元数据的方式。相应的Spring 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;
}
}
在前面的例子,我们看到Setter会匹配定义在XML里的属性,接下来的例子会使用构造器注入:
<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定义中指定的构造器参数会被用作Examplebean的构造器参数。
现在来看使用构造器的例子,Spring调用静态工厂方法来返回对象的实例:
<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"/>
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;
}
}
静态工厂方法参数由<constructor-arg/>
元素提供,实际上这和使用构造器是一样的。工厂方法 返回的类的类型并不一定要与包含静态工厂方法的类类型一致,虽然在这个例子中是一样的。 实例工厂方法(不是静态的)与此相同(除了使用factory-bean属性代替class属性外),所以这里不作详细讨论。
7.4.2 依赖配置详解
正如在前面章节所提到的,你可以定义bean的属性和构造器参数作为其他所管理的bean的依赖(协作), 或者是内联的bean。基于XML的Spring配置元数据支持使用<property/>
和 <constructor-arg/>
元素定义。
直接变量 (基本类型, String类型等)
<property/>
元素的value值通过可读的字符串形式来指定属性和构造器参数。Spring的conversion service 把String转换成属性或者构造器实际需要的类型。
<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>
接下来的例子使用p 命名空间简化XML配置
<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="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>
以上的XML更加简洁;但是,编码的错误只有在运行时才会被发现而不是编码设计的时候,除非你在定义bean的时候, 使用 IntelliJIDEA 或者 Spring Tool Suite (STS) 支持动态属性补全的IDE。IDE的帮助是非常值得推荐的。
你也可以配置java.util.Properties实例,就像这样:
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property></bean>
Spring容器使用Javabeans PropertyEditor把元素内的文本转换为java.util.Properties 实例。由于这种做法非常简单,所以这是Spring团队在很多地方采用内嵌的<value/>
元素代替value属性。
idref元素
idref元素用来将容器内其他bean的id(值是字符串-不是引用)传给元素<constructor-arg/>
或者 <property/>
<bean id="theTargetbean" class="..."/>
<bean id="theClientbean" class="...">
<property name="targetName">
<idref bean="theTargetbean" />
</property></bean>
上面的bean定义片段完全等同于(在运行时)下面片段:
<bean id="theTargetbean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetbean" /></bean>
第一种形式比第二种形式更好,因为使用idref标签允许容器在部署时验证引用的bean是否存在。 在第二种形式中,传给 client bean中属性targetName的值并没有被验证。 只有当 client bean完全实例化的时候错误才会被发现(可能伴随着致命的结果)。如果 client bean是原型 bean。那么这个错误和异常可能只有再容器部署很长一段时间后才能被发现。
【注】idref元素上的local属性在4.0之后不再支持,因为它不再提供普通bean的价值。当你升级到4.0 schema时需要修改存在的idref local为idref bean。
与ProxyFactorybean bean定义中使用<idref/>
元素指定AOP 拦截器配置 (版本不低于Spring 2.0)相同之处在于:当你指定拦截器名称的时候使用<idref/>
元素可以防止你拼错拦截器的id。
引用其他bean(协作者)
在 <constructor-arg/>
或者 <property/>
可以使用ref 元素。该元素用来将bean中指定属性的值设置为对容器的另外一个bean(协作者)的引用。 该引用bean将被作为依赖注入,而且再注入之前会被初始化(如果协作者是单例)。所有的引用最终都是另一个对象的引用。 bean的范围和验证依赖于你指定的bean,local,或者parent属性的id/name。
通过<ref/>
标签的bean属性定义目标bean是最常见的形式,通过该标签可以引用同一容器或者父容器任何bean,无论是否在 相同的xml文件中。xml的bean元素的值既可以目标bean的id属性也可以是其中一个目标bean的name 属性值。
<ref bean="somebean"/>
通过parent属性指定目标bean来创建bean的引用,该bean是当前容器下的父级容器。parent属性值既可以是目标bean的id也可以是 name属性值。而且目标bean必须在当前容器的父级容器中。使用parent属性的主要用途是为了用某个与父级容器中的bean同名的代理来包装父级容器中的一个bean。
<!-- 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>
【注】ref元素上的local属性4.0 beans xsd之后不在支持,因为它能提供的值不比普通bean多。当你升级到4.0schema时需要修改存在的ref local 为ref bean`。
内部bean
A <bean/>
element inside the <property/>
or <constructor-arg/>
elements defines a so-called inner bean. 所谓的内部bean就是指在 <property/>
或者<constructor-arg/>
元素内部使用<bean/>
定义bean。
<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 总是 匿名的 并且他们总是伴随着外部bean创建。同时将内部bean注入到包含该内部bean之外的bean是不可能的。
集合
在<list/>, <set/>, <map/>, 和<props/>
元素中,你可以设置值和参数分别对应Java的集合类型List, Set, Map, 和 Properties
<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>
map的key或者value值,或者set的value值还可以是以下任意元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器也支持集合的 合并。开发者可以定义parent-style<list/>
, <map/>, <set/> 或者<props/> 元素并, child-style 的<list/>, <map/>, <set/> 或者<props/>
元素继承和覆盖自父集合。也就是说。父集合元素合并后的值就是子集合的最终结果,而且子集中的元素值将覆盖父集中对应的值。
关于合并的章节涉及到了parent-child bean机制。不熟悉父子bean的读者可参见relevant section.
接下来的例子展示了集合的合并:
<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的定义中<props/>元素上的
merge=true属性的用法。当childbean被解析并且被容器初始化,产生的实例包含了
adminEmails ,Properties集合,其adminEmails将与父集合的adminEmails属性进行合并。
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子bean的Properties集合将从父<props/>
集成所有属性元素。同时子bean的support值将覆盖父集合的相应值。
<list/>, <map/>, 和 <set/>
集合类型的合并处理都基本类似。<list/>
元素某个方面有点特殊,这和List集合类型的语义学有关 换句话说,比如维护一个有序集合的值,父bean的列表内容将排在子bean李彪内容的前面。对于Map, Set, 和 Properties集合类型没有顺序的概念, 因此作为相关的Map, Set, 和 Properties实现基础的集合类型在容器内部排序的语义。
集合合并的限制
你不能合并两种不能类型的集合(比如Map 和 List),如果你这么做了将会抛出相应的异常。merge属性必须在继承 的子bean中定义。定义在父bean的集合属性上指定的merge属性是多余的并且得不到期望的合并结果。
强类型集合
Java 5 引入了泛型,这样你可以使用强类型集合。换句话说绳命一个只能包含String类型元素的Collection是可能的(比如)。 如果使用Spring来给bean注入强类型的Collection,你可以利用Spring的类型转换,在向强类型Collection添加元素前,这些元素将被转换。
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>
当foo bean的accounts 属性准备注入时,通过反射获得强类型Map<String, Float>
元素类型的泛型信息。Spring的底层类型转换 机制会把各种value元素值转换为Float,因此字符串9.99, 2.75 和3.99将会转换为实际的Float 类型。
Null和空字符串
Spring会把空属性当做空字符串处理。以下的基于XML配置的片段将email属性设置为空字符串。
<bean class="Examplebean">
<property name="email" value=""/></bean>
先前的例子等同于以下Java代码:
examplebean.setEmail("")
<null/>
元素处理null值,例如:
<bean class="Examplebean">
<property name="email">
<null/>
</property></bean>
上面的配置等同于下面的Java代码:
examplebean.setEmail(null)
XML使用p命名空间简化
使用p命名空间可以用bean 元素的属性代替<property/>
元素来描述属性值或者协作bean。
Spring支持名称空间的可扩展配置with namespaces,这些名称空间基于一种XML Schema定义。这章节涉及到的beans 配置都是定义在一个XML Schema文档理。但是p命名空间不是定义在XSD文件而是存在于Spring内核中。
下面的例子展示了两种XML片段,其结果是一样的:第一个使用了标准XML格式,第二种使用了p命名空间。
<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 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>
在例子中,使用p命名空间的bean定义有了一个叫email的属性。这告诉Spring要包含这个属性的声明。正如前面所说的, p命名空间不需要schema定义,因此你可以设置属性的名字作为bean的property的名字。
接下来的例子包括了两种以上bean的定义,都引用了另外一个bean。
<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 name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean></beans>
正如你看到的,例子不仅使用p命名空间包含了一个属性值,而且使用了一个特殊的格式声明了一个属性的引用。在第一个bean 定义中使用了<property name="spouse" ref="jane"/>
创建一个john bean 对jane bean的引用,第二个bean的定义使用了p:spouse-ref=”jane”,它们做了同样一件事情。在这个例子中spouse是属性名,而-ref部分声明了这不是一个直接的值而是另一个bean的引用。
【注】p命名空间没有标准XML格式那么灵活。举个例子,声明属性的引用是以Ref结尾的,采用p命名空间将会产生冲突,但是采用标准XML 格式则不会。我们建议你小心的选择并和团队成员交流你的想法,避免在XML文档中同时使用所有的三种方法。
XML shortcut with the c-namespace
和p命名空间the section called “XML使用p命名空间简化”类似,Spring3.1 引入了c命名空间,使用内联的构造参数代替嵌套的constructor-arg元素
让我们回顾一下the section called “构造器注入”使用c命名空间的例子:
<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="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
<!-- traditional declaration -->
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
<constructor-arg value="[email protected]"/>
</bean>
<!-- c-namespace declaration -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="[email protected]"/>
</beans>
和p命名空间约定的一样(bean的引用以-ref结尾),c命名空间使用它们的名称作为构造器参数。同时它需要声明即使它没有 XSD schema中定义(但是它存在于Spring内核中)
极少数的情况下构造器参数的名称不可用(通常字节码没有经过调试信息编译),可以使用备份进行参数索引。
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
【注】因为XML的语法,索引标记需要_主导作为XML属性名称,这不能已数字开头(即使某些IDE这么允许).
在实践中,构造器解析机智机制在匹配参数上相当高效。除非你需要这么做,我们建议您在配置中使用符号名称。
组合属性名称
当你设置bean属性时可以使用组合或者嵌套属性名称,只要路径上所有组件除了最终属性不为空。看以下bean的定义:
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" /></bean>
foo bean有一个fred属性,fred属性又有个bob属性,bob属性又有个属性,最后把 sammy属性设置值为123。为了是这个起作用,foo的fred属性和 fred的bob属性在bean被构造后都不能为空,否则会抛出NullPointerException异常。
7.4.3 使用 depends-on
如果一个bean是另外一个bean的依赖,这通常意味着这个bean可以设置成为另外一个bean的属性。在XML配置文件中你可以使用<<beans-ref-element,<ref/>element>>
实现依赖。但是某些时候bean之间的依赖并不是那么直接。举个例子:类的静态块初始化,比如数据库驱动的注册。`depends-on属性 可以同于当前bean初始化之前显式地强制一个或多个bean被初始化。下面的例子中使用了depends-on属性来指定一个bean的依赖。
<bean id="beanOne" class="Examplebean" depends-on="manager"/><bean id="manager" class="Managerbean" />
为了实现多个bean的依赖,你可以在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" />
【注】depends-on属性在bean的定义中可以指定初始化时的依赖和指定相应的销毁时的依赖,该依赖只针对于singleton bean 这样depends-on可以控制销毁顺序。
7.4.4 延迟初始化bean
ApplicationContext实现的默认行为就是再启动时将所有singleton bean提前进行实例化。 通常这样的提前实例化方式是好事,因为配置中或者运行环境的错误就会被立刻发现,否则可能要花几个小时甚至几天。如果你不想 这样,你可以将单例bean定义为延迟加载防止它提前实例化。延迟初始化bean会告诉Ioc容器在第一次需要的时候才实例化而不是在容器启动时就实例化。
在XML配置文件中,延迟初始化通过<bean/>
元素的lazy-init属性进行控制,比如:
<bean id="lazy" class="com.foo.ExpensiveToCreatebean" lazy-init="true"/><bean name="not.lazy" class="com.foo.Anotherbean"/>
当ApplicationContext实现上面的配置时,设置为lazy的bean将不会在ApplicationContext启动时提前实例化,而not.lazy
bean 却会被提前实例化。
但是当一个延迟加载的bean是单例bean的依赖,但这个单例bean又不是 延迟加载,ApplicationContext在启动时创建了延迟加载 的bean,因为它必须满足单例bean的依赖。因此延迟加载的bean会被注入单例bean,然而在其他地方它不会延迟加载。
你也可以使用<beans/>
元素上的default-lazy-init属性在容器层次上控制延迟加载。比如:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... --></beans>
7.4.5 自动装配协作者
Spring容器可以自动装配相互协作bean的关联关系。因此,如果可能的话,可以自动让Spring检测ApplicationContext的内容自动 处理协作者(其他bean)。自动装配有以下好处:
自动装配可以显著得减少指定属性或者构造器参数的需求。(其他的机制比如bean模板discussed elsewhere in this chapter在这方面也是由价值的)
当对象发生变化时自动装配可以更新配置。比如如果你需要给一个类添加依赖,那么这个依赖可以被自动满足而不需要你去修改配置。 因此自动依赖在开发时尤其有用,当系统趋于稳定时改为显式装配。
当使用XML配置脚注:[See Section 5.4.1, “依赖注入”],可以使用<bean/>
元素的autowire属性 为定义的bean指定自动装配模式。你可以指定自动装配per bean,选择那种方式来自动装配。
Table 7.2. 自动装配模式
byType 或者 constructor 自动装配模式也可以应用于数组和指定类型的集合。在这种情况下容器中的所有匹配的自动装配对象将 被应用于满足各种依赖。对于key值类型为String的强类型Map也可以自动装配。一个自动装配的Map value值将由所匹配类型的bean所填充。
你可以结合自动装配和依赖检查,后者将会在自动装配完成之后进行。
自动装配的局限性和缺点
在工程里一致使用自动装配,这将会工作得很好。如果自动装配并不常使用,只使用在一个或两个bean的定义上, 它可能会对开发者产生困扰。
考虑一下自动装配的局限性和缺点:
property 和 constructor-arg 显式的依赖设置总是会覆盖自动装配。你不能装配所谓的简单属性比如原始的Strings, 和 Classes (这样的简单属性数组也是)。这种缺陷是故意设计的。
自动装配没有显示编写精确。虽然在上面的表格提到的,Spring很小心得避免猜测模糊的情况,这可能会导致意想不到的结果。 Spring管理的对象之间的关系不再记录明确。
自动装配的依赖信息可能不能用于根据Spring容器生成文档的的工具。
在容器内部可能存在多个bean的定义与自动装配的setter方法或者构造器参数匹配。对于数组,集合或者Map来说,这不是问题。 但是对于单值依赖来说,就会存在模棱两可的问题。如果bean定义不唯一,装配时就会抛出异常。
针对于上述场景,你会有多个选项:
放弃自动装配以便于明确依赖关系。
在bean定义中通过设置autowire-candidate属性为false避免该bean自动装配,这将会在下一节中详细描述。
在bean定义中设置<bean/>
元素上的primary属性为true,将该bean设置为首选自动装配bean。
使用注解配置实现更加细粒度的控制,详情见Section 5.9, “Annotation-based container configuration”。
将bean排除在自动装配之外
在提前实例化bean的基础上,你可以将bean排除在自动装配之外。在Spring XML格式中,将元素中的autowire-candidate属性 设置为false。容器会使特定的bean定义不可于自动装配(包括注解配置比如@Autowired)
` 元素在它的default-autowire-candidates属性 接受一个或多个模式。譬如,为了限制
你也可以对使用bean名字进行模式匹配来对自动装配进行限制。顶层的
对于那些从来就不会被其他bean采用自动装配的方式注入的bean而言,这是有用的。不过这并不意味这被排除的bean自己就 不能使用自动装配来注入其他bean。更确切得说,该bean本身不会被考虑作为其他bean自动装配的候选者。
7.4.6 方法注入
在大部分的应用场景中,容器中的大部分bean是singletons类型的。当一个单例bean需要和另外一个单例bean, 协作时,或者一个费单例bean要引用另外一个非单例bean时,通常情况下将一个bean定义为另外一个bean的属性值就行了。不过对于具有不同生命周期的bean 来说这样做就会有问题了,比如在调用一个单例类型bean A的某个方法,需要引用另一个非单例(prototype)类型bean B,对于bean A来说,容器只会创建一次,这样就没法 在需要的时候每次让容器为bean A提供一个新的bean B实例。
上面问题的一个解决方法是放弃控制反转,你可以实现ApplicationContextAware接口来让bean A感知到容器, 并且在需要的时候通过使用使用getbean(“B”)向容器请求一个(新的)bean B实例。下面的例子使用了这个方法:
//a class that uses a stateful Command-style class to perform some processingpackage fiona.apple;
//Spring-API importsimport 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(Map commandState) {
//grab a new instance of the appropriate Command
Command command = createCommand();
//set the state on the (hopefully brand new) Command instance
command.setState(commandState);
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;
}
}
上面的例子并没有达到期望的效果,因为业务代码和Spring框架产生的耦合。方法注入,作为Spring Ioc容器的高级特性,可以以一种 干净的方法来处理这种情况。
你可以在 this blog entry 阅读更多关于方法注入的动机。
Lookup 方法注入
Lookup方法具有使容器覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例。在上述场景中,Lookup方法注入适用于原型bean。 Lookup方法注入的内部机制是Spring利用了CGLIB库在运行时生成二进制代码的功能,通过动态创建Lookup方法bean的子类从而达到复写Lookup方法的目的。
【注】为了使动态子类起作用,Spring容器要子类化的类不能是final,并且需要复写的方法也不能是final。同样的,要测试一个包含 抽象方法的类也稍微有些不同,你需要子集编写它的子类提供该抽象方法的实现。最后,作为方法注入目标的bean不能是序列化的。 在Spring 3.2之后再也没必要添加CGLIB到classpath,因为CGLIB的类打包在了org.springframework下并且在Spring核心JAR中有所描述。 这样做既方便,又避免了与其他使用了不同版本CGLIB的项目的冲突。
再看一下在之前代码片段中的CommandManager类,你可以发现Spring容器会自动复写createCommand()方法的实现。CommandManager类 将不会有任何的Spring依赖,下面返工的例子可以看出:
package fiona.apple;
//no more Spring imports!
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),此方法的定义需要按以下形式进行:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象,动态生成的子类会实现该方法。沟则,动态生成的子类会覆盖类里的具体方法。譬如:
<!-- a stateful bean deployed as a prototype (non-singleton) --><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>
标识为commandManager的bean在需要一个新的command bean实例时会调用createCommand()方法。你必须将command
bean部署为 原型(prototype)类型,如果这是实际需要的话。如果部署为singleton。那么每次将返回相同的 command
bean。
【注】感兴趣的读者也许发现了ServiceLocatorFactorybean(在包org.springframework.beans.factory.config下 )可以使用。
ServiceLocatorFactorybean的用法与另一个实用类ObjectFactoryCreatingFactorybean类似,
但是它允许你指定子集的lookup接口,不一定非要用Spring的lookup接口。要详细了解这种方法参考 这些类的javadoc。
自定义方法的替代方案
比起Lookup方法注入来,还有一种较少用到的方法注入形式,该注入能使用bean的另一个方法实现去替换自定义方法的方法。 除非你真的需要该功能,否则可以略过本节。
使用基于XML配置文件时,你可以使用replaced-method元素来达到用另一个方法来取代已有方法的目的。考虑下面的类,我们将覆盖 computeValue方法。
public class MyValueCalculator {
public String computeValue(String input) {
//some real code...
}
//some other methods...
}
实现org.springframework.beans.factory.support.MethodReplacer接口的类提供了新的方法定义。
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
//get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
下面的bean定义中指定了要配置的原始类和将要复写的方法:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method></bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
你可以在<replaced-method/>
元素中可以包含多个<arg-type/>
元素,这些元素用来标明被复写的方法签名。只有被复写的方法 存在重载的情况和同名的多个方法变体。为了方便,参数的类型字符可以采用全限定类名的简写。例如,下面的字符串都标识参数类型 为
java.lang.String:
java.lang.String
String
Str
因为参数的数目通常足够用来区别每个可能的选择,这个结晶能减少很多键盘输入的工作,它允许你只输入最短的匹配参数类型的字符串。