在这之中最重要的就是spring框架的控制反转(IoC)容器。彻底处理好IoC容器之后,对面向切面编程(AOP)技术的掌握将必不可少。Spring框架有自己的AOP框架,这个框架在概念方面很容易理解,并且成功解决了j2ee中80%的AOP需求。
Spring对AspectJ的集成在本文也有讲到。AspectJ是j2ee领域对AOP最成熟也是特性最丰富的实现。
这个章节讲述了Spring框架对控制反转(IoC)规则的实现。IoC也被理解为依赖注入(DI)。这是一个这样的过程:对象只通过构造函数参数、工厂方法参数,或者在对象实例被构造方法或者通过工厂方法返回之后,通过设置属性的值来定义它们的依赖(指的是:需要一同协作的其他对象)。容器在创建这些bean的时候会注入这些依赖。这个过程本质上是一种反转(因此得名——控制反转):本来bean是通过直接自己来实例化依赖的类或者通过服务定位器设计模式之类的机制来定位到依赖的类,从而自己控制依赖的实例化和定位,现在这被反转了。
org.springframework.beans和org.springframework.context这两个包是Spring框架IoC容器的基础。前者中的BeanFactory接口提供了能够管理任何类型对象的先进的配置机理。ApplicationContext是BeanFactory接口的一个子接口。它针对Spring的AOP特性增加了更加可用的集成:信息资源处理(在国际化中的使用)、事件发布、应用层确切的上下文例如专门提供给web 应用的WebApplicationContext接口。
简而言之,BeanFactory接口提供了配置框架和基本的功能而ApplicationContext接口又提供了更多针对企业开发的功能。ApplicationContext是BeanFactory的一个完全意义上的超集,在本章我们只使用ApplicationContext进行讨论。想要只用BeanFactory的更多用法自己去看文档(The BeanFactory.)吧。
在Spring中,那些在你的应用中处于“脊梁骨”级别并且收录在Spring IoC容器中管理的对象被称作bean。一个bean是指一个被Spring IoC容器初始化、组装、管理的对象。但是从另一个角度看,bean只是你所开发的应用中众多对象中的一个。beans和它的依赖,与容器所使用的“配置元数据”相互映照。
org.springframework.context.ApplicationContext这个接口代表了IoC容器,负责完成前面所提到的bean的初始化、配置和装配。容器通过读取配置元数据来获取对哪些类进行初始化、配置和装配的指令。配置元数据在xml、java注解或者java代码中体现。配置元数据使你能够表示那些组成你的应用的对象和它们之间丰富的相互依赖关系。
Spring中有几个随取随用的ApplicationContext的实现。在独立应用中,创建一个 ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext的示例很常见。尽管xml是定义配置元数据的传统格式,但是只要提供少量xml配置来声明使用附加的元数据格式,你也能够通过注解和java代码作为元数据来对容器发出指令。
在大多数应用场景中,并不需要具体的用户代码来实例化一个或者多个Spring IoC容器例如,在一个web应用场景中,应用中的web.xml文件中只需要一个大概8行的模板web descriptor XML语句就足够满足创建容器的需求。如果你正在使用装备了Spring工具套装了eclipse开发环境,那你仅仅只需要动几下鼠标和键盘就能够生成一个上面的模板配置。
下面的图表在一个很高的层次展示了Spring是怎么工作的。你的应用中的java 类通过配置元数据被结合到一起。因此在ApplicationContext被创建和初始化之后,你就得到了一个完全配置好的可执行的系统或者应用。
正如前面的图表所示,Spring IoC容器会用到某种格式的配置元数据;这个配置元数据会展示你作为开发者想要Spring容器如何初始化、配置、装配你的应用中的对象。
配置元数据通常是通过简单直观的xml格式来提供的,这也是本章用来讲述Spring IoC容器关键概念和特性的配置元数据格式。
注:XML并不是配置元数据的唯一格式。Spring IoC容器本身和配置元数据的书写形式完全解耦。最近很多开发者选择基于Java代码的配置形式来开发应用。
想要使用其他的元数据格式,可以参考以下链接:
Annotation-based configuration(基于注解的配置):Spring 2.5引入了基于注解的配置元数据。
Java-based configuration(基于java的配置):从Spring 3.0起,许多由Spring JavaConfig项目提供的特性被引入了Spring框架。因此你可以在的项目代码之外通过java语言而不是XML语言来定义beans(配置元数据中的定义信息)。要使用这些新特性,参见@Configuration, @Bean, @Import和@DependsOn这些注解。
Spring的配置至少包含一个但是一般不止一个将要给容器处理的bean的定义。基于XML的配置元数据使用包含在元素中的元素来配置这些beans。基于java的配置元数据通常在一个包含注解@Configuration的java类中使用@Bean注解配置bean。
这些配置与实际构成你的应用的对象相对应。通常你会定义服务层对象、数据访问层对象、展示层对象例如Struts的Action 实例,框架对象例如Hibernate的SessionFactories,JMS Queues,等等。通常我们不会在容器中配置domain对象,应为创建和装载domain对象通常是dao对象和业务逻辑的事情。但是,使用集成了AspectJ的Spring框架,你将能够配置在IoC容器控制范围之外创建的对象,详情参见Using AspectJ to dependency-inject domain objects with Spring。
下面的例子展示了基于XML的配置元数据的基本结构。
<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="...">
bean>
<bean id="..." class="...">
bean>
beans>
这里的id属性是用来区分不同的bean定义的。class定义了bean的类型,必须使用完全规范化的类名。id属性的值可以引用合作的对象。id引用合作对象的xml在本例中并没有显示;参见1.4.依赖项来获取更多信息。
提供给ApplicationContext的路径位置或者路径,是一种让容器从各种各样的位置,比如本地文件系统、类路径等等位置,加载配置元数据的资源字符串。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在你了解了Spring的IoC容器之后,你可能想知道更多关于资源的抽象(在资源这一节会讲到),这个提供了基于URL语法来从特定位置获取输入流的机制。值得注意的是,资源的作用就是就是用于构造应用上下文(容器)。正如“应用上下文和资源路径”这一章节所述。 |
---|
下面展示service层对象的配置文件service.xml:
<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="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
bean>
beans>
下面展示数据访问层配置文件dao.xml:
?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
bean>
beans>
在上面的例子中,service层包含PetStoreServiceImpl类和两个JpaAccountDao和JpaItemDao类型的对象(基于JPA对象-关系映射标准)。property元素的name属性指代是bean的属性名字,而ref属性指的是另一个bean定义的名字。ref和id的关联体现了相互协作的对象之间的依赖关系。想要知道配置一个对象的依赖的细节,参考“依赖”章节。
把bean定义分散到多个配置文件是有用的。通常每个配置文件都代表着架构的一个逻辑层或者子模块。
你可以使用应用上下文的构造函数来加载所有这些xml片段。这个构造函数有多个Resource地址,就像本小节前面所展示的那样。另一种可选的方式,是使用多个重复的
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
beans>
在上面的例子里,外部的bean定义从三个配置文件中加载:services.xml, messageSource.xml, and themeSource.xml。所有的导入都是基于当前路径的相对路径,所以services.xml必须要跟执行加载的代码文件的路径或者类路径同级,而messageSource.xml和themeSource.xml文件必须在导入它们的文件的resource子目录下。正如你看到的,开头的斜线会被忽略。但是,考虑到这些路径是相对的,最好不要用打头的斜杠。所导入的这些文件的内容,包括顶级的
可以但是不建议的一种做法是,使用…/来引用父目录的配置文件。这样可能会导致引用当前应用外的文件。特别提醒,这种方式不适用与类路径URL(比如classpath:…/services.xml),运行时解析程序会选择最近的类路径根目录然后进入它的父目录。类路径的改变可能会导致最终定向到一个不同的错误的目录。——————————分隔符——————————你始终可以使用完整有效的绝对路径,比如:file:C:/config/services.xml or classpath:/config/services.xml。但是注意,这样会导致你的应用配置和具体的目录结构相耦合。对非使用绝对路径不可的场景,合适的做法是使用间接的绝对路径,使用${}占位符来在运行时解析环境变量,以拿到绝对路径。 |
---|
命名空间本身提供了重要的指令特性。除了朴素的bean定义之外,使用Spring提供的XML名字空间提供了更进一步的配置特性——如context和util名字空间。
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<String> userList = service.getUsernameList();
最灵活的变体是GenericApplicationContext与reader代理的结合,例如结合读取xml文件的XmlBeanDefinitionReader,如下示例:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
你可以搭配GenericApplicationContext混合使用多种不同的reader代理,从不同的配置源头获取bean定义。
你可以使用getBean()方法获取你的bean。ApplicationContext也有其他方法获取bean,只是理想情况下你的代码不应该调用。实际上,你的代码也不应该调用ApplicationContext,这样就不会跟Spring的API耦合。例如,Spring和web框架的结合提供了各种各样web框架组件的注入服务,比如说controller和JSF管理的bean,让你可以通过配置元数据为一个bean声明这些依赖。
Spring容器管理着一个或者多个bean。这些bean根据你提交给容器的配置元数据创建。
在容器自身内部,这些配置元数据被当做BeanDefinition对象,包含下述内容:
元数据会被解析成bean定义的一系列属性。下面的表格描述了这些属性:
除了包含创建bean信息的bean定义之外,ApplicationContext还支持在容器之外创建的对象注册进来。这通过ApplicationContext的getBeanFactory()方法实现。这个方法会返回BeanFactory的默认实现DefaultListableBeanFactory。DefaultListableBeanFactory支持这种外部的对象注册,调用registerSingleton(…) and registerBeanDefinition(…)方法即可。但是,一般的应用只会使用配置元数据创建的bean。
bean元数据和手动提供的单例Bean实例应该被尽早注册,为了让容器在自动注入和自省的过程中尽早理解他们。虽然在运行时覆盖已经存在的元数据和单例bean实例某种程度上是被支持的,但在运行时(如此同时容器正在被并发访问)注册一个新的bean却没有被官方支持。并且,这还可能导致并发访问异常、容器状态不一致等问题。 |
---|
每个bean有一个或者多个标识符。bean的标识符必须在容器内唯一。一个bean通常只有一个标识符。但是,如果需要多个,那其他的标识符可以被当做是别名。
在基于XML的配置元数据中,你可以用id或者name来作为bean的标识符。id只允许你确认一个。按照惯例name是字母数字组成的,但是实际上你也可以包含特殊字符。如果你想要为bean指定其他别名,你可以在name属性中确认这些值,通过,;或者空格来分隔多值。历史上,在Spring 3.1之前,id是作为xsd:id格式被定义的,只允许使用特定的字符。在3.1及其之后,id被定义为xsd:string格式。注意id的唯一性是容器强制要求的,虽然没有被xml解析器所要求。
bean的id或者name并非是必填的。如果你不明确指定bean的id或者名字,容器会为它自动生成一个名字。但是,如果你想要通过ref元素或者服务定位器模式来查找bean,那么你必须提供bean名字。不提供名字的动机与使用内部bean和自动注入依赖有关。
bean命名惯例 |
---|
惯例是像java命名自己的域变量名那样来命名bean名字。这会让你的配置更好理解。 |
对于从类路径扫描到的组件,spring为没有名字的组件生产bean的名字。根据上述规则:使用简单的类名并把首字母改成小写。但是,如果头两个字母都是大写,那么就保留原有的类名。这与java.beans.Introspector.decapitalize(Spring在这里使用)定义的规则一致。 |
---|
在bean定义自身中,你可以使用最多一个id值和任意多个name值来为bean定义多个名字。这些名字都是等价的,在某些场合,比如让每个组件用跟组件相关的名字指向同一个依赖。
但是有时候,只能在定义bean的时候确认别名是不够的。有时候需要为其他地方定义的bean指定别名。这种案例通常是一个大系统包含多个子系统,每个子系统都有自己的bean定义文件。xml配置元数据中你可以使用下面的元素:
<alias name="fromName" alias="toName"/>
这样,名为fromName的bean增加了一个别名toName。
举个例子。子系统A喜欢用subsystemA-dataSource命名数据源bean,子系统B喜欢用subsystemB-dataSource命名数据源bean。在组成主应用的时候,主应用使用myApp-dataSource来指代这个bean。要让一个bean同时具有三个名字,可以增加如下配置元数据:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
这样,每个组件和主应用都有自己的名字访问数据源,这个名字是唯一的,不会和其他bean冲突(实际上创建了一个名字空间),但是仍然指向同一个Bean。
如果使用java配置,@Bean注解也可以用于产生别名。参见“使用@Bean注解”章节。 |
---|
一个bean定义实际上是一个创造更多的bean的“配方”。容器会在需要对象的时候寻找指定的bean的配方,使用bean定义封装的配置元数据创建实际的bean。
如果使用基于xml的配置元数据,你通过
内部类:如果你想配置bean定义指向一个静态内部类,你必须使用内部类的二进制名字。比如想要指定com.example 包下com.example 类内部的静态类OtherThing,你需要指定class属性为com.example.SomeThing$OtherThing。注意美元分隔符,这是用于区分内部类名和外部类的类名。
所有的正常类都可以通过构造函数实例化,Spring对其都可以兼容。这些类型不需要事先特定的接口或者依照某种格式。但是,取决于你使用哪一种IoC方式,你可能要提供一个默认的构造函数。
Spring的IoC容器本质上可以管理任何类型的bean,而不只是严格意义上的JavaBean。大部分Spring使用者都更喜欢只有默认(无参)构造方法和属性后模式化的setter、getter方法的JavaBean。你的bean里面也可以有跟多外来的非bean格式的类。比如,你需要使用一个历史理由的连接池代码,这个Spring也可以管理。
通过xml配置元数据,你可以这样确定你的bean:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
想要知道配置构造函数参数和实例化只会设置属性参数的细节,参考“依赖注入”章节。
当要管理的bean是静态工厂方法返回的,你需要将class属性指向包含工厂方法的类并且使用factory-method这个属性工厂方法的名字。你要能够调用这个方法(带着可选的参数,后面会说到),并且返回一个有效的对象,就像通过构造函数创建的一样。这种bean定义的用法之一就是调用历史遗留代码的工厂方法。
下面的bean定义确定了一个通过工厂方法生产的bean。定义只指定了包含工厂方法的类,返回的bean的类型是不确定的。在这个例子中,createInstance()方法必须是静态的。下面的例子展示了如何指定工厂方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
下面是配置相对应的类:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
工厂方法参数和对象实例化只会属性的配置原理详见“依赖和配置详解”章节。
类似于通过静态工厂方法,通过实例工厂方法也可以创建bean。要使用这个机制,先把class属性置空。通过factory-bean属性指定一个已经存在的bean,并且这个bean要包含可以创建对象的实例方法。用factory-method属性来这只这个方法名。下面的例子告诉你如何配置这个bean:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
下面是对应的class:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类可以也有多个工厂方法,如下:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
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;
}
}
这个方法表明工厂类自身也可以通过依赖注入来管理和配置。参见“依赖和配置详解”章节。
在Spring文档中,factory bean指的是那些在容器中被配置并通过自身的一个实例或者静态工厂方法来创建对象的bean。作为对照,“FactoryBean”(注意大写字母)指的是Spring内置的FactoryBean接口的实现类。 |
---|
确定一个bean的运行时类型并不是一件无关紧要的事情。bean元数据定义里的class类型只是一个最初的类引用,可能伴随着一个工厂方法的生命或者本质指代的就是一个FactoryBean类,这些都可能到时运行时的bean类型未知。甚至都不一定有class属性(对于使用factory-bean属性的实例层工厂方法来说)。此外,AOP代理可能会用基于接口的代理包装一个bean实例,很少会暴露出这个bean的本来类型(只暴露出它所实现的接口类型)。
推荐的获取bean运行时类型的方式是,根据bean的名字调用BeanFactory.getType方法来获取。这个方法把所有的情况考虑在内了,会返回 BeanFactory.getBean方法所返回的对象的类型。
下面的章节告诉你一个个bean定义是如何整合依赖项实现最终应用的。
依赖注入是指对象只通过构造函数、工厂方法参数和实例的setter方法来定义其依赖的过程。然后容器会在这些类创建的时候注入依赖。类本来是应该自己控制依赖项的实例化和位置的,现在这交给容器来进行了,这就是控制反转。
使用依赖注入机制代码会更简洁更解耦。被注入的对象本身不知道依赖项在哪里,也不知道依赖项的实现类型。对象也因此更加容易测试,这是因为依赖项是接口或者抽象类型的话,可以方便地用stub或者mock实现,因此可以更好做单元测试。
依赖注入主要有两种:基于构造函数的依赖注入和基于setter方法的依赖注入。
构造函数注入是指容器在创建对象的时候通过构造函数参数注入依赖项。对于静态工厂方法来说也是一样的处理方式,每个方法参数代表一个依赖项。下面的例子是一个只能被constructor inject的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...
}
构造函数参数通过参数类型来匹配依赖项。如果bean定义里面的参数没有潜在模糊性,bean定义里面给的参数顺序就是调用构造函数的时候注入的参数的顺序。如下:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设参数thingTwo和thingThree没有继承关系,即没有潜在的模糊性。因此你不需要配置bean定义里面的参数顺序和参数类型也行:
<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>
知道引用的是哪个bean,也就知道那个bean的类型,也就可以跟构造函数的参数类型匹配了。但是如果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;
}
}
对于上例,如果你明确指定了配置中参数类型,那么基础类型参数也可以匹配成功:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
bean>
你也可以明确指定参数顺序:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
bean>
这对于区分有相同类型的参数十分有用。注意,序号从0开始。
你也可以在配置中指定方法参数名
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
bean>
需要注意的是,要使用这种格式的配置,Spring需要能够在运行时得知方法参数名,而这需要你在编译java的时候打开debug选项。默认的javac编译器是release选项,所以默认是不能这么写的。如果真的要使用上述配置,需要在代码中使用jdk的 @ConstructorProperties注解明确指定参数名称:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于setter的依赖注入发生于容器调用无参构造方法或者无参静态方法之后,通过容器调用setter方法实现注入。
下面的例子展示一个只能通过setter注入的bean:
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...
}
容器也支持构造方法注入之后的bean再通过setter方法注入依赖。你可以直接使用BeanDefinition类来定义依赖。但是大部分Spring用户不会直接写代码用这个类,而是使用配置元数据、注解或者使用@Bean方法的java配置类生成bean定义。这些配置最终都会内部转换成BeanDefinition,被容器加载。
总体原则是,使用constructor注入必须的依赖,使用setter注入非必须的依赖。虽然使用@Required注解也会让setter注入的依赖变成必须的。但是构造函数注入必须依赖并且通过代码来做校验是更推荐的。
Spring团队总体推荐使用constructor注入,因为这样可以保证组件是不变的,而必须的依赖是非空的。进一步说,通过constructor注入的bean返回的实例肯定是完全初始化的状态。
setter注入的非必须依赖在调用的时候一定要做非空校验。setter注入的一个好处是让bean支持重新配置和重新注入。
容器执行依赖解析过程如下:
spring在容器创建的时候校验所有的配置。但是bean的属性只会在bean被创建之后才会配置进去。单例的bean(默认都是)在容器创建的时候创建。详见bean作用域章节。否则,bean只在被需要的时候才会被创建。一个bean的创建会导致它所有的依赖的bean,以及依赖的bean的依赖项,等等,都被创建。因此,错误的依赖关系可能不会在容器启动的时候就出现,而是在之后出现。
如果你主要使用constructor注入,那你可能会遇到无法解析的循环依赖。
举例来说:类A通过构造函数注入类B,类B又通过构造函数注入了依赖A。如果你这样定义两个bean,Spring会在运行时抛出BeanCurrentlyInCreationException异常。
一个可能的解决办法是将使用constructor注入改为使用setter注入。或者只使用setter注入。虽然不推荐,但是使用setter注入循环依赖是可行的。
会有这样的问题,是因为Spring会要求相互依赖的两个类有一个先实例化,而无论哪一个都要求另外一个先实例化作为自身实例化的前提条件,导致了先有鸡还是先有蛋的问题。
总体上,你可以相信Spring会做正确的事情。Spring会尽可能完地设置属性解析依赖。因此有可能你启动Spring的时候没有异常,但是运行了一段时间之后有一个bean被创建,依赖被解析,就异常了。这些延迟出现的问题也是Spring默认bean的作用域为singleton的原因。当然,你也可以修改默认配置,让singleton bean不预加载。
如果没有循环依赖,被依赖的bean总是要比当前bean早配置。这意味着bean A如果依赖bean B,那么容器在调用bean A的setter方法之前,会先完全配置好B。
下面的配置片段是针对setter注入的xml配置元数据:
<bean id="exampleBean" class="examples.ExampleBean">
<property name="beanOne">
<ref bean="anotherExampleBean"/>
property>
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
对应的bean class:
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;
}
}
下面是bean class:
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 class如下:
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;
}
}
工厂方法返回的对象类型并不一定是工厂类的类型。通过工厂实例对象的方法实例化bean于此类似,不再赘述。
正如前面章节提到的,你可以配置bean的属性、方法参数为其他bean的引用,或者直接配置一个数值。Spring的xml配置元数据因此支持
property的value属性是用人类可读的字符类型来表示属性值,Spring的conversion service会将其转换成需要的类型:
<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="misterkaoli"/>
</bean>
使用p名字空间可以让配置更简洁:
<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="misterkaoli"/>
beans>
你也可以配置一个java.util.Properties的实例作为属性值:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
value>
property>
bean>
容器会使用PropertyEditor把value元素的内容转换成Properties实例。这也是Spring更喜欢使用value子元素而不是value属性的原因。
当要把bean的名字作为直接值传递给属性的时候,使用idref元素是一种可以防止错误的方式:
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
property>
bean>
使用value属性传递直接值是等效的,只是容器启动时不会检查是否存在这个名字的bean,而idref是会的:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
bean>
一个常见的使用idref的位置是在配置AOP拦截器的时候,可以有效避免指定的拦截器名字错误。
property和constructor-arg元素的ref子元素,作用是将属性值配置为对其他bean的引用。使用ref的bean属性可以配置当前容器或者父容器的任意bean为依赖。bean属性的值可以是另外一个bean的id,或者name。
<ref bean="someBean"/>
使用ref元素的parent属性让你可以获取到父容器的一个bean,并自动用一个代理将其包装成一个同名的bean。注意只有当你有层级容器的时候才可以这样做。
<bean id="accountService" class="com.something.SimpleAccountService">
bean>
<bean id="accountService"
property>
bean>
你也可以在property或者constructor-arg元素的中使用bean元素定义一个内部bean。
内部bean没有名字,也没有作用域。即使配置了容器也会忽略这两个属性。因为内部bean不可能在闭包类以外的地方被访问和注入。
,
<bean id="moreComplexObject" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]prop>
<prop key="support">[email protected]prop>
<prop key="development">[email protected]prop>
props>
property>
<property name="someList">
<list>
<value>a list element followed by a referencevalue>
<ref bean="myDataSource" />
list>
property>
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
map>
property>
<property name="someSet">
<set>
<value>just some stringvalue>
<ref bean="myDataSource" />
set>
property>
bean>
map的key或者value,或者集合的一个value,可以是下面的任意元素:
bean | ref | idref | list | set | map | props | value | null
Spring支持集合合并。但是这是基于父子bean的配置。子bean的同名集合类型属性可以与父bean的同名集合属性相合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]prop>
<prop key="support">[email protected]prop>
props>
property>
bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<props merge="true">
<prop key="sales">[email protected]prop>
<prop key="support">[email protected]prop>
props>
property>
bean>
<beans>
注意其中的merge=true属性,最终合并的结果为子bean的adminEmails有如下值:
[email protected]
[email protected]
[email protected]
同样的配置对set、list和map类型也是有效的。其中,因为list是有序的,所以父list的元素会在子list的之前。
不能合并不同的类型,否则会抛出异常。只能在子bean中配置集合合并。
java5引入泛型之后,你可以配置集合类只能包含特定类型的元素。这被称为强类型集合。如果你为bean的强类型集合属性注入值,那么Spring会尝试在注入之前做值类型转换,如下:
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>
配置中的值会被转换成Float类型。
Spring把配置文件的空值当做空字符串。如下:
<bean class="ExampleBean">
<property name="email" value=""/>
bean>
上面的配置和下面的java代码类似
exampleBean.setEmail("");
null元素表示null值,如下
<bean class="ExampleBean">
<property name="email">
<null/>
property>
bean>
等同于java代码:
exampleBean.setEmail(null);
下面的例子中p名字空间实现相同属性配置效果,但是更简洁:
<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="[email protected]"/>
bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="[email protected]"/>
beans>
上例通过使用p名字空间的email属性,这告诉Spring容器在这个元素内部加上属性值声明。
下面的例子使用名字空间来声明对另外一个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名字空间的spouse-ref属性声明了对另一个bean的引用。其中spouse是属性名,-ref表示这里配置的不是直接值,而是对另一个bean的引用。
与p-namespace类似,c-namespace在Spring3.1引入,允许使用行内属性来替代子元素constructor-arg。
<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"/>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="[email protected]"/>
bean>
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="[email protected]"/>
beans>
对于少数构造函数参数名未知的情况(没有用debug方式编译),你可以使用带参数序号的属性:
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="[email protected]"/>
在配置bean属性的时候,你可以使用复合的或者嵌套的属性名,只要复合属性名的除最后一个之外的属性都不是null:
这个bean的fred的bob的Sammy属性被设置为123。fred、bob和Sammy必须不是null,否则会报空指针异常。
使用depends-on属性能够确保在当前bean初始化之前,depend-on的bean会先初始化,这对于依赖另一个bean的初始化静态资源(比如保持在静态域中的数据库连接)的情形很有用。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示依赖多个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" />
默认的singleton bean是会在容器启动时预加载。如果要改变这一点(改成在需要时实例化),可以在xml中使用 lazy-init="true"属性:
但是,如果这个懒加载的bean被一个singleton的bean依赖,这个懒加载bean还是会在容器启动时加载。
要想改变所有bean为懒加载,可以修改beans元素的default-lazy-init属性:
<beans default-lazy-init="true">
beans>
自动注入的好处不必多说,可以有效减少配置的编写和修改。
你可以通过bean元素的autowire属性指定该bean的自动注入模式,autowire的值有如下几种:
模式 | 说明 |
---|---|
no | 默认的模式,即不自动注入。bean的依赖必须通过ref属性或元素明确指定 |
byName | 根据属性的名字去查找同名的bean作为依赖注入(根据属性的setter方法得到属性名) |
byType | 根据属性的类型去查找同类型的bean作为依赖注入(根据属性的setter方法参数类型得到属性类型)。如果找到多个同类型的bean,会抛出中止异常。如果一个没找到,那么什么也不会发生,属性不会被设置。 |
constructor | 类似于byType,只不过是基于构造函数参数的。如果找到多个同类型的bean,会抛出中止异常。 |
整个项目统一使用自动注入是最好的,否则如果只有少数的bean使用自动注入,那会让人容易产生误解。
限制条件和缺点如下:
为了避免受到这些缺点影响,你可以:
你可以声明一个bean不是自动注入候选者,set autowire-candidate to false。
你也可以配置beans元素的 default-autowire-candidates 属性,用于把模式匹配的bean排除在候选者之外。如把bean名字以Repository结尾的bean从候选者排除,可以设置default-autowire-candidates =*Repository。支持逗号分隔多模式匹配。
不同bean的生命周期是不同的。当你用一个singleton bean去依赖一个非singleton bean 的时候,singleton bean所依赖的始终是同一个非singleton bean(比如request bean),而这个非singleton bean的生命周期可能已经结束了。如何在每次调用singleton bean方法的时候获取到一个最新的非 singleton bean呢?
方法一是冗余Spring的代码,放弃一部分的控制反转,使用ApplicationContext的getBean方法:
// 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;
}
}
其实还有更简洁的获取不同生命周期bean的方式,方法注入。
方法注入就是容器替换bean的方法,为bean返回特定的bean。容器基于 CGLIB library 的字节码生成来自动生成一个子类覆盖bean方法。
注意:
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);
如果被注入的方法是抽象的,容器自动生成子类会实现它,否则覆盖它。
方法注入的配置如下:
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
bean>
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
bean>
如果使用基于注解的配置,那么方法注入会简单很多:
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();
}
注意你大多数情况应该为注入方法写一个基本的桩代码实现,因为组件扫描会忽略抽象类。当然,如果你明确注册这个bean,那是不受影响的。
另一个比起lookup方法注入不那么有用的方法注入,是用bean的一个方法替换另外一个方法。这个章节你可以在你需要使用这一特性的时候专门来看。
(这个方法会冗余Spring的代码,也会更复杂。,我就不翻译了。)
当你创建一个bean定义,你就相当于创建了一个菜谱。根据这个菜谱,容器可以创建实际的bean。bean定义是“菜谱”这个概念是很重要的,这意味着你可以根据这个菜谱创建任意多个bean。
你不仅可以通过“菜谱”控制bean的依赖、属性值,你还可以通过这个菜谱决定根据它创建的对象的生命周期。
Spring提供了六种生命周期,其中后四种只有web-aware的ApplicationContext才具备。你可以将bean配置为其中之一:
作用域 | 描述 |
---|---|
singleton | 默认是这个配置,限制一个bean定义只对应一个bean 实例。 |
prototype | 限制一个bean定义对应任意多个bean实例 |
request | 限制一个bean定义对应一个HTTP request生命周期。也就是说每一个HTTP request都有且仅有一个根据bean定义创建的实例 |
session | 限制一个bean定义对应一个session的生命周期 |
websocket | 限制一个bean定义对应一个websocket的生命周期 |
每个容器只有一个该bean定义的bean实例。不同于Gang of Four (GoF) 设计模式中的单例模式,GoF的是每个ClassLoader只有一个实例。
你应该使用prototype来配置所有的有状态的bean,使用singleton配置无状态的bean。
和其他作用域相比,Spring不会管理prototype bean的完整生命周期。Spring只会实例化、配置、组装、交付Prototype bean,交付完只会就没有任何记录了。因此,对于prototype bean,销毁生命周期回调不会被调用。客户端代码必须自己释放prototype bean的昂贵资源。要想要Spring来释放prototype bean的资源,你可以自定义一个bean post-processor,来记录那些需要释放资源的prototype bean。
普通的依赖方式,prototype bean只会注入一次。如果想要singleton bean每次都能拿到不同的prototype bean,参考方法注入章节。
以上作用域必须基于感知web的ApplicationContext,否则会抛出IllegalStateException异常。
为了支持上述作用域,需要增加一些配置。
如果是使用Spring WEB MVC容器,则不需要增加任何配置。
其他老一点的框架,如struts或者早期的Servlet容器,则需要添加一些配置。具体参考原文。
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
每个request请求都会产生一个新的LoginAction实例。当request请求被完全处理完,对应的bean实例也会被丢弃。
注解配置如下:
@RequestScope
@Component
public class LoginAction {
// ...
}
与request类似
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
@SessionScope
@Component
public class UserPreferences {
// ...
}
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
这个作用域的bean是每个ServletContext的生命周期只会有一个,并且作为普通的ServletContext属性存储。与singleton的区别是,singleton是每个ApplicationContext一个,而它是每个ServletContext一个。
如果你想要把一个短作用域的bean注入到一个长作用域的bean里面去,你一般需要注入该bean的一个代理类proxy。这个proxy有这个bean的所有公共接口,最关键的是它可以根据当前关联的作用域找到对应的bean,并将方法调用代理到实际的bean方法中去。
注意点:
声明proxy的配置只有一行:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
bean>
<bean id="userService" class="com.something.SimpleUserService">
<property name="userPreferences" ref="userPreferences"/>
bean>
beans>
被注入的地方根本不知道注入的是一个代理proxy,实际上代理类也是bean的子类型只不过公共方法都被重写(CGLIB proxies),因此调用过程完全没有感知到差异。
bean作用域是可以拓展的,你甚至可以重写已有的定义域。但是singleton和prototype不可重写,并且重写内置作用域是不好的做法。
实现org.springframework.beans.factory.config.Scope 接口即可自定义作用域。更详细的接口介绍详见spring的java doc。
原文讲解不好理解,暂不翻译。
同上,暂忽略。
spring框架提供了一些接口,使用这些接口,你可以自定义bean的生命周期。本小节将其归类为三点:
你可以实现spring的InitializingBean和DisposalBean接口来影响容器对bean生命周期的管理。在你的bean初始化和销毁的时候,容器会先后调用afterPropertiesSet()与destroy()接口,来执行特定的操作。
在现代的spring应用中,通过使用JSR-250规范定义的@PostConstruct和@PreDestroy注解来获取生命周期的回调,基本上被认为是最佳实践。使用这些注解,意味着你的bean无需与spring制定的接口耦合在一起。更多细节,详见Using @PostConstruct and @PreDestroy |
---|
在spring框架内部,框架使用BeanPostProcessor接口的实现来处理任何它能找到的回调接口,然后调用相应的方法。如果你需要一些自定义特性,或者其他生命周期相关的动作,spring不会默认提供,但是你可以自己实现一个BeanPostProcessor来实现这一点。更多信息,详见 Container Extension Points.
除了 初始化和销毁回调之外,spring管理的对象还实现了Lifecycle接口,因此这些对象可以参与到应用启动和关闭的过程,这一过程被容器自身的生命周期所驱动。
这些生命周期回调接口将在这一小节被调用。
在容器给bean设置了必要的属性之后,org.springframework.beans.factory.InitializingBean接口让bean得以执行初始化工作。InitializingBean接口只定义了一个单独的方法:
void afterPropertiesSet() throws Exception;
我们不推荐你使用InitializingBean接口,因为它会产生跟spring代码不必要的耦合。作为另一个选择,我们推荐你使用@PostConstruct注解或者确定一个POJO(普通Java对象)的初始化方法。如果使用基于xml的配置元数据来定义bean,你可以使用init-method属性来指定bean中一个无参返回值为void的方法来作为初始化回调方法。如果使用Java配置,你可以使用@Bean注解的initMethod属性。详见Receiving Lifecycle Callbacks.以下示例可作参考:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
上面的例子和下面的实现,几乎是完全相同的作用:
<bean id="exampleInitBean" class="examples.ExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
然而,第一个例子没有冗余spring的代码。
实现org.springframework.beans.factory.DisposableBean接口可以让一个bean在其所在的容器被销毁时,所实现的接口函数被回调。DisposableBean接口只定义了一个简单的方法。
void destroy() throws Exception;
同样,我们也不推荐使用DisposableBean回调接口,因为这样会将代码与spring耦合,这是不必要的。作为另一种选择,我们推荐使用@PreDestroy注解或者通过bean定义确定一个普通的方法。如果使用基于xml的配置元数据,我们使用
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public cleanup () {
// do some destruction work (like releasing pooled connections)
}
}
上面的例子和下面的实现,几乎是完全相同的作用:
<bean id="exampleInitBean" class="examples.ExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
然而,第一个例子没有冗余spring的代码。
你可以为 |
---|
如果你不通过实现spring指定的InitializingBean接口和DisposableBean接口来实现初始化和销毁回调,那么你一般会定义特定的方法,方法命一般是init()、initialize()、dispose()等等。理想情况下,这一类生命周期回调方法应该是跨项目标准化的。这样所有的开发者都会使用相同的方法名,这样更有益于保持一致性。
你可以配置spring容器为每个bean“查找”指定的名字的初始化和销毁方法。这意味着,作为一个应用开发者,你可以编写一个类并指定一个名为init的初始化回调方法,而整个过程无需为每个bean定义init-method=“init”属性。spring IoC容器在bean被创建时调用该方法(与前述的标准生命周期回调一致)。这一特性也强制要求采用一致的初始化、销毁回调方面命名惯例。
假设你的初始化回调方法为init(),销毁回调方法为destroy(),那么你的类定义将与下面的类似:
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.");
}
}
}
你可以在bean定义中使用这个类,类似于下面的例子:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
bean>
beans>
最上层的beans元素的default-init-method属性的存在,导致spring IOC容器将每一个类中的init方法,认作是初始化回调方法。当一个bean被创建并完成组装,如果这个bean有这样的一个方法,那么这个方法就会在恰当的时候被调用。
类似的,你也可以配置默认的销毁回调方法(在xml中,是这样的),通过配置顶级原色beans的default-destroy-method属性。
如果已有bean存在跟命名惯例不一致的初始化、销毁回调方法,你也可以在单个bean元素中通过init-method属性和destroy-method属性来制定特定的回调方法。
spring容器保证,当一个bean的所有依赖都完成了诸如,该bean本身也被提供出来时,这个bean所配置的初始化回调会被立即调用。因此,初始化回调方法的调用过程,是通过原本的bean实例引用来实现的。这意味着AOP拦截器等还没有应用到bean上。在一个bean被完全创建之后,一个AOP代理(举个栗子)与它的拦截器链才会被应用。如果代理类和目标bean是分开定义的,你的代码甚至可以直接绕过代理,直接与原生的目标bean交互。因此,使用拦截器来处理init方法是违反一致性原则的,因为这样做会将目标bean的生命周期和代理或者拦截器的生命周期混在一起,并且当你的代码与原生的目标bean交互时,还会留下奇怪的语义。
针对spring 2.5版本,控制bean生命周期的动作,你有三种选项。
如果一个bean,同时使用了多种生命周期管理机制,并且使用每种机制配置的回调方法名都不一样,那么这些回调方法将会按照下面的顺序依次被调用。然而,如果使用不同的机制配置的回调方法名相同–比如都配置了init()方法作为初始化方法–这个方法只会执行一次,就像前一章节所解释的那样(然而小鱼表示上一小节并没有发现什么解释ヽ(ー_ー)ノ) |
---|
使用多种生命周期配置机制配置同一个bean,但是配置的是不同的初始化方法,这些方法会按照以下顺序被调用:
Lifecycle接口为任何有自身生命周期要求的对象定义了必要的方法(比如启动和关闭一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何spring管理的对象都有可能实现这个Lifecycle接口。然后,当ApplicationContext自身收到启动或者关闭信号时(比如,运行时关闭、重启应用),它会级联地调用所有在context范围内的,实现了Lifecycle的实例相关方法。它通过委托给LifecycleProcessor来实现这个功能。
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意LifecycleProcessor本身也是Lifecycle的一个子接口。它增加了两个另外的方法来对context的刷新和关闭做出响应。
注意一般的org.springframework.context.Lifecycle接口是朴素的契约,作为明确的启动和停止之提醒手段,并不意味着context刷新时的自动重启能触发它。如果想要更加详细地空控制一个bean的自动启动过程(包含启动的各个阶段),可以考虑通过实现org.springframework.context.SmartLifecycle这个接口来做到这一 点 ;同样请注意停止的回调通知也不保证在destruction之前到来。对于通常情况的应用关闭,在整个应用的销毁回调被传播之前,实现了Lifecycle的bean会先收到停止的通知消息。然而,对于context的生命周期中的热更新或者中止热更新这两种场景,只有destroy方法会被调用 |
---|
启动和关闭的调用顺序其实很重要。如果两个对象之间存在依赖关系,依赖者相对于被依赖者,总是后启动,先销毁。然而,有时候,直接的依赖项是未知的。你可能只知道特定类型的一些对象要另一类型的对象要先启动。在这种情况下,SmartLifecycle接口定义了另外一个选项,叫做getPhase()方法,正如在它的父接口Phased中所定义的那样。下面列出Phased接口的定义:
public interface Phased {
int getPhase();
}
下面列出SmartLifecycle接口的定义:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
在应用启动的时候,拥有最低相位的对象最先启动。在应用停止的时候,顺序则相反。因此,如果一个对象实现了SmartLifecycle接口并且它的getPhase()方法返回了Integer.MIN_VALUE,那么这个对象会最先被启动,并最晚被关闭。在频谱的另一头,如果一个对象的相位值是Integer.MAX_VALUE,那么这个对象将会最后被启动并最先被停止(只是有可能这样,因为还取决于其他要运行的进程)。当考虑设置相位值时,应该要知道,任何“正常”的实现了Lifecycle而没有实现SmartLifecycle接口的对象,它的默认相位值是0。因此,一个负数的相位值,意味着这个对象会比其他标准的组件更早启动(也更晚停止)。同理,对于正数相位值,情况相反。
SmartLifecycle接口定义的stop方法会接受一个回调作为参数。任何实现类在做完自身的关闭操作之后,都必须调用回调对象的run()方法。这使得必要的异步关闭成为可能,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor,等到每个相位的对象组的超时时间之后,才会调用回调。你可以覆盖默认的lifecycleProcessor实例,通过在context中定义一个名为LifecycleProcessor的bean。如果你只想修改超时时间,定义下面的bean就足够了。
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<property name="timeoutPerShutdownPhase" value="10000"/>
bean>
正如前面所提到的,LifecycleProcessor接口同样也为context的刷新和关闭定义了回调方法。后者会驱动shutdown过程就好像stop()接口被显式调用了一样,但是这是在context关闭时发生的。另一方面,“refresh”这个回调,赋予了SmartLifecycle的bean另一个特性。当context刷新的时候(所有对象都被实例化和初始化之后),该回调就会被调用。这个时间点,默认的Lifecycle processor会检查每个SmartLifecycle对象的isAutoStartup()方法。如果是true,那个对象就会被启动,而不是等待context或者它自身的start()方法被调用(不像context刷新,对于一个标准的context实现来说,context启动不会自动发生)。如前所述,相位phase值和任何的“依赖”关系,将决定启动顺序。
这一小节仅针对非web应用。Spring web应用ApplicationContext实现已经有代码来优雅地关闭IOC容器,当相关的web应用被关闭时。 |
---|
当你在非web环境使用spring的IOC容器(比如富客户端桌面环境)。向jvm注册一个shutdown hook。这么做保证了优雅的关闭过程,同时也会调用关联的singleton bean的destroy方法,这样所有的资源都会被释放。你必须正确地配置和实现这些destroy回调方法。
要注册一个shutdown hook,只需要调用ConfigurableApplicationContext接口的registerShutdownHook()方法,如下所示:
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...
}
}
当ApplicationContext创建一个实现了org.springframework.context.ApplicationContextAware接口的实例时,这个会被提供一个该ApplicationContext的引用。下面列出org.springframework.context.ApplicationContextAware接口的定义
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以已编程的方式来使用创建它的那个ApplicationContext,通过ApplicationContext接口或者把它强转换为一个已知的ApplicationContext接口子类(例如ConfigurableApplicationContext,它提供了额外的方法)。一种用法是通过编码的方式来拿到其他的bean。有时候这一能力很有用。然而,总体来说,你要避免这样做,因为这样会冗余spring的代码,并且违反了控制反转样式–按照控制反转,依赖项是通过属性配置给到bean的。ApplicationContext的其他方法提供了对文件资源的访问、发布应用事件的方法,还有访问MessageSource的方法。在 Additional Capabilities of the ApplicationContext中对这些额外方法有详细的描述。
自动注入是另外一种可选择的获取ApplicationContext引用的方式。传统的constructor和byType注入模式(正如在依赖注入小节中描述的那样),通过构造函数参数或者setter方法参数的方式,都可以提供一个ApplicationContext类型的引用。更灵活一点,包括注入到域或者多参数方法的参数中,可以使用基于注解的注入方式。如果你想要这么做,需要在构造函数、域或者方法参数中加上@Autowired注解。更多细节,详见Using @Autowired
当一个ApplicationContext创建一个实现了 org.springframework.beans.factory.BeanNameAware的类,这个类会被给到一个相关的bean定义中名字的引用。下面列出了BeanNameAware接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
这个回调方法在所有的正常bean属性填入bean之后被调用,调用事件早于初始化回调比如InitializingBean,afterPropertiesSet,或者一个自定义的init方法。
除了ApplicationContextAware接口和BeanNameAware接口之外,Spring提供了广泛的Aware回调接口,这些回调接口使得bean可以向容器表明,bean需要哪些特定的基础依赖项。一般来讲,接口名字表明了依赖类型。下面的表格总结了最重要的Aware接口:
再次提醒,使用这些接口,会使得你的代码和spring的api绑定,违反控制反转样式。
一个bean definition可以包含很多配置信息,包括构造函数的参数,属性值和对容器指定的信息,比如初始化方法、静态工厂方法名,等等。子bean definition从父definition那里继承配置信息。子bean definition可以覆盖值,也可以根据需要新增一些值。使用父子bean definition可以节省大量的编码。实际上,这也是模板化的一种形式。
如果你通过编程的方式来使用ApplicationContext接口,那么ChildBeanDefinition这个类就可以用来代表子Bean定义。大部分人不会在这个层次上使用它。相反,他们会以声明的方式在ClassPathXmlApplicationContext这个类中配置bean definition。如果你使用基于xml的配置元数据,你可以指定一个bean的parent属性,来表明这个bean definition是parent bean definition的子bean定义。下面是示例:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
bean>
如果子bean definition没有指定class,它会使用父 bean definition的class,并且也可以覆盖一些值。在后续的场景中,子bean definition必须要兼容父bean class(那是因为它必须接受父定义的属性值)。
一个子bean definition从父定义中继承作用域、构造函数参数、属性值和方法,并且可以选择重写这些内容。你在子定义中定义的任何作用域、初始化方法、销毁方法、或者静态工厂方法都会覆盖啊父定义中的相应设置。
剩余的设置项总是从子bean definition中获取:依赖项、自动注入模式、依赖检查、单例模式、懒加载。
上述例子明确通过abstract属性来表明了父bean定义是抽象的。如果父bean definition没有指定一个class,那么明确标记父bean definition的abstract是要求做到的。如下示例:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
bean>
此例中,父bean definition不能实例化,因为它是不完整的,并且还是abstract。当一个bean definition的abstract为true,那么它就只能作为子定义的父定义使用,也就是作为一个模板使用。试着去单独使用这样一个abstract父定义,通过在其他bean的属性中引用他或者直接调用getBean()方法传入bean ID来获取这个bean,你会发现代码返回error。类似地,容器内部方法preInstantiateSingletons()也会忽略abstract的bean定义。
ApplicationContext会默认提前初始化所有的单例作用域的bean。因此,如果你有一个bean definition只作为模板(父bean definition)使用,但是也确定了一个class,那么你必须确保它的abstract属性为true。否则,Application context会尝试去提前初始化这个本该是抽象的bean |
---|
一般来说,应用开发者无需开发ApplicationContext接口实现类的子类来拓展容器。而实际上,spring IoC容器可以通过插入几个特定集成接口的实现来被拓展。下面几个章节将会描述这些集成接口。
BeanPostProcessor接口定义了回调接口,通过自定义实现这些接口(或者覆盖spring对这些接口的默认实现),你可以自己定义bean的实例化逻辑,依赖解析逻辑等等。如果你想要在spring容器完成了bean的实例化、配置、初始化之后,再执行你自己的自定义逻辑,你可以通过插入一个或者多个BeanPostProcessor实现来实现这一点。
你可以配置多个BeanPostProcessor实例,并且你也可以通过配置实例的order属性来决定实例执行的先后顺序。但是,只有BeanPostProcessor的实现类同时实现了Ordered接口,你才能配置这个属性。如果你编写自己的BeanPostProcessor,你也要考虑实现这个Ordered接口。想知道更多细节,参考BeanPostProcessor和ordered接口的javadoc。也可以参考programmatic registration of BeanPostProcessor instances这一小节。
BeanPostProcessor实例能操作bean实例。这意味着,spring IoC容器先实例化一个bean实例,然后BeanPostProcessor在执行它的工作;;;BeanPostProcessor的作用域为每个容器。只有你使用容器继承的时候,这才有影响。如果你在一个容器中定义一个BeanPostProcessor,它就只会在这个容器里面向生效。换句话说,一个容器中定义的BeanPostProcessor不会再另外一个容器中生效,即使它们是同一个层级。;;;如果想要真正地影响bean定义(影响bean的蓝图,而不是实例化之后的处理过程),你需要使用BeanFactoryPostProcessor接口,参考Customizing Configuration Metadata with a BeanFactoryPostProcessor |
---|
org.springframework.beans.factory.config.BeanPostProcessor接口只包含两个回调方法。当这样一个class被注册为容器的post-processor,对于每一个这个容器创建的bean,在容器调用bean的初始化方法之前(如InitializingBean.afterPropertiesSet()或者bean定义声明的init方法)和bean的所有初始化方法被调用之后,post-processor可以对bean实例做任何事情,当然什么都不做也是可以的。一个post-processor一般是检查bean的回调接口,或者把把一个bean包装成一个代理类。一些spring AOP框架类就是这样的post-processor,目的就是实现代理逻辑。
ApplicationContext会自动探测配置元数据中的实现了BeanPostProcessor接口的bean。ApplicationContext会注册这些bean作为post-processor,这样在bean创建的过程中,这些post-processor就会被调用。在容器中,post-processor的定义方式和普通的bean没有区别。
注意,当使用Java配置类中的@Bean工厂方法来声明一个BeanPostProcessor的时候,这个工厂方法的返回值必须是实现类自身或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口。这样才能提前向容器表明这个bean具有post-processor的特性。否则,ApplicationContext不能自动探测出它的类型。因为为了影响其他bean的初始化过程,BeanPostProcessor必需要早于其他bean创建。因此早期的类型检查是至关重要的。
通过编码的方式注册BeanPostProcessor接口:虽然推荐的BeanPostProcessor的注册方式是通过ApplicationContext的自动探测,但是你也可以通过编码的方式注册它,使用ConfigurableBeanFactory的addBeanPostProcessor方法就可以做到这一点。当你需要根据条件判断来决定是否注册BeanPostProcessor或者需要复制一个层级中的BeanPostProcessor时,这一方式就非常有用。但是,请注意,通过方法注册的BeanPostProcessor没有实现Ordered接口。在这里,注册的顺序就是执行的顺序。还要注意,通过方法注册的BeanPostProcessor永远比自动探测到的BeanPostProcessor要先执行,哪怕它明确指定了顺序 |
---|
BeanPostProcessor实例和AOP 自动代理:实现了BeanPostProcessor接口的类是特殊的,容器会区别对待这些类 。所有的BeanPostProcessor实例和它们直接引用的bean都会在容器启动的时候初始化,作为ApplicationContext的一个特定启动阶段。然后,所有的BeanPostProcessor实例都会有序地被注册,以便应用于容器中所有后续加载的bean。因为AOP 自动代理也是作为一个BeanPostProcessor来实现,所以无论是BeanPostProcessor还是它直接依赖的实例,都没有资格被自动代理。因此,他们没有织入切面。---------------------------段落分割线-------------------------对于任何这样的bean,你都会看到一条消息日志:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).--------------------------段落分割线--------------------------- |
如果你通过自动注入或者@Resource注解(可能回退到自动注入)来为你的BeanPostProcessor注入依赖,spring在通过类型来查找依赖的时候,可能会访问到多个意想不到的bean候选者,进而使得这些bean候选者没有资格被自动代理或者被执行其他的前置处理操作。例如,如果你有一个依赖通过在域或者setter方法上标注@Resource注解来引入,但是域名和setter方法名又没有跟某一个bean的名称完全匹配,并且@Resource也没有使用name属性,那么spring为了根据类型查找bean,就会访问到所有同类型的bean。 |
– |
下面的例子展示了如何在一个ApplicationContext中编写、注册和使用一个BeanPostProcessor实例。
示例:Hello World,BeanPostProcessor 样式
第一个示例阐述了基本的用法。这个示例展示了一个基本的BeanPostProcessor实现,这个实现在容器创建bean的时候,调用每个bean的toString()方法,并将结果字符串打印到系统控制台。
下面列出自定义的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;
}
}
下面的beans元素使用上面的InstantiationTracingBeanPostProcessor类:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
https://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
lang:groovy>
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
beans>
你可以注意到InstantiationTracingBeanPostProcessor仅仅只是被定义了。它甚至都没有一个名字,并且,作为一个bean,你也可以把它作为依赖注入到任何地方。(前面的那个配置块也定义了一个bean,但是是基于Groovy脚本的。spring的动态语言支持详见 Dynamic Language Support章节)
下面的Java应用运行上述的代码和配置:
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
示例:RequiredAnnotationBeanPostProcessor
结合自定义的BeanPostProcessor实现类,使用回调接口或者注解,是拓展spring IoC容器的常规方式。spring的RequiredAnnotationBeanPostProcessor就是这样一个例子——一个随spring一起发行的BeanPostProcessor实现,用于保证被注解标记的bean属性能被注入值。
我们要了解的下一个拓展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语义跟BeanPostProcessor类似,只是主要有一点不同:BeanFactoryPostProcessor作用于配置元数据。具体就是,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据并以一种潜在的方式改变它,在容器实例化除了BeanFactoryPostProcessor以外的bean之前。
同样,你也可以配置多个BeanFactoryPostProcessor,并且通过配置ordered属性类决定它们执行的顺序。当前,前提是实现类要同时实现Ordered接口。你自定义的实现类也是如此。查看BeanFactoryPostProcessor 和 Ordered的javadoc可以获得更多细节。
如果你是想要改变一个bean实例(根据配置元数据创建的对象),你要使用你的事BeanPostProcessor。虽然在技术上可以做到在BeanFactoryPostProcessor中操作bean实例,通过使用BeanFactory.getBean(),但是这么做会导致过早的bean实例化,违反标准的容器生命周期。这可能导致负面影响,比如错过bean的预处理过程。----------------段落分割-----------------同样BeanFactoryPostProcessor也是作用于每个容器。这只在你使用容器层级的时候才有影响。如果你在一个容器中定义一个BeanFactoryPostProcessor,那么它只会对这个容器里的bean definition生效。即使是同一个层级的容器,一个容器里面的BeanFactoryPostProcessor也不会影响另一个容器里面的bean definition。 |
---|
当一个ApplicationContext里面声明了一个bean工厂的post processor,这个processor就会自动运行来对容器里面的配置元数据做出更改。spring预定义了bean工厂post-processor,比如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。你也可以使用一个自定义的BeanFactoryPostProcessor——比如说注册一个属性编辑器。
一个ApplicationContext自动探测任何实现了BeanFactoryPostProcessor接口的bean。在合适的时候,它会把这些bean作为factory-post-processor。你可以像部署普通的bean一样部署这些bean。
跟BeanPostProcessor一样,你可能也不希望将BeanFactoryPostProcessor配置成懒初始化。如果没有bean引用Bean(factory)PostProcessor,那个post-processor压根就不会实例化。因此,试图让它懒初始化会被忽略。并且,即使你把 |
---|
示例:类名替换之PropertySourcesPlaceholderConfigurer
你可以通过使用PropertySourcesPlaceholderConfigurer,来将bean definition的属性值外部化到独立的使用Java标准Properties格式的文件中。这样做可以让开发者能够不用冒险去修改xml definition文件或者容器的配置文件,就能够部署自定义配置的应用程序。
参考下面的基于xml的配置元数据片段,其中定义了含有占位符值的DataSource bean。
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
这个例子展示了通过外部Properties文件配置的属性值。到来运行时,PropertySourcePlaceholderConfigurer会与用于配置元数据,并替换DataSource的一些属性。要替换的值以${property-name}格式的占位符来表示,这符合 Ant、log4j和JSP EL样式。
实际的值来自于另一个标准Java Properties格式的配置文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此${jdbc.username}字符串会被替换成“sa”,其他占位符值也会被配置文件中key值相同的值所替代。PropertySourcesPlaceholderConfigurer会检查bean definition中的大部分的属性、配置值。更进一步,你可以自定义占位符的前缀和后缀。
基于spring 2.5引入的context命名空间,你也可以通过声明一个配置元素来配置一个配置占位符。你可以对location属性配置配置一个或者多个逗号分割的文件路径,如下例所示:
PropertySourcesPlaceholderConfigurer不仅仅是从你指定的Properties文件中查找属性。如果它不能从你指定的配置文件中找到属性,那么它会从spring环境变量和Java系统变量中查找。
有时候你可以通过PropertySourcesPlaceholderConfigurer来替换类名,当你需要在运行时选择特定的实现类时,这是有用的。下面的例子告诉你怎么做: |
---|
<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<value>classpath:com/something/strategy.propertiesvalue>
property>
<property name="properties">
<value>custom.strategy.class=com.something.DefaultStrategyvalue>
property>
bean>
<bean id="serviceStrategy" class="${custom.strategy.class}"/>
如果这个类在运行时没有指向一个有效的类,那么对于non-lazy-init的bean来说,在ApplicationContext的preInstantiateSingletons()阶段,对这个bean的解析会失败。 |
---|
示例:PropertyOverrideConfigurer
PropertyOverrideConfigurer是另外一个bean 工厂post processor,类似于PropertySourcesPlaceholderConfigurer,但是有一点不同,就是它的初始bean定义可以有默认值或者完全没有值。如果一个用来覆盖属性的Properties文件里面没有对应bean属性的入口,那么默认的属性值就会生效。
注意bean定义并没有意识到自己会被覆盖,因此从xml bean定义文件里面不能明显看出override配置器有被使用。在有多个PropertyOverrideConfigurer的情况下,基于覆盖机制,最后一个PropertyOverrideConfigurer覆盖的属性值会生效。
Properties文件配置行格式如下:
beanName.property=value
下面展示一个这种格式的实例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
配合一些容器中的定义,这个示例文件可以对含有driverClassName属性的名为dataSource的bean生效。
复合属性值也是支持的,只要要被覆盖的属性路径上,除了最后一个元素之外的元素都是非null值。下面的例子,名为 tom的bean 的fred属性的bob属性的sammy属性被设置为数值123:
tom.fred.bob.sammy=123
指定的覆盖值总是文字值。不会被转换为bean的引用。当xml 文件中bean定义初始值指定了一个bean引用的时候,这一惯例仍然生效。 |
---|
通过使用Spring 2.5发布的context命名空间,可以使用一个专门的配置片段来配置属性覆盖器:
你可以为那些本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。
FactoryBean是spring IoC容器实例化逻辑的一个插拔点。如果你有复杂的初始化逻辑代码,并且这些代码如果使用xml来表达会很冗余,用Java来表示更简单,那么你就可以选择自定义一个FactoryBean,把复杂的初始化逻辑写在里面。然后把你的FactoryBean插入到容器中。
FactoryBean接口提供了三个方法:
FactoryBean的概念和接口在spring框架中被多次用到。Spring自身涵盖了超过50个FactoryBean接口的实现。
当你需要容器返回一个FactoryBean对象而不是这个工厂产生的bean实例的时候,可以把工厂bean的id加上&符号前缀,再调用ApplicationContext的getBean()方法。所以,如果有一个id为myBean的FactoryBean,调用容器的getBean(myBean)将返回工厂的产品。而调用getBean(&myBean)将返回工厂实例本身。
相比于xml,注解是更好的配置Spring的方式吗? |
---|
对注解配置的介绍带来了一个疑问:是否使用注解来配置是比XML更好呢?要快速回答的话,视情况而定。仔细回答的话,每一种方法都有好与不好的一面,通过,由开发者来判断哪一种策略更适合他们。由于注解被定义的方式所带来的特性,注解声明的时候包含了很多上下文信息,因此可以实现更加简短的配置。而xml的优点在于,无需接触或者修改源代码就可以吧component组装起来。有些人更喜欢在接近源代码的地方组装bean,但是有些人觉得有注解的类不再是pojo并且配置变得更分散和难以控制。 |
无论你选择哪种,spring都能适应,甚至还能支撑混合使用。值得一提的是,通过JavaConfig的方式,spring允许你用一种非侵入源码的方式来使用注解。 |
基于注释的配置提供了一种替代XML配置的方法,它依赖字节码元数据来连接组件,而不是尖括号声明。开发者不使用XML来描述bean依赖,而是通过使用相关类、方法或字段声明上的注解来将配置移动到组件类本身。正如示例 Example: The RequiredAnnotationBeanPostProcessor所说的,使用BeanPostProcessor再搭配上注解,是一种通用的拓展spring IoC容器的方法。例如,spring 2.0引入了使用@Required注解来修饰强制需要的属性。spring 2.5让使用相同的通用方法来驱动依赖注入成为可能。本质上,@Autowired注解提供了在 Autowiring Collaborators中提到的相同的功能,但是提供了更细颗粒度的控制和更广泛的适用性。spring 2.5也加入了对 JSR-250注解的支持,比如@PostConstruct 和 @PreDestroy。Spring 3.0加入了对JSR-330注解(Java的依赖注入)的支持,比如javax.inject包中的@Inject 和@Named。这些注解的细节可以在相关章节找到。
注解注入比xml注入要先执行。所以如果同时使用注解和xml来配置属性,xml配置的属性会把注解配置的属性覆盖 |
---|
通常,你是通过单个的bean定义来注册这些注解。但是通过使用下面的xml配置元素,它们也可以被隐式地注册(注解context命名空间的内容):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
beans>
(隐式注册的预处理器包括AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, 还有先前提到的RequiredAnnotationBeanPostProcessor)
@Required注解应用于bean属性的setter方法,如下:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
这个注解表明受影响的bean属性必须在配置时通过在bean definition里面定义属性值或者通过自动注入来填充值。如果修饰的bean属性没有填入值,那么容器就会抛异常。这就会带来急切和明确的失败,避免后续空指针实例的产生。即使有这个注解,我们仍然推荐你把断言放到bean class本身(比如init 方法)。这样做的话,就可以强制保证需要的属性值是可用的,即使是在容器外面。
在Spring Framework 5.1 @Required这个注解被正式弃用了,为了使用构造函数注入来设置属性值(或者说是为了使用InitializingBean.afterPropertiesSet() 的自定义实现搭配bean的setter方法来实现这一效果。) |
---|
在本小节的例子中,JSR 330的@Inject注解可以替代Spring的@Autowired注解,详见这里。 |
---|
你可以把@Autowired注解应用于构造函数,如下示例:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
从Spring Framework 4.3起,如果目标bean只定义了一个构造函数,@Autowired注解就不再是必要的了(这种情况,不需要配置,没有注解,仍然会自动注入依赖。有点违背默认的注入方式。)。然而,如果有多个构造函数,并且没有默认的构造函数,那么你就必须要使用@Autowired来修饰其中一个构造函数,这样容器才知道使用哪个构造函数来创建。详见 constructor resolution。 |
---|
你也可以用@Autowired注解修饰传统的setter方法,如下:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
你可以用@Autowired注解来修饰任意方法名的有多参数的方法,如下示例:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
你也可以用@Autowired注解修饰属性,甚至把注解和构造函数混合使用:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
确保你的目标组件的类型跟你注入点的参数类型是一致的,否则会在运行时提示no type match found而注入失败。 对于xml定义的bean或者通过包扫描发现的组件class,容器通常会提前知道具体的类型。对于@bean修饰的工厂方法,你最好确保返回类型有足够的说明性。对于实现了多个接口的组件和潜在地指向实现类的组件,考虑在工厂方法返回的时候返回最确切的返回值。(至少明确到注入点知道要注入哪个bean) |
---|
你也可以通过用@Autowired来修饰某个类型的数组来让Spring给你所有的该类型的bean。示例如下:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
对于明确了泛型的集合类也是如此:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
如果你想要你的bean在这样的集合中有序,可以让bean实现org.springframework.core.Ordered 接口或者使用@Order或者标准的@Priority注解。否则,bean的顺序就是它们的bean definition的定义顺序。 |
---|
你也可以在目标class基本或者@Bean方法上使用@Order注解,这一般用于单个的bean(相对于一个class被多个bean指定的情况)。@Order的值可能影响bean在注入点的优先级,但是放心它不会影响单例bean的启动顺序。启动顺序一般由依赖关系和@DependsOn决定。 |
注意标准的javax.annotation.Priority注解不能用于方法级别。它的含义大概相当于有值的@Order注解和每个类型的单个bean的@Primary注解相结合。 |
只要Map类型的属性的key类型是String,那么这个Map类型也可以被注入。Map类型会包含所有value类型的bean,而key的值是这些bean的name。下面是例子:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
如果没有合适类型的候选者,那么注入点的自动注入过程会失败。如果是声明的数组、集合或者Map类型,那么至少要有一个候选者匹配,否则也会失败。
对于@Autowired注解修饰的属性或者方法,默认的动作是将其视为要求所有的依赖都必须存在。要改变这一点,参考如下示例,通过设置注解的非必须属性,告诉框架忽略没有找到匹配的bean的注入点:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
如果依赖条件无法被满足(对于多参数方法,任意一个依赖项找不到),被修饰为非必须(non-required)的方法(域)根本就不会被调用。
构造函数和工厂方法上面的@Autowired注解的Required属性是一个特例。因为Spring 构造函数解析算法会考虑处理多个构造函数的场景。构造函数和工厂方法的参数默认实际上是要求非空的,只不过是基于在单构造函数的场景下的一些特殊的规则。比如对于多元素注入点(数组、集合和Map),如果如果没有匹配的bean,那么注入的对象会是一个空实例。这允许一个通用的实现模式:所有的依赖关系都可以在一个独特的多参数构造函数中声明。例如,声明一个单独的没有@Autowired注解修饰的public 构造函数。
对于bean的类,只有一个构造函数能够被声明为required=true,以告知容器使用这个构造方法来初始化bean实例。所以,如果@Autowired注解的required属性为默认的true,那么应该只有一个构造函数可以被它修饰。如果有多个构造函数被这个注解修饰,它们必须声明required=false,这样才能作为构造函数候选者(类似于xml中的autowire=constructor)。能够匹配到最多bean的构造函数将会被Spring 容器选中。如果以上均不满足,那么默认的构造函数(如果有)会被选中。类似的,如果一个类有多个构造函数,但是都没有被@Autowired注解修饰,那么也是默认的构造函数会被使用(如果有)。如果一个类只定义了一个构造函数,不管有没有被注解修饰,它都会被容器选中。注意注解修饰的构造函数不一定是public的。--------------------分隔符------------------------@Autowired的required属性被推荐使用,用于替代@Required 注解在setter方法上的使用场景。设置required属性为false表示,如果找不到依赖,就忽略这个注入点。在另一方面,@Require更强大一点。如果没有满足的依赖,它会产生相应的异常。 |
---|
还有另一个可选的方式,你可以通过java8的java.util.Optional类来表示这种非必须满足的依赖:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
自Spring Framework5.0起,你也可以使用@Nullable(任意包中的任意类型,如JSR305的javax.annotation.Nullable)注解来实现这一点:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
你也可以使用@Autowired接口来注入一些众所周知的可解析的依赖接口类型,如BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, and MessageSource等等。这些接口和它们的拓展接口都可以直接解析出来,不需要特别的引入步骤。下面的例子注入一个ApplicationContext的bean:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired, @Inject, @Value,和@Resource注解都由Spring的BeanPostProcessor实现负责解析。这意味着你不可以在你自己的BeanPostProcessor或者BeanFactoryPostProcessor使用这些注解。而必须通过XML或者Spring的@Bean注解来显式地注入依赖。 |
---|
因为通过类型自动注入依赖往往会产生多个候选者,因此对选择依赖的过程多一些控制是必要的。实现这个的方法之一就是使用Spring的@Primary注解。对于单值的依赖,@Primary表示它修饰的bean在有多个bean作为候选者时应该被优先选中。如果候选者中只有一个@Primary修饰的bean,那么这个bean就是将被注入的bean。
参考下例,firstMovieCatalog被定义为主要的MovieCatalog:
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
基于上面的配置,下面的MovieRecommender将会注入firstMovieCatalog:
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
对应的bean定义是这样的:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
bean>
<bean class="example.SimpleMovieCatalog">
bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
beans>
对于多个候选者选一个的场景,@Primary是有效的解决方案。但如果你想对选择过程控制地再精细一点,你可以使用Spring的@Qualifier注解。你可以使用具体的参数将限定符的值联系起来。这样就可以缩小类型匹配的集合,这样每个参数都可以选择特定的bean来注入。在最简单的例子里面,限定符的值可以是朴素的描述性的值,如下:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
你也可以使用@Qualifier注解来修饰构造函数或者普通方法的单个参数:
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;
}
// ...
}
下面的例子展示对应的Bean定义:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/>
bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/>
bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
beans>
限定符值为main的bean会被注入到拥有相同限定符值的构造函数参数里面去;限定符值为action的bean会被注入到拥有相同限定符值的构造函数参数里面去;
作为一种备用的匹配方式,bean的名字会被当做默认的限定符值。因此,你也可以定义bean的id为main,这样不用单独的
,但是支持语义限定符的@Autowired在根据类型注入的过程中是基本的组成部分。这意味着限定符的值,即使是指定了默认的bean名字,也总是一种基于类型匹配结果的缩小范围的东西。它们并不表示对一个唯一的bean id 的引用。合理的限定符的值是main或者EMEA或者president,表示特定组件的除了id之外的某一特性。而对于一些匿名的bean,id可能是自动生成的,如上例所示。
限定符对指定了泛型的集合类也是生效的,就像前面提到的——举例来说,Set
在类型匹配的候选者中,让限定符值针对目标bean名字进行选择,不需要在注入点加上@Qualifier注解。如果没有其他的指示符(比如限定符或者@Primary注解),对于有多个候选依赖项的情景,spring会根据注入点的名字(也就是属性名或者参数名)匹配bean名字并选择同名的候选者(如果有的话)。 |
---|
这意味着,如果你想要基于注解通过bean名字来注入依赖,不要只使用@Autowired,即使它也能做到根据名字从类型匹配的bean候选者中选择依赖。替代做法是,使用JSR-250定义的@Resource注解,顾名思义,这个注解的定义的用途是根据组件的唯一名称来匹配组件,注入点声明的类型与匹配过程无关。@Autowired注解则不同,它是基于类型匹配的结果,再根据限定符从类型匹配的结果中过滤出需要的bean。
对于那些本身就是集合、Map、数组类型的bean,使用@Resource注解用bean的唯一名字来注入是很好的选择。即便如此,从4.3起,你还是可以通过@Autowired注解来匹配这些集合类型,只要这些集合类型的元素类型是确定的。这种情况下,你可以继续使用限定符对同类型的依赖项做筛选,如上概述。
自框架4.3版本起,@Autowired注解也会注入自身依赖(也就是引用使用注解的当前类本身)。但是要注意自我注入是一直灾备切换的方案。常规的对其他元素的依赖仍然是优先的。这种场景下,自我依赖不会参与到依赖候选者中去,并且也因此,自我依赖不可能是primary的。另一方面,自我依赖的优先级总是最低的。在实践中,自我依赖一般是没有别的办法了才会采取的最后的一种手段(比如通过事务代理来调用自己的其他方法)。这种场景下,考虑把受影响的方法拆解到代理bean中去。可以选择@Resource注解来获取一个档当前bean的代理。
在java配置类中注入@Bean方法返回的bean本质上也是一种自我引用。要么是在方法参数中懒解析这些引用(与在配置类中注入依赖的域相反),要么将@Bean方法声明为static,将其从配置类实例和生命周期中解耦。否则这个bean自身应用只会在fallback阶段被引用。 |
---|
@Autowired注解可以应用于域、构造方法、多参数方法,允许通过限定符在参数层面缩小匹配的依赖范围。与之相反,@Resource注解只可以应用于域和单参数的setter方法。因此,如果你的注入目标是构造方法或者多参数方法,你应该坚持使用限定符。
你可以自定义限定符注解。要这么做的话,定义一个基于@Qualifier注解的注解,如下例所示:
@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;
}
// ...
}
下一步,你需要为候选者bean定义提供信息。你需要在bean定义中加入
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
beans>
在类路径扫描和组件管理模块,你可以看到在xml中基于注解的方式提供限定符元数据的方式。具体参考Providing Qualifier Metadata with Annotations。
在某些场合,使用没有值的注解可能就够用了。在注解只是为了达到通用的目的,并且设计要修饰多种类型的依赖时,这就够用。比如说,你可以提供一个断网时可以搜索的线下目录。第一步,定义下面的注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
用注解修饰域或者属性:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
然后给一个bean设置限定符类型type值,如下:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
bean>
当然你也可以在自定义注解中定义有名字的属性,作为简单的value值的补充或者替代。如果在注入点的注解有多个属性值,那么只有满足所有属性值的bean才会被注入。举例如下:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format 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;
// ...
}
最终,你要在bean定义中匹配相应的限定符值。下面的例子阐述了你可以使用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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Action"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Comedy"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="DVD"/>
<meta key="genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="BLURAY"/>
<meta key="genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
</beans>
除了使用 @Qualifier注解,你也可以使用java泛型作为bean资格限定的暗示。比如说,假设你有如下配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
设想上述两个bean实现了通用的接口,(就是Store and Store),你可以用泛型作为限定条件,注入对应Store类型的实例:
@Autowired
private Store<String> s1; // qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // qualifier, injects the integerStore bean
泛型限定条件也可以应用于list、map和数组类型。下例注入了一个泛型List:
// Inject all Store beans as long as they have an generic
// Store beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
CustomAutowireConfiger是一个BeanPostProcessor,是用来给你注册自定义限定符注解的。使用CustomAutowireConfigurer注册的话,即使不继承@Qualifier注解也没有关系。下面是例子:
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
AutowireCandidateResolver通过以下几点确定依赖项的候选者:
当有多个依赖匹配当前的注入点,主要依赖的确认过程为:如果候选者中有一个bean是明确地有primary属性为true,那么这么bean就会被选中。
Spring也支持使用在域或者属性的setter方法使用JSP-250规定的@Resource注解。这是Java EE的一种通用的模式:比如在JSF管理的bean和 JAX-WS endpoint中。Spring管理的对象也可以很好地支持这种模式。
@Resource有一个name属性。默认的,Spring把这一属性理解成想要注入的bean的名字。换句话说,它的语义是根据名字注入bean,如下示例:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果没有指定name属性,那么name属性值将取注解所修饰的属性名。如果修饰的是域,那么取域变量名。如果修饰的是setter方法,则取setter方法修饰的属性名。下面的例子setter方法将会注入一个名为movieFinder的bean:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
注解提供的name属性将会被ApplicationContext中的CommonAnnotationBeanPostProcessor使用。如果你明确地配置了Spring的SimpleJndiBeanFactory,这个name会被JNDI解析。但是,我们建议你依赖默认的机制并使用Spring的JNDI查找能力来保留间接层。 |
---|
@Resource注解的独特用法,与@Autowired类型,在没有明确指定bean名字时,会根据类型匹配一个primary的bean。并且同样也可以根据这个规则注入常用的bean,如:the BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, and MessageSource 等接口类型的bean。
因此,下面的例子会先寻找名为customerPreferenceDao的bean,如果没有找到,就会fallback,去根据CustomerPreferenceDao类型寻找primary的bean:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@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();
}
}
在使用java配置文件的@Bean方法来声明PropertySourcesPlaceholderConfigurer的时候,方法必须是static的。 |
---|
使用上面的配置可以保证在遇到无法解析的${}占位符时,spring初始化阶段就会抛出错误。你可也可以使用setPlaceholderPrefix, setPlaceholderSuffix, or setValueSeparator这些方法来自定义占位符的格式。
Spring Boot会默认配置PropertySourcesPlaceholderConfigurer的bean,并且默认会从application.properties和application.yml文件中获取值。 |
---|
Spring内置(Built-in)的值转换器,可以自动完成简单的类型转换(比如int和Integer)。使用逗号分隔的多个值也会自动的转换成数组,无需额外的操作。
也可以像下面这个提供一个默认值:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
在这个场景背后,有一个Spring的BeanPostProcessor使用ConversionService来将@Value注解的值转换成目标类型的值。如果你想自定义ConversionService以满足某些特殊的类型转换场景,考虑下面的例子:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
当@Value注解包含SpEL expression 表达式,值会被动态计算出来,如下:
@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<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
CommonAnnotationBeanPostProcessor不仅是处理@Resource注解,还处理其他的JSR-250定义的生命周期注解:javax.annotation.PostConstruct和javax.annotation.PreDestroy。
自Spring 2.5引入之后,Spring为这些注解提供了回调函数的机制,如前面初始化之回调函数和对象销毁之回到函数两个小节介绍的那样。前提是在ApplicationContext中注册了CommonAnnotationBeanPostProcessor,那么带有上述两个注解的方法将会跟Spring的bean生命周期回调接口的调用时机相同。
在下面的例子里面,缓存在初始化的时候被填充,在实例类销毁的时候被清除:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
在目前为止的所有示例中,虽然注解也能做到依赖注入,但是最基本的bean definition还是要在xml文件中定义,注解只能驱动依赖注入。 本节将描述如何含蓄地通过扫描类路径来检测候选者组件。候选者组件指的是满足过滤条件的会自动注册bean definition的类。这样就不需要使用xml来注册bean definition。与之替代的,是可以使用注解(比如@Component)、AspectJ 类型表达式、或者你自定义的过滤条件来决定哪些类自动注册bean定义到容器。
从Spring3.0开始,越来越多Spring JavaConfig项目的特性融入到了Spring Framework核心项目中。这让你可以使用java来配置bean,而不是传统的xml文件。@Configuration, @Bean, @Import, and @DependsOn 这些注解正是JavaConfig特性的一部分。
Spring提供了一些构造型注解,如:@Component,以及@Component修饰的@Service,@Controller,@Repository等等。
其中@Component是所有Spring组件的通用构造型。@Repository、@Service和@Controller是@Component针对特定用途具化的产物。具体用途包括切面切入点的识别、具有框架某一层次的语义等等。
只用于修饰其他注解的注解是元注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
// ...
}
通过aliasFor继承了元注解的部分属性的注解,是组合注解:
@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;
}
上例的组合注解@SessionScope写死了@Scope属性,但是允许自定义proxyMode属性。
在@Configuration修饰的类上加上@ComponentScan注解,然后确定basePackages属性为你想要扫描的顶级包路径,那么上述构造型注解修饰的类都会被自动探测到。
如下,先配置组件自动扫描:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
然后用构造型注解,定义下述组件类:
package org.example;
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
package org.example;
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
这样容器就会自动注册这两个组件相应的BeanDefinition实例。
如果使用xml格式配置,如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
beans>
note:xml中配置组件扫描会自动启用
此外,启用组件扫描,AutowiredAnnotationBeanPostProcessor 和CommonAnnotationBeanPostProcessor也会被自动探测并启用。即使没有在xml中定义这两个组件的bean定义。
**info:**可以配置annotation-config来不启用上述两个组件。
默认的,组件扫描会认为配置的包路径下所有的被@Component注解或其组合注解修饰的类都是要注册的组件。但是,除此之外还可以对扫描结果范围做进一步的补充和排除。使用@Component注解的includeFilters和excludeFilters属性就可以做到这一点。
这两个属性都需要制定一个filter注解元数据,而filter主要包含type和pattern两个属性,含义如下:
filter type | example pattern | description |
---|---|---|
annotation(default) | org.example.SomeAnnotation | 目标组件的类型被此注解或者其组合注解修饰 |
assignable | org.example.SomeClass | 目标组件实现or继承这个接口or类型 |
aspectj | org.example…*Service+ | 目标组件要满足这个aspectj类型表达式 |
regex | org.example.Default.* | 目标组件的完整类名满足正则表达式 |
custom | org.example.MyTypeFilter | 指定一个实现了org.springframework.core.type.TypeFilter的实例来过滤扫描到的组件 |
下面的例子告诉你,如何通过过滤器忽略所有被@Repository修饰的组件,同时允许扫描包含“stub”类名的组件。
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
下面是等效的xml配置:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
context:component-scan>
beans>
info:设置属性useDefaultFilters=false能够禁用默认的过滤器。从而让@Component注解修饰的类型也不会被扫描进来。
Spring 组件也可以给容器贡献bean定义元数据。你可以使用在@Configuration里面使用的@Bean注解,如下:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
上面例子的@Qualifier注解的作用是补充@Bean注解产生的Bean定义的信息,类似的方法级注解还有@Scope、@Lazy还有其他一些自定义限定符注解。
组件支持域自动注入和方法自动注入,除此之外额外支持了@Bean方法的一些自动注入手段:
@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);
}
}
上例的@Value("#{privateInstance.age}")注解用于注入另外一个bean的age属性。Spring支持#{privateInstance.age}这样的EL表达式。
可以为@Bean方法定义InjectionPoint类型(或者其子类DependencyDescriptor类型)参数,用于获取创建类的注入点信息:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
@Component类里面的@Bean方法和@Configuration类里面的@Bean方法的区别在于,@Configuration类里面的@Bean方法调用其他依赖的时候,是会走CGLIB代理的。(译者表示:具体不懂,也没有例子,下次回头再看)
info:如果你的@Bean是生成BeanFactoryPostProcessor或者BeanPostProcessor类型的bean,那么这个方法最好是静态方法,这样就可以在初始化bean之前调用这两个方法,不会触发其他bean的初始化;如果你的@Bean是修饰@Configuration类的非静态方法,那么这个方法的可见性必须不能为private或者final,为了可以被覆盖。
自动扫描的到的Bean的名字由BeanNameGenerator策略生成。如果构造型注解有value,那么value就是对应bean的名字。如果没有,那生成器就以类名首字母小写为bean名(类的小写非限定类名)。
下面的bean名字为myMovieListener:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
下面的bean名字为MovieFinderImpl:
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
当组件的类名相同时,生成的bean名字可能会有冲突。那么你就要自定义bean名字生成策略:你只需要实现BeanNameGenerator接口(注意提供一个无参构造函数),并在scanner中指定该类为名字生成策略:
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
Spring Framework 5.2.3中引入的FullyQualifiedAnnotationBeanNameGenerator可以用于此目的。
总体来说,定义会被明确引用的component的时候,最好确定value值;如果全部交给容器自动注入,那么自动生成名字的策略也是足够的。
与Spring管理的所有组件一样,自动探测的组件的默认作用域是singleton。你可以使用@Scope注解自定义作用域:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
可以通过实现 ScopeMetadataResolver 来自定义组件作用域生成策略。
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
beans>
可以通过scoped-proxy属性来制定作用域bean代理模式,可选值有no, interfaces(jdk动态代理)和targetClass(CGLIB代理):
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
beans>
使用类级别的@Qualifier注解为组件bean确定限定符:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}