本次介绍基于Spring5.0+版本,详情可参考官方文档。
1.Ioc简介
IoC也称为依赖项注入(DI)。这是一个对象仅通过构造函数参数、工厂方法的参数或对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即使用的其他对象)的过程。然后容器在创建bean时注入这些依赖项。
Spring框架的IoC容器的基础包:org.springframework.beans
和org.springframework.context。
1.1 BeanFactory和ApplicationContext
BeanFactory提供了一种高级配置机制,能够管理任何类型的对象。它与我们常使用的ApplicationContext是什么关系呢?
ApplicationContext接口,它由BeanFactory接口派生而来,因而提供BeanFactory所有的功能。ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,ApplicationContext包还提供了以下的功能:
• MessageSource, 提供国际化的消息访问
• 资源访问,如URL和文件
• 事件传播
• 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
...
}
public interface ListableBeanFactory extends BeanFactory {...}
区别:ApplicationContext通过继承其它接口来提供更多的功能。
关于更多的BeanFactory功能,我们将在下面进行详细介绍。
具体的区别可以参考博客:https://blog.csdn.net/hi_kevin/article/details/7325554
在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如何工作的高级视图。您的应用程序类与配置元数据相结合,这样,在创建并初始化ApplicationContext之后,您就拥有了一个完全配置和可执行的系统或应用程序。
1.2.1 配置元数据
配置元数据主要是告诉Spring容器如何来实例化、配置和组装对象。配置元数据通常以简单直观的XML格式表示。
注:基于xml的元数据不是惟一允许的配置元数据形式。Spring IoC容器本身与实际编写配置元数据的格式完全解耦。现在,许多开发人员为他们的Spring应用程序选择基于java配置。
其它spring容器元数据配置格式:基于注释(2.5版本支持)、基于java配置(3.0版本支持)。
Spring配置由容器必须管理的,通常至少一个和多个bean定义组成。基于xml的配置元数据将这些bean配置为顶级<beans/>元素中的<bean/>元素。Java配置通常在@Configuration类中使用@ bean注释的方法。
示例:
<?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="..."> //1 2
<!-- 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>
1. id属性是标识单个bean定义的字符串。
2. class属性定义bean的类型并使用完全限定的类名。
1.2.2 实例化一个容器
提供给ApplicationContext构造函数的位置路径是资源字符串,它允许容器从各种外部资源(如本地文件系统、Java类路径等)装载配置元数据。
/*Create a new ClassPathXmlApplicationContext with the given parent, loading the definitions from the given XML files.*/
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
下面的示例显示了服务层对象(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>
property name元素引用JavaBean属性的名称,而ref元素引用另一个bean定义的名称。id和ref元素之间的这种链接表达了协作对象之间的依赖关系。
组合基于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定义是从三个文件加载的:server.xml, messageSource.ml和themeSource.xml。所有位置路径都相对于执行导入的定义文件,因此services.xml必须与执行导入的文件位于相同的目录或类路径位置,而messageSource.xml和themeSource.xml必须位于导入文件位置之下的资源位置。可以看到,前面的斜杠被忽略了。但是,考虑到这些路径是相对的,所以最好不要使用斜杠。根据Spring模式,要导入的文件的内容,包括顶级的<beans/>元素,必须是有效的XML bean定义。
注意:
a.它是可能的,但不推荐,引用文件在父目录使用一个相对的"../”路径。这样做会在当前应用程序之外的文件上创建一个依赖项。特别地,对于classpath: URLs(例如,classpath:.. ./services.xml),不推荐使用这个引用,因为运行时解析过程会选择“最近的”类路径根,然后查看它的父目录。类路径配置更改可能导致选择不同的、不正确的目录。
b.您总是可以使用完全限定的资源位置,而不是相对路径:例如,file:C:/config/services.xml或classpath:/config/services.xml。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。通常更可取的做法是为这些绝对位置保留一个间接的地址——例如,通过“${…}”占位符,这些占位符在运行时根据JVM系统属性解析。
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();
1.3 Bean介绍
Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML <bean/>定义的形式)。
在容器内部,这些bean定义被表示为BeanDefinition对象,其中包含(其他信息)以下元数据:
- 包限定的类名:通常是定义的bean的实际实现类。
- Bean行为配置元素,它表示Bean在容器中的行为(范围、生命周期回调,等等)。
- 对该bean执行其工作所需的其他bean的引用。这些引用也称为协作者或依赖项。
- 要在新创建的对象中设置的其他配置设置—例如,池的大小限制或管理连接池的bean中使用的连接数。
此元数据转换为组成每个bean定义的一组属性。下表描述了这些属性:
Property | Explained in… |
---|---|
Class | |
Name | |
Scope | |
Constructor arguments | |
Properties | |
Autowiring mode | |
Lazy initialization mode | |
Initialization method | |
Destruction method |
下面会对这些依赖性进行详细介绍。
除了包含关于如何创建特定bean的信息的bean定义之外,ApplicationContext实现还允许注册容器外创建的现有对象(由用户创建)。这是通过getBeanFactory()方法访问ApplicationContext的BeanFactory来实现的,该方法返回BeanFactory的DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法支持这种注册。但是,典型的应用程序只使用通过常规bean定义元数据定义的bean。(我们可以通过这种方法来使用自定义的对象为bean)
注意:Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤期间正确地推断它们。虽然在某种程度上支持覆盖现有的元数据和现有的单例实例,但是在运行时注册新bean(同时对工厂进行实时访问)并没有得到官方支持,这可能会导致并发访问异常、bean容器中的不一致状态,或者两者都有。
1.3.1 Naming 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提供名称或id。如果您没有显式地提供名称或id,则容器将为该bean生成唯一的名称。但是,如果希望通过名称引用该bean,则必须通过使用ref元素或服务定位器样式查找来提供名称。不提供名称的动机与使用内部bean和自动装配协作者有关。 关于id和name的区别和使用,可以参考:https://www.cnblogs.com/alisonGavin/p/6870169.html
Bean命名约定
约定是在命名bean时使用标准Java约定作为实例字段名。也就是说,bean名称以小写字母开头,并从那里开始采用驼峰格式。此类名称的示例包括accountManager、accountService、userDao、loginController等。
注意:通过在类路径中扫描组件,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的配置元数据可以通过子系统-数据源的名称引用数据源。子系统B的配置元数据可以通过子系统-数据源的名称引用数据源。在组合使用这两个子系统的主应用程序时,主应用程序以myApp-dataSource的名称引用数据源。要使所有三个名称都引用同一个对象,可以将以下别名定义添加到配置元数据:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过一个惟一的名称来引用数据源,这个名称保证不会与任何其他定义冲突(有效地创建一个名称空间),但是它们引用的是同一个bean。
Java配置
如果使用Javaconfiguration, 可以使用 @Bean注解来提供别名详情点击
[beans-java-bean-annotation] .
1.3.2 实例化bean
bean定义本质上是创建一个或多个对象的方法。当被请求时,容器查看命名bean的配方,并使用该bean定义封装的配置元数据来创建(或获取)一个实际对象。
如果使用基于xml的配置元数据,则指定要在<bean/>元素的class属性中实例化的对象的类型(或类)。这个类属性(在内部是BeanDefinition实例上的一个类属性)通常是强制性的。(有关异常,请参见使用实例工厂方法和Bean定义继承进行实例化。)你可以通过以下两种方式之一使用Class属性:
- 通常,在容器本身通过反射调用其构造函数直接创建bean的情况下,指定要构造的bean类,这有点类似于使用新操作符的Java代码。
- 要指定包含用于创建对象的静态工厂方法的实际类,在不太常见的情况下,容器调用类上的静态工厂方法来创建bean。从静态工厂方法调用返回的对象类型可以是同一个类,也可以完全是另一个类。
内部类的名字
如果希望为静态嵌套类配置bean定义,则必须使用嵌套类的二进制名。
例如,如果您在com中有一个类名为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"/>
有关向构造函数提供参数(如果需要)和在构造对象后设置对象实例属性的机制的详细信息,请参阅Injecting Dependencies.。
用静态工厂方法实例化
在定义使用静态工厂方法创建的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;
}
}
有关向工厂方法提供(可选)参数和在对象从工厂返回后设置对象实例属性的机制的详细信息, 详情Dependencies and Configuration in Detail.
使用实例工厂方法实例化
与通过静态工厂方法实例化类似,使用实例工厂方法实例化将从容器中调用现有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本身。详情看Dependencies and Configuration in Detail.
在Spring文档中,“工厂bean”指的是在Spring容器中配置的bean,它通过实例或静态工厂方法创建对象。相反,FactoryBean(注意大小写)指的是特定于spring的FactoryBean。
1.4 依赖关系
典型的企业应用程序不包含单个对象(或Spring中的bean)。即使是最简单的应用程序也有几个对象一起工作,以呈现最终用户所看到的一致的应用程序。下一节将解释如何从定义许多独立的bean定义过渡到一个完全实现的应用程序,其中对象通过协作实现目标。
1.4.1 依赖注入
依赖项注入(DI)是一个过程,在这个过程中,对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回后在对象实例上设置的属性来定义它们的依赖项(即它们与之一起工作的其他对象)。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),通过直接构造类或服务定位器模式来控制依赖项的实例化或位置。
使用DI原则,代码更简洁,并且当对象提供其依赖项时,解耦更有效。对象不查找其依赖项,也不知道依赖项的位置或类。结果,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
依赖注入有两种主要的变体:基于构造的依赖注入(Constructor-based dependency injection)和基于setter的依赖注入(Setter-based dependency injection)。
Constructor-based依赖注入
基于构造函数的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。
构造函数参数解析
构造函数的参数解析匹配是通过使用参数的类型来实现的。如果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>
构造函数参数类型匹配
在前面的场景中,如果使用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依赖注入
基于setter的DI是在调用无参数构造函数或无参数静态工厂方法来实例化bean之后,通过容器调用bean上的setter方法来完成的。
下面的示例显示了一个只能通过使用纯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团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变的对象,并确保所需的依赖项不是空的。此外,注入构造函数的组件总是以完全初始化的状态返回给客户机(调用)代码。附带说明一下,大量的构造函数参数是一种不好的代码味道,这意味着类可能有太多的责任,应该对其进行重构,以更好地解决问题的适当分离。
Setter注入应该主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类对象可以在以后进行重新配置或重新注入。因此,通过 JMX MBeans进行管理是setter注入的一个引人注目的用例。
使用对特定类最有意义的DI样式。有时,在处理您没有源代码的第三方类时,需要为您做出选择。例如,如果第三方类没有公开任何setter方法,那么构造函数注入可能是惟一可用的DI形式。
依赖性解析过程
- 容器执行bean依赖项解析如下:
- ApplicationContext是用描述所有bean的配置元数据创建和初始化的。配置元数据可以由XML、Java代码或注释指定。
- 对于每个bean,其依赖项都以属性、构造函数参数或静态工厂方法的参数的形式表示(如果您使用该方法而不是普通构造函数)。这些依赖项是在bean实际创建时提供给bean的。
- 每个属性或构造函数参数都是to值的实际定义
在创建容器时,Spring容器验证每个bean的配置。但是,在实际创建bean之前,不会设置bean属性本身。在创建容器时,将创建单例作用域的bean并将其设置为预实例化(默认值)。作用域是在 Bean Scopes中定义的。否则,仅在请求bean时才创建它。当bean的依赖项及其依赖项的依赖项(等等)被创建和分配时,bean的创建可能会创建一个bean图。注意,这些依赖项之间的解析不匹配可能会出现得很晚——也就是说,在首次创建受影响的bean时。
循环依赖
如果主要使用构造函数注入,可能会创建无法解析的循环依赖场景。
例如:类A需要一个类B通过构造函数注入的实例,而类B需要一个类A通过构造函数注入的实例。如果您将bean配置为类A和B相互注入,Spring IoC容器将在运行时检测这个循环引用,并抛出一个BeanCurrentlyInCreationException异常。
一种可能的解决方案是编辑某些类的源代码,由setter而不是构造函数来配置。另外,避免构造函数注入,只使用setter注入。换句话说,尽管不建议这样做,但您可以使用setter注入配置循环依赖项。
与典型的情况不同(没有循环依赖关系),bean a和bean B之间的循环依赖关系强制在完全初始化之前将一个bean注入另一个bean(典型的先有鸡还是先有蛋的场景)。
您通常可以相信Spring会做正确的事情。它在容器加载时检测配置问题,例如对不存在的bean的引用和循环依赖关系。在实际创建bean时,Spring会尽可能晚地设置属性和解析依赖项。这意味着,如果在创建对象或其依赖项时出现问题(例如,bean由于缺少或无效的属性而抛出异常),在请求对象时,已正确加载的Spring容器稍后可以生成异常。一些配置问题的潜在延迟可见性是ApplicationContext实现默认预实例化单例bean的原因。在实际需要这些bean之前花费一些前期时间和内存来创建它们,您将在创建ApplicationContext时发现配置问题,而不是稍后。您仍然可以覆盖这个默认行为,这样单例bean就可以延迟初始化,而不是预先实例化。
如果不存在循环依赖项,那么当一个或多个协作bean被注入一个依赖bean时,每个协作bean在被注入依赖bean之前都要完全配置好。这意味着,如果bean依赖bean B, B Spring IoC容器完全配置bean之前调用bean的setter方法A。换句话说,bean实例化(如果它不是一个单例预先实例化),其设置依赖项,相关的生命周期方法(如configured init method 或InitializingBean callback method)调用。
依赖注入的例子
下面的示例将基于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/>元素中的子元素类型。
直接值(原语、字符串等)
元素的value属性将属性或构造函数参数指定为人类可读的字符串表示形式。Spring的转换服务用于将这些值从字符串转换为属性或参数的实际类型。下面的例子显示了设置的各种值:
<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工具套件)来支持在创建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。通过使用JavaBeans的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是 prototypebean,则此类型错误和由此产生的异常可能要在部署容器之后很久才能发现。
注意:在4.0 bean XSD中不再支持idref元素的本地属性,因为它不再提供常规bean引用之上的值。升级到4.0模式时,将现有的idref本地引用更改为idref bean。
<idref/>元素带来价值的一个常见地方(至少在Spring 2.0之前的版本中)是在ProxyFactoryBean bean定义中的AOP拦截器配置中。在指定拦截器名称时使用<idref/>元素可以防止对拦截器ID的拼写错误。
对其他bean的引用(协作者)
ref元素是<constructor-arg/>或<property/>定义元素中的最后一个元素。在这里,将bean的指定属性的值设置为对容器管理的另一个bean(合作者)的引用。所引用的bean是要设置其属性的bean的依赖项,并且在设置属性之前根据需要对其进行初始化(如果合作者是单例bean,那么容器可能已经对其进行了初始化)。所有引用最终都是对另一个对象的引用。范围确定和验证取决于是否通过bean、本地属性或父属性指定另一个对象的ID或名称。
通过<ref/>标记的bean属性指定目标bean是最常见的形式,并且允许在同一个容器或父容器中创建对任何bean的引用,而不管它是否在同一个XML文件中。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值相同。下面的例子展示了如何使用ref元素:
<ref bean="someBean"/>
通过parent属性指定目标bean将创建对当前容器的父容器中的bean的引用。父属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值相同。目标bean必须位于当前bean的父容器中。您应该主要在容器层次结构中使用此bean引用变体,并且希望将现有的bean与具有与父bean相同名称的代理包装在父容器中。下面的两个清单展示了如何使用父属性:
<!-- 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>
注意:在4.0 bean XSD中不再支持ref元素的本地属性,因为它不再提供常规bean引用之上的值。升级到4.0模式时,将现有的ref本地引用更改为ref bean。
内部Beans
在<property/>或<constructor-arg/>元素中定义了一个内部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的范围。
Collection集合
<list/>、<set/>、<map/>、<props/>元素分别设置了Java集合类型list、set、map和properties的属性和参数。下面的例子展示了如何使用它们:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">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
Collection Merging
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定义的adminemail属性的<props/>元素上使用了merge=true属性。当容器解析并实例化子bean时,得到的实例有一个adminEmails属性集合,其中包含将子bean的adminEmails集合与父bean的adminemail集合合并的结果。下面的清单显示了结果:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子属性集合的值集继承父<props/>的所有属性元素,支持值的子值覆盖父集合中的值。
这种合并行为同样适用于<list/>、<map/>和<set/>集合类型。在<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属性准备注入时,强类型映射<String, Float>的元素类型的泛型信息通过反射可用。因此,Spring的类型转换基础结构将各种值元素识别为Float类型,并将字符串值(9.99、2.75和3.99)转换为实际的Float类型。
Null and Empty String Values
Spring将属性的空参数等作为空字符串处理。以下基于xml的配置元数据片段将email属性设置为空字符串值("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的例子相当于下面的Java代码:
exampleBean.setEmail("");
<null/>元素处理空值。下面的清单显示了一个例子:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置相当于以下Java代码:
exampleBean.setEmail(null);
使用p-名称空间的XML快捷方式
p-namespace允许您使用bean元素的属性(而不是嵌套的<property/>元素)来描述协作bean的属性值,或者两者都使用。
Spring支持带有名称空间的可扩展配置格式,这些名称空间基于XML模式定义。本章讨论的bean配置格式是在XML模式文档中定义的。但是,p-namespace并没有在XSD文件中定义,它只存在于Spring的核心中。
下面的示例显示了解析为相同结果的两个XML片段(第一个使用标准XML格式,第二个使用p-namespace):
<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定义中名为email的p-namespace属性。这告诉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-namespace的属性值,而且还使用一种特殊格式来声明属性引用。第一个bean定义使用<property name="spouse" ref="jane"/>来创建bean john对bean jane的引用,而第二个bean定义使用p:spouse-ref="jane"作为属性来完成完全相同的工作。在本例中,spouse是属性名,而-ref部分指出这不是一个直接的值,而是对另一个bean的引用。
注意:p-namespace不如标准XML格式灵活。例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式则不冲突。我们建议您仔细选择您的方法,并与您的团队成员进行沟通,以避免生成同时使用这三种方法的XML文档。
使用c-名称空间的XML快捷方式
与p-namespace的XML快捷方式类似,在Spring 3.1中引入的c-namespace允许配置构造函数参数的内联属性,而不是嵌套的构造函数-参数元素。
下面的例子使用c:命名空间来做与基于构造函数的依赖注入相同的事情:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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>
命名空间使用与p: one相同的约定(bean引用的尾随-ref),用于根据构造函数参数的名称设置它们。类似地,即使没有在XSD模式中定义它(它存在于Spring核心中),也需要在XML文件中声明它。
对于构造函数参数名不可用的罕见情况(通常如果编译字节码时没有调试信息),可以使用回退参数索引,如下所示:
<!-- 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属性时,可以使用复合或嵌套属性名,只要路径的所有组件(最后的属性名除外)都不为空。考虑以下bean定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
something bean有一个fred属性,一个bob属性,一个sammy属性,最后一个sammy属性被设置为123。为了使其工作,在构造bean之后,fred的属性和fred的bob属性不能为空。否则,将抛出NullPointerException。
1.4.3 使用depends-on
如果一个bean是另一个bean的依赖项,这通常意味着将一个bean设置为另一个bean的属性。通常使用基于xml的配置元数据中的<ref/>元素来完成此任务。然而,有时候bean之间的依赖关系不那么直接。例如,当需要触发类中的静态初始化器时,例如注册数据库驱动程序时。依赖属性可以显式地强制在使用此元素的bean初始化之前对一个或多个bean进行初始化。下面的示例使用depends-on属性来表示对单个bean的依赖:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
为了表示对多个bean的依赖,提供一个bean名称列表作为依赖属性的值(逗号、空格和分号是有效的分隔符):
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
depends-on属性既可以指定一个初始化时间依赖项,也可以指定一个对应的销毁时间依赖项。与给定bean定义依赖关系的依赖bean首先被销毁,先于给定bean本身被销毁。因此,依赖也可以控制关机顺序。
1.4.4 延迟初始化Beans(懒加载)
默认情况下,ApplicationContext实现将创建和配置所有的单例bean作为初始化过程的一部分。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即被发现,而不是几小时甚至几天之后。当此行为不可取时,您可以通过将bean定义标记为延迟初始化来防止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时。
在XML中,这种行为是由<bean/>元素上的lazy-init属性控制的,如下面的示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当前面的配置被ApplicationContext使用时,当ApplicationContext启动时,懒bean不会被预先实例化,反之则不会。懒bean被急切地预先实例化。
然而,当延迟初始化的bean是未延迟初始化的单例bean的依赖项时,ApplicationContext在启动时创建延迟初始化的bean,因为它必须满足单例的依赖项。延迟初始化的bean被注入到一个没有延迟初始化的单例bean中。
您还可以使用<beans/>元素上的default-lazy-init属性来控制容器级别的延迟初始化,如下面的示例所示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5 自动装配的合作者
Spring容器可以自动创建协作bean之间的关系。通过检查ApplicationContext的内容,您可以让Spring为您的bean自动解析协作者(其他bean)。自动装配有以下优点:
- 自动装配可以大大减少指定属性或构造函数参数的需要。(本章其他地方讨论的其他机制,如bean模板,在这方面也很有价值。)
- 自动装配可以随着对象的演化更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而不需要修改配置。因此,自动装配在开发过程中特别有用,当代码库变得更加稳定时,自动装配可以避免切换到显式连接的选项。
在使用基于xml的配置元数据(请参阅依赖项注入)时,可以使用<bean/>元素的autowire属性为bean定义指定autowire模式。自动装配功能有四种模式。您可以为每个bean指定自动装配,因此可以选择自动装配哪些bean。下表描述了四种自动装配模式:
Mode | Explanation |
---|---|
| (默认)没有自动装配。Bean引用必须由ref元素定义。对于较大的部署,不建议更改默认设置,因为显式地指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了一个系统的结构。 |
| 通过属性名自动装配。Spring寻找与需要自动装配的属性同名的bean。例如,如果一个bean定义被按名称设置为autowire,并且它包含一个masterproperty(也就是说,它有一个setMaster(..)方法),Spring会查找一个名为master的bean定义并使用它来设置属性。 |
| 如果容器中恰好存在该属性类型的一个bean,则允许自动获取该属性。如果存在多个异常,则抛出一个致命异常,这表明您不能为该bean使用byType自动装配。如果没有匹配的bean,则什么也不会发生(属性没有设置)。 |
| 类似于byType,但适用于构造函数参数。如果容器中没有一个构造函数参数类型的bean,则会引发致命错误。 |
使用byType或构造函数自动装配模式,您可以连接数组和类型化集合。在这种情况下,将提供容器中与预期类型匹配的所有autowire候选对象,以满足依赖性。如果期望的键类型是String,则可以自动装配强类型映射实例。自动装配映射实例的值由所有匹配期望类型的bean实例组成,映射实例的键包含相应的bean名称。
自动装配的局限性和缺点
自动装配在跨项目一致使用时效果最好。如果通常不使用自动装配,那么开发人员使用它来连接一个或两个bean定义可能会感到困惑。
考虑自动装配的局限性和缺点:
- 属性和构造参数设置中的显式依赖项总是覆盖自动装配。您不能自动生成简单的属性,如原语、字符串和类(以及这些简单属性的数组)。这种限制是由设计造成的。
- 自动装配不如显式连接精确。尽管如前面的表中所述,Spring小心地避免猜测,以免产生可能产生意外结果的歧义。spring管理对象之间的关系不再明确记录。
- 连接信息可能对从Spring容器生成文档的工具不可用。
- 容器中的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。对于数组、集合或映射实例,这不一定是个问题。然而,对于期望单个值的依赖项,这种模糊性不是任意解决的。如果没有唯一的bean定义可用,则抛出异常。
在后一种情况下,你有几个选择:
放弃自动装配,支持显式连接。
通过将bean定义的autowire-candidate属性设置为false来避免自动装配,如下一节所述。
通过将其<bean/>元素的主属性设置为true,将单个bean定义指定为主候选。
实现基于注释的配置提供的更细粒度的控制,如基于注释的容器配置中所述Annotation-based Container Configuration。
从自动装配中排除一个Bean
在每个bean的基础上,可以将bean排除在自动装配之外。在Spring的XML格式中,将<bean/>元素的autowire-candidate属性设置为false。容器使特定的bean定义对自动装配基础设施(包括诸如@Autowired之类的注释样式配置)不可用。
注意:autowire-candidate属性被设计为只影响基于类型的自动装配。它不会影响按名称显示的引用,即使指定的bean没有标记为autowire候选bean,也会解析这些引用。因此,如果名字匹配,自动装配仍然会注入一个bean。
您还可以根据bean名称的模式匹配来限制自动装配候选对象。顶级<beans/>元素在其default-autowire-candidate属性中接受一个或多个模式。例如,要将autowire候选状态限制为名称以Repository结尾的任何bean,请提供一个值*Repository。要提供多个模式,请在逗号分隔的列表中定义它们。bean定义的autowire-candidate属性的显式值true或false总是优先。对于这样的bean,不适用模式匹配规则。
这些技术对于那些您不希望通过自动装配将其注入到其他bean中的bean非常有用。这并不意味着不能通过使用自动装配来配置被排除的bean。相反,bean本身不是自动装配其他bean的候选对象。
1.4.6 方法注入
在大多数应用程序场景中,容器中的大多数bean都是单例的。当一个单例bean需要与另一个单例bean协作,或者一个非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖性。当bean的生命周期不同时,就会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能是在A的每个方法调用上。容器只创建一次单例bean A,因此只有一次机会来设置属性。容器不能每次需要bean B的新实例时都向bean A提供一个。
解决的办法是放弃一些控制反转。您可以通过实现applicationcontext - ware接口,并在每次bean A需要时调用容器的getBean(“B”)来请求(通常是一个新的)bean B实例,从而使bean A知道容器。下面的例子展示了这种方法:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前一种方法是不可取的,因为业务代码知道Spring框架并与之耦合。方法注入是Spring IoC容器的一个比较高级的特性,它允许您干净地处理这个用例。
您可以在这篇博客中了解更多关于方法注入的动机。
查找方法注入
查找方法注入是容器覆盖容器管理bean上的方法并返回容器中另一个命名bean的查找结果的能力。查找通常涉及到一个原型bean,如前一节描述的场景所示。Spring框架通过使用来自CGLIB库的字节码生成来动态生成覆盖该方法的子类,从而实现了这种方法注入。
注意:
- 要使这个动态子类工作,Spring bean容器子类的类不能是final,要覆盖的方法也不能是final。
- 对具有抽象方法的类进行单元测试需要您自己对该类进行子类化,并提供抽象方法的存根实现。
- 组件扫描也需要具体的方法,这需要具体的类来获取。
- 进一步的关键限制是,查找方法不能与工厂方法一起工作,特别是不能与配置类中的@Bean方法一起工作,因为在这种情况下,容器不负责创建实例,因此不能动态地创建运行时生成的子类。
在前面代码片段中的CommandManager类的情况下,Spring容器动态地覆盖createCommand()方法的实现。CommandManager类没有任何Spring依赖项,正如重新处理的示例所示:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含要注入的方法的client类中(本例中的CommandManager),要注入的方法需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖在原始类中定义的具体方法。考虑下面的例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
标识为commandManager的bean在需要myCommand bean的新实例时调用自己的createCommand()方法。如果实际需要的话,您必须小心地将myCommand bean部署为原型。如果它是单例的,那么每次都会返回相同的myCommand bean实例。
或者,在基于注释的组件模型中,可以通过@Lookup注释声明查找方法,如下面的示例所示:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更具体地说,您可以依赖于根据声明的查找方法返回类型解析目标bean:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
请注意,您通常应该使用具体的存根实现来声明此类带注释的查找方法,以便使它们与Spring的组件扫描规则兼容,其中抽象类在缺省情况下被忽略。此限制不适用于显式注册或显式导入的bean类。
注意:
访问范围不同的目标bean的另一种方法是ObjectFactory/ Provider注入点。将作用域bean视为依赖项Scoped Beans as Dependencies。
您还可以在org.springframe .bean .factory中找到ServiceLocatorFactoryBean。配置包)是有用的。
任意的方法替换
与查找方法注入相比,一种不太有用的方法注入形式是能够用另一种方法实现替换托管bean中的任意方法。您可以安全地跳过本节的其余部分,直到您真正需要此功能为止。
对于基于xml的配置元数据,您可以使用replace -method元素将已部署bean的现有方法实现替换为其他方法实现。考虑下面的类,它有一个我们想要覆盖的叫做computeValue的方法:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
实现org.springframe .bean .factory.support的类。MethodReplacer接口提供了新的方法定义,如下例所示:
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
部署原始类并指定方法覆盖的bean定义类似于以下示例:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
您可以在< replacedmethod />元素中使用一个或多个< argtype />元素来指示被覆盖方法的方法签名。只有在方法重载且类中存在多个变量时,才需要对参数进行签名。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配java.lang.String:
java.lang.String
String
Str
因为参数的数量通常足以区分每种可能的选择,所以这个快捷方式可以节省大量的输入,因为它允许您只输入与参数类型匹配的最短字符串。