Version 5.2.2.RELEASE
参考文档的这一部分涵盖了Spring框架中不可或缺的所有技术。
其中最重要的是Spring框架的控制反转(IoC)容器。在对Spring框架的IoC容器进行了全面的处理之后,还对Spring的面向切面编程(AOP)技术进行了全面的介绍。Spring框架有自己的AOP框架,它在概念上很容易理解,并且成功地解决了Java企业编程中80%的AOP需求。
还提供了Spring与AspectJ集成的内容(目前在特性方面是最丰富的,当然也是Java企业应用中最成熟的AOP实现)。
IoC容器
本章讨论Spring的控制反转(IoC)容器。
1.1 介绍Spring IoC容器和bean
本章介绍了控制反转(IoC)原理的Spring框架实现。IoC也称为依赖项注入(DI)。
这是一个对象仅通过构造函数参数、工厂方法的参数或对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即使用的其他对象)的过程。
然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),通过使用类的直接构造或服务定位器模式等机制来控制其依赖项的实例化或位置。
org.springframework.beans
和org.springframework.context
包是Spring框架的IoC容器的基础。BeanFactory
接口提供了能够管理任何类型对象的高级配置机制。ApplicationContext
是BeanFactory
的子接口。它补充道:
- 更容易与Spring的AOP特性集成
- 消息资源处理(用于国际化)
- 事件发布
- 特定于应用程序层的上下文,如用于web应用程序的
WebApplicationContext
简而言之,BeanFactory
提供了配置框架和基本功能,而ApplicationContext
添加了更多企业特定的功能。ApplicationContext
是BeanFactory
的一个完整超集,在本章描述Spring的IoC容器时专门使用它。有关使用BeanFactory
而不是ApplicationContext
的更多信息,请参见[beans-beanfactory]。
在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是由Spring IoC容器实例化、组装和管理的对象。否则,bean只是应用程序中的众多对象之一。bean及其之间的依赖关系反映在容器使用的配置元数据中。
1.2 容器概述
org.springframework.context.ApplicationContext
interface表示Spring IoC容器,负责实例化、配置和组装bean。容器通过读取配置元数据获取关于要实例化、配置和组装哪些对象的指令。配置元数据用XML、Java注释或Java代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。
ApplicationContext
接口的几个实现由Spring提供。在独立应用程序中,通常创建ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
的实例。虽然XML一直是定义配置元数据的传统格式,但是可以通过提供少量XML配置以声明方式支持这些额外的元数据格式,从而指示容器使用Java注释或代码作为元数据格式。
在大多数应用程序场景中,不需要显式的用户代码来实例化一个或多个Spring IoC容器实例。例如,在web应用程序场景中,在应用程序的web.xml
文件中使用8行(大约)简单的样板web描述符XML通常就足够了(参见[context-create])。如果您使用Spring工具套件(一个eclipse支持的开发环境),那么只需几次鼠标单击或击键,您就可以轻松地创建这个样板配置。
下图显示了Spring如何工作的高级视图。您的应用程序类与配置元数据相结合,这样,在创建并初始化ApplicationContext
之后,您就有了一个完全配置和可执行的系统或应用程序。
图1所示 SpringIOC容器
1.2.1 配置元数据(Configuration Metadata)
如上图所示,Spring IoC容器使用配置元数据的一种形式。此配置元数据表示作为应用程序开发人员,您如何告诉Spring容器实例化、配置和组装应用程序中的对象。
配置元数据通常以简单直观的XML格式提供,本章的大部分内容都使用这种格式来传达Spring IoC容器的关键概念和特性。
基于xml的元数据不是惟一允许的配置元数据形式。Spring IoC容器本身与实际编写配置元数据的格式完全解耦。现在,许多开发人员为他们的Spring应用程序选择基于java的配置。
有关在Spring容器中使用其他形式的元数据的信息,请参阅:
- Annotation-based configuration:spring2.5引入了对基于注释的配置元数据的支持。
- Java-based configuration:从Spring 3.0开始,Spring JavaConfig项目提供的许多特性成为Spring核心框架的一部分。因此,您可以使用Java而不是XML文件来定义应用程序类外部的bean。要使用这些新特性,请参见@Configuration、@Bean、@Import和@DependsOn注释。
Spring配置由容器必须管理的至少一个和通常多个bean定义组成。基于xml的配置元数据将这些bean配置为顶级<beans/>
元素中的<bean/>
元素。Java配置通常在@Configuration
类中使用@bean
注释的方法。
这些bean定义对应于组成应用程序的实际对象。通常,您要定义服务层对象、数据访问对象(DAOs)、表示对象(如Struts Action
实例)、基础设施对象(如Hibernate SessionFactories
)、JMS队列
等等。通常,不会在容器中配置细粒度域对象,因为通常由DAOs和业务逻辑负责创建和加载域对象。但是,您可以使用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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
id
属性是标识单个bean定义的字符串。class
属性定义bean的类型并使用完全限定的类名。
id
属性的值引用协作对象。本例中没有显示用于引用协作对象的XML。有关更多信息,请参见Dependencies。
1.2.2 实例化一个容器
提供给ApplicationContext
构造函数的位置路径是资源字符串,它允许容器从各种外部资源(如本地文件系统、JavaCLASSPATH
等)加载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在了解了Spring的IoC容器之后,您可能想要了解更多关于Spring的资源抽象(如[参考资料]中所述),它提供了一种方便的机制,用于从URI语法中定义的位置读取InputStream。特别是,资源路径用于构造应用程序上下文,如[resources-app-ctx]中所述。
下面的示例显示了服务层对象(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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
在前面的示例中,服务层由PetStoreServiceImpl
类和JpaAccountDao
和JpaItemDao
类型的两个数据访问对象(基于JPA对象-关系映射标准)组成。property name
元素引用JavaBean属性的名称,而ref
元素引用另一个bean定义的名称。id
和ref
元素之间的这种链接表达了协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参阅Dependencies。
组合基于xml的配置元数据
让bean定义跨越多个XML文件可能很有用。通常,每个单独的XML配置文件表示体系结构中的逻辑层或模块。
可以使用应用程序上下文构造函数从所有这些XML片段加载bean定义。此构造函数接受多个资源
位置,如前一节所示。或者,使用一个或多个<import/>
元素来从另一个或多个文件加载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
必须位于导入文件位置之下的资源位置。
可以看到,前面的斜杠被忽略了。但是,考虑到这些路径是相对的,所以最好不要使用斜杠。根据Spring模式,要导入的文件的内容,包括顶级的<beans/>
元素,必须是有效的XML bean定义。
它是可能的,但不推荐,引用文件在父目录使用一个亲戚"…/”路径。这样做会在当前应用程序之外的文件上创建一个依赖项。
特别地,对于“classpath
”:URLs(例如,“classpath:.. ./services.xml
”),不推荐使用这个引用,因为运行时解析过程会选择“最近的”类路径根,然后查看它的父目录。
类路径配置更改可能导致选择不同的、不正确的目录。
您总是可以使用完全限定的资源位置,而不是相对路径:例如,file:C:/config/services.xml
或classpath:/config/services.xml
。
但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。
通常更可取的做法是为这些绝对位置保留一个间接的地址——例如,通过“${…}”占位符,这些占位符在运行时根据JVM系统属性解析。
名称空间本身提供了import指令特性。除了普通bean定义之外,还有一些配置特性可以在Spring提供的XML名称空间选择中找到——例如,context
上下文和util
名称空间。
The Groovy Bean Definition DSL
略
1.2.3 容器的使用
ApplicationContext
是能够维护不同bean及其依赖项的注册表的高级工厂的接口。通过使用方法T getBean(String name, Class<T> requiredType)
,可以检索bean的实例。
ApplicationContext
允许您读取bean定义并访问它们,如下面的示例所示:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
最灵活的变体是GenericApplicationContext
与reader委托相结合—例如,XmlBeanDefinitionReader
用于XML文件,如下例所示:
您可以在相同的ApplicationContext
上混合和匹配这样的reader委托,从不同的配置源读取bean定义。
然后可以使用getBean
检索bean的实例。ApplicationContext
接口有一些用于检索bean的其他方法,但在理想情况下,应用程序代码不应该使用它们。实际上,您的应用程序代码应该完全不调用getBean()
方法,因此完全不依赖于Spring api。例如,Spring与web框架的集成为各种web框架组件(如控制器和jsf管理的bean)提供了依赖项注入,允许您通过元数据(如自动装配注释)声明对特定bean的依赖项
1.3 Bean概述
Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML <bean/>
定义的形式)。
在容器内部,这些bean定义被表示为BeanDefinition
对象,其中包含(其他信息)以下元数据:
- 包限定的类名:通常是定义的bean的实际实现类。
- Bean行为配置元素,它表示Bean在容器中的行为(范围、生命周期回调,等等)。
- 对该bean执行其工作所需的其他bean的引用。这些引用也称为协作者或依赖项。
- 要在新创建的对象中设置的其他配置设置—例如,池的大小限制或管理连接池的bean中使用的连接数。
- A package-qualified class name: typically, the actual implementation class of the bean being defined.
- Bean behavioral configuration elements, which state how the bean should behave in the container (scope, lifecycle callbacks, and so forth).
- References to other beans that are needed for the bean to do its work. These references are also called collaborators or dependencies.
- Other configuration settings to set in the newly created object — for example, the size limit of the pool or the number of connections to use in a bean that manages a connection pool.
此元数据转换为组成每个bean定义的一组属性。下表描述了这些属性:
表1 bean定义
Property | Explained in… |
---|---|
Class | Instantiating Beans |
Name | Naming Beans |
Scope | Bean Scopes |
Constructor arguments | Dependency Injection |
Properties | Dependency Injection |
Autowiring mode | Autowiring Collaborators |
Lazy initialization mode | Lazy-initialized Beans |
Initialization method | Initialization Callbacks |
Destruction method | Destruction Callbacks |
除了包含关于如何创建特定bean的信息的bean定义之外,ApplicationContext
实现还允许注册容器外创建的现有对象(由用户创建)。这是通过getBeanFactory()
方法访问ApplicationContext的BeanFactory来实现的,该方法返回BeanFactory的DefaultListableBeanFactory
实现。DefaultListableBeanFactory
通过registerSingleton(..)
和registerBeanDefinition(..)
方法支持这种注册。但是,典型的应用程序只使用定义的bean
注意:Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤期间正确地推断它们。虽然在某种程度上支持覆盖现有的元数据和现有的单例实例,但是在运行时注册新bean(同时对工厂进行实时访问)并没有得到官方支持,这可能会导致并发访问异常、bean容器中的不一致状态,或者两者都有。
1.3.1 命名Bean
每个bean都有一个或多个标识符。这些标识符在承载bean的容器中必须是惟一的。一个bean通常只有一个标识符。但是,如果需要多个标识符,则可以将额外的标识符视为别名。
在基于xml的配置元数据中,可以使用id
属性、name
属性或两者都使用来指定bean标识符。id
属性允许您精确的指定一个id。通常,这些名称是字母数字(“myBean”、“someService”等),但它们也可以包含特殊字符。如果希望为bean引入其他别名,还可以在name
属性中指定它们,中间用逗号(,
)、分号(;
)或空格分隔。注意,在Spring 3.1之前的版本中,id
属性被定义为xsd: id
类型,这限制了可能的字符。从3.1开始,它被定义为xsd:string
类型。注意,容器仍然强制bean id
惟一性,但不再由XML解析器强制。
您不需要为bean提供name
或id
。如果您没有显式地提供name
或id
,则容器将为该bean生成唯一的名称。但是,如果希望通过名称引用该bean,则必须通过使用ref
元素或服务定位器
样式查找来提供名称。不提供名称的动机与使用内部bean和自动装配合作者有关。
Bean命名的约定
约定是在命名bean时使用标准Java约定作为实例字段名。也就是说,bean名称以小写字母开头,并从那里开始采用驼峰格式。此类名称的示例包括accountManager
、accountService
、userDao
、loginController
等。
统一地命名bean使您的配置更容易阅读和理解。另外,如果您使用Spring AOP,在将通知应用到一组按名称关联的bean时,它会有很大帮助。
注意:通过在类路径中扫描组件,Spring按照前面描述的规则为未命名的组件生成bean名称:本质上,使用简单的类名并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,如果有多个字符,并且第一个和第二个字符都是大写的,则保留原来的大小写。这些规则与java.beans.Introspector.decapitalize
(Spring在这里使用)定义的规则相同。
在Bean定义之外给Bean取别名
在bean定义本身中,通过使用id
属性指定的最多一个名称和name
属性中任意数量的其他名称的组合,可以为bean提供多个名称。这些名称可以是相同bean的别名,在某些情况下非常有用,比如通过使用特定于该组件本身的bean名称,让应用程序中的每个组件引用公共依赖项。
然而,指定实际定义bean的所有别名并不总是足够的。有时需要为在其他地方定义的bean引入别名。这在大型系统中是很常见的,在这些系统中,配置在每个子系统之间被分割,每个子系统都有自己的一组对象定义。在基于xml的配置元数据中,可以使用<alias/>
元素来完成此任务。下面的例子演示了如何做到这一点:
<alias name="fromName" alias="toName"/>
在这种情况下,在使用别名定义之后,名为fromName
的bean(在同一个容器中)也可以被称为toName
。
例如,子系统A的配置元数据可以通过子系统A-数据源
的名称引用数据源。子系统B的配置元数据可以通过子系统B-数据源
的名称引用数据源。在组合使用这两个子系统的主应用程序时,主应用程序以myApp-dataSource
的名称引用数据源。要使所有三个名称都引用同一个对象,可以将以下别名定义添加到配置元数据:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过一个惟一的名称来引用数据源,这个名称保证不会与任何其他定义冲突(有效地创建一个名称空间),但是它们引用的是同一个bean。
Java-configuration
如果使用Javaconfiguration,则可以使用@Bean
注释来提供别名。详见[beans-java-bean-annotation]。
1.3.2 实例化bean
bean定义本质上是创建一个或多个对象的方法。当被请求时,容器查看命名bean的配方,并使用该bean定义封装的配置元数据来创建(或获取)一个实际对象。
如果使用基于xml的配置元数据,则指定要在<bean/>
元素的class
属性中实例化的对象的类型(或类)。这个class
属性(在内部是BeanDefinition
实例上的一个Class
属性)通常是强制性的。(有关异常,请参见使用实例工厂方法和Bean定义继承进行实例化。)你可以通过以下两种方式之一使用Class属性:
- 通常,在容器本身通过反射调用其构造函数直接创建bean的情况下,指定要构造的bean类,这有点类似于使用
new
操作符的Java代码。 - 要指定包含用于创建对象的
静态
工厂方法的实现类,在不太常见的情况下,容器调用类上的静态
工厂方法来创建bean。从静态
工厂方法调用返回的对象类型可以是同一个类,也可以完全是另一个类。
内部类的名字
如果希望为静态
内部类配置bean定义,则必须使用内部类的二元制命名。
例如,如果您在com.example
中有一个类名为SomeThing
。这个类有一个静态的内部类叫做OtherThing
, 那么bean定义上的class属性的值就是是com.example.SomeThing$OtherThing
。
注意,在名称中使用$
字符将内部类名与外部类名分隔开。
用构造函数实例化
当您通过构造函数方法创建一个bean时,所有的普通类都可以被Spring使用并与Spring兼容。也就是说,正在开发的类不需要实现任何特定的接口,也不需要以特定的方式进行编码。只需指定bean类就足够了。但是,根据具体bean使用的IoC类型,可能需要一个默认(空)构造函数。
Spring IoC容器几乎可以管理您希望它管理的任何类。它不仅限于管理真正的javabean。大多数Spring用户更喜欢实际的javabean,它只有一个默认的(无参数的)构造函数,以及根据容器中的属性建模的适当的setter和getter方法。您还可以在容器中包含更多非bean风格的类。例如,如果您需要使用完全不符合JavaBean规范的遗留连接池,Spring也可以管理它。
使用基于xml的配置元数据,您可以按如下方式指定bean类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关向构造函数提供参数(如果需要)和在构造对象后设置对象实例属性的机制的详细信息,请参阅注入依赖项
用静态工厂方法实例化
在定义使用静态工厂方法创建的bean时,使用class
属性指定包含静态
工厂方法的类,使用factory-method
属性指定工厂方法本身的名称。您应该能够调用这个方法(带有可选参数,如后面所述)并返回一个活动对象,该对象随后被视为是通过构造函数创建的。这种bean定义的一个用途是在遗留代码中调用静态
工厂。
下面的bean定义指定通过调用工厂方法来创建bean。定义不指定返回对象的类型(类),只指定包含工厂方法的类。在本例中,createInstance()
方法必须是一个静态方法。下面的例子演示了如何指定工厂方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
下面的例子展示了一个可以使用前面的bean定义的类:
The following example shows a class that would work with the preceding bean definition:
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:
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
下面的例子展示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类也可以包含多个工厂方法,如下例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator 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();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种方法表明,可以通过依赖项注入(DI)来管理和配置工厂bean本身。详细查看依赖项和配置。
This approach shows that the factory bean itself can be managed and configured through dependency injection (DI). See Dependencies and Configuration in Detail.
注意:在Spring文档中,“factory bean”指的是在Spring容器中配置的bean,它通过实例
或静态
工厂方法创建对象。相反,FactoryBean
(注意大小写)指的是特定于spring的FactoryBean
。
1.4 依赖
典型的企业应用程序不包含单个对象(或Spring中的bean)。即使是最简单的应用程序也有几个对象一起工作,以呈现最终用户所看到的一致的应用程序。下一节将解释如何从定义许多独立的bean定义过渡到一个完全实现的应用程序,在这个应用程序中,对象通过协作来实现一个目标。
1.4.1 依赖注入
依赖项注入(DI)是一个过程,在这个过程中,对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回后在对象实例上设置的属性来定义它们的依赖项(即它们与之一起工作的其他对象)。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转过程),通过直接构造类或服务定位器模式来控制其依赖项的实例化或位置
使用DI原则,代码更简洁,并且当对象提供其依赖项时,解耦更有效。对象不查找其依赖项,也不知道依赖项的位置或类。结果,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现
DI存在于两个主要的变体中: Constructor-based dependency injection and Setter-based dependency injection.
基于构造器的依赖注入
基于构造函数的DI是通过容器调用带有许多参数的构造函数来完成的,每个参数代表一个依赖项。调用带有特定参数的静态
工厂方法来构造bean几乎是等价的,本讨论将参数分别用于构造函数和静态
工厂方法。
下面的例子展示了一个只能依赖注入构造函数注入的类:
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...
}
注意,这个类没有什么特别之处。它是一个不依赖于容器特定接口、基类或注释的POJO。
构造函数参数解析(Constructor Argument Resolution)
构造函数参数解析匹配通过使用参数的类型进行。如果在bean定义的构造函数参数中不存在潜在的歧义,则在bean定义中定义构造函数参数的顺序与在实例化bean时将这些参数提供给适当的构造函数的顺序相同。考虑以下类别:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设ThingTwo
和ThingThree
类没有继承关系,那么就不存在潜在的歧义。因此,下面的配置工作得很好,您不需要在<constructor-arg/>
元素中显式地指定构造函数参数索引或类型。
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
当引用另一个bean时,类型是已知的,可以进行匹配(就像前面的例子一样)。当使用简单类型时,例如<value>true</value>
, 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;
}
}
构造函数参数类型匹配(Constructor argument type matching)
在前面的场景中,如果您使用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>
除了解决多个简单值的模糊性之外,指定索引还解决了构造函数具有相同类型的两个参数时的模糊性。
构造函数参数的名字(构造函数参数的名字)
你也可以使用构造函数的参数名来消除歧义,如下例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,要使这一操作开箱即用,您的代码必须在启用调试标志的情况下进行编译,以便Spring可以从构造函数中查找参数名。如果不能或不希望使用debug标志编译代码,可以使用@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-based依赖注入
在调用无参数构造函数或无参数静态
工厂方法实例化bean之后,容器调用bean上的setter方法来完成基于setter的DI。
下面的示例显示了一个只能通过使用纯setter注入进行依赖注入的类。这个类是传统的Java。它是一个不依赖于容器特定接口、基类或注释的POJO。
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
为其管理的bean支持基于构造函数和基于setter的DI。在通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。您可以以BeanDefinition
的形式配置依赖项,将其与PropertyEditor
实例一起使用,以将属性从一种格式转换为另一种格式。但是,大多数Spring用户并不直接使用这些类(也就是说,以编程方式),而是使用XML bean
定义、带注释的组件(也就是说,使用@Component
、@Controller
等进行注释的类)或基于java的@Configuration
类中的@Bean
方法。然后,在内部将这些源转换为BeanDefinition
的实例,并用于加载整个Spring IoC容器实例。
基于构造函数还是基于setter的DI?
由于您可以混合使用基于构造函数和基于setter的DI,因此使用构造函数实现强制依赖项和使用setter方法或配置方法实现可选依赖项是一个很好的经验法则。注意,可以使用setter方法上的@Required注释使属性成为必需的依赖项;但是,最好是构造函数注入参数的编程式验证。
Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变的对象,并确保所需的依赖项不是null。此外,注入构造函数的组件总是以完全初始化的状态返回给客户机(调用)代码。顺便提一下,大量的构造函数参数是一种不好的代码味道,这意味着类可能有太多的责任,应该重构它们,以便更好地处理关注点的适当分离。
Setter注入主要应该只用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,在代码使用依赖项的任何地方都必须执行非空检查。setter注入的一个好处是,setter方法使该类的对象可以稍后重新配置或重新注入。因此,通过JMX mbean进行管理是setter注入的一个引人注目的用例。
使用对特定类最有意义的DI样式。有时,在处理您没有源代码的第三方类时,需要为您做出选择。例如,如果第三方类没有公开任何setter方法,那么构造函数注入可能是惟一可用的DI形式。
依赖性解析过程
容器执行bean依赖项解析如下:
ApplicationContext
是用描述所有bean的配置元数据创建和初始化的。配置元数据可以通过XML、Java代码或注释来指定。- 对于每个bean,其依赖关系以属性、构造函数参数或静态工厂方法的参数的形式表示(如果您使用该方法而不是普通构造函数)。这些依赖项在bean实际创建时提供给bean。
- 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
- 值的每个属性或构造函数参数都从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将以字符串格式提供的值转换为所有内置类型,如
int
、long
、string
、boolean
等。
Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,不会设置bean属性本身。在创建容器时,将创建单例作用域的bean并将其设置为预实例化(缺省值)。作用域是在Bean作用域中定义的。否则,只有在请求bean时才会创建它。当bean的依赖项及其依赖项的依赖项(等等)被创建和分配时,bean的创建可能会创建一个bean图。注意,这些依赖项之间的解析不匹配可能会出现得很晚——也就是说,在首次创建受影响的bean时。
循环依赖(Circular dependencies)
如果主要使用构造函数注入,则可能会创建无法解析的循环依赖场景。
例如:类A需要一个通过构造函数注入的类B的实例,而类B需要一个通过构造函数注入的类A的实例。如果将bean配置为类A和类B相互注入,Spring IoC容器会在运行时检测这个循环引用,并抛出一个BeanCurrentlyInCreationException异常。
一种可能的解决方案是编辑一些类的源代码,这些类将由setter而不是构造函数配置。或者,避免构造函数注入,只使用setter注入。换句话说,尽管不建议这样做,但您可以使用setter注入配置循环依赖项。
与典型的情况(没有循环依赖关系)不同,bean a和bean B之间的循环依赖关系强制在完全初始化之前将一个bean注入另一个bean(典型的先有鸡还是先有蛋的场景)。
您通常可以相信Spring会做正确的事情。它在容器加载时检测配置问题,例如对不存在的bean和循环依赖关系的引用。Spring在实际创建bean时尽可能晚地设置属性和解析依赖项。这意味着,当您请求一个对象时,如果在创建该对象或其依赖项之一时出现问题,已正确加载的Spring容器稍后可以生成异常----例如,由于缺少或无效的属性,bean会抛出异常。一些配置问题的延迟可见性是ApplicationContext
实现在默认情况下预先实例化单例bean的原因。以在实际需要这些bean之前创建它们的前期时间和内存为代价,您会在创建ApplicationContext
时发现配置问题,而不是在以后。您仍然可以覆盖这个默认行为,这样单例bean就可以惰性地初始化,而不是预先实例化。
如果不存在循环依赖关系,当一个或多个协作bean被注入到一个依赖bean中时,每个协作bean在被注入到依赖bean之前都被完全配置。这意味着,如果bean A依赖于bean B, Spring IoC容器会在调用bean A的setter方法之前完全配置bean B。换句话说,bean被实例化(如果它不是预实例化的单例对象),它的依赖项被设置,相关的生命周期方法(例如配置的init方法
或InitializingBean回调方法
)被调用。
依赖注入的例子
下面的示例将基于xml的配置元数据用于基于setter的DI。Spring XML配置文件的一小部分指定了一些bean定义,如下所示:
<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"/>
下面的例子显示了相应的ExampleBean
类:
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文件中指定的属性。下面的例子使用了基于构造函数的DI:
<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"/>
下面的例子显示了相应的ExampleBean
类:
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"/>
下面的例子显示了相应的ExampleBean
类:
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
属性之外),因此我们不在这里讨论这些细节。
1.4.2 详细介绍依赖项和配置
如前一节所述,可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用或内联定义的值。Spring的基于xml的配置元数据支持其<property/>
和<constructor-arg/>
元素中的子元素类型。
直接值(原语、字符串等)
<property/>
元素的value
属性将属性或构造函数参数指定为人类可读的字符串表示形式。Spring的转换服务用于将这些值从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-namespace进行更简洁的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
https://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更简洁。然而,拼写错误是在运行时而不是设计时发现的,除非您使用IDE(如IntelliJ IDEA或Spring工具套件),该IDE支持在创建bean定义时自动完成属性。强烈建议提供这样的IDE帮助。
您还可以配置java.util.Properties
实例,如下:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- 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容器将<value/>
元素内的文本转换为java.util.Properties
实例,使用javabean PropertyEditor
机制。这是一个很好的快捷方式,也是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是否确实存在。在第二个变体中,对传递给客户机bean的targetName
属性的值不执行任何验证。只有在实际实例化客户端
bean时才会发现输入错误(很可能会导致致命的结果)。如果客户端
bean是原型bean,则此错误和由此产生的异常可能要在部署容器之后很久才能发现。
<idref/>
元素带来价值的一个常见位置(至少在Spring 2.0之前的版本中)是在ProxyFactoryBean
bean定义中的AOP拦截器配置中。在指定拦截器名称时使用<idref/>
元素可以防止将拦截器ID拼写错误。
对其他bean的引用(协作者)
ref
元素是<constructor-arg/>
或<property/>
定义元素中的最后一个元素。在这里,将bean的指定属性的值设置为对容器管理的另一个bean(合作者)的引用。被引用的bean是要设置其属性的bean的依赖项,并且在设置属性之前根据需要对其进行初始化。(如果协作者是一个单例bean,那么它可能已经被容器初始化了。)所有引用最终都是对另一个对象的引用。作用域确定和验证取决于是否通过bean
、local
或parent
属性指定另一个对象的ID或名称。
通过<ref/>
标记的bean
属性指定目标bean是最常见的形式,并且允许在同一个容器或父容器中创建对任何bean的引用,不管它是否在同一个XML文件中。bean
属性的值可以与目标bean的id
属性相同,也可以与目标bean的name
属性中的一个值相同。下面的例子展示了如何使用ref
元素:
<ref bean="someBean"/>
通过parent
属性指定目标bean将创建对当前容器的父容器中的bean的引用。parent
属性的值可以与目标bean的id
属性或目标bean的name
属性中的一个值相同。目标bean必须位于当前bean的父容器中。当您有一个容器的层次结构,并且您希望将一个现有的bean与一个与父bean同名的代理包装在一个父容器中时,您应该主要使用这个bean引用变体。下面这对清单展示了如何使用parent
属性:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
内部Bean
在<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或名称。如果指定,容器不使用这样的值作为标识符。容器还会忽略创建时的作用域
标志,因为内部bean始终是匿名的,并且始终使用外部bean创建。不可能独立地访问内部bean,也不可能将它们注入到协作的bean中,而不是注入到封闭的bean中。
作为一种特殊情况,从自定义范围接收销毁回调是可能的——例如,对于单例bean中包含的请求范围的内部bean。内部bean实例的创建与它所包含的bean绑定在一起,但是销毁回调允许它参与请求作用域的生命周期。这不是一个常见的场景。内部bean通常只是简单地共享其包含的bean的范围。
集合
<list/>
、<set/>
、<map/>
和<props/>
元素分别设置了Java Collection
类型<list/>
、<set/>
、<map/>
和<props/>
的属性和参数。下面的例子展示了如何使用它们:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
映射键或值或集合值的值也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
合并集合
Spring容器还支持合并集合。应用程序开发人员可以定义父<list/>
、<map/>
、<set/>
或<props/>
元素,并具有子<list/>
、<map/>
、<set/>
或<props/>
元素从父集合继承和覆盖值。也就是说,子集合的值是父集合和子集合的元素合并的结果,子集合的元素覆盖父集合中指定的值。
关于合并的这一节将讨论父-子bean机制。不熟悉父bean和子bean定义的读者可能希望在继续之前阅读相关部分。
下面的例子演示了集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</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">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意,在子bean定义的adminEmails
属性的<props/>
元素上使用了merge=true属性。当容器解析并实例化子bean
时,产生的实例有一个adminEmails
属性集合,其中包含将子adminEmails
集合与父adminemail
集合合并的结果。下面的清单显示了结果:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子属性集合的值集继承父<props/>
的所有属性元素,支持值的子值覆盖父集合中的值。
这种合并行为类似地适用于<list/>
、<map/>
和<set/>
集合类型。在<list/>
元素的特定情况下,将维护与list
集合类型(即有序值集合的概念)相关联的语义。父列表的值位于子列表的所有值之前。对于映射、集合和属性集合类型,不存在排序。因此,对于容器内部使用的关联映射、集合和属性实现类型下的集合类型,没有有效的排序语义。
集合合并的限制
您不能合并不同的集合类型(例如Map
和List
)。如果您确实尝试这样做,将抛出一个适当的异常
。merge
属性必须在较低的、继承的子定义上指定。在父集合定义上指定merge
属性是冗余的,并且不会导致所需的合并。
强类型集合
随着Java 5中泛型类型的引入,您可以使用强类型集合。也就是说,可以声明一个集合
类型,使其只能包含(例如)字符串
元素。如果使用Spring来依赖地将强类型集合
注入到bean中,则可以利用Spring的类型转换支持,以便在将强类型集合
实例的元素添加到集合
之前将其转换为适当的类型。下面的Java类和bean定义说明了如何做到这一点:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<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>
当something
bean的accounts
属性准备注入时,关于强类型 Map<String, Float>
可通过反射获得。因此,Spring的类型转换基础设施将各种值元素识别为Float
类型,并将字符串值(9.99
、2.75
和3.99
)转换为实际的Float
类型。
Null和空字符串值
Spring将属性之类的空参数视为空字符串。以下基于xml的配置元数据片段将email属性设置为空字符串值("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
前面的例子相当于下面的Java代码:
exampleBean.setEmail("");
元素的作用是:处理空值。下面的清单显示了一个示例:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
以上配置相当于以下Java代码:
exampleBean.setEmail(null);
带有p-名称空间的XML快捷方式
p-名称空间允许您使用bean元素的属性(而不是嵌套的元素)来描述与之合作的bean的属性值,或者两者都使用。
Spring支持带有名称空间的可扩展配置格式,名称空间基于XML模式定义。本章中讨论的bean配置格式是在XML模式文档中定义的。但是,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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
该示例显示了bean定义中p-名称空间中名为email的属性。这告诉Spring包含一个属性声明。如前所述,p-namespace没有模式定义,因此可以将属性名设置为属性名。
下一个示例包括另外两个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
https://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"/>
来创建一个从bean john
到bean jane
的引用,第二个bean定义使用p:spouse-ref="jane"
作为一个属性来完成完全相同的工作。在本例中,spouse
是属性名,而-ref
部分表明这不是一个直接的值,而是对另一个bean的引用。
p-名称空间不如标准XML格式灵活。例如,声明属性引用的格式与以
Ref
结尾的属性冲突,而标准XML格式不会。我们建议您仔细选择您的方法,并与您的团队成员沟通,以避免同时生成使用所有三种方法的XML文档。
使用c-名称空间的XML快捷方式
与使用p-namespace的XML快捷方式类似,Spring 3.1中引入的c-namespace允许使用内联属性配置构造函数参数,而不是嵌套构造函数参数元素。
下面的例子使用了c:名称空间来做与基于from构造器的依赖注入相同的事情:
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
c:
名称空间使用与p:
one (bean引用的尾部-ref
)相同的约定来按名称设置构造函数参数。类似地,它需要在XML文件中声明,即使它没有在XSD模式中定义(它存在于Spring核心中)。
对于构造函数参数名不可用的罕见情况(通常是在编译字节码时没有调试信息的情况下),可以使用回退到参数索引,如下所示:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
由于XML语法,索引表示法要求出现前导
_
,因为XML属性名不能以数字开头(尽管一些ide允许这样做)。<constructor-arg>
元素也可以使用对应的索引表示法,但这种表示法并不常用,因为在这里,简单的声明顺序通常就足够了。
在实践中,构造函数解析机制在匹配参数方面非常有效,因此,除非确实需要,我们建议在整个配置中使用名称符号。
复合属性名
在设置bean属性时,可以使用复合或嵌套属性名,只要路径的所有组件(最终属性名除外)不为null
。考虑下面的bean定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
something
bean有一个fred
属性,这个fred
属性有一个bob
属性,这个bob
属性又有一个sammy
属性,最终的sammy
属性被设置为123
。为了使其工作,在构造bean之后,某些东西的fred
属性和fred
的bob
属性不能为null
。否则,将抛出NullPointerException
。