介绍 Spring IoC 容器和 bean
本章涵盖了 Spring Framework实现控制翻转 (IoC) 的原则。 IoC 有时也被称为依赖注入 (DI)。这是一个对象定义他们依赖的过程,其中对象之间的相关性,也就是说,它们一起工作,只能通过构造函数参数,参数工厂方法或设置在其构造后的对象实例或者是从一个工厂方法返回的对象实例的属性上。容器在创建的 bean 注入这些依赖。这个过程是根本的反转,因此称为控制反转(IoC),bean
本章涵盖了 Spring Framework实现控制翻转 (IoC) 的原则。 IoC 有时也被称为依赖注入 (DI)。这是一个对象定义他们依赖的过程,其中对象之间的相关性,也就是说,它们一起工作,只能通过构造函数参数,参数工厂方法或设置在其构造后的对象实例或者是从一个工厂方法返回的对象实例的属性上。容器在创建的 bean 注入这些依赖。这个过程是根本的反转,因此称为控制反转(IoC),bean 本身通过直接构造类,或作为 Service Locator(服务定位器)模式的机制,来控制其依赖的实例或依赖的位置。
org.springframework.beans 和 org.springframework.context 是Sprig 框架的 IoC 容器的基础包的。 BeanFactory 提供能够管理任何类型的对象的高级配置机制。 ApplicationContext 是 BeanFactory 的一个子接口。它增加了与 Spring AOP 功能的整合更容易;消息资源处理(用于国际化),事件发布;和应用层的上下文,如WebApplicationContext 中的 Web 应用程序使用。
简而言之,BeanFactory 提供了配置框架和基本功能,而ApplicationContext 则增加了更多支持企业特定的功能。一个ApplicationContext 是 BeanFactory 的一个完整的超集,本章将专对 门 Spring 的 IoC 容器进行描述。有关使用 BeanFactory 来代替ApplicationContext 的更多信息,请参见第6.16节,“BeanFactory”。
在 Spring 中,形成了应用程序的骨干,该对象由 Spring IoC 容器管理被称为 bean。一个 bean 是由 Spring IoC 容器实例化,组装,并以其他方式管理的对象。否则,一个 bean 只是简单的应用程序中的许多对象之一。bean,以及它们之间的相关性,反映在所使用的容器的配置元数据中(configuration metadata )。
容器总览
org.springframework.context.ApplicationContext 代表 Spring IoC 容器,并负责实例化,配置和组装上述 bean 的接口。容器是通过对象实例化,配置,和读取配置元数据汇编得到对象的构建。配置元数据可以是用 XML,Java 注解,或 Java 代码来展示。它可以让你描述组成应用程序的对象和对象间丰富的相互依赖。
Spring ApplicationContext 接口提供了几种即装即用的实现方式。在独立应用中,通常以创建 ClassPathXmlApplicationContext 或FileSystemXmlApplicationContext 的实例。虽然 XML 一直是传统的格式来定义配置元数据,但也可以指示容器使用 Java 注解或代码作为元数据格式,并通过提供少量的XML配置以声明方式启用这些额外的元数据格式的支持。
在大多数应用场合,不需要明确的用户代码来实例化一个 Spring IoC 容器的一个或多个实例。例如,在 web 应用程序中,在应用程序的 web.xml文件中一个简单的样板网站的 XML 描述符通常就足够了(见第6.15.4,“便捷的 ApplicationContext 实例化 Web 应用程序”)。如果您使用的是基于Eclipse的 Spring Tool Suite开发环境,该样板配置可以很容易地用点击几下鼠标或键盘创建。
下面的图展示了 Spring 是如何工作的高级视图。您的应用程序的类是通过配置元数据来结合的,以便 ApplicationContext 需要创建和初始化后,你有一个完全配置和可执行的系统或应用程序。
Figure 6.1. The Spring IoC container
配置元数据
如上述图所示,Spring IoC 容器使用 配置元数据( configuration metadata);这个配置元数据代表了应用程序开发人员告诉 Spring 容器在应用程序中如何来实例化,配置和组装对象。
传统的配置元数据是一个简单而直观的 XML 格式,这是大多数本章用来传达关键概念和 Spring IoC 容器的功能。
基于 XML 的元数据并不是配置元数据的唯一允许的形式。 Spring IoC容器本身是完全从此配置元数据实际写入格式脱钩。现在,许多开发商选择适合自己的 Spring 应用程序的基于 Java 的配置。
更多其他格式的元数据见:
- 基于注解的配置:Spring 2.5 的推出了基于注解的配置元数据支持。
- 基于Java的配置:Spring3.0 开始,由 Spring JavaConfig 项目提供了很多功能成为核心 Spring 框架的一部分。因此,你可以通过使用Java,而不是 XML 文件中定义外部 bean 到你的应用程序类。要使用这些新功能,请参阅 @Configuration,@Bean,@Import 和 @DependsOn 注解。
Spring 配置至少一个,通常不止一个 bean 来由容器来管理。基于 XML 的配置元数据显示这些 bean 配置的<bean/>
包含于顶层元素<beans/>
元素。 Java 配置通常使用 @Configuration 类中 @Bean 注解的方法。
这些 bean 定义对应于构成应用程序的实际对象。通常,您定义服务层对象,数据访问对象(DAO),展示对象,如 Struts Action 的情况下,基础设施的对象,如 Hibernate 的 SessionFactories,JMS Queues,等等。通常一个不配置细粒度域对象在容器中,因为它通常负责 DAO 和业务逻辑来创建和负载域对象。但是,你可以使用 Spring 和 AspectJ的集成配置在 IoC 容器的控制之外创建的对象。请参阅使用 AspectJ 在 Spring 中进行依赖关系注入域对象。
以下示例显示基于 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="...">
<!-- 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 用于参照协作对象未在本例中示出;请参阅 依赖以获取更多信息。
实例化容器
实例化 Spring IoC 容器是简单的。提供给 ApplicationContext构造器的路径就是实际的资源字符串,使容器装入从各种外部资源的配置元数据,如本地文件系统, Java CLASSPATH,等等。
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
当你了解 Spring 的 IoC 容器,你可能想知道更多关于 Spring 的Resource 抽象,如第7章,资源,它提供了一种方便的从一个 URI 语法定义的位置读取一个InputStream 描述。具体地,资源路被用作在第7.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">
<!-- 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
http://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 元素之间的这种联系表达了合作对象之间的依赖关系。对于配置对象的依赖关系的详细信息,请参阅依赖。
基于XML的元数据配置
它可以让 bean 定义跨越多个 XML 文件,这样做非常有用。通常,每个单独的 XML 配置文件代表你的架构一个逻辑层或模块。
您可以使用应用程序上下文构造从所有这些 XML 片段加载 bean 定义。这个构造函数的多个 Resource 位置,作为上一节中被证明。另外,使用 <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>
services.xml,messageSource.xml 及 themeSource.xml 在前面的例子中,外部 bean 定义是由三个文件加载而来。所有位置路径是相对于导入文件的,因此 services.xml 是必须和导入文件在同一目录或类路径中的位置,而 messageSource.xml 和 themeSource.xml 来必须在导入文件的 resources 以下位置。正如你所看到的,前面的斜线被忽略,但考虑到这些路径是相对的,它更好的形式是不使用斜线。该文件的内容被导入,包括顶级<beans />
元素,必须根据 Spring Schema 是有效的XML bean 定义。
这是可能的,但不推荐,引用在使用相对“../”的路径的父目录中的文件。这样将创建一个文件,该文件是当前应用程序之外的依赖。特别是,该引用不推荐“classpath:” URL(例如,“classpath:../services.xml”),在运行时解决过程中选择了“就近”的类路径的根,然后查找到它的父目录。类路径配置的变化可能导致不同的,不正确的目录的选择。 您可以随时使用完全合格的资源位置,而不是相对路径:例如,file:C:/config/services.xml" 或 "classpath:/config/services.xml"。但是,要知道,你这是是在耦合应用程序的配置到特定的绝对位置。通常优选间接的方式应对这种绝对路径,例如,通过“${…}”在运行时解决了对JVM系统属性占位符。
使用容器
ApplicationContext 是能够保持 bean 定义以及相互依赖关系的高级工厂接口。使用方法 T getBean(String name, Class requiredType)就可以取得 bean 的实例。
ApplicationContext 中可以读取 bean 定义并访问它们,如下所示:
// create and configure beans
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
您可以使用 getBean() 方法来获取 bean 的实例。 ApplicationContext 接口有一些其他的方法来获取 bean,但理想的应用程序代码不应该使用它们。事实上,你的应用程序代码不应该调用的getBean() 方法,也不应该对 Spring 的 API 有任何依赖性。正如,Spring为各种框架类提供的依赖注入,如控制器和 JSF 管理的bean。
Bean 总览
Spring IoC 容易管理一个或者多个 bean。 bean 由你提供给容器的配置元数据创建,例如,在 XML 中的<bean/>
定义。
容器内部,这些 bean 定义表示为 BeanDefinition 对象,其中包含(其他信息)以下元数据:
- 限定包类名称:作为已经被定义bean的实际实现的代表。
- bean 行为配置元素,定义了容器中的Bean应该如何行为(范围、生命周期回调,等等)。
- bean 需要引用其他 bean 来完成工作,这些引用也称为合作者或依赖关系。
- 其他配置设置来设置新创建的对象,例如,连接使用 bean 的数量管理连接池,或者池的大小限制。
以下是每个 bean 定义的属性。
Table 6.1. The bean definition
属性 | 解释 |
---|---|
class | Section 6.3.2, “Instantiating beans” |
name | Section 6.3.1, “Naming beans” |
scope | Section 6.5, “Bean scopes” |
constructor arguments | Section 6.4.1, “Dependency Injection” |
properties | Section 6.4.1, “Dependency Injection” |
autowiring mode | Section 6.4.5, “Autowiring collaborators” |
lazy-initialization mode | Section 6.4.4, “Lazy-initialized beans” |
initialization method | the section called “Initialization callbacks” |
destruction method | the section called “Destruction callbacks” |
除 bean 定义里包含的如何创建一个特定的bean的信息, ApplicationContext 的实现还允许由用户注册现有创建在容器之外的对象。这是通过访问 ApplicationContext 的 BeanFactory 的 getBeanFactory() 方法 返回 BeanFactory 的实现 DefaultListableBeanFactory 。DefaultListableBeanFactory 支持这种通过 registerSingleton(..) 和registerBeanDefinition(..) 方法来注册。然而,典型的应用程序只能通过元数据定义的 bean 来定义。
需要尽早注册 Bean 元数据和手动实现单例的实例,这是为了使容器正确推断它们在自动装配和其他内省的步骤。虽然覆盖现有的元数据和现有的单例实例在某种程度上是支持的,新 bean 在运行时(同时动态访问工厂)注册不是官方支持,可能会导致并发访问 bean 容器中的异常和/或不一致的状态。命名Bean
每个 bean 都有一个或多个标识符。这些标识符在容器托管 bean 必须是唯一的。bean 通常只有一个标识符,但如果它需要不止一个,可以考虑额外的别名。
在基于 xml 的配置元数据,您可以使用 id 和 / 或名称属性指定 bean 标识符(。id 属性允许您指定一个 id。通常这些名字字母数字(“myBean”、“fooService”,等等),但可以包含特殊字符。如果你想介绍其他别名 bean,您还可以指定属性名称,由逗号分隔(,),分号(;),或白色空格。作为一个历史因素的要注意,在 Spring 3.1 版本之前,id 属性被定义为 xsd:ID类型,它限制可能的字符。3.1,它被定义为一个 xsd:string 类型。注意,bean id 独特性仍由容器执行,虽然不再由 XML 解析器。
你不需要提供一个 bean 的名称或id。如果没有显式地提供名称或id, 容器生成一个唯一的名称给 bean 。然而,如果你想引用 bean 的名字,通过使用 ref 元素或使用 Service Locator(服务定位器)风格查找,你必须提供一个名称。不使用名称的原因是,内部 bean 和自动装配的合作者。
bean 名约定
约定是使用标准 Java 实例字段名称命名 bean 时的约定。也就是说,bean 名称开始以小写字母开头,后面采用“骆峰式”。例如“accountManager”、“accountService’,‘userDao’,‘loginController’,等等。
一致的beans命名可以让您的配置容易阅读和理解,如果你正在使用Spring AOP,当你通过 bean 名称应用到 advice 时,这会对你帮助很大。
bean 的别名
在对 bean 定义时,除了使用 id 属性指定一个唯一的名称外,为了提供多个名称,需要通过 name 属性加以指定,所有这个名称都指向同一个bean,在某些情况下提供别名非常有用,比如为了让应用每一个组件都能更容易的对公共组件进行引用。然而,在定义 bean 时就指定所有的别名并不总是很恰当。有时我们期望能够在当前位置为那些在别处定义的bean引入别名。在XML配置文件中,可以通过<alias/>
元素来完成 bean 别名的定义,例如:
<alias name="fromName" alias="toName"/>
在这种情况下,如果容易中存在名为 fromName 的 bean 定义,在增加别名定义后,也可以用 toName 来引用。
例如,在子系统 A 中通过名字 subsystemA-dataSource 配置的数据源。在子系统B中可能通过名字 subsystemB-dataSource 来引用。当两个子系统构成主应用的时候,主应用可能通过名字 myApp-dataSource 引用数据源,将全部三个名字引用同一个对象,你可以将下面的别名定义添加到应用配置中:
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />
现在每个子系统和主应用都可以通过唯一的名称来引用相同的数据源,并且可以保证他们的定义不与任何其他的定义冲突。
基于 Java 的配置
如果你想使用基于 Java 的配置,@Bean 注解可以用来提供别名,详细信息请看 Section 6.12.3, “Using the @Bean annotation”
实例化bean
bean 定义基本上就是用来创建一个或多个对象的配置,当需要一个 bean 的时候,容器查看配置并且根据 bean 定义封装的配置元数据创建(或获取)一个实际的对象。
如果你使用基于 XML 的配置,你可以在<bean/>
元素通过 class 属性来指定对象的类型。这个 class 属性,实际上是 BeanDefinition 实例中的一个 Class 属性。这个 class 属性通常是必须的(例外情况,查看“使用实例工厂方法实例化” 章节和 Section 6.7, “Bean定义的继承”),使用 Class 属性的两种方式:
- 通常情况下,直接通过反射调用构造方法来创建 bean,和在 Java 代码中使用 new 有点像。
- 通过静态工厂方法创建,类中包含静态方法。通过调用静态方法返回对象的类型可能和 Class 一样,也可能完全不一样。
内部类名。如果你想配置使用静态的内部类,你必须用内部类的二进制名称。例如,在 com.example 包下有个 Foo 类,这里类里面有个静态的内部类Bar,这种情况下bean定义的class属性应该…com.example.Foo$Bar 注意,使用$字符来分割外部类和内部类的名称。
通过构造函数实例化
当你使用构造方法来创建 bean 的时候,Spring 对类来说并没有什么特殊。也就是说,正在开发的类不需要实现任何特定的接口或者以特定的方式进行编码。但是,根据你使用那种类型的 IoC 来指定 bean,你可能需要一个默认(无参)的构造方法。
Spring IoC 容器可以管理几乎所有你想让它管理的类,它不限于管理POJO。大多数 Spring 用户更喜欢使用 POJO(一个默认无参的构造方法和setter,getter方法)。但在容器中使用非 bean 形式(non-bean style)的类也是可以的。比如遗留系统中的连接池,很显然它与 JavaBean规范不符,但 Spring 也能管理它。
当使用基于XML的元数据配置文件,可以这样来指定 bean 类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
给构造方法指定参数以及为bean实例化设置属性将在后面的依赖注入中详细说明。
使用静态工厂方法实例化
当采用静态工厂方法创建 bean 时,除了需要指定 class 属性外,还需要通过 factory-method 属性来指定创建 bean 实例的工厂方法。Spring将调用此方法(其可选参数接下来介绍)返回实例对象,就此而言,跟通过普通构造器创建类实例没什么两样。
下面的 bean 定义展示了如何通过工厂方法来创建bean实例。注意,此定义并未指定返回对象的类型,仅指定该类包含的工厂方法。在此例中,createInstance() 必须是一个 static 方法。
<beanid="clientService"class="examples.ClientService"factory-method="createInstance"/>
publicclassClientService{
privatestatic ClientService clientService = new ClientService();
privateClientService(){}
publicstatic ClientService createInstance(){
return clientService;
}
}
给工厂方法指定参数以及为bean实例设置属性的详细内容请查阅依赖和配置详解。
使用实例工厂方法实例化
与通过 静态工厂方法 实例化类似,通过调用工厂实例的非静态方法进行实例化。 使用这种方式时,class属性置为空,而factory-bean属性必须指定为当前(或其祖先)容器中包含工厂方法的bean的名称,而该工厂bean的工厂方法本身必须通过factory-method属性来设定。
<!-- 工厂bean,包含createInstance()方法 --><beanid="serviceLocator"class="examples.DefaultServiceLocator"><!-- 其他需要注入的依赖项 --></bean><!-- 通过工厂bean创建的ben --><beanid="clientService"factory-bean="serviceLocator"factory-method="createClientServiceInstance"/>
publicclassDefaultServiceLocator{
privatestatic ClientService clientService = new ClientServiceImpl();
privateDefaultServiceLocator(){}
public ClientService createClientServiceInstance(){
return clientService;
}
}
一个工厂类也可以有多个工厂方法,如下代码所示:
<beanid="serviceLocator"class="examples.DefaultServiceLocator"><!-- 其他需要注入的依赖项 --></bean><beanid="clientService"factory-bean="serviceLocator"factory-method="createClientServiceInstance"/><beanid="accountService"factory-bean="serviceLocator"factory-method="createAccountServiceInstance"/>
publicclassDefaultServiceLocator{
privatestatic ClientService clientService = new ClientServiceImpl();
privatestatic AccountService accountService = new AccountServiceImpl();
privateDefaultServiceLocator(){}
public ClientService createClientServiceInstance(){
return clientService;
}
public AccountService createAccountServiceInstance(){
return accountService;
}
}
这种做法表明工厂bean本身也可以通过依赖注入(DI)进行管理配置。查看依赖和配置详解。
在Spring文档中,factory bean是指在Spring容器中配置的工厂类通过 实例 或 静态 工厂方法来创建对象。相比而言, FactoryBean (注意大小写) 代表了Spring中特定的 FactoryBean
依赖
依赖注入
基于构造函数的依赖注入
publicclass SimpleMovieLister { // the SimpleMovieLister has a dependency on a MovieFinderprivate MovieFinder movieFinder; // a constructor so that the Spring container can inject a MovieFinderpublic SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted... }
构造参数解析
package x.y; publicclass Foo { public Foo(Bar bar, Baz baz) { // ... } }没有潜在歧义的存在,假设
Bar
和
Baz
没有相应的继承关系。因此下面的定义文件可以正常解析,你不用在<constructor-arg/>
元素里
指定构造函数参数的索引或者是明确的类型。
<beans><beanid="foo"class="x.y.Foo"><constructor-argref="bar"/><constructor-argref="baz"/></bean><beanid="bar"class="x.y.Bar"/><beanid="baz"class="x.y.Baz"/></beans>
package examples; publicclass ExampleBean { // Number of years to calculate the Ultimate Answerprivateint years; // The Answer to Life, the Universe, and Everythingprivate String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }在上面的场景中,只有你用 type 元素明确指定了构造参数的类型,框架才能正确的匹配。例如:
<beanid="exampleBean"class="examples.ExampleBean"><constructor-argtype="int"value="7500000"/><constructor-argtype="java.lang.String"value="42"/></bean>
<beanid="exampleBean"class="examples.ExampleBean"><constructor-argindex="0"value="7500000"/><constructor-argindex="1"value="42"/></bean>除了解决构造参数存在多个简单值模棱两可的情况,指定一个索引还可以解决构造参数是两个相同类型的情况。注意索引是从0开始的。
<beanid="exampleBean"class="examples.ExampleBean"><constructor-argname="years"value="7500000"/><constructor-argname="ultimateAnswer"value="42"/></bean>记住,你必须把Debug表示设置为enabled来编译才能让你的代码正常工作,因为只有这样Spring才能搜索构造函数的参数名字。如果你不能或者是不想以Debug模式来编译你的代码,你可以使用 @ConstructorProperties
package examples; publicclass ExampleBean { // Fields omitted@ConstructorProperties({"years", "ultimateAnswer"})public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
基于setter的依赖注入
publicclass SimpleMovieLister { // the SimpleMovieLister has a dependency on the MovieFinderprivate MovieFinder movieFinder; // a setter method so that the Spring container can inject a MovieFinderpublicvoid setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted... }
@Component
,
@Controller
来声明类
),或者是用基于JAVA
@Configuration
类的
@Bean
方法。这些资源然后在内部被转换为
BeanDefinition
实例,然后被用来加载整个Spring IoC容器实例。
依赖解析过程
ApplicationContext
类被创建并初始化描述所有Bean的元数据配置。元数据配置是通过XML,JAVA代码,或者是生命来指定的。- 对于每一个Bean,他的依赖都会以属性的形式、构造参数的形式、或者是静态工厂方法的方式(如果你用静态工厂方法代替默认构造函数)被表达。当Bean被创建的时候,这些依赖就会提供给Bean。
- 每一个属性或者构造参数都是一个实际要被设置值的定义,或者是容器中另一个Bean的引用。
- 每一个属性或者是构造参数的值都会被转换为一个构造参数或者是属性的实际类型,默认情况下,Spring可以把一个字符串值转换为任何的基础类型,像int,long,String,boolean等等。
循环依赖
如果你主要用构造函数注入,就很可能遇到循环依赖无法解析的情况。ApplicationContext
默认的预先加载单例Bean的原因。这些Bean在它被需要的时候提前的创建时间和内存的花费,使你在ApplicationContext
被创建时就能发现配置的错误,而不是之后。你可以重写默认的行为使单例Bean被延迟初始化,而不是提前初始化。
依赖注入的例子
下面的例子使用基于XML的配置来使用setter方法依赖注入。一下是Spring XML配置的一部分,指定了一些bean的定义:<beanid="exampleBean"class="examples.ExampleBean"><!-- setter injection using the nested ref element --><propertyname="beanOne"><refbean="anotherExampleBean"/></property><!-- setter injection using the neater ref attribute --><propertyname="beanTwo"ref="yetAnotherBean"/><propertyname="integerProperty"value="1"/></bean><beanid="anotherExampleBean"class="examples.AnotherBean"/><beanid="yetAnotherBean"class="examples.YetAnotherBean"/>
publicclass ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; privateint i; publicvoid setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } publicvoid setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } publicvoid setIntegerProperty(int i) { this.i = i; } }在上例中,setter方法被声明以用来匹配XML文件中指定的属性。下面的例子用了基于构造函数的依赖注入:
<beanid="exampleBean"class="examples.ExampleBean"><!-- constructor injection using the nested ref element --><constructor-arg><refbean="anotherExampleBean"/></constructor-arg><!-- constructor injection using the neater ref attribute --><constructor-argref="yetAnotherBean"/><constructor-argtype="int"value="1"/></bean><beanid="anotherExampleBean"class="examples.AnotherBean"/><beanid="yetAnotherBean"class="examples.YetAnotherBean"/>
publicclass ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; privateint i; public ExampleBean( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; } }Bean定义中指定的构造参数将被用来作为 ExampleBean 的构造函数参数。
<beanid="exampleBean"class="examples.ExampleBean"factory-method="createInstance"><constructor-argref="anotherExampleBean"/><constructor-argref="yetAnotherBean"/><constructor-argvalue="1"/></bean><beanid="anotherExampleBean"class="examples.AnotherBean"/><beanid="yetAnotherBean"class="examples.YetAnotherBean"/>
publicclass ExampleBean { // a private constructorprivate 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.publicstatic ExampleBean createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations...return eb; } }
依赖和配置细节
直接的值(基础类型,String等等)
<beanid="myDataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"><!-- results in a setDriverClassName(String) call --><propertyname="driverClassName"value="com.mysql.jdbc.Driver"/><propertyname="url"value="jdbc:mysql://localhost:3306/mydb"/><propertyname="username"value="root"/><propertyname="password"value="masterkaoli"/></bean>下面用了p-namespace来展示一种更简洁的方式:
<beansxmlns="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"><beanid="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足够简洁,但是拼写错误是在运行时而不是设计时被检测出来,除非你用了如Intellij IDEA或者是Spring Tool Suite来支持在创建bean定义时自动属性完成功能。这些IDE提示是高度推荐的。
<beanid="mappings"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><!-- typed as a java.util.Properties --><propertyname="properties"><value> jdbc.driver.className=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb </value></property></bean>
PropertyEditor
机制把
<value/>
标签内的文本转换为
java.util.Properties
实例,这是一个不错的捷径,而且是一个少有Spring团队喜欢嵌套
<value/>
标签胜过
value
属性的方式。
idref属性
<constructor-arg/>
或者
<property/>
标签。
<beanid="theTargetBean"class="..."/><beanid="theClientBean"class="..."><propertyname="targetName"><idrefbean="theTargetBean" /></property></bean>上面的bean配置片段完全等同于下面的片段(在运行期):
<beanid="theTargetBean"class="..." /><beanid="client"class="..."><propertyname="targetName"value="theTargetBean" /></bean>第一种方式比第二种更好,因为用 idref 标签允许容器在部署期间校验引用或者命名的bean是否真实存在。在第二个变种里,传递给客户端bean的
targetName
属性的值是不会校验的。只有在客户端bean被实例化的时候错误才会被发现(会伴随一些致命的错误)。如果bean是一个prototype
范围的bean,只有在容器被加载以后才会发现拼写错误和导致的异常。
local 标签的idref 在4.0版本中不再被支持, 因为它不再支持提冲常规bean引用的值。当更新到4.0版本后,简单把你的idref local改为idref bean就行 |
其他Bean的引用(协作的bean)
ref
元素是在<constructor-arg/>
或
<property/>
中定义的元素的最终元素。用来让你通过配置另一个被容器管理的bean的引用来设置指定bean属性的值。这些bean引用是将被设置bean的属性的依赖。只有在设置属性之前,需要这些属性的时候,依赖bean才会被实例化(如果以来的bean是单例范围的bean,她会在容器初始化时就被实例化)。所有的依赖最终都是另一个对象的引用。范围和校验取决于你指定的对象的bean,local或者是parent标签的id/name属性。
<refbean="someBean"/>通过 parent 属性指定一个目标bean来创建一个在当前容器父容器中的bean的引用。parent 属性的值应该也和目标bean id 属性的值一致,或者和目标bean的name属性一致,并且目标bean必须在当前容器的父容器中。当你有容器的继承关系或你想用一个在父容器中有相同名字的代理作为父bean来包裹你现在bean时,可以用这种bean引用的变种。
<!-- in the parent context --><beanid="accountService"class="com.foo.SimpleAccountService"><!-- insert dependencies as required as here --></bean>
<!-- in the child (descendant) context --><beanid="accountService"<!--beannameisthesameastheparentbean-->class="org.springframework.aop.framework.ProxyFactoryBean"><propertyname="target"><refparent="accountService"/><!-- notice how we refer to the parent bean --></property><!-- insert other configuration and dependencies as required here --></bean>
local 标签的idref 在4.0版本中不再被支持, 因为它不再支持提冲常规bean引用的值。当更新到4.0版本后,简单把你的idref local改为idref bean就行 |
内部bean
<property/>
或
<constructor-arg/>
内部定义的内部bean。
<beanid="outer"class="..."><!-- instead of using a reference to a target bean, simply define the target bean inline --><propertyname="target"><beanclass="com.example.Person"><!-- this is the inner bean --><propertyname="name"value="Fiona Apple"/><propertyname="age"value="25"/></bean></property></bean>内部bean不需要定义id或name;如果指定了,容器不会使用那个值作为标识。容器创建内部bean时也会忽视掉 scope
集合
<list/>
, <set/>
, <map/>
,和
<props/>
标签来设置JAVA类型
List
,
Set
,
Map
和
Properties
的属性和参数。
<beanid="moreComplexObject"class="example.ComplexObject"><!-- results in a setAdminEmails(java.util.Properties) call --><propertyname="adminEmails"><props><propkey="administrator">administrator@example.org</prop><propkey="support">support@example.org</prop><propkey="development">development@example.org</prop></props></property><!-- results in a setSomeList(java.util.List) call --><propertyname="someList"><list><value>a list element followed by a reference</value><refbean="myDataSource" /></list></property><!-- results in a setSomeMap(java.util.Map) call --><propertyname="someMap"><map><entrykey="an entry"value="just some string"/><entrykey ="a ref"value-ref="myDataSource"/></map></property><!-- results in a setSomeSet(java.util.Set) call --><propertyname="someSet"><set><value>just some string</value><refbean="myDataSource" /></set></property></bean>map的健和值的值或者是set的值也可以是以下这些元素中任何一个:
bean | ref | idref | list | set | map | props | value | null
集合的合并
<list/>
, <map/>
, <set/>
或
<props/>,
并且定义子类型
<list/>
,
<map/>
,
<set/>
或
<props/>
来继承和重写父集合的值。子集合的值是父集合元素和子集合元素合并的结果,当用自己和元素重写父集合中指定的元素时。
<beans><beanid="parent"abstract="true"class="example.ComplexObject"><propertyname="adminEmails"><props><propkey="administrator">administrator@example.com</prop><propkey="support">support@example.com</prop></props></property></bean><beanid="child"parent="parent"><propertyname="adminEmails"><!-- the merge is specified on the child collection definition --><propsmerge="true"><propkey="sales">sales@example.com</prop><propkey="support">support@example.co.uk</prop></props></property></bean><beans>注意 child bean定义的 adminEmails属性的 <props/>标签的 merge=true 属性。当child bean被用到或者是被容器实例化时,最终的结果有一个
adminEmails
Properties
集合包含子adminEmails
和父
adminEmails
集合合并的结果。
administrator=administrator@example.com sales=sales@example.com support=support@example.co.uk子
Properties
集合的值通过继承父
<props/>
标签的值,并且子bean的
support
的值重写了父容器的值。
<list/>
,
<map/>
和
<set/>
集合类型。对于特别的<list/>元素的特殊情况,<list/>集合类型的语义是,一个维护的有序的值得集合。父list的值是在子list值之前的。对于Map,Set和Properties
集合类型,不存在顺序。
因此没有排序的集合类型语义实际上构成相关的Map,Set和Properties
实现类型的容器在内部使用。
集合合并的限制
Map
和一个
List合并),如果你尝试那么做就会抛出异常。
merge
属性必须在低层次的,继承的,子集合中定义。在父集合中设置
merge
属性是多余的并且不会获得想要的合并结果。
强类型集合
publicclass Foo { private Map<String, Float> accounts; publicvoid setAccounts(Map<String, Float> accounts) { this.accounts = accounts; } }
<beans><beanid="foo"class="x.y.Foo"><propertyname="accounts"><map><entrykey="one"value="9.99"/><entrykey="two"value="2.75"/><entrykey="six"value="3.99"/></map></property></bean></beans>当
foo
的
accounts
属性准备被注入的时候,强类型
Map<String, Float>元素类型的泛型信息就会通过反射生效。这样Spring类型转换结构会把各种值元素认为是
Float类型,把String值
9.99, 2.75和
3.99转换成真正的
Float
类型。
Null和空字符串
<beanclass="ExampleBean"><propertyname="email"value=""/></bean>上面的例子和下面的JAVA代码是相同的:
exampleBean.setEmail("")
<null/>元素代表
null值。例如:
<beanclass="ExampleBean"><propertyname="email"><null/></property></bean>上面的配置和下面的JAVA代码是相同的:
exampleBean.setEmail(null)
用p-namespace简写XML
<property/>
元素,来描述你属性的值或者是依赖的bean。
<beansxmlns="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"><beanname="classic"class="com.example.ExampleBean"><propertyname="email"value="foo@bar.com"/></bean><beanname="p-namespace"class="com.example.ExampleBean"p:email="foo@bar.com"/></beans>例子展示了一个在bean定义中用p-namespace定义的email属性。这告诉Spring包含一个属性声明。正如前面提到的,p-namespace不需要有一个模式定义,你可以把标签的名字设置为属性名。
<beansxmlns="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"><beanname="john-classic"class="com.example.Person"><propertyname="name"value="John Doe"/><propertyname="spouse"ref="jane"/></bean><beanname="john-modern"class="com.example.Person"p:name="John Doe"p:spouse-ref="jane"/><beanname="jane"class="com.example.Person"><propertyname="name"value="Jane Doe"/></bean></beans>
spouse
是一个属性名,-ref
代表这不是一个直接的值而是对另一个bean的引用。
p-namespace不像标准的XML格式那样灵活。例如,对于结尾是ref的属性就会和证明属性引用的格式冲突,这种情况不会再XML标准格式上发生。我们建议谨慎选择你的方式,并且和你的团队成员沟通好,来避免在配置XML文档时同时使用三种方式。 |
用c-namespace简写XML
<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="foo@bar.com"/> </bean> <!-- c-namespace declaration --> <bean id="foo"class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/> </beans>c:命名空间和 p:用相同的命名方式(结尾 -ref代表bean的引用)来通过他们的名字设置构造参数。它也不是通过XSD模式定义的(而是存在Spring核心模块中)。
<!-- c-namespace index declaration --> <bean id="foo"class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
由于XML语法的因素,索引符号需要_的存在,因为属性不成不能以数字开头(即使有一些IDE支持) |
复合属性名称
<beanid="foo"class="foo.Bar"><propertyname="fred.bob.sammy"value="123" /></bean>bean
foo
下面有一个
fred
属性,
fred
下面有一个
bob
属性,
bob
下面有一个
sammy
属性,最终
sammy
被设置为123。为了使这个能工作,必须保证bean被创建以后
foo下的
fred
和
fred
下的
bob
不为空,要不就会抛出空指针异常
NullPointerException
。
使用depends-on
<ref/>
标签来完成配置。但是,有些时候bean之间的依赖没有那么直接。例如,一个类中静态初始化器需要被触发,比如数据库驱动注册。
depends-on属性可以明确的强制一个或多个bean在被用到需要初始化之前被初始化。下面的例子展示用
depends-on属性来表达一个单例的依赖。
<beanid="beanOne"class="ExampleBean"depends-on="manager"/><beanid="manager"class="ManagerBean" />通过提供一个bean名称的列表作为depend-on的值,用逗号,空格或者分号作为一个可用的分隔符,来表达对多个bean的依赖。
<beanid="beanOne"class="ExampleBean"depends-on="manager,accountDao"><propertyname="manager"ref="manager" /></bean><beanid="manager"class="ManagerBean" /><beanid="accountDao"class="x.y.jdbc.JdbcAccountDao" />
Bean中的depends-on属性不仅可以指定初始化时间的依赖,并且仅仅对于单例范围的bean的情况,还可以指定一个相应的销毁时间的依赖。depends-on指定的依赖关系会在给定的bean被销毁之前被销毁。因此depends-on属性可以控制依赖关系关闭的顺序。 |
延迟初始化bean
ApplicationContext
默认情况下会立即创建和注册单例范围的bean做为它初始化的一部分。通常情况下这种提前的初始化是可取的,因为在配合和周边环境中的错误可以被及时发现,如果不这样就会在数小时后甚至数天之后才会被发现。当不需要这样做的时候,你可以通过定义bean的延迟加载来阻止单例bean提前初始化。配置为延迟加载的bean告诉IoC容器当他被请求的时候再实例化而不是容器启动的时候。
<beanid="lazy"class="com.foo.ExpensiveToCreateBean"lazy-init="true"/><beanname="not.lazy"class="com.foo.AnotherBean"/>当前面的配置被 ApplicationContext执行,名字是 lazy的bean当 ApplicationContext启动的时候不会立即提前初始化,而名字是 not.lazy的bean会立即执行提前初始化。
ApplicationContext
在启动的时候创建延迟加载的bean,因为它必须满足单例的依赖关系。延迟初始化的bean被注入答单例bean里,这里他就不会延迟加载。
<beansdefault-lazy-init="true"><!-- no beans will be pre-instantiated... --></beans>
自动装配
- 自动装配可以大量减少指定属性或者构造参数的需要。(其他机制例如本章将会讨论到的bean模板技术也会避免这些操作)
- 自动装配可以随着你的对象改变而改变。例如,你需要为类增加一个依赖,在不用更改配置的情况下依赖就可以满足自动装配。因此自动装配在开发期间尤其有用,不需要使显示的写的选项失效可以使代码更稳定。
<bean/>
元素的autowire
属性为bean定义指定自动装配模式。自动装配功能有四种模式,你为每个bean指定自动装配,因此你可以选择哪一个进行自动装配。
Table 6.2. 自动装配模式
模式 | 解释 |
---|---|
no | 默认不装配。bean引用必须通过 |
byName | 通过属性名来自动装配。当属性需要被自动装配时Spring寻找有相同名字的bean来进行装备。例如,如果一个bean的autowiring属性设置为byName,并且它包含一个master变量(也就是他有一个setMaster(...)方法),Spring寻找名字是master的bean,用它为变量赋值。 |
byType | 如果恰好容器中有一个bean的属性类型时,允许变量进行自动装配。如果多于一个时,异常就会抛出,预示着你可能不能把bean的autowiring属性设置为byType。若果没有匹配的类型,什么也不会发生,变量不会被设置。 |
constructor | 类似于byType,但是提供是的构造参数。如果恰好容器中没有bean的构造参数类型,一个错误就会抛出。 |
自动装配的局限和缺点
- 显示的用
property
和constructor-arg设置依赖通常会覆盖自动装配。你不能自动装配简单属性,像基础类型,Strings,类(包含简单类型的数组)。设计就是如此。 - 自动装配没有显示装配准确。尽管,正如上面表格提到的,当遇到歧义可能导致预想不到的结果时,Spring很小心的避免猜测。
- 装配信息可对不能用于从Spring容器产生文档的工具。
- 容器中的多个bean定义有可能都符合setter方法和构造参数被自动装配。对于数组,集合或者map,这不是问题。但是对于那些希望单个值的依赖,这个歧义就不能武断的解决了。如果没有唯一的bean定义可用,就会抛出异常。
- 放弃自动装配选择显示装配
- 避免自动装配时设置bean定义的autowire-candidate属性为false,正如下面一节描述的
- 通过设置<bean/>的primary属性为true,指定一个唯一的bean定义作为主要的候选
- 通过声明的配置来实现一些细微粒度的控制,正如在6.9“基于声明的配置”中描述的
从自动装配剔除指定的bean
@Autowired
)。
true
或false
会被优先处理,对于这些bean,模式匹配上的都不会使用。
方法注入
ApplicationContextAware
接口来使A意识到容器的存在,然后通过调用getBean("B")来在每次需要B的时候请求容器产生一个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; publicclass 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!returnthis.applicationContext.getBean("command", Command.class); } publicvoid setApplicationContext( ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }上面的做法实不可取的,应为业务代码意识到了容器的存在并且和Spring容器耦合了。方法注入,一些Spring IoC容器更高级的特征,允许用一种更简洁的方式实现这种情况。
搜索方法注入
|
CommandManager
类,你会看到Spring容器会动态的重写
createCommand()方法的实现。你的
CommandManager
类将不会有任何Spring的依赖,正如下面修改了的例子中看到的:
package fiona.apple; // no more Spring imports!publicabstractclass 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?protectedabstract Command createCommand(); }客户端类包含的方法被注入了(在这里是
CommandManager
类),要被注入的方法需要下面的格式:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
<!-- a stateful bean deployed as a prototype (non-singleton) --><beanid="command"class="fiona.apple.AsyncCommand"scope="prototype"><!-- inject dependencies here as required --></bean><!-- commandProcessor uses statefulCommandHelper --><beanid="commandManager"class="fiona.apple.CommandManager"><lookup-methodname="createCommand"bean="command"/></bean>Bean定义为当任何时候它需要一个 command Bean的实例的时候都会调用它自己的 createCommand()方法。你必须小心的把
command
Bean加载为原型范围,就像实际需要的那样。如果加载为单例范围,每次就会返回相同的实例。
感兴趣的读者可能也发现 |
任意方法替换
publicclass 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 */publicclass 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 ...; } }你可以通过使用一个或多个包含在 <replaced-method/>元素里的
<arg-type/>
属性来表明方法的方法签名要被重写。只有当方法是重载的或者类中存在多个变种时参数的签名才是必须的。为了方便,参数的类型字符串可以是参数全名的一个子字符串。例如,下面的都会匹配
java.lang.String。
java.lang.String String Str因为参数的个数通常足够来区分每一个可能的选择,这样缩写可以节省很多输入,通过允许你输入一个简单的字符串来匹配参数类型。
更多推荐
所有评论(0)