1.Spring IOC容器和Bean的介绍
本章介绍了控制反转(IOC)原理的Spring框架实现。IOC也称为依赖注入(DI)。它是一个过程,对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在其上设置的属性来定义它们的依赖项(即它们使用的其他对象)。然后,容器在创建Bean时注入这些依赖项。这个过程基本上是Bean本身的逆过程(因此称为控制反转)
org.springframework.beans
和org.springframework.context
包是Spring Framework的IOC容器的基础。 BeanFactory
接口提供了一种高级配置机制,能够管理任何类型的对象。 ApplicationContext是BeanFactory的子接口。它增加了:
- 更容易与Spring的AOP特性集成
- 消息资源处理(用于国际化)
- 事件发布
- 应用程序层特定的上下文,如
web
应用程序中使用的WebApplicationContext
。
简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。 ApplicationContext是BeanFactory的完整超集,在本章中对Spring的IOC容器的描述专门使用。
在Spring中,构成应用程序主干并由Spring IOC容器管理的对象称为Bean。 Bean是由Spring IOC容器实例化,组装和以其他方式管理的对象。 否则,Bean仅仅是应用程序中许多对象之一。 Bean及其之间的依赖关系反映在容器使用的配置元数据中。
2.容器概述
org.springframework.context.ApplicationContext
接口表示Spring IOC容器,并负责实例化,配置和组装Bean。 容器通过读取配置元数据来获取有关要实例化,配置和组装哪些对象的指令。 配置元数据以XML,Java注解或Java代码来表示。 它使您能够表达组成应用程序的对象以及这些对象之间的丰富相互依赖关系。
Spring提供了ApplicationContext
接口的几个实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
的实例。虽然XML是定义配置元数据的传统格式,但是您可以通过提供少量XML配置以声明方式启用对这些附加元数据格式的支持,来指示容器使用Java注解或Java代码作为元数据格式。
在大多数应用场景中,不需要实例化用户代码即可实例化一个Spring IOC容器的一个或多个实例。 例如,在Web应用程序场景中,应用程序的web.xml文件中简单的八行(约)样板Web描述符XML通常就足够了。 如果您使用Spring Tools for Eclipse(Eclipse支持的开发环境),则只需单击几下鼠标或击键即可轻松创建此样板配置。
下图显示了Spring的工作原理的高级视图。 您的应用程序类与配置元数据结合在一起,以便在创建和初始化ApplicationContext之后,您将拥有一个完全配置且可执行的系统或应用程序。
2.1配置元数据
如上图所示,Spring IOC容器使用一种形式的配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉Spring容器在应用程序中实例化、配置和组装对象。
配置元数据传统上是以简单直观的XML格式提供的,本章的大部分内容都使用它来传达Spring IoC容器的关键概念和特性。
注意:基于XML的元数据不是配置元数据的唯一允许形式。 Spring IOC容器本身与实际写入此配置元数据的格式完全脱钩。 如今,许多开发人员为他们的Spring应用程序选择基于Java的配置。
有关在Spring容器中使用其他形式的元数据的信息,请参见:
- 基于注释的配置:Spring 2.5引入了对基于注释的配置元数据的支持。
- 基于java的配置:从Spring 3.0开始,Spring Java Config项目提供的许多特性成为了核心Spring框架的一部分。因此,您可以使用Java Config 而不是XML文件来定义应用程序类的外部bean。可以使用
@Configuration
、@Bean
、@Import
和@DependsOn
注解。
Spring配置由容器必须管理的至少一个(通常是一个以上)bean定义组成。 基于XML的配置元数据将这些bean配置为顶级元素内的元素。 Java配置通常在@Configuration类中使用@Bean注解的方法。
这些Bean定义对应于组成应用程序的实际对象。通常,您定义服务层对象、数据访问对象(DAO等等。通常,不需要在容器中配置细粒度的域对象,因为创建和加载域对象通常是DAO和业务逻辑的职责。但是,您可以使用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。
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/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>
下面的示例显示数据访问对象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/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对象关系映射标准)。 属性名称元素引用JavaBean属性的名称,而ref元素引用另一个Bean定义的名称。 id和ref元素之间的这种联系表达了协作对象之间的依赖性。
让Bean定义跨越多个XML文件可能很有用。通常,每个单独的XML配置文件表示体系结构中的一个逻辑层或模块。
您可以使用应用程序上下文构造函数从所有这些XML片段中加载Bean定义。 如上一节中所示,此构造函数具有多个Resource位置。 或者,使用元素的一个或多个实例从另一个文件加载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 Schema,导入的文件的内容(包括顶级元素)必须是有效的XML Bean定义。
注意:可以但不建议使用相对的“ …/”路径引用父目录中的文件。 这样做会创建对当前应用程序外部文件的依赖关系。 特别是,不建议对classpath:
URL(例如,classpath:../ services.xml
)使用此引用,在URL中,运行时解析过程会选择“最近的”类路径根目录,然后查看其父目录。 类路径配置的更改可能导致选择其他错误的目录。
您始终可以使用完全限定的资源位置来代替相对路径:例如,file:C:/config/services.xml
或classpath:/config/services.xml
。 但是请注意,您正在将应用程序的配置耦合到特定的绝对位置。 通常最好对这样的绝对位置保留一个间接寻址,例如通过在运行时针对JVM系统属性解析的“ $ {…}”占位符。
名称空间本身提供导入指令特性。除了普通bean定义之外,Spring提供的XML名称空间中还有其他配置特性,例如context和util名称空间。
作为外部化配置元数据的另一个示例,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定义文件。
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();
使用Groovy配置,引导看起来非常相似。 它有一个不同的上下文实现类,该类可识别Groovy(但也了解XML Bean定义)。 以下示例显示了Groovy配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体是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的依赖。
3.Bean的概述
Spring IOC容器管理一个或多个bean。 这些Bean是使用您提供给容器的配置元数据创建的(例如,以XML 定义的形式)。
在容器本身内,这些bean定义表示为BeanDefinition对象,其中包含(以及其他信息)以下元数据:
- 一个包限定的类名:通常是定义的bean的实际实现类。
- Bean行为配置元素,它声明Bean在容器中应该如何行为(作用域、生命周期回调,等等)。
- 引用其他Bean进行其工作所需的Bean。 这些引用也称为协作者或依赖项。
- 在新创建的对象中要设置的其他配置设置,例如池的大小限制或在管理连接池的Bean中要使用的连接数量。
该元数据转换为组成每个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的注册不被正式支持,并且可能导致并发访问异常,Bean容器中的状态不一致或都会发生。
3.1Naming Beans
每个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时对实例字段名使用标准Java约定。也就是说,bean名称以小写字母开头,并从那里使用驼峰大小写。这类名称的示例包括accountManager、accountService、userDao、loginController等等。
一致地命名Bean使您的配置更易于阅读和理解。 另外,如果您使用Spring AOP,则在将建议应用于按名称相关的一组Bean时,它会很有帮助。
通过在类路径中进行组件扫描,Spring会按照前面描述的规则为未命名的组件生成Bean名称,本质上,采用简单的类名称并将其初始字符转换为小写。 但是,在(不寻常的)特殊情况下,如果有多个字符并且第一个和第二个字符均为大写字母,则会保留原始大小写。 这些规则与java.beans.Introspector.decapitalize
(Spring在此使用)定义的规则相同。
在Bean定义本身中,可以使用由id属性指定的最多一个名称和name属性中任意数量的其他名称的组合为bean提供多个名称。 这些名称可以是同一个bean的等效别名,并且在某些情况下很有用,例如,通过使用特定于该组件本身的bean名称,让应用程序中的每个组件都引用一个公共依赖项。
但是,在实际定义Bean的地方指定所有别名并不总是足够的。 有时需要为在别处定义的Bean引入别名。 在大型系统中通常是这种情况:在大型系统中,配置在每个子系统之间分配,每个子系统都有自己的对象定义集。 在基于XML的配置元数据中,可以使用元素来完成此操作。 以下示例显示了如何执行此操作:
<alias name="fromName" alias="toName"/>
- 在这种情况下,(在同一个容器中)名为
fromName
的Bean在使用了这个别名定义之后,也可以被称为toName
。
例如,子系统A的配置元数据可以通过子系统A-dataSource的名称引用数据源。 子系统B的配置元数据可以通过子系统B-dataSource的名称引用数据源。 组成使用这两个子系统的主应用程序时,主应用程序通过myApp-dataSource的名称引用数据源。 要使所有三个名称都引用相同的对象,可以将以下别名定义添加到配置元数据中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过唯一的名称引用数据源,并且可以保证不与任何其他定义冲突(有效地创建名称空间),但是它们引用的是同一bean。
注意:如果使用Javaconfiguration,则@Bean批注可用于提供别名。
3.2实例化Beans
Bean定义实质上是创建一个或多个对象的方法。 当被询问时,容器将查看命名Bean的配方,并使用该Bean定义封装的配置元数据来创建(或获取)实际对象。
如果使用基于XML的配置元数据,则在元素的class属性中指定要实例化的对象的类型(或类)。 此类属性(在内部是BeanDefinition实例的Class属性)通常是必需的。 可以通过以下两种方式之一使用Class属性:
- 通常,在容器本身通过反射性地调用其构造函数直接创建Bean的情况下,指定要构造的Bean类,这在某种程度上等同于使用new运算符的Java代码。
- 要指定包含要创建对象的静态工厂方法的实际类,在不太常见的情况下,容器将在类上调用静态工厂方法以创建Bean。 从静态工厂方法的调用返回的对象类型可以是同一类,也可以是完全不同的另一类。
注意:如果希望为静态嵌套类配置Bean定义,则必须使用嵌套类的二进制名称。例如,如果您在com.example
包中有一个名为SomeThing
的类,并且此SomeThing
类具有一个名为OtherThing
的静态嵌套类,则Bean定义上的class属性的值为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;
}
}
这种方法表明可以通过依赖项注入(DI)来管理和配置工厂bean本身。
注意:在Spring文档中,“ factory bean”是指在Spring容器中配置并通过实例或静态工厂方法创建对象的Bean。 相比之下,FactoryBean(请注意大小写)是指特定于Spring的FactoryBean实现类。
确定特定Bean的运行时类型并非易事。 Bean元数据定义中的指定类只是初始类引用,可能与声明的工厂方法结合使用,或者是FactoryBean类,这可能导致Bean的运行时类型不同,或者在实例的情况下根本不进行设置级别的工厂方法(通过指定的factory-bean名称解析)。 此外,AOP代理可以使用基于接口的代理包装Bean实例,而目标Bean的实际类型(仅是其实现的接口)的暴露程度有限。
找出特定Bean的实际运行时类型的推荐方法是对指定Bean名称的BeanFactory.getType调用。 这考虑了上述所有情况,并返回了针对相同Bean名称的BeanFactory.getBean调用将返回的对象的类型。