本章介绍Spring的控制反转(IoC)容器。
1.1. Spring IoC容器和bean简介
本章介绍了Spring框架中控制反转(IoC)原理的实现。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的更多信息,请参见BeanFactory。
在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是由Spring IoC容器实例化、组装和管理的对象。否则,bean只是应用程序中众多对象中的一个。容器使用的配置元数据中存放着bean及其之间的依赖关系。
1.2 容器概述
org.springframework.context.ApplicationContext接口表示Spring IoC容器,并负责实例化、配置和组装bean。容器通过读取配置元数据获取关于要实例化、配置和组装哪些对象的指令。配置元数据用XML、Java注释或Java代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。Spring提供了ApplicationContext接口的几个实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。虽然XML一直是定义配置元数据的传统格式,但您可以通过提供少量XML配置来声明性地支持这些额外的元数据格式,从而指示容器使用Java注释或代码作为元数据格式。
在大多数应用程序场景中,并不需要显式的让用户的代码来实例化Spring IoC容器的一个或多个实例。例如,在web应用程序场景中,应用程序的web. XML文件中简单的使用8行(大约8行)XML代码通常就足够了(请参阅方便的ApplicationContext实例化web应用程序)。如果您为Eclipse (Eclipse驱动的开发环境)使用Spring工具,那么只需单击几下鼠标或按键,就可以轻松地创建这个样板配置。【idea现在更方便】
下图展示了Spring工作方式的高级视图。您的应用程序类与配置元数据相结合,这样,在创建并初始化ApplicationContext之后,您就拥有了一个完全配置的可执行系统或应用程序。

Figure 1. The Spring IoC container
1.2.1.配置元数据
如上图所示,Spring IoC容器使用一种配置元数据。此配置元数据表示作为应用程序开发人员,您如何告诉Spring容器实例化、配置和组装应用程序中的对象
配置元数据通常以简单直观的XML格式提供,本章的大部分内容都是用XML格式来传达Spring IoC容器的关键概念和特性
基于xml的元数据并不是唯一允许的配置元数据形式。Spring IoC容器本身完全与配置元数据实际编写的格式分离。现在,许多开发人员为他们的Spring应用程序选择基于java的配置。
有关在Spring容器中使用其他形式元数据的信息,请参见:
- 基于注释的配置:Spring 2.5引入了对基于注释的配置元数据的支持。
- 基于java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多特性成为了核心Spring框架的一部分。因此,您可以通过使用Java而不是XML文件来定义应用程序类外部的bean。要使用这些新特性,请参阅@Configuration、@Bean、@Import和@DependsOn注释。
Spring配置由必须管理的至少一个(通常不止一个)bean定义的容器组成。基于xml的配置元数据将这些bean配置为顶级
这些bean定义对应于组成应用程序的实际对象。通常,您定义服务层对象、数据访问对象(dao)、表示对象(如Struts动作实例)、基础设施对象(如Hibernate sessionfactory)、JMS队列,等等。通常,不需要在容器中配置细粒度的域对象,因为创建和加载域对象通常是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/beanshttps://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。有关更多信息,请参见依赖项。
1.2.2. 实例化一个容器
ApplicationContext构造函数需要的是路径资源字符串,允许容器从各种外部资源(如本地文件系统、Java类路径等)加载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在了解了Spring的IoC容器之后,您可能想更多地了解Spring的资源(
Resource)抽象(如参考资料中所述),它为从URI语法中定义的位置读取InputStream提供了一种方便的机制。特别是,资源路径用于构造应用程序上下文,如应用程序上下文和资源路径中所述。
下面的示例显示了服务层对象(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/beanshttps://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>
下面的示例显示了数据访问对象dao .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/beanshttps://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定义。这个构造函数接受多个资源位置,如前一节所示。或者,使用
<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必须位于当前文件位置下方的资源位置【多了一层目录】。如您所见,前导斜杠被忽略。但是,考虑到这些路径是相对的,最好不要使用斜杠。根据Spring模式,被导入的文件的内容,包括顶级的
命名空间本身提供了import指令特性。除了简单的bean定义之外,Spring提供的XML名称空间选择还提供了其他配置特性——例如,context(上下文)和util名称空间。
Groovy Bean定义DSL
作为外部化配置元数据的进一步示例,bean定义也可以用Spring的Groovy bean定义DSL表示,如Grails框架所示。通常,这样的配置存在于一个”.groovy”文件的结构如下面的例子所示:
beans {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定义文件。
1.2.3. 使用容器(IOC容器)
ApplicationContext是高级工厂的接口,它能够维护不同bean及其依赖项的注册中心。通过使用方法T getBean(String name, Class<T> requiredType),您可以通过此方法检索bean的实例。
ApplicationContext允许您读取bean定义并访问它们,如下面的示例所示:
// 创建和配置bean的上下文ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");// 获取Bean的实例PetStoreService service = context.getBean("petStore", PetStoreService.class);// 使用实例,也就是对象List<String> userList = service.getUsernameList();
下面是一些从Groovy中加载bean,现在普遍都是spring boot进行开发了,只是大致了解一下即可,可以跳过
在Groovy配置中,引导看起来非常相似。它有一个不同的上下文实现类,该类支持groovy(但也理解XML bean定义)。下面的例子展示了Groovy的配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体是结合reader委托使用的GenericApplicationContext——例如,使用XML文件的XmlBeanDefinitionReader,如下例所示:
GenericApplicationContext context = new GenericApplicationContext();new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");context.refresh();
还可以为Groovy文件使用GroovyBeanDefinitionReader,如下面的示例所示:
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的依赖。
1.3. Bean 概述
Spring IoC容器管理一个或多个bean。这些bean是用您提供给容器的配置元数据创建的(例如,以XML
在容器本身内,这些bean定义表示为BeanDefinition对象,其中包含(以及其他信息)以下元数据:
- 全路径类名:通常是被定义的bean的实际实现类。
- Bean的行为配置元素,它声明Bean在容器中应该如何使用(范围、生命周期回调,等等)。
- 对其他bean的引用是该bean完成其工作所需要的。这些引用也被称为合作者或依赖关系。
- 在新创建的对象中设置其他配置设置—例如,池的大小限制或管理连接池的bean中使用的连接数量。
该元数据转换为一组组成每个bean定义的属性。下表描述了这些属性:
Table 1. The Bean definition
| 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来完成的,getBeanFactory()方法返回BeanFactory的DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法支持这种注册。然而,典型的应用程序只使用通过常规bean定义元数据定义的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提供名称或id。如果不显式地提供名称或id,则容器为该bean生成一个惟一的名称。但是,如果希望通过使用ref元素或服务定位器样式查找来通过名称引用该bean,则必须提供名称。不提供名称的动机与使用内部bean和自动装配协作者有关。
Bean命名的约定 约定是在命名bean时使用标准Java约定来命名实例字段名。也就是说,bean名称以小写字母开头,并从那里采用驼峰格式。这些名称的例子包括
accountManager、accountService、userDao、loginController等等。一致地命名bean可以使配置更易于阅读和理解。另外,如果您使用Spring AOP,在将通知应用到一组与名称相关的bean时,它会有很大帮助。
通过类路径中的组件扫描,Spring为未命名的组件生成bean名,遵循前面描述的规则:本质上,采用简单的类名并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,当有多个字符且第一个和第二个字符都是大写时,原始的大小写将被保留。这些规则与java.beans. introspector .decapital (Spring在这里使用的)定义的规则相同。
在Bean定义之外对Bean进行别名化
在bean定义本身中,通过使用id属性指定的最多一个名称和name属性中任意数量的其他名称的组合,可以为bean提供多个名称。这些名称可以是同一个bean的等价别名,在某些情况下非常有用,例如,通过使用特定于该组件本身的bean名称,让应用程序中的每个组件引用一个公共依赖项。
但是,指定实际定义bean的所有别名并不总是足够的。有时,为在其他地方定义的bean引入别名是可取的。在大型系统中,配置被划分到每个子系统中,每个子系统都有自己的一组对象定义,这种情况很常见。在基于xml的配置元数据中,您可以使用
<alias name="fromName" alias="toName"/>
在这种情况下,命名为fromName的bean(在同一个容器中)也可以在使用这个别名定义之后被称为toName。
例如,子系统A的配置元数据可以引用名为subsystemA-dataSource的数据源。子系统B的配置元数据可以引用名为subsystemB-dataSource的数据源。在组合使用这两个子系统的主应用程序时,主应用程序使用myApp-dataSource来引用数据源。要使所有三个名称都指向同一个对象,可以向配置元数据添加以下别名定义:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/><alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过唯一的名称引用数据源,并且保证不会与任何其他定义(有效地创建一个名称空间)冲突,但它们引用的是同一个bean。
我们现在更多的是使用@Bean注解来装配Bean,同时也可以通过@Bean来实现别名
1.3.2. 实例化bean
bean定义本质上是创建一个或多个对象。当被请求时,容器查看指定bean的对象,并使用该bean定义封装的配置元数据创建(或获取)实际对象。
如果使用基于xml的配置元数据,则指定要在
- 通常,在容器本身通过反射地调用其构造函数直接创建bean的情况下,指定要构造的bean类,这在某种程度上相当于带有new操作符的Java代码。
- 在不太常见的情况下,容器调用类上的静态工厂方法来创建bean,在这种情况下,指定包含静态工厂方法的实际类。调用静态工厂方法返回的对象类型可以是同一个类,也可以完全是另一个类。
嵌套的类名
如果要为嵌套类配置bean定义,可以使用嵌套类的二进制名称或源名称。
例如,如果你在com.example中有一个类叫做SomeThing。这个SomeThing类有一个静态嵌套类OtherThing,它们可以用$符号($)或点(.)分隔。因此,bean定义中的class属性的值应该是com.example.OtherThing或com.example.SomeThing$OtherThing。
用构造函数实例化
当您通过构造函数方法创建bean时,所有普通类都可以被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定义的类:
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;}}
这种方法表明,工厂bean本身可以通过依赖项注入(DI)进行管理和配置。请参阅依赖项和配置的详细信息。
在Spring文档中,“factory bean”指的是在Spring容器中配置并通过实例或静态工厂方法创建对象的bean。相比之下,
FactoryBean(注意大写)指的是特定于spring的FactoryBean实现类。
确定Bean的运行时类型
确定特定bean的运行时类型非常重要。bean元数据定义中的指定类只是一个初始类引用,它可能与声明的工厂方法结合在一起,或者是一个FactoryBean类,它可能导致bean的不同运行时类型,或者在实例级工厂方法的情况下根本不设置(而是通过指定的工厂bean名称解析)。此外,AOP代理可以用基于接口的代理包装bean实例,该代理只公开目标bean的实际类型(只公开其实现的接口)。
了解特定bean的实际运行时类型的推荐方法是使用BeanFactory.getType,调用指定的bean名称。这将考虑上述所有情况,并返回BeanFactory.getBean类型。getBean调用将返回相同的bean名称。
1.4. 依赖关系
典型的企业应用程序不包含单个对象(或Spring术语中的bean)。即使是最简单的应用程序也有几个对象一起工作,以表示最终用户认为一致的应用程序。下一节将解释如何从定义大量独立的bean定义过渡到一个完全实现的应用程序,在该应用程序中,对象相互协作以实现目标。
使用DI原则,代码会更清晰,当对象与它们的依赖项一起提供时,解耦会更有效。对象不查找它的依赖项,也不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖项在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
依赖注入主要有两种变体:基于构造函数的依赖注入和基于setter的依赖注入。
Constructor-based依赖注入
基于构造函数的DI是通过容器调用带有许多参数的构造函数来完成的,每个参数表示一个依赖项。调用带有特定参数的静态工厂方法来构造bean几乎是等价的,本文以类似的方式处理构造函数的参数和静态工厂方法的参数。下面的例子展示了一个只能通过构造函数注入进行依赖注入的类:
public class SimpleMovieLister {// the SimpleMovieLister has a dependency on a MovieFinderprivate final 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...}
注意,这个类没有什么特别之处。它是一个不依赖于容器特定接口、基类或注释的POJO。
