这部分参考文档涵盖了Spring Framework所有的技术。
在这之中最重要的部分是控制反转 Inversion of Control(IoC)容器。在IoC容器之后是面向切面编程Aspect-Oriented Programming (AOP) 技术。Spring 框架拥有它自己的AOP框架,概念上是容易理解的,在企业级Java编程中成功的解决了80%AOP的需求。
还提供Spring和AspectJ(目前最富有的-在功能方面-最成熟的AOP实现在Java企业级空间)整合覆盖。
最后,通过对测速驱动开发test-driven-development(TDD)的软件开发方式当然是由Spring团队主张,所以涵盖Spring对集成测试支持的是覆盖(和单元测试的最佳实践)。Spring团队已经发现正确的使用Ioc使得单元测试和集成测试更加的简单(在哪些类的setter方法和适当的构造函数的存在使他们在测试中更容易绑定在一起,不需要设置服务定位器注册诸如此类).......本章专讲测试希望是你相信是这样的。
本章涵盖Spring框架实现控制反转(IoC)原理。IoC也叫做依赖注入(DI)。这是一个过程,对象之间的依赖,即他们和其他对象工作,只有通过构造函数参数,工厂方法参数,或者建立对象实例后从工厂方法创建的或者返回的特性。当创建这些Bean时,容器会注入相关的依赖。这个过程根本上可逆,故名控制反转(IoC),对于这个Bean本身控制实例化或者位置依赖关系通过直接建造类,或者Service Locator模式等机制。
org.springframework.beans
和 org.springframework.context包是IoC容器的基础,
BeanFactory
接口提供了一个能够管理任何类型的对象的高级配置机制。ApplicationContext
是BeanFactory的一个子接口。它添加了更容易与Spring AOP功能集成;消息资源处理(使用国际化),事件发布;和应用程序层特殊的上下文如用于web应用程序的WebApplicationContext
。
总之,BeanFactory
提供配置框架和基本的功能,ApplicationContext
添加更多的企业特定功能。ApplicationContext
是BeanFactory的一个完整的超集,是专门用于在本章描述Spring的IoC容器。有关使用BeanFactory
代替ApplicationContext的更多信息,参见4.17节“The BeanFactory”.
在Spring,来自于应用程序支柱的对象,是由Spring IoC容器管理,被称为bean。一个bean就是一个对象被实例化,组装,否则有Spring IoC容器管理。否则,bean就是很多对象中的一个在你的应用程序中。Beans,以及它们之间的关系,反映在使用的容器中的配置元数据。
接口org.springframework.context.ApplicationContext代表Spring IoC容器和负责实例化、配置、装配上述Beans。容器获得指定的哪些对象实例化、配置、通过读取配置元数据组装。配置元数据通过XML、Java注解湖或者Java代码表现。它允许你表达那些组成应用程序的对象和这些对象之间的相互依赖关系。
几个ApplicationContext
的实现提供了即装即用的Spring。在独立的应用程序中通常是创建一个fClassPathXmlApplicationContext
或者 FileSystemXmlApplicationContext
实例。而XML一直是传统的的形式定义配置元数据,你可以指示容器使用Java注解或者元数据格式的代码提供少量的XML配置以声明方式支持这些额外的元数据格式。
在大多数的应用程序场景,用户代码不许要实例化Spring IoC容器的一个或者多个实例。例如,在web应用程序场景,一个简单的八(左右)行引用XML web描述在 web.xml
文件中,应用程序通常会足够(见 Section 4.16.4, “Convenient ApplicationContext instantiation for web applications”)。如果你使用SpringSource工具套件 SpringSource Tool Suite 的Eclipse驱动开发环境,这个样板配置可以点击几下鼠标或者键盘简单的创建。
下面的图是一个关于Spring如何工作的高层次的视图。应用程序类联合配置元数据,在ApplicationContext被创建和初始化之后。你有一个全面的配置和可执行的系统或者应用程序。
图4.1. The Spring IoC container
如前面的图所示,Spring IoC容器消耗配置元数据的一种形式;配置元数据代表你作为一个应用程序开发者高数Spring容器如何实例化、配置和组装应用程序中的对象。
配置元数据传统提供的形式是一个简单的直观的XML格式,本章中大多数使用XML格式传达Spring IoC容器的关键概念和特性。
注意 | |
---|---|
基于XML的元数据不是唯一的允许配置元数据的形式。Spring IoC容器自身完全解耦于配置元数据的形式,许多开发商在他们的Spring应用程序中选择Java-based configuration 。 |
关于在Spring容器中使用其他形式的元数据的消息,参见:
@Configuration
, @Bean
, @Import
和 @DependsOn
annotations. Spring配置包含至少一个通常不止一个容器必须管理的Bean定义。基于XML配置元数据展示Bean配置
元素在顶级的
里面。Java配置通常使用@Bean主角方法在一个 @Configuration
类中。
这些Bean定义对应于构成应用程序的实际对象。通常你定义的服务层对象、数据访问对象(DAOs)、表示对象如Struts Action实例、基础建设对象如Hibernate SessionFactories和JMS Queues等等。通常不在容器中配置细粒度的领域对象,因为
创建和加载领域对象是DAOs
和业务逻辑的责任。然而,你可以用Spring集成中的AspectJ配置IoC容器控制之外创建的对象。请参阅 Using AspectJ to dependency-inject domain objects with Spring.
下面的示例展示基于XML的配置元数据的基本结构:
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
id="..." class="...">
id="..." class="...">
id属性是string,你可以用它来识别个人的Bean定义。calss属性定义Bean的类型和
使用完全限定类名。id属性的值是指协作对象。将XML用于引用协作对象不是在这个例子中所示,见 Dependencies 获取更多的消息。
实例化一个Spring IoC容器是直接的。提供给ApplicationContext
构造器的位置路径实际上是支援字符串,允许容器加载配置元数据从各种各样的外部资源,如本地文件系统、从JavaCLASSPATH等等。
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
注意 | |
---|---|
在你解了Spring IoC容器,你也许想知道更多关于Spring |
下面的例子显示了服务层对象(services.xml)的配置文件:
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
name="accountDao" ref="accountDao"/>
name="itemDao" ref="itemDao"/>
下面的例子显示了数据访问对象daos.xml
文件:
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JapItemDao">
前面的例子中,服务层由PetStoreServiceImpl类和两个数据访问对象JpaAccountDao
and JpaItemDao(基于JPA对象/关系映射标准)构成。属性
name
元素指的是JavaBean属性的名称,而 ref元素引用另一个Bean定义的名称。id和ref元素之间的这种联系表达了合作对象之间的依赖关系。对于配置对象依赖性的详细信息,见
Dependencies.
它可以是非常有用的Bean定义跨多个XML文件。通常每个单独的XML配置文件代表你的架构中的一个逻辑层或者模块。
你可以使用应用程序上下文的构造器从所有的这些XML片段加载Bean定义。这个构造器有多个资源位置,因为在上一节所示。或者,使用一个或者多个
元素从另外一个或多个文件加载Bean定义。如:
resource="services.xml"/> resource="resources/messageSource.xml"/> resource="/resources/themeSource.xml"/> id="bean1" class="..."/> id="bean2" class="..."/>
在上面的例子中,外部Bean定义从services.xml
, messageSource.xml和
themeSource.xml三个文件加载。所有的加载路径相对于定义文件导入,因此 services.xml必须在同一个目录或者类路径位置文件导入,
messageSource.xml
和themeSource.xml
必须低于导入文件位置的resources位置。如你所见,前导斜杠被忽略,但考虑到这些路径是相对的,不是用斜杠是更好的方式。被导入文件的内容,包括顶级的
元素,必须是根据Spring架构的有效XML Bean定义。
注意 | |
---|---|
这是可能的,但是不推荐,使用相对路径“../”在父目录中的参考文件。这样做可以创建一个依赖于当前应用程序以外的文件的依赖。特别是,“classpath:”URLs(如 "classpath:../services.xml")这样的引用是不推荐的,在运行解析过程中选择“最近”的类路径然后查看它的父目录。类路径配置改变可能导致不同的选择,不正确的目录。 你可以总是使用完全限定的资源位置,而不是相对路径,比如:‘file:C:/config/services.xml’或者'classpath:/config/services.xml'。然而,了解到了这些你可以把应用程序的配置耦合到特定的绝对位置。它通常保持着一种间接的绝对位置,比如通过“${...}”占位符,解决了JVM系统运行时的性能问题。 |
ApplicationContext
是用于维护不同Beans注册表和他们的依赖关系的先进的工厂接口。使用方法T getBean(String name, Class
ApplicationContext
可以读取Bean定义和访问他们,如下:
// create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}); // retrieve configured instance PetStoreService service = context.getBean("petStore", PetStoreService.class); // use configured instance ListuserList = service.getUsernameList();
你使用 getBean()找到Bean的实例。
ApplicationContext
接口有一些其他的方法获取Bean,但是你的应用程序代码最好是不要使用它们。事实上,你的应用程序代码不需要调用getBean()
方法,因此它们是不依赖Spring API的。比如,Spring集成web 框架时为各种的web框架类提供了依赖注入,如控制器和 JSF-managed Bean。
Spring IoC容器管理一个或者多个Beans。这些通过你提供给容器的配置元数据Beans被创建,比如,在XML形式的
定义。
容器本身,这些Bean定义代表BeanDefinition对象,其中包含(以及其他信息)一下元数据:
元数据转换为属性设置,组成每一个bean定义。
表 4.1. Bean定义
属性 | 解释… |
---|---|
class |
Section 4.3.2, “Instantiating beans” |
name |
Section 4.3.1, “Naming beans” |
scope |
Section 4.5, “Bean scopes” |
constructor arguments |
Section 4.4.1, “Dependency injection” |
properties |
Section 4.4.1, “Dependency injection” |
autowiring mode |
Section 4.4.5, “Autowiring collaborators” |
lazy-initialization mode |
Section 4.4.4, “Lazy-initialized beans” |
initialization method |
the section called “Initialization callbacks” |
destruction method |
the section called “Destruction callbacks” |
ApplicationContext
实现还允许注册现有的对象,用户在容器外创建的。这是通过方法 getBeanFactory()返回BeanFactory的实现 DefaultListableBeanFactory 访问 ApplicationContext BeanFactory完成。 DefaultListableBeanFactory
支持通过方法 registerSingleton(..)和 registerBeanDefinition(..)注册。然而,典型的应用程序工作 仅仅需要Bean定义通过元数据Bean定义。
每一个Bean都有一个或者多个标识符。这些标识符必须是唯一的在托管这些Bean的容器中。一个Bean常常只有一个标识符,但是如果他需要多个,可以考虑额外的别名。
在基于XML的配置元数据中,你可以用id、name属性来指定Bean的标识符。id属性允许你精确的指定一个id。通常这些名字是数字字符(myBean, fooService等),但是也可能包含一些特殊字符。如果你想介绍其他的别名给这些Bean,你可以指定他们在name属性中,用逗号(,)分号(;)或者空格分开,作为历史说明,在Spring 3.1以前的版本,id属性被定义为xsd:ID
type,限制可能的字符。在3.1被定义为 xsd:string
type。注意,Bean id唯一性仍然由容器执行,只是不再通过XML解析器。
你不需要提供一个name或者id给Bean。如果没有显示的name或者id被提供,容器会为Bean生成一个唯一的名称。然而,若果你希望通过Bean的名称引用它,通过使用ref
元素或者Service Locator 方式查找,你必须提供一个名称。没有提供名称的动机都跟使用内部豆和自动装配合作。
Bean命名约定
用于实例字段命名的约定是使用标准的Java约定,Bean命名小写字母开头。这种名称的例子(没有逗号)'accountManager'
, 'accountService'
,'userDao'
,'loginController'等。
命名Bean始终让你的配置容易阅读和理解,如果你使用Spring AOP,它会很有帮助当申请建议一组Bean通过name关联。
一个Bean定义本身,你可以给Bean提供多个name属性,通过使用组合id属性指定一个name和其他的name通过name属性指定。这些names是同一个Bean的等效别名,并可以用于某些情况下,如允许在一个应用程序各组成部分参见同一个公共的依赖通过使用Bean特定于该组成部分自身的name属性。
然而,Bean实际上定义的指定所有别名并不总是足够的。它有时需要引入一个别名定义在其他地方的Bean。这是通常的情况下,在大系统中的配置分散在每个子系统,每个子系统都有自己的一组对象的定义。在基于XML的配置数据,你可以使用
完成。
name="fromName" alias="toName"/>
在这样的情况下,一个Bean在同样的容器中它叫做formName,在使用了别名定义之后,又被叫做toName。
比如,配置元数据在子系统A可以指定数据源名字为subsystemA-dataSource。配置元数据在子系统B中也可以指定数据源名字为subsystemB-dataSource。这两个子系统组成了主应用程序,这个主程序可以指定数据源的名字为myApp-dataSource。为了把三个名字引用到同一个对象,你需要添加MyApp配置元数据别名定义如下:
name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
name="subsystemA-dataSource" alias="myApp-dataSource" />
现在每组件和主应用程序都定义了数据源通过独一无二的name,保证不会与任何其他定义(有效地创建一个命名空间),然而他们是指的同一个Bean。
Java 配置
如果你想使用Java配置, @Bean注解可以提供别名,参见
Section 4.12.3, “Using the @Bean annotation” 细节。
Bean定义基本上是创建一个或者多个对象的配方。当Bean被询问,容器就会去查看配方,通过Bean定义创建(获得)一个实际对象并使用配置元数据封装。
元素 class属性 指定对象的类型(或者类)那就是被实例化的类型,这个 class
属性,它的内部是一个 在 BeanDefinition
实例的 Class
property ,一般是强制的。(对于例外情况,参见 the section called “Instantiation using an instance factory method” 和 Section 4.7, “Bean definition inheritance” )您可以使用 Class
属性通过 以下两种方法之一:
com.example
package,这个Foo类有一个静态的内部类叫Bar,‘class’属性的值在这个Bean定义应该是...
Spring IoC容器几乎可以管理你想让它管理的任何类,它不限制于管理真的JavaBean。大多数Spring用户更喜欢实际的JavaBean,只有一个默认(无参数)构造器及setter、getter方法在容器中。你也可以在你的容器中有特殊的非Bean样式的类。例如,如果你需要使用绝对不符合JavaBean规范的传统连接池,Spring也能管理好它。
基于XML的配置元数据你可以指定你的Bean类如下:
id="exampleBean" class="examples.ExampleBean"/>
name="anotherExample" class="examples.ExampleBeanTwo"/>
关于机构提供的构造器参数(如果需要),设置对象实例属性在对象被构造后的详细信息,参见 Injecting Dependencies.
当定义一个通过静态工厂方法创建的Bean,你使用 class
属性指定这个包含的静态工厂方法的类和factory-method属性指定工厂方法本身。你应该能调用这个方法(用后述的可选参数)并返回一个活跃的对象,随后被视为这是通过构造器被创建。使用一个这样的Bean定义是调用静态工厂在遗留代码中。
下面的Bean定义指定Bean将通过调用工厂方法来创建。该定义不指定返回对象的类型(类),只是包含工厂方法的类。这个例子中的 createInstance()
方法必须是静态的方法。
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; } }
有关机构提供(可选)参数工厂方法和设置对象实例属性的详情,当对象从工厂返回,参见Dependencies and configuration in detail.
类似于通过静态工厂方法实例化,通过一个实例工厂方法从容器中调用一个存在的Bean的非静态方法创建一个新的Bean来实例化。为了使用这个机制,让class
属性为空,而在 factory-bean
属性指定在当前(或父/祖先)容器中Bean的name ,该Bean包含将要被调用来创建对象的实例方法,用factory-method
属性设置工厂方法自身的名字。
id="serviceLocator" class="examples.DefaultServiceLocator"> id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; } }
一个工厂类也可以持有超过一个工厂方法如下所示:
id="serviceLocator" class="examples.DefaultServiceLocator"> id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/> id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private static AccountService accountService = new AccountServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; } public AccountService createAccountServiceInstance() { return accountService; } }
这种做法表明,工厂Bean可以通过依赖注入(DI)进行管理和配置,请参见 Dependencies and configuration in detail.
注意 | |
---|---|
在Spring文档中,工厂Bean值得是配置在Spring容器中,将通过一个实例或者静态工厂方法创建一个Bean,相比之下, |
一个典型的企业级应用程序不仅仅只有一个对象(或者Bean)。甚至最简单的应用程序都有几个对象在一起工作,最终用户看到一个连贯的应用程序。下一节解释你如何去定义一定数量的Bean定义,从独立到完全实现对象协作来实现一个目标的应用程序。
依赖注入Dependency injection (DI) 是一个过程,对象之间的依赖关系。它们协作完成任务的其他对象,只能通过构造器参数、工厂方法参数、或者当它被构造或者从工厂方法返回时在对象实例上设置属性。容器创建Bean的时候会注入这些依赖。这个过程是从根本上可逆的,因此也叫做控制反转 Inversion of Control (IoC) ,Bean自身控制其实例化或者依赖自身的位置通过使用直接建设类或者服务定位器模式the Service Locator pattern.
当对象提供他们的依赖关系,代码使用DI原理,解耦是更有效的。对象不看它们的依赖,不知道依赖的位置和类。这样,你的类就变得容易测试,特备是当依赖是在接口或者抽象基础类时,它允许短的或者模拟的实现可以使用单元测试。
DI存在于两个主要的变种 Constructor-based dependency injection 和 Setter-based dependency injection.
基于构造器的依赖注入是通过容器调用一个构造器和一些参数,每个参数都代表一个依赖。这与调用特殊参数的静态工厂方法构造Bean是等价的,讨论处理构造器的参数和静态方法的参数也是一样的。下面的示例是一个类只能通过构造器注入完成依赖注入过程。注意,这个类没有什么特别的地方,它是一个不依赖容器特定接口、基础类或者注解的POJO。
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定义的构造器参数的顺序就是当Bean被实例化时被提供给的构造器的顺序。参考下面的类:
package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } }
没有潜在歧义现象存在,假设 Bar
和 Baz类在遗传上没有关系。因此,按照以下方式配置就好了,你不需要明确的指定构造器参数的索引或者类型通过
元素。
id="foo" class="x.y.Foo"> ref="bar"/> ref="baz"/> id="bar" class="x.y.Bar"/> id="baz" class="x.y.Baz"/>
当另一Bean的引用是已知的,并且匹配可以发生(就好像前面的例子)。当一个简单的类型被使用,如
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
属性显示的指定构造器参数类型。如下:
id="exampleBean" class="examples.ExampleBean">
type="int" value="7500000"/>
type="java.lang.String" value="42"/>
使用 index属性显示的指定构造器参数的索引,如下:
id="exampleBean" class="examples.ExampleBean">
index="0" value="7500000"/>
index="1" value="42"/>
除了解决简单的多值模糊,指定一个索引也解决了歧义,当构造器有两个同样类型的参数时,指数是基于0。
你也可以使用构造器参数名称消除歧义:
id="exampleBean" class="examples.ExampleBean">
name="years" value="7500000"/>
name="ultimateanswer" value="42"/>
请记住,离开box工作,你的代码必须与调试标志编译,使Spring可以从构造器查找参数名称。如果你不与调试标志(不想)编译你的代码你可以使用 @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的依赖注入通过容器在调用了无参数构造器或者无参数静态工厂方法实例化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... }
Constructor-based or setter-based DI?
ApplicationContext
支持基于构造器和Setter的依赖注入管理Beans。也支持在构造器依赖注入之后做基于Setter的依赖注入。你配置依赖在BeanDefinition的形式,你使用PropertyEditor
实例转化属性从一种格式到另一种格式。然而,大多数的Spring用户不使用这些类直接工作(编程),而是使用XML定义文件,然后将其内部转换为这些类的实例,并用于加载整个Spring IoC容器实例。
基于构造器或者基于Setter 依赖注入?
既然你可以混用,构造器和Setter的依赖注入,经验告诉我们使用构造器参数用于强制性的依赖,Setter用于可选的依赖。注意在Setter中使用 @Required 注解可以是用作Setter所需的依赖项。
Spring团队提倡使用Setter注入,由于大量的构造器参数可能使程序变得笨拙,尤其是当属性是可选的时候。Setter方法也使该类的对象适合进行重新分配或者重新注入。通过 JMX MBeans 管理就是一个引人注入的用例。
一些纯粹主义者青睐于基于构造器的注入。提供所有对象依赖意味着,在一个完全初始化的状态下对象,总是返回给客户端(调用端)的代码。缺点是使得对象变得不那么适合重新配置和重新注入。
使用依赖注入最适合一个特定的类。有时,当处理没有源代码的第三方类,这个选择是适合你的。旧式的类不能暴露任何Setter方法,所以构造函数注入是唯一可用的依赖注入。
容器执行Bean依赖解析过程如下:
根据配置元数据描述的所有Bean,ApplicationContext被
创建和初始化。配置元数据可以通过XML、Java代码或者注解指定。- 对于每一个Bean,它的依赖以属性、构造器参数、静态工厂方法参数的形式被表达,如果你使用不是常规的构造器。这些依赖被提供给Bean,当Bean实际上被创建的时候。
- 在容器中,每个属性或者构造器参数都是值的实际定义被设置或者引用到其他的Bean里。
- 每一个属性或者构造器参数都是一个值,他们从指定的格式转换为真实的类型。在默认Spring会转换把一个String类型的值转换为所有内置类型,如
int
,long
,String
,boolean
,等。
Spring容器验证每一个Bean的配置在容器被创建时,包括验证Bean引用的属性是一个有效地Bean。然而,Bean属性本省并不会被设置直到Bean实际被创建。Bean的单身作用域和设置预先实例化(默认)被创建当容器被创建的时候。作用域在Section 4.5, “Bean scopes”中定义。另外,Bean在被请求的时候创建。创建一个Bean可能会导致创建Bean图,作为Bean依赖关系和依赖的依赖(等等)被创建和分配。
循环依赖
如果你使用构造器注入,就可能创建一个循环依赖的情况。
比如:Class A 请求Class B的一个实例,通过构造器注入,Class B请求Class A的一个实例通过构造器注入。如果你为Class A和Class B配置Bean相互注册到对方,Spring IoC容器检测到这种循环引用就会抛出BeanCurrentlyInCreationException异常。
一个可能的解决办法处理是编写一些类的源代码用Setter配置,而不是构造器。或者,避免构造器注入,只使用Setter注入。
不同于典型案例(没有循环依赖),Bean A和Bean B之间一个环形依赖强加到一个Bean在注入对方以前完全初始化自身(一个典型的鸡/蛋情况)。
你通常信任Spring做的正确的事。它可以检测配置问题,比如应用一个不存在的Bean和循环依赖在容器加载的时候。Spring设置属性和解决依赖越晚越好,当Bean实际被创建的时候。这意味着,当你请求一个对象,如果在创建对象或者依赖时有问题,Spring容器已经正确的加载稍后会生成一个异常。比如,Bean抛出一个异常作为缺少或者无效的属性所致。这可能推迟一些配置的可见性,问题是为什么ApplicationContext
实现默认预实例化的单例Bean。在一些前期的时间和内存实际需要才创建这些Bean的版本,你会发现配置问题,当ApplicationContext
被创建而不是以后。你仍然可以覆盖此默认行为,以便单例Bean被懒初始化,而不是预先实例化。
如果没有循环依赖存在,当一个或者多个协作Bean被注入一个依赖Bean,每个协作Bean完全配置在被注入到依赖之前。这意味着,如果Bean A已经依赖Bean B,Spring IoC容器完全配置Bean B 在调用Bean A的Setter方法之前。换句话说,Bean 被实例化了(如果不是提前实例化单例),它的依赖已经被设置,以及相关的生命周期方法(如 configured init method 或者 InitializingBean callback method )被调用。
一下的例子使用基于XML配置元数据和基于Setter注入。Spring XML配置文件的一小部分指定一些Bean定义:
id="exampleBean" class="examples.ExampleBean"> name="beanOne"> bean="anotherExampleBean"/> name="beanTwo" ref="yetAnotherBean"/> name="integerProperty" value="1"/> id="anotherExampleBean" class="examples.AnotherBean"/> id="yetAnotherBean" class="examples.YetAnotherBean"/>
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文件。下面的例子使用基于构造器的依赖注入:
id="exampleBean" class="examples.ExampleBean"> bean="anotherExampleBean"/> ref="yetAnotherBean"/> type="int" value="1"/> id="anotherExampleBean" class="examples.AnotherBean"/> id="yetAnotherBean" class="examples.YetAnotherBean"/>
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被告知调用静态工厂方法来返回对象实例:
id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> ref="anotherExampleBean"/> ref="yetAnotherBean"/> value="1"/> id="anotherExampleBean" class="examples.AnotherBean"/> id="yetAnotherBean" class="examples.YetAnotherBean"/>
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(协作者),或者作为内联定义的值。基于XML的配置元数据支持子元素类型在
和
元素里面。
元素的value属性指定属性或者容器参数作为一个人类可读的字符串形式。Spring conversion service 被用作从String转换他们值到实际的类型属性或者参数。
id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
name="driverClassName" value="com.mysql.jdbc.Driver"/>
name="url" value="jdbc:mysql://localhost:3306/mydb"/>
name="username" value="root"/>
name="password" value="masterkaoli"/>
下面的例子使用了p-namespace 更简洁的XML配置:
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
http://www.springframework.org/schema/beans/spring-beans.xsd">
id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
前面的XML更简洁,当然,错误在运行时被发现,而不是设计时,除非你使用的IDE,比如IntelliJ IDEA or the SpringSource Tool Suite (STS),支持自动属性完成,当你创建Bean定义。这样IDE支持高度推荐。
你可以配置java.util.Properties实例:
id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
name="properties">
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
Spring容器使用JavaBeans PropertyEditor机制
转换text代替
元素到java.util.Properties实例。这是一个很好的捷径,是少数地方Spring团队做喜欢使用嵌套
idref元素是一个简单的错误验证的id(字符串值-不是引用)传递容器中的另一Bean
或者
id="theTargetBean" class="..."/>
id="theClientBean" class="...">
name="targetName">
bean="theTargetBean" />
上面的Bean定义代码片段完全是等价的(运行时)以下代码片段:
id="theTargetBean" class="..." />
id="client" class="...">
name="targetName" value="theTargetBean" />
第一种形式是优选的第二个,因为使用idref允许容器在部署时验证所被引用的Bean是否存才。在第二种变种中,没有验证传递到客户端的Bean的targetName
属性的值。错误只能当客户端Bean实际实例化时发现(可能伴随着致命的错误)。如果客户端Bean是一个prototype Bean,这个错误和产生的异常可能是容器部署后发现。
此外,如果被引用的Bean在同一个XML文件中,且Bean名字就是Bean id,你可以使用local
属性,它允许XML解析器来较早验证Bean的id,在解析XML文件时。
name="targetName">
bean="theTargetBean"/>
常见的地方(至少版本早于Spring 2.0),ProxyFactoryBean
Bean定义。使用
ref 元素是 bean
, local,
or parent
属性指定的其他对象的id/name。
通过 Bean属性标记指定的target Bean是最一般的形式,并允许创建一个引用在同一个容器或者父容器,任何Bean不管他是否是同一个XML文件中。这个Bean 的value属性的只可以与target Bean的id属性相同,或是target Bean的name属性的值之一。
bean="someBean"/>
通过parent属性创建到Bean的引用指定target Bean,在当前容器的父容器。parent属性可以是相同于target Bean的id,或者name属性之一,并且target name必须在当前容器的父容器。你使用这个Bean的引用变量,当你有一个容器层次结构,并且你想通过代理包装一个已有的Bean到父容器中,它有一个被父Bean一样的名称。
id="accountService" class="com.foo.SimpleAccountService">
id="accountService" <-- bean name is the same as the parent bean --> class="org.springframework.aop.framework.ProxyFactoryBean"> name="target"> parent="accountService"/>
|
注意 |
---|---|
ref元素中的local属性在4.0Bean xsd中不再支持,因为它不提供超过普通Bean的价值参考了。只是改变你现有的ref local引用到ref Bean就可以升级到4.0模式。 |
元素代替
or
元素定义一个 所谓的内部Bean
id="outer" class="...">
name="target">
class="com.example.Person">
name="name" value="Fiona Apple"/>
name="age" value="25"/>
内部定义不需要定义id 或者name;容器忽略这些值。忽略scope
标签。内部Bean总是匿名的,他们总是被其他外部的Bean创建。注入内部Bean到合作的Bean是不可能实现的,除了封闭Bean。
在
,
, , 和
, 你可以分别设置Java Collection类型List、Set、Map和Properties属性和参数。
id="moreComplexObject" class="example.ComplexObject">
name="adminEmails">
key="administrator">[email protected]
key="support">[email protected]
key="development">[email protected]
name="someList">
a list element followed by a reference
bean="myDataSource" />
name="someMap">
key="an entry" value="just some string"/>
key ="a ref" value-ref="myDataSource"/>
name="someSet">
just some string
bean="myDataSource" />
Map类型的key或者value的值,或者set的值,可以是下列元素之一:
bean | ref | idref | list | set | map | props | value | null
Spring容器支持集合的合并。一个应用程序开发人员可以定义父样式的
, ,
或者
元素,子样式的
, ,
或者
本节关于父子Bean合并机制的讨论。如果读者不熟悉父与子Bean的定义,可以去阅读以下相关章节。
下面的示例说明了集合合并:
id="parent" abstract="true" class="example.ComplexObject"> name="adminEmails"> key="administrator">[email protected] key="support">[email protected] id="child" parent="parent"> name="adminEmails"> merge="true"> key="sales">[email protected] key="support">[email protected]
注意在child
Bean中的adminEmails
属性下的
元素中使用merge=true属性。当
child
Bean被容器处理并实例化时,结果实例中有一个adminEmails
Properties
的集合,包含了合并子Bean的adminEmails
集合和父Bean的adminEmails
集合。
[email protected] [email protected] [email protected]
子容器的Properties集合值的设置继承了从父
这中合并行为的应用和
, 和
元素特定的情况下,和List集合类型相关的语义,也就是说,
ordered
集合值的概念,是要维护的:父值优先于所有子List的值。在Map,Set和Properties实现类型相关的集合类型没有排序语义的作用。
本能合并不同类型的集合(如Map和List),如果你要尝试这么去做,那么就会抛出Exception。merge属性必须在低级的,继承的,子Bean中来指定:在父集合中指定merge属性是冗余的,也不会看到想要的合并结果。
在Java5或者更高的版本中,你可以使用强类型集合。也就是说,可以声明一个集合类型,它可以仅仅包含Sting元素(例如)。如果你使用Spring依赖注入一个强类型的集合到一个Bean中,你可以利用Spring 的类型转换来支持这样的强类型集合实例的元素,在被加到集合之前,可以转换成合适的类型。
public class Foo { private Mapaccounts; public void setAccounts(Map accounts) { this.accounts = accounts; } }
id="foo" class="x.y.Foo"> name="accounts"> key="one" value="9.99"/> key="two" value="2.75"/> key="six" value="3.99"/>
当foo Bean的accounts属性准备好注入时,关于强类型元素Map9.99, 2.75和
3.99
被转换成实际的Float类型。
Spring将属性的空参数当做String。下面基于XML的配置元数据片段设置了电子邮件属性为空String值(”“)。
class="ExampleBean">
name="email" value=""/>
上面的代码和下面的Java代码是一样的:exampleBean.setEmail("")。
class="ExampleBean">
name="email">
上面的卑职和下面的Java代码一致:exampleBean.setEmail(null)
.
p-namespace可以使你使用Bean的元素属性,而不是内嵌的
Spring支持使用namespace的可扩展的配置格式,这是基于XML的Schema定义。本章中讨论的Bean配置格式是定义在XML的Schema下的。然而p-namspace这不是定义在XSD文件下的,仅仅存在于Spring核心。
下面的示例展示了两个XML片段,解析得到相同的结果:第一个使用了标准的XML格式,第二个使用了p-namespace。
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
http://www.springframework.org/schema/beans/spring-beans.xsd">
name="classic" class="com.example.ExampleBean">
name="email" value="[email protected]"/>
name="p-namespace" class="com.example.ExampleBean"
p:email="fo[email protected]"/>
示例展示了p-namespace下的属性,在Bea的定义中称为Email。这告诉Spring包含属性声明。正如前面提到的,p-namespace没有Schema定义,所以你可以设置属性的名称和Bean中属性的名称一样。
下面示例包含了两个Bean定义,两者都有对其他Bean的引用。
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
http://www.springframework.org/schema/beans/spring-beans.xsd">
name="john-classic" class="com.example.Person">
name="name" value="John Doe"/>
name="spouse" ref="jane"/>
name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
name="jane" class="com.example.Person">
name="name" value="Jane Doe"/>
正如你所见,这个例子包含着不仅仅使用了p-namespace的属性值,也使用了特殊格式声明属性的引用。在第一个Bean中使用了spouse
是属性名,而-ref部分表明了这不是一个直接值而是一个队其他Bean的引用。
注意 | |
---|---|
p-namespace没有标准的XML格式那么灵活。比如。声明属性引用的格式和以ref结尾的属性相冲,而标准的XML格式则不会。我们创建谨慎悬着所用的方法并和开发团队成员充分地交流,避免产生同事使用这三种方法的XML。 |
和 “XML shortcut with the p-namespace”相似,c-namespace在Spring 3.1中被引入,允许使用内联属性来配置构造器参数而不是使用construction-arg元素。
我们来看“Constructor-based dependency injection”中的示例,现在使用c-namespace:
"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 http://www.springframework.org/schema/beans/spring-beans.xsd">"bar" class="x.y.Bar"/> "baz" class="x.y.Baz"/> "foo" class="x.y.Foo"> "bar" />"baz" />"[email protected]" />"foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="[email protected]"/>
c:namespce使用了和p:(以-ref结尾的Bean引用)相同的转换机制通过他们的名称来设置构造器参数。这样一来,即使没有在XSD Schema(但是在与Spring核心的内部)中定义,还是需要声明出来。
在极少数的情况下,构造器参数名称是不可用的(通常如果字节码在编译是没有调试信息),我们可以使用参数索引:
"foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
注意 | |
---|---|
因为XML的语法,索引符号需要在其头部使用_作为XML属性名称,因为XML中属性是不能以数字开头的(尽管一些IDE是允许的)。 |
在实际运用中,构造器解析机制在匹配参数时是非常有效率的,所以除非真的需要,我们需要在配置中使用名称符号。
在设置Bean的属性时,只要路径中的所有组件,除了最后一个属性名称是非null的,可以使用复合或者嵌套的属性名称。参考如下:
id="foo" class="foo.Bar">
name="fred.bob.sammy" value="123" />
foo Bean有个fred的属性,它还有一个bob的属性,而它仍有一个sammy的属性,而最终的sammy属性被设置成为数值123.为了让这样的配置可用,foo的属性fred,fred的属性bob在Bean被够着好后必须不能为null,否则会抛出NullPointerException异常。
使用一个Bean是另一个Bean的依赖,通常表明了它会被设置成为另一个的属性。典型的情况是在基于XML的配置元数据中使用 元素来完成。然而,有时在两个Bean之间的依赖并不是直接的:比如,类中的静态初始化器需要触发,就像数据库驱动程序的注册。depends-on属性可以明确的强调一个或者多个Bean在使用这个元素的Bean被初始化之前被初始化。下面的示例使用了depends-on属性来表示一个独立的Bean依赖:
id="beanOne" class="ExampleBean" depends-on="manager"/>
id="manager" class="ManagerBean" />
为了表示对过个Bean的依赖,提供一个Bean名称的列表作为depends-on属性的值即可,要用逗号,空白或者分号分隔开,他们都是有效地分隔符。
id="beanOne" class="ExampleBean" depends-on="manager,accountDao"> name="manager" ref="manager" /> id="manager" class="ManagerBean" /> id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
注意 | |
---|---|
在Bean定义中的depends-on属性可以指定初始化时的依赖,也可以是仅仅是单例Bean,对应销毁时的依赖。和给定Bean定义了depends-on关系依赖Bean首先被注销,先于给定的Bean本身。因此depends-on也能控制关闭顺序。 |
在默认情况下,ApplicationContext
的实现积极的创建和配合所有的单例Bean,作为初始化过程的一部分。通常来说,这种预实例化是非常可取的,因为配置或者周边环境中的错误可以直接被发现,而不是在几个小时或者几天之后去发现。当这种行为不可用时,你可以阻止单例Bean的预实例化,在Bean定义中使用延迟初始化来标记一下就可以了。延迟初始化Bean告诉IoC容器在该Bean第一次被请求时来实例化,而不是在启动时实例化。
在XML中,这个行为可以通过
id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
name="not.lazy" class="com.foo.AnotherBean"/>
当前配置被ApplicationContext处理时,命名为lazy的Bean并不会在ApplicationContext启动时被预实例化,而not.lazy Bean会被预先实例化。
而到延迟初始化Bean是一个单例的依赖时,且这个单例Bean不是延迟初始化,那么ApplicationContext也会在启动时创建延迟初始化的Bean,因为它必须满足单例Bean的依赖。延迟初始化的Bean被注入到单例Bean中的时候他就不是延迟初始化的了。
你也可以在容器级别来控制延迟初始化,在
元素上使用default-lazy-init属性,比如:
default-lazy-init="true">
Spring 容器可以自动装配协作Bean之间的关系。你可以允许Spring自动为你的Bean来解析学作者(其他Bean),通过检查ApplicationContext的内容即可。自动装配有下列优势:
当使用基于XML的配置元数据,你可以为Bean定义指定自动装配模式,在autowire
属性即可。自动装配功能有五种模式。你可以为每个Bean指定自动装配,因此可以选着为哪一个来指定自动装配。
表 4.2. 自动装配模式
模式 | 解释 |
---|---|
no |
(默认情况)没有自动装配。Bean的引用必须通过ref元素来定义。对于大型的部署,修改默认设置不是推荐的,因为明确地指定协作者会给与更多的控制和清晰。某种程度上来说,它勾勒出了系统的结构。 |
byName |
通过属性名称来自动装配。Spring以相同名称来查找需要被自动装配的Bean。比如,如果Bean被设置成由名称来自动装配,并含有一个master属性(也就是说,有setMaster(..)方法),Spring会查找名为master的Bean定义,并且用它来设置属性。 |
byType |
如果Bean的属性类型在容器中存在的话,就允许属性被自动装配。如果存在多余一个,就会抛出致命的异常,这就说明了对那个Bean不能使用byType自动装配。如果没有匹配的Bean存在,就不会有任何效果:属性就不会被设置。 |
constructor |
Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised. 和byType类似,但是是应用于构造器的参数。如果容器中没有确定的构造器参数类型的Bean存在,就会发生致命的错误。 |
使用bytype或者constructor自动装配模式,你可以装配数组和集合类型。这种情况下所有在容器内匹配期望类型的自动装配候选者会被用来满足依赖。如果期望的键类型是String,你可以自动装配强类型的Map。自动装配的Map值会包括匹配期望类型的实例,而Map的键会包含对应的Bean名称。
你可以联合装配行为和依赖检查,这会在自动装配完成之后执行。
在项目中一直使用时,自动装配是非常不错的。如果自动装配通常是不使用的,它就可能会迷惑开发人员使用它去装配仅仅一两个Bean。
考虑一下自动装配的限制和缺点:
在property和
constructor-arg
中设置的明确依赖通常覆盖自动装配。不能自动装配所谓的简单属性,比如原生态类型,String和Class(还有这样简单属性的数字)。这是由于设计的限制。在后面一种情况中,你有几种选择:
元素的primary属性为true来指定单独的Bean作为主要的候选者。 在每个Bean的基础上,你可以从自动装配中来排除Bean。在Spring的XML格式配置中,设置
的 autowire-candidate为false;容器会把指定的Bean对自动装配不可用(包含注解风格的配置,比如
@Autowired
)
你也可以基于Bean的名称模式匹配来限制自动装配候选者。顶级的default-autowire-candidates
属性接受一个或者多个模式。比如,为了限制自动装配候选者到任意名称以Repository结尾的状态,提供*Repository值。要提供多个模式,把他们定义在以逗号分隔的列表中。Bean定义中autowire-candidate
属性的true或者false明确的值通常是优先的,而且对于这些Bean来说,模式匹配规则是不适用的。
这些技术对哪些永远想不通过自动装配被注入到其他Bean中的Bean来说是很有用的。这并不意味着未包含的Bean不能使用自动装配来配置。相反,Bean本身不是自动装配其他Bean的候选者。
在很多应用场景中,很多容器中的Bean是单例的。当一个单例的Bean需要和其他单例的Bean协作时,或者一个非单例的Bean需要和其它非单例的Bean协作时,典型的做法是通过定义一个Bean作为另外一个的属性来控制依赖。当Bean的生命周期不同时,问题就产生了。假设单例Bean A 需要设置非单例(prototype,原型)Bean B,或许在A 的每个方法调用上。容器仅仅创建单例BeanA一次,因此仅仅有一次机会去设置属性。容器不能每次为Bean A提供所需的Bean B新的实例。
解决方法是放弃一些控制反转。你可以通过实现ApplicationContextAware
接口以让Bean A去感知容器,而且在每次Bean A需要Bean B的实例时,可以让容器调用getBean('B')去获取(一个新的)Bean B。下面的代码就是这种方式的例子:
// a class that uses a stateful Command-style class to perform some processing package fiona.apple; // Spring-API imports import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class CommandManager implements ApplicationContextAware { private ApplicationContext applicationContext; public Object process(Map commandState) { // grab a new instance of the appropriate Command Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } protected Command createCommand() { // notice the Spring API dependency! return this.applicationContext.getBean("command", Command.class); } public void setApplicationContext( ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
前面的做法是不可取的,因为这些业务代码感知并耦合到了Spring框架中。方法注入,这种Spring IoC容器中的有点先进的功能,允许以一种干净的方式来处理这个用例。
你可以在博客文章中阅读更多的有关方法注入的动机
查找方法注入是容器在管理的Bean上覆盖方法的功能,返回容器中其他命名Bean的查找结果。查找,典型的情况是涉及原型Bean,这是在之前章节中描述的情景。Spring Framework从CGLIB类库中,通过使用字节码生成机制实现了这种方法注入,来动态的生成覆盖方法的子类。
注意 | |
---|---|
要让这些动态子类起作用,在类路径下必须有 CGLIB 的 jar 文件。这些 Spring 容器中的 子类不能是 final 类型的,要被覆盖的方法也不能是 final 类型的。而且,测试有abstract 方法的类要求你自己去编写类的子类并提供 abstract 方法的实现。最后,是方法注入目标的对象还不能被序列化。 |
看一下之前代码片段中的 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 的,动态生成的子类就实现这个方法。否则,动态生成的子类覆盖定义在源类中的确定方法。比如:
id="command" class="fiona.apple.AsyncCommand" scope="prototype"> id="commandManager" class="fiona.apple.CommandManager"> name="createCommand" bean="command"/>
当需要 command bean 的新的实例时,标识为 commandManager 的 bean 调用它自己的方法 createCommand()。部署 command bean 为原型的可必须要小心,那确实就是需要的才行。如果被部署为单例的(4.5.1 节),那么每次会返回相同的 command bean 的实例。
提示 | |
---|---|
感 兴 趣 的 读 者 可 能 会 发 现 要 使 用 ServiceLocatorFactoryBean (在org.springframework.beans.factory.config 包下)。在 ServiceLocatorFactoryBean 中的使用的方法和其它工具类是相似的,ObjectFactoryCreatingFactoryBean,但 是它允许你去指定你自己的查找接口而不是 Spring 特定的查找接口。对这些类查询一下 JavaDoc 文档,还有博客文章来获取 ServiceLocatorFactoryBean 的额外的信息。 |
在方法注入中,用处不如查找方法注入大的一种形式,就是使用另外一个方法实现来替 换被容器管理的 bean 中的任意方法的能力。用户可以安全地跳过本节的其它部分,直到你 真正需要这些功能的时候来回过头来看。
使用基于 XML 的配置元数据,对于部署的 bean,你可以使用 replaced-method 元素来使用另外一个方法替换已有的方法实现。看看下面这个类,有一个我们想要去覆盖的 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 定义和指定的方法覆盖可以是这样的:
id="myValueCalculator" class="x.y.z.MyValueCalculator"> name="computeValue" replacer="replacementComputeValue"> String id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
你可以在
java.lang.String String Str
因为用参数的数量用来区分每一个可能的选择通常是足够的,这种快捷方式可以节省大量的输入,允许你去输入匹配参数类型的最短的字符串。