原文英文链接:
https://docs.spring.io/spring/docs/5.2.3.BUILD-SNAPSHOT/spring-framework-reference/core.html#spring-core
https://github.com/spring-projects/spring-framework
(由于鄙人英文水平以及技术能力有限,可能无法做到 信达雅,如有不当之处,烦请在评论区指出,非常感谢!)
Version 5.2.3.BUILD-SNAPSHOT
参考文档的这一部分涵盖了Spring框架必不可少的所有技术。
其中最重要的是Spring框架的控制反转(IoC)容器。对Spring框架的IoC容器进行彻底处理之后,将全面介绍Spring的面向方面编程(AOP)技术。Spring框架拥有自己的AOP框架,该框架在概念上易于理解,并且成功解决了Java企业编程中AOP要求的80%的难题。
还提供了Spring与AspectJ的集成(就功能而言,目前是最丰富的-当然肯定是Java企业领域中最成熟的AOP实现)。
本章介绍了Spring的控制反转(IoC)容器。
本章介绍了控制反转(IoC)原理的Spring框架实现。IoC也称为依赖注入(DI)。在此过程中,对象仅通过构造函数参数,工厂方法的参数或从工厂方法构造或返回对象实例后,通过在其上设置的属性来定义其依赖项(即与之一起工作的其他对象)的过程 。然后,容器在创建bean时注入那些依赖项。此过程跟bean自己控制实例化,或者通过直接构造类或服务定位器模式定位其依赖项的方式相反(因此称为控制反转)。
org.springframework.beans
和org.springframework.context
包是Spring框架的IoC容器的基础。
BeanFactory
接口提供了一种高级配置机制,能够管理任何类型的对象。
ApplicationContext
是的子接口BeanFactory
。它增加了:
与Spring的AOP功能轻松集成
消息资源处理(用于国际化)
事件发布
应用层特定的上下文,例如WebApplicationContext
用于Web应用程序中的。
简而言之,BeanFactory
提供了配置框架和基本功能,并ApplicationContext
增加了更多针对企业的功能。该ApplicationContext
是BeanFactory的
完整的超集,在本章中专门描述Spring的IoC容器。有关使用BeanFactory
而不是ApplicationContext的信息,
参见 BeanFactory获得
更多信息。
在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。Bean是由Spring IoC容器实例化,组装和以其他方式管理的对象。否则,bean仅仅是应用程序中许多对象之一。Bean及其之间的依赖关系反映在容器使用的配置元数据中。
org.springframework.context.ApplicationContext
接口代表Spring IoC容器,并负责实例化,配置和组装Bean。容器通过读取配置元数据来获取有关要实例化,配置和组装的对象的指令。配置元数据以XML,Java注解或Java代码表示。它定义了组成应用程序的对象以及这些对象之间的相互依赖关系。
Spring提供了ApplicationContext
接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
的实例 。尽管XML是定义配置元数据的传统格式,但是您可以通过Java注解或代码作为元数据格式,并提供少量XML配置,来声明对这些附加元数据格式的支持。
在大多数应用场景中,不需要显式用户代码即可实例化一个Spring IoC容器的一个或多个实例。例如,在Web应用程序场景中,应用程序文件中的简单八行 web.xml
通常就足够了(请参阅Web应用程序的便捷ApplicationContext实例化)。如果使用 Spring Tool Suite(基于Eclipse的开发环境),则只需单击几次鼠标或击键即可轻松创建此样板配置。
下图显示了Spring的工作原理的高级视图。您的应用程序类与配置元数据结合在一起,因此,在ApplicationContext
创建和初始化后,您将拥有一个完全配置且可执行的系统或应用程序。
图1. Spring IoC容器
如上图所示,Spring IoC容器使用一种形式配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉Spring容器实例化,配置和组装应用程序中的对象。
传统上,配置元数据以简单直观的XML格式提供,这是本章大部分内容用来传达Spring IoC容器的关键概念和功能的内容。
|
基于XML的元数据不是配置元数据的唯一允许形式。Spring IoC容器本身与实际写入此配置元数据的格式完全脱钩。如今,许多开发人员为他们的Spring应用程序选择 基于Java的配置。 |
有关在Spring容器中使用其他形式的元数据的信息,请参见:
基于注解的配置:Spring 2.5引入了对基于注解的配置元数据的支持。
基于Java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多功能成为核心Spring Framework的一部分。因此,您可以使用Java而不是XML文件来定义应用程序类外部的bean。要使用这些新功能,请参阅 @Configuration
, @Bean
, @Import
,和@DependsOn
注释。
Spring配置由容器必须管理的至少一个(通常是一个以上)bean定义组成。基于XML的配置元数据将这些bean配置为
顶级元素内的
元素。Java配置通常@Bean
在@Configuration
类中使用带注解的方法。
这些bean定义对应于组成应用程序的实际对象。通常,您定义服务层对象,数据访问对象(DAO),表示对象(例如Struts Action
实例),基础结构对象(例如Hibernate SessionFactories
,JMS Queues
等)。通常,不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是DAO和业务逻辑的职责。但是,您可以使用Spring与AspectJ的集成来配置在IoC容器的控制范围之外创建的对象。请参阅使用AspectJ与Spring依赖注入域对象。
以下示例显示了基于XML的配置元数据的基本结构:
|
该 |
|
该 |
该id
属性的值是指协作对象。在此示例中未显示用于引用协作对象的XML。有关更多信息,请参见 依赖项。
提供给ApplicationContext构造函数的位置路径是资源字符串,它允许容器从各种外部资源(如本地文件系统、Java类路径等)装载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
|
以下示例显示了服务层对象(services.xml)
配置文件:
以下示例显示了数据访问对象daos.xml
文件:
在前面的示例中,服务层由PetStoreServiceImpl类和JpaAccountDao和JpaItemDao类型的两个数据访问对象(基于JPA对象-关系映射标准)组成。property name元素引用JavaBean属性的名称,而ref元素引用另一个bean定义的名称。id和ref元素之间的这种链接表达了协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参阅 依赖关系。
构建基于XML的配置元数据
使bean定义跨越多个XML文件可能很有用。通常,每个单独的XML配置文件都代表体系结构中的逻辑层或模块。
您可以使用应用程序上下文构造函数从所有这些XML片段中加载bean定义。Resource
如上一节所示,此构造函数具有多个位置 。或者,使用一个或多个出现的
元素从另一个文件中加载bean定义。以下示例显示了如何执行此操作:
在前面的例子中,外部bean定义是从三个文件加载: services.xml
,messageSource.xml
,和themeSource.xml
。所有位置路径都相对于进行导入的定义文件(相对路径),因此services.xml
必须与进行导入的文件位于同一目录或类路径位置, messageSource.xml和
themeSource.xml
必须位于resources
导入文件位置下方的位置。如您所见,斜杠被忽略。但是,鉴于这些路径是相对的,最好不要使用任何斜线。
根据Spring Schema,导入的文件的内容(包括顶层元素)必须是有效的XML bean定义。
|
可以但不建议使用相对的“ ../”路径引用父目录中的文件。这样做会创建对当前应用程序外部文件的依赖。特别是,对于运行时解析过程选择“最近的”类路径根目录然后查找其父目录的 您始终可以使用完全限定的资源位置来代替相对路径:例如 |
命名空间本身提供了导入指令功能。Spring提供的一系列XML命名空间(例如context
和util命名
空间)中提供了超出普通bean定义的其他配置功能。
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定义文件。
ApplicationContext是能够维护不同bean及其依赖项的注册表的高级工厂的接口。通过使用方法T getBean(String name, Class 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 userList = service.getUsernameList();
使用Groovy配置,引导看起来非常相似。它有一个不同的上下文实现类,该类可识别Groovy(但也了解XML Bean定义)。以下示例显示了Groovy配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体是GenericApplicationContext
与读取器委托结合使用,例如,与XmlBeanDefinitionReader
XML文件结合使用,如以下示例所示:
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上混合和匹配这样的reader委托,从不同的配置源读取bean定义。
然后可以使用getBean检索bean的实例。ApplicationContext接口有一些用于检索bean的其他方法,但在理想情况下,应用程序代码不应该使用它们。实际上,您的应用程序代码应该完全不调用getBean()方法,因此完全不依赖于Spring api。例如,Spring与web框架的集成为各种web框架组件(如控制器和jsf管理的bean)提供了依赖项注入,允许您通过元数据(如自动装配注释)声明对特定bean的依赖项。
Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML
定义的形式 )。
在容器本身内,这些bean定义表示为BeanDefinition
对象,其中包含(除其他信息外)以下元数据:
包限定的类名:通常,定义了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容器中的不一致状态,或者两者都有。 |
每个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和自动装配协作者有关。
Bean命名规范
约定是在命名bean时使用标准Java约定作为实例字段名。也就是说,bean名称以小写字母开头,并从那里开始采用驼峰格式。此类名称的示例包括accountManager、accountService、userDao、loginController等。
一致地命名bean使您的配置更容易阅读和理解。另外,如果您使用Spring AOP,在将建议应用到一组按名称关联的bean时,它会有很大帮助。
通过在类路径中进行组件扫描,Spring会按照前面描述的规则为未命名的组件生成Bean名称:从本质上讲,采用简单的类名称并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,如果有多个字符并且第一个和第二个字符均为大写字母,则会保留原始大小写。这些规则与 java.beans.Introspector.decapitalize (由Spring在此处使用)定义的规则相同。 |
在Bean定义之外使用别名
在bean定义本身中,可以通过使用由id
属性指定的最多一个名称和该属性中任意数量的其他名称的组合来为bean提供多个名称name
。这些名称可以是同一个bean的等效别名,并且在某些情况下很有用,例如通过使用特定于该组件本身的bean名称,让应用程序中的每个组件都引用一个公共依赖项。
但是,在实际定义bean的地方指定所有别名并不总是足够的。有时需要为在别处定义的bean引入别名。这在大型系统中通常是这种情况,在大型系统中,配置在每个子系统之间分配,每个子系统都有自己的对象定义集。在基于XML的配置元数据中,您可以使用
元素来完成此任务。以下示例显示了如何执行此操作:
在这种情况下,fromName
在使用该别名定义之后,命名为Bean(位于同一容器中)的bean也可以称为toName
。
例如,子系统A的配置元数据可以使用的名称引用数据源subsystemA-dataSource
。子系统B的配置元数据可以使用的名称引用数据源subsystemB-dataSource
。组成使用这两个子系统的主应用程序时,主应用程序使用名称引用数据源myApp-dataSource
。要使所有三个名称都引用相同的对象,可以将以下别名定义添加到配置元数据中:
现在,每个组件和主应用程序都可以通过唯一的名称引用数据源,并保证不与任何其他定义冲突(有效地创建名称空间),但它们引用的是同一bean。
如果使用Java配置,则@Bean注解
可用于提供别名。有关详细信息,请参见使用@Bean注解。
Bean定义本质上是创建一个或多个对象的方法。当需要创建Bean时,容器将查看命名bean的创建方法,并使用该bean定义封装的配置元数据来创建(或获取)实例对象。
如果使用基于xml的配置元数据,则指定要在元素的class属性中实例化的对象的类型(或类)。这个类属性(在内部是BeanDefinition实例上的一个类属性)通常是强制性的。(请参见使用实例工厂方法和Bean定义继承进行实例化)。你可以通过以下两种方式之一使用Class属性:
通常,在容器本身通过反射调用其构造函数直接创建bean的情况下,指定要构造的bean类,这有点类似于使用新操作符的Java代码。
要指定包含用于创建对象的静态工厂方法的实际类,在不太常见的情况下,容器调用类的静态工厂方法来创建bean。从静态工厂方法调用返回的对象类型可以是同一个类,也可以完全是另一个类。
如果希望为静态嵌套类配置bean定义,则必须使用嵌套类的二进制名。
例如,如果您在com中有一个类名为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时,请使用class
属性指定包含static
工厂方法的类,并使用命名factory-method
为属性的属性来指定工厂方法本身的名称。您应该能够调用此方法(带有可选参数,如后面所述),并返回一个活动对象,该对象随后将被视为已通过构造函数创建。这种bean定义的一种用法是static
用旧版代码调用工厂。
以下bean定义指定通过调用工厂方法来创建bean。该定义不指定返回对象的类型(类),而仅指定包含工厂方法的类。在此示例中,该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:
以下示例显示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
一个工厂类也可以包含一个以上的工厂方法,如以下示例所示:
以下示例显示了相应的类:
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(类) 。 |
典型的企业应用程序不由单个对象(或Spring术语中的bean)组成。即使是最简单的应用程序,也有一些对象可以协同工作,以呈现最终用户视为一致的应用程序。下一部分将说明如何从定义多个独立的Bean定义到实现对象协作以实现目标的完全实现的应用程序。
依赖注入(DI)是一个过程,通过该过程,对象仅通过构造函数参数,工厂方法的参数或在构造或创建对象实例后在对象实例上设置的属性来定义其依赖关系(它们使用的其他对象)。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),通过直接构造类或服务定位器模式来控制依赖项的实例化或位置。
使用DI原理,代码更加简洁,当为对象提供依赖项时,解耦会更有效。该对象不查找其依赖项,并且不知道依赖项的位置或类。结果,您的类变得更易于测试,尤其是当依赖项依赖于接口或抽象基类时,它们允许在单元测试中使用stub或mock实现。
DI存在两个主要方式:基于构造函数的依赖注入和基于Setter的依赖注入。
基于构造方法的依赖注入
基于构造方法的DI是通过容器调用具有多个参数的构造方法来完成的,每个参数表示一个依赖项。调用static
带有特定参数的工厂方法来构造Bean几乎是等效的,并且本次讨论将构造方法和static
工厂方法的参数视为类似。以下示例显示了只能通过构造方法注入进行依赖项注入的类:
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
类不是通过继承关联的,则不存在潜在的歧义。因此,以下配置可以正常工作,并且您无需在
元素中显式指定构造方法参数索引或类型。
当引用另一个bean时,类型是已知的,并且可以发生匹配(与前面的示例一样)。当使用诸如的简单类型时
,Spring无法确定值的类型,因此在没有帮助的情况下无法按类型进行匹配。考虑以下类别:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
在上述情况下,如果通过使用type
属性显式指定构造函数参数的类型,则容器可以使用简单类型的类型匹配。如以下示例所示:
您可以使用该index
属性来明确指定构造函数参数的索引,如以下示例所示:
除了解决多个简单值的歧义性之外,指定索引还可以解决歧义,其中构造函数具有两个相同类型的参数。
索引从0开始。 |
您还可以使用构造函数参数名称来消除歧义,如以下示例所示:
请记住,要立即使用该功能,必须在启用调试标志的情况下编译代码,以便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的依赖注入
基于Setter的DI是通过在调用无参数构造函数或无参数static
工厂方法以实例化您的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
支持基于构造函数和的Setter DI为它所管理的bean类。在通过构造函数方法注入了某些依赖项之后,它还支持基于setter的DI。您可以将依赖项配置为BeanDefinition的形式,将其与PropertyEditor实例结合使用,将属性从一种格式转换为另一种格式。但是,大多数Spring用户并不直接使用这些类(即以编程方式),而是使用XML bean定义、带注释的组件(即使用@Component、@Controller等进行注释的类)或基于java的@Configuration类中的@Bean方法。然后,这些元数据在内部转换为BeanDefinition实例,并用于加载整个Spring IoC容器实例。
由于可以混合使用基于构造函数的DI和基于setter的DI,因此,将构造函数用于强制性依赖项并将setter方法或配置方法用于可选依赖性是一个很好的经验法则。请注意,可以 在setter方法上使用@Required批注,以使该属性成为必需的依赖项。但是,最好使用带有参数的程序验证的构造函数注入。
Spring团队通常提倡构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保不存在必需的依赖项null
。此外,构造函数注入的组件始终以完全初始化的状态返回到客户端(调用)代码。附带说明一下,大量的构造函数自变量是一种不好的代码味道,这意味着该类可能承担了太多的职责,应该对其进行重构以更好地解决关注点分离问题。
Setter注入主要应仅用于可以在类中分配合理的默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象在以后可以重新配置或重新注入。因此,通过JMX MBean进行管理是用于setter注入的引人注目的用例。
使用最适合特定class的DI风格。有时,在处理您没有源代码的第三方类时,将为您做出选择。例如,如果第三方类未公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。
依赖性解析过程
容器执行bean依赖项解析,如下所示:
使用ApplicationContext
描述所有bean的配置元数据创建和初始化。可以通过XML,Java代码或注解指定配置元数据。
对于每个bean,其依赖关系都以属性,构造函数参数或static-factory方法的参数的形式表示(如果使用它而不是普通的构造函数)。实际创建Bean时,会将这些依赖项提供给Bean。
每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
每个值的属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring能够以String类型提供值转换成所有内置类型,比如int
, long
,String
,boolean
,等等。
在创建容器时,Spring容器会验证每个bean的配置。但是,在实际创建Bean之前,不会设置Bean属性本身。创建容器时,将创建具有单例作用域并设置为预先实例化(默认)的Bean。范围在Bean范围中定义。否则,仅在请求时才创建Bean。创建和分配bean时可能会导致创建一个bean图。因为bean的依赖项及其依赖项的依赖项(etc)会被创建和分配。
请注意,这些依赖项之间的解析匹配可能会显示得较晚,即在首次创建受影响的bean时。
循环依赖
如果主要使用构造函数注入,则可能会创建无法解决的循环依赖方案。
例如:A类通过构造函数注入需要B类的实例,而B类通过构造函数注入需要A类的实例。如果您将A类和B类的bean配置为相互注入,则Spring IoC容器会在运行时检测到此循环引用,并抛出 BeanCurrentlyInCreationException
。
一种可能的解决方案是编辑某些类的源代码,这些类的源代码由设置者而不是构造函数来配置。或者,避免构造函数注入,而仅使用setter注入。换句话说,尽管不建议这样做,但是您可以使用setter注入配置循环依赖项。
与典型情况(没有循环依赖关系)不同,Bean A和Bean B之间的循环依赖关系迫使其中一个Bean在完全初始化之前被注入另一个Bean(经典的“鸡与蛋”场景)。
通常,您可以信任Spring做正确的事。它在容器加载时检测配置问题,例如对不存在的Bean的引用和循环依赖项。在实际创建bean时,Spring设置属性并尽可能晚地解决依赖关系。这意味着已经正确加载的Spring容器以后可以在请求对象时生成异常,如果创建该对象或其依赖项之一时遇到问题-例如,Bean由于缺少或无效的属性会引发异常。对于这样的配置问题,这种潜在的延迟可见性是是ApplicationContext实现默认预实例化单例bean的原因。在实际需要这些bean之前,要花一些前期时间和内存来创建它们,您会在创建bean时发现配置问题ApplicationContext
,而不是稍后发现。您仍然可以重写此默认行为,以便单例bean延迟初始化,而不是预先实例化。
如果不存在循环依赖关系,则在将一个或多个协作Bean注入到从属Bean中时,每个协作Bean在注入到从属Bean中之前都已完全配置。这意味着,如果bean A依赖于bean B,则Spring IoC容器会在对bean A调用setter方法之前完全配置beanB。换句话说,bean被实例化(如果不是预先实例化的singleton ),设置其依赖项,并调用相关的生命周期方法(例如已配置的init方法 或InitializingBean回调方法)。
依赖注入的例子
以下示例将基于XML的配置元数据用于基于setter的DI。Spring XML配置文件的一小部分指定了一些bean定义,如下所示:
以下示例显示了相应的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:
以下示例显示了相应的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调用static
工厂方法以返回对象的实例:
以下示例显示了相应的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;
}
}
static
工厂方法的参数由
元素提供,就好像实际使用了构造函数一样。工厂方法返回的类的类型不必与包含静态工厂方法的类具有相同的类型(尽管在此示例中是)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用factory-bean
属性代替class
属性之外),因此在此不讨论这些细节(译者注:见1.3 Bean的概述实例工厂方法)。
如上节所述,自定义的Bean属性或者构造函数参数可以引用其他被管理的Bean,或者定义内联的值。Spring基于XML配置的元数据可以在
和
元素中的配置子元素类型来实现这一功能。
直接值(基本类型,字符串等)
元素的value属性值将属性或构造函数参数指定为人类可读的字符串表示形式。Spring的转换服务用于将这些值从字符串转换为属性或参数的实际类型。下面的例子显示了设置的各种值:
下面的示例使用p-namespace进行更简洁的XML配置:
前面的XML更简洁。但是,除非在创建bean定义时使用支持自动属性完成的IDE(例如IntelliJ IDEA或Spring Tool Suite),否则错字是在运行时而不是设计时发现的。强烈建议您使用此类IDE帮助。
您还可以配置java.util.Properties
实例,如下所示:
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
Spring容器使用JavaBeans属性编辑器机制将java.util.Properties
实例,这是一个很好的快捷方式。并且是Spring团队确实支持使用嵌套
元素而非value
属性样式的少数几个地方之一。
idref
元素
idref元素只是将容器中另一个bean的id(字符串值,而不是引用)传递给
前面的bean定义片段(在运行时)与下面的片段完全等效:
第一种形式优于第二种形式,因为使用idref
标记可以使容器在部署时验证所引用的命名Bean实际上是否存在。在第二个变体中,不对传递给Bean targetName
属性的值进行验证client
。拼写错误仅在client
实际实例化bean 时发现(最有可能导致致命的结果)。如果该client
bean是原型 bean,则可能在部署容器很长时间后才发现此错字和所产生的异常。
在4.0 bean XSD中不再支持idref元素的local属性,因为它不再提供常规bean ref之上的值(译者注:详见idref和ref的区别)。升级到4.0模式时,将现有的idref local 引用更改为idref bean。
元素的一个常用的作用(至少在Spring 2.0之前的版本)是用在 ProxyFactoryBean
bean中定义的AOP拦截里。
在指定拦截器名称时使用元素可防止您拼写错误的拦截器ID。
引用其他Beans
ref元素是定义元素
或
中的最后一个元素。在这里,将bean的指定属性的值设置为对容器管理的另一个bean的引用。所引用的bean是要设置其属性的bean的依赖项,并且在设置属性之前根据需要对其进行初始化(如果合作者是单例bean,那么容器可能已经对其进行了初始化)。所有引用最终都是对另一个对象的引用。作用域和验证会根据bean、local或parent属性指定另一个对象的ID或name而做相应的变动。
通过标记的bean属性指定目标bean是最常见的形式,并且允许在同一个容器或父容器中创建对任何bean的引用,而不管它是否在同一个XML文件中。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值相同。下面的例子展示了如何使用ref元素:
parent
属性指定目标Bean 将创建对当前容器的父容器中的Bean的引用。该
parent
属性的值可以与目标Bean 的
id
属性或目标Bean的
name
属性中的值之一相同。目标Bean必须位于当前容器的父容器中。当你使用层级的容器结构时,并且希望与父Bean同名的代理 将现有的Bean 包装在父容器中时,就可以使用parent属性。
parent
属性:
class="org.springframework.aop.framework.ProxyFactoryBean">
元素 的 local 属性在ref 4.0 bean XSD中不再受支持,因为它不再提供常规bean 引用上的值。升级到4.0模式时,将现有ref local 引用更改ref bean 为 |
或
元素内部的 元素定义了一个内部Bean,下面是一个例子:
内部bean定义不需要定义的ID或名称。如果指定,则容器不使用该值作为标识符。容器还会忽略scope
创建时的标志,因为内部Bean始终是匿名的,并且始终与外部Bean一起创建。不可能独立地访问内部bean或将其注入到协作bean中而不是封装到封闭bean中。
作为一个特例,可以从自定义范围接收destruction 回调,例如,对于单例bean中包含的请求范围内的bean。内部bean实例的创建与其包含的bean绑定在一起,但是destruction 回调使它可以参与请求范围的生命周期。这不是常见的情况。内部bean通常只共享其包含bean的作用域。
Collections
,
,,和
Java的Collection
类型List
,Set
,Map
,和Properties
。以下示例显示了如何使用它们:
[email protected]
[email protected]
[email protected]
just some string
下面的属性可以作为map的key或者value,也可以作为set的value:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器还支持合并集合。应用程序开发人员可以定义父级
,,
或
元素,并有子级
,,
或
元素继承和重写父集合的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合的元素将覆盖父集合中指定的值。
本节讨论了关于合并的父子bean机制。不熟悉父bean和子bean定义的读者可能希望先阅读 相关部分,然后再继续。
下面的示例演示了集合合并:
[email protected]
[email protected]
[email protected]
[email protected]
注意在子bean定义的adminEmails属性的merge=true
属性。当子bean由容器解析并实例化时,生成的实例具有一个adminEmails的集合,该集合包含将子项的集合与父项的集合合并的结果 。以下清单显示了结果:
[email protected]
[email protected]
suppo [email protected]
子Properties
集合的值继承父集合中所有属性元素
,子集合支持覆盖父集合中的值。
这一合并行为同样适用于
,和
集合类型。
元素比较特殊(即值是有序集合),父list的值位于所有子级list的值之前。对于Map
,Set
和Properties
集合类型,不存在排序。因此,对于容器内部使用的关联映射、集合和属性实现类型下的集合类型,没有有效的排序语义。
集合合并的局限性
您不能合并不同的集合类型(例如映射和列表)。如果您尝试这样做,将抛出一个适当的异常。merge属性必须在较低的继承子定义上指定。在父集合定义上指定merge属性是冗余的,并且不会导致所需的合并。
强类型集合
通过在Java 5中引入泛型类型,您可以使用强类型集合。也就是说,可以声明一个集合类型,使它只能包含(例如)字符串元素。如果您使用Spring来依赖地将强类型集合注入到bean中,则可以利用Spring的类型转换支持,以便在将强类型集合实例的元素添加到集合之前将其转换为适当的类型。下面的Java类和bean定义说明了如何做到这一点:
public class SomeClass {
private Map accounts;
public void setAccounts(Map accounts) {
this.accounts = accounts;
}
}
当something bean的accounts属性准备注入时,强类型映射的元素类型的泛型信息通过反射得到。因此,Spring的类型转换基础结构将各种值元素识别为Float类型,并将字符串值(9.99、2.75和3.99)转换为实际的Float类型。
Null and Empty String Values
Spring将属性的空参数等作为空字符串处理。以下基于xml的配置元数据片段将email属性设置为空字符串值("")。
上面的例子相当于下面的Java代码:
exampleBean.setEmail("");
元素处理null
值。以下清单显示了一个示例:
上面的例子相当于下面的Java代码:
exampleBean.setEmail(null);
XML Shortcut with the p-namespace
p-namespace允许您使用bean元素的属性(而不是嵌套的
Spring支持带有命名空间的可扩展配置格式,这些命名空间基于XML模式定义。本章讨论的bean配置格式是在XML模式文档中定义的。但是,p-namespace并没有在XSD文件中定义,它只存在于Spring的核心中。
下面的示例显示了解析为相同结果的两个XML片段(第一个使用标准XML格式,第二个使用p-namespace):
该示例在bean定义中显示了p-namespace中名为email的属性。这告诉Spring这是一个属性声明。如前所述,p-namespace没有模式定义,因此可以将property name设置为属性名。
下一个例子包括两个以上的bean定义,它们都有对另一个bean的引用:
这个示例不仅包含一个使用p-namespace的属性值,而且还使用一种特殊格式来声明属性引用。第一个bean定义使用来创建bean john对bean jane的引用,而第二个bean定义使用p:spouse-ref="jane"作为属性来完成完全相同的工作。在本例中,spouse是属性名,而 -ref 部分指出这不是一个直接的值,而是对另一个bean的引用。
p命名空间不如标准XML格式灵活。例如,用于声明属性引用的格式与以ref的属性发生冲突,而标准XML格式则没有。我们建议您谨慎选择方法,并将其传达给团队成员,以避免同时使用这三种方法生成XML文档。 |
XML Shortcut with the c-namespace
与p-namespace的XML快捷方式类似,在Spring 3.1中引入的c-namespace允许配置构造函数参数的内联属性,而不是嵌套的构造函数-参数元素。
下面的例子使用c:命名空间来做与基于构造函数的依赖注入相同的事情:
c命名空间使用与p命名空间有相同的约定,通过名字设置构造函数时(-ref代表着引用)。类似地,即使没有在XSD模式中定义它(它存在于Spring核心中),也需要在XML文件中声明它。
对于构造函数参数名不可用的少数情况(通常如果编译字节码时没有调试信息),可以使用回退参数索引,如下所示:
由于XML语法的原因,索引表示法要求使用前导 _ ,因为XML属性名称不能以数字开头(即使某些IDE允许)。相应的索引符号也可用于 元素,但并不常用,因为声明的简单顺序通常就足够了。 |
Compound Property Names
设置bean属性时,可以使用复合属性名称或嵌套属性名称,只要路径中除最终属性名称之外的所有组件都没有null
。考虑以下bean定义:
something bean有一个fred属性,fred属性有一个bob属性,bob属性有一个sammy属性,最后一个sammy属性被设置为123。为了使其工作,在构造bean之后,fred的属性和fred的bob属性不能为空。否则,将抛出NullPointerException。
depends-on
如果一个bean是另一个bean的依赖项,这通常意味着将一个bean设置为另一个bean的属性。通常使用基于xml的配置元数据中的元素来完成此任务。然而,有时候bean之间的依赖关系不那么直接。例如,当需要触发类中的静态初始化器时,例如注册数据库驱动程序时。依赖属性可以显式地强制在使用此元素的bean初始化之前对一个或多个bean进行初始化。下面的示例使用depends-on属性来表示对单个bean的依赖:
要表达对多个bean的依赖关系,请提供一个bean名称列表作为该depends-on
属性的值(逗号,空格和分号是有效的分隔符):
depends-on属性既可以指定一个初始化时间依赖项,也可以指定一个对应的销毁时间依赖项(仅在单例bean情况下)。与给定bean定义依赖关系的依赖bean首先被销毁,先于给定bean本身被销毁。因此,依赖也可以控制销毁顺序。
默认情况下,ApplicationContext实现将创建和配置所有的单例bean作为初始化过程的一部分。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即被发现,而不是几小时甚至几天之后。如果不想使用这种方式,您可以通过将bean定义标记为延迟初始化来防止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时。
在XML中,这种行为是由元素上的lazy-init属性控制的,如下面的示例所示:
当前面的配置被ApplicationContext使用时,当ApplicationContext启动时,lazy bean不会被预先实例化,而not lazy bean则会被预先实例化。
然而,当延迟初始化的bean是未延迟初始化的单例bean的依赖项时,ApplicationContext在启动时创建延迟初始化的bean,因为它必须满足单例的依赖项。延迟初始化的bean被注入到一个没有延迟初始化的单例bean中。
您还可以使用元素上的default-lazy-init属性来控制容器级别的延迟初始化,如下面的示例所示:
Spring容器可以自动创建协作bean之间的关系。通过检查ApplicationContext的内容,您可以让Spring为您的bean自动解析协作者(其他bean)。自动装配有以下优点:
自动装配可以大大减少指定属性或构造函数参数的需要。(本章其他地方讨论的其他机制,如bean模板,在这方面也很有价值。)
自动装配可以随着对象的演化更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而不需要修改配置。因此,自动装配在开发过程中特别有用,当代码库变得更加稳定时,自动装配可以避免切换到显式连接的选项。
在使用基于xml的配置元数据(请参阅依赖项注入)时,可以使用
模式 | 说明 |
---|---|
|
(默认)无自动装配。Bean引用必须由 |
|
按属性名称自动装配。Spring寻找与需要自动装配的属性同名的bean。例如,如果一个bean定义被设置为按名称自动装配并且包含一个 |
|
如果容器中恰好只有一个该属性类型的bean,则使该属性自动装配。如果存在多个,则会引发致命异常,这表明您不能用 |
|
类似于 |
使用byType
或constructor
自动装配模式,可以装载数组和类型化集合。在这种情况下,将提供容器中与期望类型匹配的所有自动装配的候选者,以满足依赖关系。如果期望的key类型为字符串,则可以自动装载强类型Map实例。自动装配Map实例的值由所有匹配期望类型的bean实例组成,Map实例的key包含相应的bean名称。
Limitations and Disadvantages of Autowiring
自动装配在跨项目一致使用时效果最好。如果通常不使用自动装配,那么开发人员使用它来连接一个或两个bean定义可能会感到困惑。
考虑自动装配的局限性和缺点:
属性和构造参数设置中的显式依赖项总是覆盖自动装配。您不能自动生成简单的属性,如原语、字符串和类(以及这些简单属性的数组)。这种限制是由设计造成的。
自动装配不如显式连接精确。尽管如前面的表中所述,Spring小心地避免猜测,以免产生可能产生意外结果的歧义。spring管理对象之间的关系不再明确记录。
连接信息可能对从Spring容器生成文档的工具不可用。
容器中的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。对于数组、集合或映射实例,这不一定是个问题。然而,对于期望单个值的依赖项,这种模糊性不是任意解决的。如果没有唯一的bean定义可用,则抛出异常。
在后一种情况下,你有几个选择:
放弃自动装配,支持显式连接。
通过将bean定义的autowire-candidate属性设置为false来避免自动装配,如下一节所述。
通过将其元素的主属性设置为true,将单个bean定义指定为主候选。
实现基于注释的配置提供的更细粒度的控制,如基于注释的容器配置中所述。
从自动装配中排除一个Bean
在每个bean的基础上,可以将bean排除在自动装配之外。在Spring的XML格式中,将元素的autowire-candidate属性设置为false。容器使特定的bean定义对自动装配基础设施(包括诸如@Autowired之类的注释样式配置)不可用。
autowire-candidate属性被设计为只影响基于类型的自动装配。它不会影响按名称显示的引用,即使指定的bean没有标记为autowire候选bean,也会解析这些引用。因此,如果名字匹配,自动装配仍然会注入一个bean。
您还可以根据bean名称的模式匹配来限制自动装配候选对象。顶级元素在其default-autowire-candidate属性中接受一个或多个模式。例如,要将autowire候选状态限制为名称以Repository结尾的任何bean,请提供一个值*Repository。要提供多个模式,请在逗号分隔的列表中定义它们。bean定义的autowire-candidate属性的显式值true或false总是优先。对于这样的bean,不适用模式匹配规则。
这些技术对于那些您不希望通过自动装配将其注入到其他bean中的bean非常有用。这并不意味着不能通过使用自动装配来配置被排除的bean。相反,bean本身不是自动装配其他bean的候选对象。
在大多数应用程序场景中,容器中的大多数bean都是单例的。当一个单例bean需要与另一个单例bean协作,或者一个非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖性。当bean的生命周期不同时,就会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能在A的每个方法都需要使用B。容器只创建一次单例bean A,因此只有一次机会来设置属性。容器不能每次需要bean B时都向bean A提供一个新的bean B实例。
解决的办法是放弃一些控制反转。您可以通过实现ApplicationContextAware
接口,并在每次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容器的一项高级功能,使您可以干净地处理此用例。
您可以在此博客条目中了解有关方法注入动机的更多信息 。
Lookup Method Injection
查找方法注入是容器重写CommandManager Bean上的方法并返回容器中另一个命名Bean。查找通常涉及原型bean,如上一节中所述。Spring框架通过使用从CGLIB库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。
|
对于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();
}
在包含要注入的方法的客户端类(CommandManager
在本例中为)中,要注入的方法需要以下形式的签名:
[abstract] theMethodName(no-arguments);
如果方法为abstract
,则动态生成的子类将实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。考虑以下示例:
标识为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根据lookup方法的声明的返回类型来解析:
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视为依赖项。
您还可以在org.springframe .bean .factory中找到ServiceLocatorFactoryBean配置包.
Arbitrary Method Replacement(任意方法替换)
与查找方法注入相比,一种不太有用的方法注入形式是能够用另一种方法实现替换托管bean中的任意方法。您可以安全地跳过本节的其余部分,直到您真正需要此功能为止。
对于基于xml的配置元数据,您可以使用replace -method元素将已部署bean的现有方法实现替换为其他方法实现。考虑下面的类,它有一个我们想要覆盖的叫做computeValue的方法:
public class 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
*/
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定义类似于以下示例:
String
您可以在< replaced-method />元素中使用一个或多个< arg-type />元素来指示被覆盖方法的方法签名。只有在方法重载且类中存在多个变量时,才需要对参数进行签名。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配java.lang.String:
java.lang.String
String
Str
因为参数的数量通常足以区分每个可能的选择,所以通过让您仅键入与参数类型匹配的最短字符串,此快捷方式可以节省很多输入。
当你创建一个bean定义时,你将创建一个recipe用于创建由该bean定义定义的类的实际实例。bean定义是recipe的想法很重要,因为它意味着,与类一样,你可以从一个recipe创建多个对象实例。
你不仅可以控制要插入到由特定bean定义创建的对象中的各种依赖项和配置值,还可以控制由特定bean定义创建的对象的范围。这种方法强大而灵活,因为你可以选择通过配置创建的对象的范围,而不必在Java类级别上考虑对象的范围。可以将bean定义为部署在多种作用域中的一种。Spring框架支持6种作用域,其中4种只有在使用web-aware的ApplicationContext时才可用。你还可以创建自定义范围。
下表描述了支持的范围:
范围 | 描述 |
---|---|
singleton | (默认)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。 |
prototype | 将单个bean定义的作用域限定为任意数量的对象实例。 |
request | 将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有一个在单个bean定义后面创建的bean实例。仅在可感知网络的Spring上下文中有效 |
session | 将单个bean定义的范围限定为HTTP的生命周期 |
application | 将单个bean定义的作用域限定为的生命周期 |
websocket | 将单个bean定义的作用域限定为的生命周期 |
从Spring 3.0开始,线程作用域可用,但默认情况下未注册。有关更多信息,请参见的文档 SimpleThreadScope 。有关如何注册此自定义范围或任何其他自定义范围的说明,请参阅《 使用自定义范围》。 |
只管理一个单例bean的一个共享实例,所有与该bean ID匹配的bean的请求都会导致spring容器返回这个特定的bean实例。
换句话说,当您定义一个bean定义并将其定义为一个单例对象时,Spring IoC容器只创建该bean定义的对象的一个实例。此单一实例存储在此类单例bean的缓存中,该指定bean的所有后续请求和引用都将返回缓存的对象。下图显示了单例范围的工作方式:
Spring的单例bean概念与四人组(GoF)模式书中定义的单例模式不同。单例对象对对象的作用域进行硬编码,这样每个类加载器只能创建一个特定类的实例。Spring单例的范围是针对每个容器和每个bean。这意味着,如果您在单个Spring容器中为特定类定义一个bean,那么Spring容器将创建由该bean定义的类的有且仅有一个实例。单例范围是Spring的默认范围。要在XML中将bean定义为单例,您可以定义如下例所示的bean:
bean部署的非单例Prototype 范围导致在每次发出对特定bean的请求时创建一个新的bean实例。也就是说,bean被注入到另一个bean中,或者您通过容器上的getBean()方法调用请求它。通常,您应该为所有有状态bean使用Prototype 范围,为无状态bean使用单例范围。
下图说明了Spring原型的作用域:
(数据访问对象(DAO)通常不配置为原型,因为典型的DAO不拥有任何对话状态。对于我们而言,使用单例模式更方便。)
以下示例将bean定义为XML原型:
与其他作用域不同,Spring不管理Prototype bean的完整生命周期。容器实例化、配置或以其他方式组装原型对象并将其交给客户端,而不对该Prototype 实例继续记录。因此,尽管初始化生命周期回调方法在所有对象上都被调用,而与范围无关,但是在原型的情况下,配置的销毁生命周期回调不会被调用。客户端代码必须清理原型作用域的对象,并释放原型bean持有的昂贵资源。为了让Spring容器释放原型作用域bean所持有的资源,可以尝试使用一个自定义bean后处理器(bean post-processor),它持有一个需要清理的bean引用。
在某些方面,Spring容器在Prototype 作用域bean方面的角色可以替代Java new操作符。所有超过那个点的生命周期管理都必须由客户端处理。(有关Spring容器中bean生命周期的详细信息,请参阅生命周期回调。)
当你使用依赖于原型bean的单例作用域bean时,请注意依赖项是在实例化时解析的。因此,如果你依赖地将一个原型作用域的bean注入到一个单例作用域的bean中,一个新的原型bean将被实例化,然后依赖地注入到单例bean中。这个原型实例是提供给单例bean的唯一实例。
但是,假设您希望单例作用域bean在运行时重复获取原型作用域bean的新实例。您不能依赖地将一个原型作用域的bean注入到您的单例bean中,因为这种注入只发生一次,当Spring容器实例化单例bean并解析和注入它的依赖项时。如果您需要在运行时多次使用原型bean的新实例,请参阅方法注入。
只有在使用web-aware的Spring ApplicationContext实现(如XmlWebApplicationContext)时,请求、会话、应用程序和websocket作用域才可用。如果您将这些作用域与常规的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,则会抛出一个IllegalStateException,它会报错一个未知的bean作用域。
Initial Web Configuration
为了在请求、会话、应用程序和websocket级别(web范围的bean)上支持bean的作用域,需要在定义bean之前进行一些小的初始配置。(标准范围:单例和原型不需要这个初始设置。)
如何完成这个初始设置取决于特定的Servlet环境。
如果您在Spring Web MVC中访问作用域bean,实际上,在由Spring DispatcherServlet处理的请求中,不需要特殊的设置。DispatcherServlet已经公开了所有相关状态。
如果您使用Servlet 2.5 web容器,并在Spring的DispatcherServlet之外处理请求(例如,在使用JSF或Struts时),您需要注册org.springframe .web.context.request。RequestContextListener ServletRequestListener。对于Servlet 3.0+,这可以通过使用WebApplicationInitializer接口以编程方式实现。或者,对于较旧的容器,将以下声明添加到web应用程序的web.xml文件中:
...
org.springframework.web.context.request.RequestContextListener
...
另外,如果监听器设置有问题,可以考虑使用Spring的RequestContextFilter。filter-mapping映射依赖于周围的web应用程序配置,因此您必须对其进行适当的更改。下面的清单显示了web应用程序的过滤部分:
...
requestContextFilter
org.springframework.web.filter.RequestContextFilter
requestContextFilter
/*
...
DispatcherServlet、RequestContextListener和RequestContextFilter都做完全相同的事情,即将HTTP请求对象绑定到服务该请求的线程。这使得在请求和会话范围内的bean可以在调用链的更底层使用。
Request scope
考虑以下XML配置来定义bean:
通过为每个HTTP请求使用LoginAction bean定义,Spring容器创建了LoginAction bean的新实例。也就是说,loginAction bean的作用域在HTTP请求级别。您可以随意更改创建的实例的内部状态,因为从相同的loginAction bean定义创建的其他实例在状态中看不到这些更改。它们是特定于个人请求的。当请求完成处理时,作用域为请求的bean被丢弃。
在使用注释驱动的组件或Java配置时,可以使用@RequestScope注释将组件分配给请求范围。下面的例子演示了如何做到这一点:
@RequestScope
@Component
public class LoginAction {
// ...
}
Session Scope
考虑以下XML配置来定义bean:
Spring容器通过为单个HTTP会话的生命周期使用UserPreferences bean定义来创建UserPreferences bean的新实例。换句话说,userPreferences bean有效地限定在HTTP会话级别。与请求范围内bean一样,你可以改变内部状态的实例创建尽可能多的你想要的,知道其他HTTP会话实例也使用相同的实例创建userPreferences bean定义看不到这些变化状态,因为他们是特定于一个单独的HTTP会话。当HTTP会话最终被丢弃时,作用域为该特定HTTP会话的bean也被丢弃。
在使用注解驱动的组件或Java配置时,可以使用@SessionScope注解将组件分配给会话范围。
@SessionScope
@Component
public class UserPreferences {
// ...
}
Application Scope
考虑以下XML配置来定义bean:
通过为整个web应用程序使用一次AppPreferences bean定义,Spring容器创建了AppPreferences bean的一个新实例。也就是说,appPreferences bean的作用域在ServletContext级别,并存储为一个常规的ServletContext属性。这有点类似于spring 单例bean,但在两个重要方面不同:它是每个ServletContext的单例而不是每个spring ApplicationContext的单例(可能有几个在任何给定的web应用程序),它实际上是公开的,因此作为ServletContext属性可见。
在使用注解驱动的组件或Java配置时,可以使用@ApplicationScope注解将组件分配给应用程序范围。下面的例子演示了如何做到这一点:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
Scoped Beans as Dependencies
Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖项)的连接。如果您想将(例如)一个HTTP请求作用域的bean注入到另一个更大的作用域的bean中,您可以选择注入一个AOP代理来代替作用域的bean。也就是说,您需要注入一个代理对象,它与作用域对象公开相同的公共接口,但也可以从相关作用域(如HTTP请求)检索实际目标对象,并将方法调用委托给实际对象。
你可以在作用域为单例的bean上使用 在 同样,作用域代理不是以生命周期安全的方式从较短的作用域访问bean的唯一方法。您还可以将注入点(即,构造函数或setter参数或自动装配字段)声明为 作为扩展变体,您可以声明 为此的JSR-330变体将被调用 |
以下示例中的配置只有一行,但是了解其背后的“原因”和“方式”很重要:
//定义代理的行
要创建这样的代理,您可以将子
在前面的示例中,单例bean (userManager)被注入了对HTTP会话范围的bean的引用(userPreferences)。这里的要点是userManager bean是单例的:它仅针对每个容器实例化一次,其依赖项(在本例中只有一个,即userPreferences bean)也仅注入一次。这意味着userManager bean只对同一个userPreferences对象(即最初注入它的对象)进行操作。
这不是您在将一个较短的作用域bean注入到一个较长的作用域bean时想要的行为(例如,将一个HTTP会话作用域的协作bean作为依赖项注入到单例bean中)。相反,您需要一个单一的userManager对象,并且对于HTTP会话的生存期,您需要一个特定于HTTP会话的userPreferences对象。因此,容器创建一个对象,该对象公开与UserPreferences类完全相同的公共接口(理想情况下是UserPreferences实例的对象),该对象可以从作用域机制(HTTP请求、会话,等等)获取真正的UserPreferences对象。容器将此代理对象注入userManager bean,该bean不知道此UserPreferences引用是代理。在本例中,当UserManager实例调用依赖注入的UserPreferences对象上的方法时,它实际上是在调用代理上的方法。然后代理从HTTP会话中获取真实的UserPreferences对象,并将方法调用委托给检索到的真实的UserPreferences对象。
因此,在将请求和会话范围的bean注入到协作对象中时,需要以下(正确和完整的)配置,如下面的示例所示:
Choosing the Type of Proxy to Create
默认情况下,当Spring容器为使用
元素标记的bean创建代理时,将创建基于CGLIB的类代理。
|
或者,您可以配置Spring容器来为此类作用域bean创建标准的基于JDK接口的代理,通过为
元素的代理目标类属性的值指定false。使用基于JDK接口的代理意味着在应用程序类路径中不需要额外的库来影响这样的代理。然而,这也意味着作用域bean的类必须实现至少一个接口,并且作用域bean被注入的所有协作者必须通过其接口之一引用bean。以下示例显示基于接口的代理:
有关选择基于类或基于接口的代理的详细信息,请参阅代理机制。
bean的作用域机制是可扩展的。您可以定义自己的作用域,甚至重新定义现有作用域,尽管后者被认为是不好的做法,您不能覆盖内置作用域singleton
和prototype
作用域。
Creating a Custom Scope
要将自定义范围集成到Spring容器中,您需要实现org.springframework.beans.factory.config.Scope
此部分中描述的 接口。有关如何实现自己的范围的想法,请参阅Scope
Spring Framework本身和Scope
javadoc 附带的实现 ,其中详细说明了需要实现的方法。
该Scope
接口有四种方法可以从作用域中获取对象,将其从作用域中删除,然后将其销毁。
例如,会话作用域实现返回会话范围的Bean(如果不存在,则该方法将其绑定到会话上以供将来参考之后,将返回该Bean的新实例)。以下方法从基础范围返回对象:
Object get(String name, ObjectFactory> objectFactory)
会话范围的实现,例如,从基础会话中删除了会话范围的bean。应该返回该对象,但是如果找不到具有指定名称的对象,则可以返回null。以下方法从基础范围中删除该对象:
Object remove(String name)
以下方法注册在销毁作用域或销毁作用域中的指定对象时应执行的回调:
void registerDestructionCallback(String name, Runnable destructionCallback)
以下方法获取基础范围的会话标识符:
String getConversationId()
有关 销毁回调的更多信息,请参见javadoc或Spring范围实现。
Using a Custom Scope
在编写和测试一个或多个自定义Scope
实现之后,您需要使Spring容器意识到您的新作用域。以下方法是Scope
在Spring容器中注册新方法的主要方法:
void registerScope(String scopeName, Scope scope);
此方法在ConfigurableBeanFactory
接口上声明,该接口可通过spring附带的大多数具体ApplicationContext实现的
BeanFactory属性获得。
该registerScope(..)
方法的第一个参数是与范围关联的唯一名称。Spring容器本身中的此类名称示例为singleton
和 prototype
。该registerScope(..)
方法的第二个参数是Scope
您希望注册和使用的自定义实现的实际实例。
假设您编写了自定义Scope
实现,然后注册它,如下面的示例所示。
下一个示例使用SimpleThreadScope Spring附带的,但默认情况下未注册。对于您自己的自定义Scope 实现,说明将是相同的。 |
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后,您可以按照您的custom的作用域规则创建bean定义, Scope
如下所示:
使用自定义作用域实现,不仅限于作用域的编程式注册,还可以通过使用CustomScopeConfigurer类以声明的方式进行作用域注册。
当放置 在FactoryBean 实现中时,作用域是工厂Bean本身,而不是范围的对象getObject() 。 |
Spring框架提供了许多接口,您可以使用这些接口来定制bean的性质。本节将它们分组如下:
生命周期回调
应用程序ContextAware和BeanNameAware
其他aware接口
为了与容器对bean生命周期的管理进行交互,可以实现Spring InitializingBean
和DisposableBean
接口。容器为前者调用afterPropertiesSet() 为后者调用destroy(),以便
bean在初始化和销毁bean时执行某些操作。
void afterPropertiesSet() throws Exception;
public class ExampleBean {
public void init() {
// do some initialization work
}
}
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
void destroy() throws Exception;
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
接下来,你可以在xml中定义它:
⾸先创建⼀个⽬标bean,然后才能应⽤⼀个带有拦截器链的AOP代理。如果⽬标bean和代理是单独定义的,那么你的代码甚⾄可以影响原始⽬标bean。因此,将拦截器应⽤于init⽅法会导致不⼀致的结果,因为这样做会将⽬标bean的⽣命周期耦合到其代理或拦截器,并在代码直接与原始⽬标bean交互时留下奇怪的语义。
总结⽣命周期机制
InitializingBean和DisposableBean回调接⼝
⾃定义init() 和destroy() ⽅法。
@PostConstruct 和 @PreDestroy
你可以将3种⽅式结合使⽤。
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,具有最低phase的对象⾸先启动。停⽌时,按相反顺序执⾏。因此,实现SmartLifecycle且其getPhase()⽅法返回Integer.MIN_VALUE值的对象将是第⼀个开始和最后⼀个停⽌的对象。
同样的,Integer.MAX_VALUE值的phase值将表⾯对象应在最后启动并⾸先停⽌(可能是因为它取决于要运⾏的其他进程)。在考虑phase值时,还必须知道不实现SmartLifecycle的任何“正常”⽣命周期对象的默认阶段是0。因此,任何负phase值都表示⼀个对象应该在这些标准组件之前开始(并在它们之后停⽌)。对于任何正phase值,顺序相反。
SmartLifecycle定义的Stop⽅法接受回调。任何实现都必须在该实现的关闭过程完成后调⽤该回调的run()⽅法。在需要的时候,可以实现异步的关闭,因为LifecycleProcessor接⼝的默认实现DefaultLifecycleProcessor在每个phase中的对象中调⽤该回调时,会等待⼀个超时值。每个phase默认超时为30秒。你可以通过在上下⽂中定义⼀个名为lifecycleProcessor的bean来覆盖默认的⽣命周期处理器实例。如果只想修改超时,那么定义以下内容就⾜够了:
如前所述,LifecycleProcessor接⼝还定义了⽤于刷新和关闭上下⽂的回调⽅法。当stop()被显示调⽤的时候会启动shutdown的进程,但是这个⽅法只会在上下⽂关闭时被调⽤。另⼀⽅⾯,“刷新”回调启⽤了SmartLifecycle bean的另⼀个功能。刷新上下⽂时(在所有对象都已实例化和初始化之后),将调⽤该回调。此时,默认的⽣命周期处理器检查每个SmartLifecycle对象的isAutoStartup()⽅法返回的布尔值。如果为true,则该对象将在该点启动,⽽不是等待显式调⽤上下⽂或其⾃身的start()⽅法(与上下⽂refresh不同,对于标准上下⽂实现,上下⽂start不会⾃动触发)。如前所述,phase值和任何“depends-on”关系决定启动顺序。
在⾮Web应⽤程序中优雅的关闭Spring IoC容器
如果你使⽤Spring IOC容器在⾮web应⽤程序环境中(富⽂本桌⾯程序),需要注册⼀个shutdown hook到JVM中。这样将保证优雅的关闭,并且在单例bean中调⽤相关的销毁⽅法,让所有的资源得到释放。当然,你必须正确的配置和实现这些销毁⽅法。
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
ApplicationContextAware
and BeanNameAware
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以⼿动控制创建他的ApplicationContext,可以通过ApplicationContext接⼝,也可以将这个引⽤转换成
ApplicationContext的⼦类(⽐如提供了额外功能的ConfigurableApplicationContext)。
他的⼀个应⽤就是在程序中获取其他的beans。这种使⽤⽅式有时候会很有⽤,但是因为其和Spring耦合在⼀起,违背了IOC原则,所以应该避免这样使⽤。应该将协作者设置成bean的属性值。
ApplicationContext的其他⽅法提供了⽂件资源,发布应⽤程序事件和访问MessageSource的功能。
在Spring2.5中,⾃动装载是另外⼀个获取到ApplicationContext引⽤的⽅法。传统的constructor和byType⾃动装载模式,能将ApplicationContext当做constructor的参数,或者setter⽅法的参数。为了更灵活的使⽤,⽐如⾃动装载字段或者⽅法的多个参数,可以使⽤@Autowired注解在字段或者⽅法上。这样ApplicationContext将会⾃动装配到需要的地⽅。
当ApplicationContext创建了⼀个org.springframework.beans.factory.BeanNameAware的实现类时,这个类提供了对其在关联对象中定义的名字的引⽤。 如下所示:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
在填充普通bean属性之后,在初始化回调(如InitializingBean、afterPropertiesSet或⾃定义init⽅法)之前该回调会被调⽤。
Aware
Interfacesbean定义可以包含很多配置信息,包括构造函数参数、属性值和容器特定的信息,例如初始化⽅法、静态⼯⼚⽅法名等。⼦bean定义从⽗定义继承配置数据。⼦定义可以覆盖⼀些值,或者根据需要添加其他值。使⽤⽗bean和⼦bean定义可以节省⼤量的输⼊。实际上,这是⼀种templating。
如果以编程⽅式使⽤ApplicationContext接⼝,则⼦bean定义由ChildBeanDefinition类表示。⼤多数⽤户并不需要直接使⽤这个类。相反,他们在类(如ClassPathXmlApplicationContext)中声明性地配置bean定义。使⽤基于XML的配置元数据时,可以通过使⽤parent属性来表名这是⼀个⼦bean定义,并将⽗bean指定为此属性的值。以下示例显示了如何执⾏此操作:
如果没有指定bean类,则⼦bean定义使⽤⽗定义中的bean类,但也可以覆盖它。在后⼀种情况下,⼦bean类必须与⽗类兼容(即,它必须接受⽗类的属性值)。
⼦bean定义从⽗bean继承作⽤域、构造函数参数值、属性值和⽅法重写,并可以选择添加新值。指定的任何范围、初始化⽅法、销毁⽅法或静态⼯⼚⽅法设置都将覆盖相应的⽗设置。
其余的设置总是取⾃⼦定义:依赖、⾃动装载模式、依赖项检查、singleton和lazy init。
前⾯的示例使⽤abstract属性显式地将⽗bean定义标记为abstract。如果⽗定义没有指定类,则需要显式地将⽗bean定义标记为抽象,如下示例所示:
⽗bean不能单独实例化,因为它是不完整的,并且还显式地标记为抽象的。当定义是抽象的时,它只能⽤作纯模板bean定义,该定义⽤作⼦定义的⽗定义。试图单独使⽤这样⼀个抽象⽗bean,将其作为另⼀个bean的引⽤属性来引⽤,或者使⽤⽗bean id执⾏显式getbean()调⽤,都会返回⼀个错误。类似地,容器的内部preInstantiateSingletons()⽅法忽略定义为抽象的bean定义。
默认情况下,ApplicationContext预实例化所有单例。因此,重要的是(⾄少对于singleton bean⽽⾔),如果你有⼀个(⽗)bean定义,⽽该定义指定了⼀个类,那么你必须确保将abstract属性设置为true,否则应⽤程序上下⽂将实际(尝试)预实例化abstract bean。
通常,应⽤程序开发⼈员不需要⼦类化ApplicationContext实现类。相反,可以通过插⼊特殊集成接⼝的实现来扩展 Spring IOC容器。接下来的⼏节将描述这些集成接⼝。
BeanPostProcessor
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
下⾯的bean使⽤了InstantiationTracingBeanPostProcessor:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
BeanFactoryPostProcessor
我们看到的下⼀个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。此接⼝的语义与BeanPostProcessor的语义相似,但有⼀个主要区别:BeanFactoryPostProcessor对Bean配置元数据进⾏操作。也就 是说,Spring IOC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化BeanFactoryPostProcessor实例以外的任何bean之前对其进⾏更改。
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
classpath:com/something/strategy.properties
custom.strategy.class=com.something.DefaultStrategy
beanName.property=value
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
tom.fred.bob.sammy=123
FactoryBean
注解是不是⽐XML配置更好? 引⼊基于注解的配置提出了这样⼀个问题:这种⽅法是否⽐XML“更好”。简短的答案是“它取决于”。⻓的答案是:每种⽅法都有其优缺点,通常情况下,由开发⼈员决定哪种策略更适合它们。由于它们的定义⽅式,注解在其声明中提供了⼤量上下⽂,从⽽导致配置更简短。然⽽,XML擅⻓在不接触源代码或重新编译组件的情况下注⼊组件。⼀些开发⼈员更喜欢将注⼊靠近源代码,⽽其他⼈则认为带注解的类不再是POJO,⽽且配置变得分散且难以控制。 ⽆论选择哪⼀种,Spring都能容纳这两种⻛格,甚⾄可以将它们混合在⼀起。值得指出的是,通过它的JavaConfig选项,Spring允许以⾮侵⼊性的⽅式使⽤注解,⽽不接触⽬标组件源代码,并且在⼯具⽅⾯,所有配置样式都由Spring⼯具套件⽀持。
基于注解的配置提供了XML设置的替代⽅案,它依赖字节码元数据来注⼊组件,⽽不是使⽤尖括号声明。开发⼈员不使 ⽤XML来描述bean连接,⽽是使⽤相关类、⽅法或字段声明上的注解将配置移动到组件类本身。如示例中所述: RequiredAnnotationBeanPostProcessor与注解结合使⽤BeanPostProcessor是扩展SpringIOC容器的常⽤⽅法。
例如,Spring2.0引⼊了@Required注解,标记属性必须存在。Spring2.5使得遵循相同的⽅法来驱动Spring的依赖注⼊成为可能。本质上,@Autowired注解提供了与Autowiring Collaborators中描述的相同的功能,但是具有更细粒度的控 制和更⼴泛的适⽤性。Spring2.5还增加了对JSR-250注解的⽀持,如@PostConstruct和@PreDstroy。Spring 3为 javax.inject包中包含的JSR-330(Java依赖注⼊)注解添加了⽀持,例如@Inject和@Named。有关这些注解的详细信息,请参阅相关部分。
注解注⼊在XML注⼊之前执⾏。因此,当两个同时使⽤时,XML配置会覆盖注解注⼊的属性。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
@Autowired
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
也可以把注解应⽤到任何名字和多个参数,如下所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
public class MovieRecommender {
private Set movieCatalogs;
@Autowired
public void setMovieCatalogs(Set movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
public class MovieRecommender {
private Map movieCatalogs;
@Autowired
public void setMovieCatalogs(Map movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional movieFinder) {
...
}
}
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Primary
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
当可以确定⼀个主要候选对象时,@Primary是⼀种在多个实例中按类型使⽤⾃动装载的有效⽅法。当你需要对选择过程进⾏更多控制时,可以使⽤Spring的@Qualifier注解。你可以将限定符值与特定参数相关联,缩⼩类型匹配集的范围,以便为每个参数选择特定的bean。在最简单的情况下,这可以是⼀个简单的描述性值,如下⾯的示例所示:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
带有main限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。
带有action限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。
然⽽,尽管你可以使⽤此约定按名称引⽤特定的bean,@Autowired是关于带有可选语义限定符的类型驱动注⼊的根本。这意味着,即使使⽤bean名称回退,限定符值在类型匹配集内也始终具有收缩语义。它们不会在语义上表示对唯⼀ bean id的引⽤。好的限定符值是main、EMEA或persistent,表示独⽴于bean id的特定组件的特征,在匿名bean定义 (如前⾯示例中的定义)的情况下,可以⾃动⽣成这些特征。
限定符也适⽤于类型化集合,如前⾯讨论的-例如,设置。在这种情况下,根据声明的限定符,所有匹配的bean都作为集合注⼊。这意味着限定符不必是唯⼀的。相反,它们构成了过滤标准。例如,可以定义多个具有相同限定符值 “action”的MovieCatalog bean,所有这些限定符值都被注⼊到⼀个⽤@qualifier(“action”)注解的集合中。
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
在类路径扫描和托管组件中,可以看到基于注解的替代⽅案,以XML形式提供限定符元数据。具体来说,请参⻅为限定符元数据提供注解。
在某些情况下,使⽤不带值的注解可能就⾜够了。当注解具有更⼀般的⽤途并且可以应⽤于多个不同类型的依赖项时,这⼀点⾮常有⽤。例如,你可以提供⼀个脱机⽬录,当没有可⽤的Internet连接时可以搜索该⽬录。⾸先,定义简单注解,如下示例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
This line adds the @Offline
annotation.
This element specifies the qualifier.
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
public enum Format {
VHS, DVD, BLURAY
}
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
@Autowired
private Store s1; // qualifier, injects the stringStore bean
@Autowired
private Store s2; // qualifier, injects the integerStore bean
// Inject all Store beans as long as they have an generic
// Store beans will not appear in this list
@Autowired
private List> s;
CustomAutowireConfigurer
example.CustomQualifier
@Resource
Spring还⽀持通过在字段或bean属性setter⽅法上使⽤jsr-250 @Resource注解(javax.annotation.Resource)进⾏注⼊。这是JavaEE中常⻅的模式:例如,在JSF托管bean和JAX-WS端点中。Spring也⽀持Spring管理对象的这种模式。
@Resource具有名称属性。默认情况下,Spring将该值解释为要注⼊的bean名称。换句话说,它遵循名称语义,如下⾯的示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Value
@Value
通常用于注入外部属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
使用以下配置:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
和以下application.properties
文件:
catalog.name=MovieCatalog
在这种情况下,catalog
参数和字段将等于MovieCatalog
值。
Spring提供了一个默认的宽松内嵌值解析器。它将尝试解析属性值,如果无法解析,${catalog.name}
则将注入属性名称(例如)作为值。如果要严格控制不存在的值,则应声明一个PropertySourcesPlaceholderConfigurer
bean,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
当配置 PropertySourcesPlaceholderConfigurer 使用JavaConfig,该 @Bean 方法必须是static 。 |
如果${}
无法解析任何占位符,则使用上述配置可确保Spring初始化失败。也可以使用方法一样 setPlaceholderPrefix
,setPlaceholderSuffix
或setValueSeparator
自定义的占位符。
Spring Boot默认配置一个 PropertySourcesPlaceholderConfigurer bean,它将从application.properties 和application.yml 文件中获取属性。 |
Spring提供的内置转换器支持允许自动处理简单的类型转换(例如转换为Integer
或int
)。多个逗号分隔的值可以自动转换为String数组,而无需付出额外的努力。
可以提供如下默认值:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
Spring BeanPostProcessor
使用ConversionService
幕后处理将String值转换@Value
为目标类型的过程。如果要为自己的自定义类型提供转换支持,则可以提供自己的 ConversionService
bean实例,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
当@Value
包含SpEL
表达式时,该值将在运行时动态计算,如以下示例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
SpEL还可以使用更复杂的数据结构:
@Component
public class MovieRecommender {
private final Map countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
@PostConstruct
and @PreDestroy
CommonAnnotationBeanPostProcessor不仅识别@Resource注解,还识别JSR-250⽣命周期注解:javax.annotation.PostConstruct和javax.annotation.PreDestroy。在Spring2.5中引⼊了对这些注解的⽀持,它提供了⽣命周期回调机制的替代⽅案,如初始化回调和销毁回调中所述。如果CommonAnnotationBeanPostProcessor注册在Spring ApplicationContext中,则在⽣命周期中与相应的Spring Lifecycle Interface⽅法或显式声明的回调⽅法相同的点调⽤包含这些注解之⼀的⽅法。在以下示例中,缓存在初始化时预填充,在销毁时清除:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
@Component
and Further Stereotype Annotations@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
// ...
}
The Component
causes @Service
to be treated in the same way as @Component
.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
@Service
@SessionScope
public class SessionScopedService {
// ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
For further details, see the Spring Annotation Programming Model wiki page.
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
@Qualifier注解将在基于微调注解的带有限定符的⾃动注⼊中讨论。该部分中的示例演示了在解析autowire候选时使⽤@Qualifier注解和⾃定义限定符注解来提供细粒度控制。因为这些示例是基于XML bean定义的,所以通过使⽤XML中 bean元素的限定符或元⼦元素,在候选bean定义上提供了限定符元数据。当依赖类路径扫描⾃动检测组件时,可以在候选类上为限定符元数据提供类型级注解。以下三个示例演示了此技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
org.springframework
spring-context-indexer
5.2.3.BUILD-SNAPSHOT
true
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.2.3.BUILD-SNAPSHOT"
}
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}
从Spring3.0之后,Spring提供了JSR-330标志注解的⽀持。这些注解和Spring注解⼀样被进⾏扫描,如果想使⽤它们, 需要包含下⾯的jar包:
javax.inject
javax.inject
1
@Inject
and @Named
你可以使⽤ @javax.inject.Inject来替代@Autowired:
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
// ...
}
}
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider movieFinder;
@Inject
public void setMovieFinder(Provider movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
// ...
}
}
import javax.inject.Inject;
import javax.inject.Named;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional movieFinder) {
// ...
}
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
@Named
and @ManagedBean
: Standard Equivalents to the @Component
Annotationimport javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
在不指定组件名称的情况下使⽤@Component是⾮常常⻅的。@Named可以以类似的⽅式使⽤,如下示例所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
Basic Concepts: @Bean
and @Configuration
Instantiating the Spring Container by Using AnnotationConfigApplicationContext
Using the @Bean
Annotation
Using the @Configuration
annotation
Composing Java-based Configurations
Bean Definition Profiles
PropertySource
Abstraction
Using @PropertySource
Placeholder Resolution in Statements
@Bean
and @Configuration
Spring新的Java配置⽀持中的中⼼构件是@Configuration和@Bean。
@Bean注解⽤于指示⽅法实例化、配置和初始化要由Spring IOC容器管理的新对象。对于熟悉Spring的
⽤@Configuration注解类表明它的主要⽤途是作为bean定义的源。此外,@Configuration classes允许通过调⽤同⼀类中的其他@Bean⽅法来定义bean间的依赖关系。最简单的@Configuration类如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
AnnotationConfigApplicationContext
下⾯是SpringAnnotationConfigApplicationContext的介绍,他是在Spring 3.0被引⼊的。
和ClassPathXmlApplicationContext需要XML⽂件作为输⼊⼀样,AnnotationConfigApplicationContext需要 @Configuration 作为实例化参数,这样可以完全不使⽤XML⽂件:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
前⾯提到了AnnotationConfigApplicationContext不仅仅可使⽤@Configuration,也可以使⽤@Component 或者JSR- 330 annotated class 。 如下所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
Building the Container Programmatically by Using register(Class>…)
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
Enabling Component Scanning with scan(String…)
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}
有经验的Spring⽤户可能很熟悉下⾯的XML配置⽤法:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
Support for Web Applications with AnnotationConfigWebApplicationContext
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
com.acme.AppConfig
org.springframework.web.context.ContextLoaderListener
dispatcher
org.springframework.web.servlet.DispatcherServlet
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
com.acme.web.MvcConfig
dispatcher
/app/*
@Bean
AnnotationDeclaring a Bean
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
transferService -> com.acme.TransferServiceImpl
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
Bean Dependencies
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
Receiving Lifecycle Callbacks
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
Specifying Bean Scope
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope
and scoped-proxy
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
Customizing Bean Naming
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
Bean Aliasing(别名)
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean Description
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
@Configuration
annotation@Configuration是⼀个类级注解,指示对象是bean定义的源。@Configuration类通过public @Bean注解⽅法声明 bean。
对@Configuration classes上的@Bean⽅法的调⽤也可⽤于定义Bean之间的依赖关系。请参⻅基本概念: @Bean@Configuration以获取⼀般介绍。
Injecting Inter-bean Dependencies
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
Lookup Method Injection
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();
}
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
Further Information About How Java-based Configuration Works Internally
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
现在在实例化容器时,不需要显示的引⼊ConfigA.class 和 ConfigB.class,只要ConfigB就够了:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
组合Java和XML配置
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
bean定义Profiles在核⼼容器中提供了⼀种机制,允许在不同环境中注册不同的bean。“环境”这个词对不同的⽤户来说 可能意味着不同的东⻄,并且这个特性可以帮助处理许多⽤例,包括:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
The standaloneDataSource method is available only in the development profile. |
The jndiDataSource method is available only in the production profile. |
激活Profile
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
-Dspring.profiles.active="profile1,profile2"
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
PropertySource
AbstractionSpring的Environment抽象在property sources的可配置层次结构上提供搜索操作。考虑以下列表:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
最重要的是,整个机制是可配置的。也许你有⼀个⾃定义的属性源,希望将其集成到这个搜索中。为此,请实现并实例化⾃⼰的PropertySource,并将其添加到当前环境的PropertySource集合中。以下示例显示了如何执⾏此操作:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
@PropertySource
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
LoadTimeWeaver
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
ApplicationContext
format
exceptions
windows
# in format.properties message=Alligators rock!
# in exceptions.properties argument.required=The {0} argument is required.
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.ENGLISH);
System.out.println(message);
}
}
# in exceptions_en_GB.properties argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlackListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
public class EmailService implements ApplicationEventPublisherAware {
private List blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blackList.contains(address)) {
publisher.publishEvent(new BlackListEvent(this, address, content));
return;
}
// send email...
}
}
public class BlackListNotifier implements ApplicationListener {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步Listener不⽀持整个特性
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
@EventListener
public void onPersonCreated(EntityCreatedEvent event) {
// ...
}
public class EntityCreatedEvent extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
}
}
为了优化应⽤程序上下⽂的使⽤和理解,你应该熟悉Spring的资源抽象,如参考资料中所述。
应⽤程序上下⽂是可⽤于加载资源对象的ResourceLoader。资源本质上是JDK java.net.URL类的功能更丰富的版本。实际上,资源的实现在适当的情况下包装了java.net.url的实例。Resource可以以透明的⽅式从⼏乎任何位置获取低级资源,包括从类路径、⽂件系统位置、使⽤标准URL可描述的任何位置以及其他⼀些变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合于实际的应⽤程序上下⽂类型。
可以将部署到应⽤程序上下⽂中的bean配置为实现特殊的回调接⼝ResourceLoaderAware,以便在初始化时⾃动回调,并将应⽤程序上下⽂本身作为ResourceLoader传⼊。你还可以公开资源类型的属性,以⽤于访问静态资源。它们像其他任何性质⼀样被注⼊其中。你可以将这些资源属性指定为简单的字符串路径,并在部署bean时依赖于从这些⽂本字符串到实际资源对象的⾃动转换。
提供给ApplicationContext构造函数的⼀个或多个位置路径实际上是资源字符串,并且以简单的形式根据特定的上下⽂实现进⾏适当的处理。例如,ClassPathXMLApplicationContext将简单位置路径视为类路径位置。也可以使⽤带有特殊前缀的位置路径(资源字符串)强制从类路径或URL加载定义,⽽不管实际的上下⽂类型如何
contextConfigLocation
/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
ApplicationContext
as a Java EE RAR File
BeanFactory
BeanFactory API为Spring的IOC功能提供了基础。其特定的契约主要⽤于与Spring和相关第三⽅框架的其他部分集成,
本节解释BeanFactory和ApplicationContext容器级别之间的差异以及引导的含义。
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);