spring-ioc 1

7.核心容器

7.1spring ioc容器和beans介绍

本章简单介绍spring 对于ioc原则的实现.IOC也被称为DI.这是一个过程,经过对象来定义他们的依赖(指那些须要和他们一块儿工做的对象),而且只能经过构造器参数,工厂方法参数,或者被构造器或被工厂方法返回实例后进行属性设置.(实际上是bean被设置)这个过程是反转的基础.控制反转,在于bean本身控制它的依赖的实例化或直接经过类的构造器定位他们,又或者经过Service Locator pattern(服务定位模式)的机制来定义.html

org.springframework.beans和org.springframework.context包是spring ioc容器的基础.beanFactory接口提供了高级的配置来管理任何种类的对象.ApplicationContext是beanFactory的一个下属接口.它实现了对spring Aop功能的简单集成,消息资源的处理(国际化中使用),事件发布,以及应用层次的特定上下文(例如,WebApplicationContext就是在web应用中使用的).java

简而言之,BeanFactory提供了配置框架和基础功能,ApplicationContext添加了许多企业定制的功能.ApplicationContext是beanFactory的彻底超集,她的用法有特定的介绍.要使用BeanFactory来取代ApplicationContext,查阅7.16部分.mysql

在spring中,bean如此定义:组成你应用基础并被spring ioC容器管理的对象;一个bean是一个能够被实例化,被组装,且能被Spring ioc 容器管理的一个对象.另外,一个bean是你应用中许多对象的一个简单抽象.beans,还有在Beans中的依赖,都经过一个容器在configuration元数据反应出来.web

7.2 容器简介

org.springframework.context.ApplicationContext接口实现了spring ioc 容器,主要负责以前提到的beans实例化,配置,组装.容器获得本身的指令去实例化,配置,以及读取配置元数据进行组装. 配置元数据能够是xml,java注解,java代码.它容许你表达组成应用的对象,以及这些对象之间复杂的关系;spring

spring提供了ApplicationContext的几种开箱即用的实现.在单独的应用中,它能够轻易的建立ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext的实例.Xml是定义配置元数据的传统格式,然你可使用java注解或代码来左右元数据格式来构造你的容器,固然这须要你提供少许的xml配置来申明对这些额外的元数据格式的支持.sql

在大部分的应用场景里,显示的用户代码不须要你去实例化一个或多个spring IOC 容器.例如,在一个web场景里在应用的web.xml文件里的一个简单的八行左右的典型的web描述XML将足够使用的.若是你使用STS的开发环境,那么这些配置将轻易的经过一些鼠标点击和键盘事件来实现.数据库

下面的图表是spring工做流程的高水平视图.你的类能够经过配置元数据来进行组装,由于当你的ApplicationContext建立和初始化后,你将会拥有一个全面的配置和可执行的系统或应用;apache

7.2.1 配置元数据

如前面的流程图所示,spring ioc 容器消费了一系列的配置元数据.这个配置元数据体现出你做为一个应用开发者如何在你的应用中使spring容器来实例化,配置并装备你应用中的对象的.api

配置元数据是在一个简单直观的xml格式中是传统的生产者,本章中大部份内容都在讲述这个spring ioc 容器的核心概念功能;数组

备注:XML-based 元数据不是配置元数据的惟一格式;spring IOC 容器的配置和书写方式其实是彻底解耦的.最近不少开发者使用 基于Java的配置来配置他们的spring应用.

从spring3.0开始,许多Spring JavaConfig project提供的功能成为了spring 核心框架的一部分.所以你可使用java而不是xml文件来定义你项目文件中额外的bean.要实现这些新功能,请查看@Configuration,@Bean,@Import和@DependsOn注解.

spring 配置至少由一个bean或者多个bean定义(他们由容器来管理);基于xml的配置会在<Beans/>级别的元素里经过<bean/>元素来配置.java配置通常是在@Configuration 类里使用@Bean注解加在方法之上.

与这些bean定义的真正对象组成了你的应用.通常你会定义服务层对象,数据访问层对象(DAOs),表现层对象,例如Struts 的action实例,框架对象如Hibernate 的SessionFactories,JMS Queues等等.通常而言,应用不会再容器里配置细粒度的领域对象,由于建立和加载这些领域对象是DAOs和服务逻辑层的责任.然而,你可使用spring 的AspectJ来在IOC 容器以外来配置对象.查看 Using AspectJ to dependency-inject domain objects with Spring.

  • 这个是基本的基于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
				http://www.springframework.org/schema/beans/spring-beans.xsd">

			<bean id="..." class="...">
				<!-- collaborators and configuration for this bean go here -->
			</bean>

			<bean id="..." class="...">
				<!-- collaborators and configuration for this bean go here -->
			</bean>

			<!-- more bean definitions go here -->

		</beans>
  • 这个id属性是一个字符串,能够用来标记每一个bean的定义.这个class属性定义bean的类型,要使用class的全路径名.

这个id属性的值指向协做对象.xml中的协做对象并无在这里展现,详情看dependencies章节

7.2.2 实例化一个容器

实例化一个spring容器能够直截了当.提供给ApplicationContext构造器的定位路径都是真实资源字符串,他们容许容器从一些额外的资源来加载配置元数据,如从本地文件系统,从java的classpath,等等. ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

  • 在了解了Spring IOC 容器以后,你可能会问关于Spirng Resource 抽象的问题,在第8章Resource中,它会提供一个完善的机制来从用URI语法定义的位置读取其文件的输出流.特别指出的是Resource路径能够用来构造应用上下文,如8.7节中所示,"Application contexts and Resource paths";

  • 下个例子是服务层对象的配置文件: <?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->
    
      	<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
      		<property name="accountDao" ref="accountDao"/>
      		<property name="itemDao" ref="itemDao"/>
      		<!-- additional collaborators and configuration for this bean go here -->
      	</bean>
    
      	<!-- more bean definitions for services go here -->
    
      </beans>
  • 下个例子是在daos.xml文件里定义数据访问对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>
  • 在上述例子中,一个服务层是由PetStoreServiceImpl类,两个DAO类--JpaAccoutDao和JPaItemDao(基于JPA ORM映射标准)组成.这个property元素的name指向JavaBean属性里的name,其中的ref属性指的是其余bean定义类的名称.id和ref元素之间的关系表示协做对象之间的依赖.配置对象依赖的细节,查看Dependencies; ###组装 基于xml的配置元数据
  • 跨越多个Xml文件来定义bean将很是有效.一般每一个独立的XMl定义文件表明一个逻辑层或你架构中的一个模块. 你可使用应用上下文构造器从全部的xml碎片中加载bean定义.这个构造器能够得到混合资源的位置.一种如上节所示.另外一种,使用一种或多个<import>元素去从其余文件里加载bean定义.例如
<beans>
		<import resource="services.xml"/>
		<import resource="resources/messageSource.xml"/>
		<import resource="/resources/themeSource.xml"/>

		<bean id="bean1" class="..."/>
		<bean id="bean2" class="..."/>
	</beans>

在上述的例子中,额外的bean定义从如下三个额外文件加载--services.xml,messageSource.xml,themeSource.xml.全部的定位路径都与定义文件的引用相关,因此引用时services.xml文件必须在相同路径或者在classpath位置下,而messageSource.xml和themeSource.xml必须在resource位置下.如你所见,反斜杠被忽略了,你能够给定相对路径,这比之前要好的多.要引入的文件内容,必须包含最高级的<beans>元素,且必须使用spring Schema来验证XML bean定义;

  • 备注:有个能够但不推荐的作法,在父路径引用文件应该加上相对路径"../".这样作会建立一个独立于当前应用的依赖文件.特别,这个方法不推荐在classpath中使用,当运行方案过程选择最近的classpath根路径,而后查找它的父路径.classpath路径配置改变可能会致使错误的结果.
    你可使用全资源路径来替代相对路径.例如,使用"file:c:/config/services.xml"或者"classpath:/config/services.xml".可是你最好使用抽象位置路径,

7.2.3 使用容器

ApplicationContext是一个先进工厂来管理一系列不一样的beans和她们的依赖的注册器.使用方法 T getBean(String name,Class<T> requiredType),你将get到你全部bean的实例

  • ApplicationContext容许你经过下面的例子来读取和访问bean定义
//create and configure beans
   ApplicationContext context =new ClassPathXmlApplicationContext(new String[]{"services.xml","dao.xml"});
   //retrieve configured instance
   PetStoreService service=context.getBean("petStore",PetStoreService.class);
    //user configured instance
   List<String> userList=service.getUsernameList();

你可用使用getBean()来获取你的Beans的实例.ApplicationContext接口有其余的方法来获取bean,但理想的应用不该该用到他们.并且,你的应用代码不该该调用getBean()方法,而且不须要依赖spring 的API.例如,你的spring web的集成框架提供了各类web框架的controller和JSF-managed beans的依赖注入.

7.3 bean 简述

一个spring IOc容器能够管理一个或多个Beans.这些Bean由你提供给容器的配置元数据来建立.例如,以XML的<bean/>形式定义的 在容器中这些bean定义被视为BeanDefinition对象,它包含如下元数据信息:

  • 一个包含包名的类名称:一个bean定义的典型实现.

  • Bean行为的配置元素,它描述了在容器中这个bean该如何表现(scope,lifecycle callbacks等)

  • Reference 协做依赖

  • 其余对新建对象的配置设置 ,例如一个管理链接池的bean中连接的数量,及其链接池的规模

    bean定义

class      instantiating beans
  name		 Naming beans
  scope   	 Bean scopes
  constructor arguments			Dependency injection
  properties   dependency injection
  autowiring mode 		Autowiring collaborators
  lazy-initialization mode   Lazy-initialized beans
  intialization method   	the section called "initialization callbacks"
  destruction method       the sction called "Destruction callbacks"

另外,bean的定义包含如何去建立一个具体的bean;ApplicationContext的实现也容许你去注册一个在IOC容器以外的已被建立的对象.它是这样实现的: 经过getBeanFactory()方法来返回App BeanFactory实现 DefaultListableBeanFactory来访问ApplicationContext BeanFactory. DefaultListableBeanFactory经过方法registerSingleton(..)和registerBeanDefinition(..)来实现注册.可是,通常的应用运行中用到的bean定义是经过元数据Bean定义来实现的

备注: bean的元数据和手动单例越早注册越好,以便于容器更好推测他们来进行组装或其余思考步骤;当你重写已存在的元数据或已存在的单例,ioc只在必定程度上支持; 注册新的bean在运行(并发实时访问bean工厂)时不会得到正式的支持,并在容器中会致使并发访问异常和/或状态不一致;

7.3.1 bean的命名

每个bean都有一个或多个标志,这些标志在管理它们的容器中必须是惟一的.一个bean通常只有一个标志,但若是它须要不止一个,那个多出来的被认为是别名.

一个基于xml的配置元数据,你可用使用id 和/或 name属性来指定bean的标识符.id属性容许你指定一个具体的bean的Id.这些名字通常都是字母(如'myBean','fooService'等),但可能包含特殊字符.若是你要引入bean的其余别名,你能够在name属性来指明他们,用逗号(,),分号(;),或者空格来隔开.在历史记录中,spring3.1里的id属性并命名为xsd:ID类型,这会限制字符.在3.1里,它被定义为xsd:String类型.记住bean的id是独一无二的,这是有容器强制决定的,而不是由XML转化器.

你不须要为每一个bean提供一个name或id.若是id或name没有指定,容器就会为bean生成一个惟一的名字.可是,若是你想经过名字来引用bean,如经过使用ref标签或者服务定位风格来查找,那么你必须提供一个名字.没有提供名字,通常都与内部bean或者自动注机制入相关.

Bean命名约定:

这个规范指在给bean命名时要使用标准的java实例字段规范.它以下:bean的名字从一个小写字母开始,使用驼峰命名法.这些名字的例子以下:"accountManager","accountService","userDao","loginController"等等. 给bean命名通常会让你的配置更容易读取和理解.若是要使用spring AOP调用advice到一串bean里时,能够经过名字查找这些bean.这也颇有帮助.

备注:当在classpath中扫描组件时,spring会自动为没有命名的bean命名,按如下规则:通常,采用其简易类名,并将第一个字母小写.然若是第一和第二个字母都大写,那么类名将保留.这个是由java.beans.Introspector.decapitalize 来定义的,固然spring也在使用

bean定义以外给bean别名

在bean定义里,你能够提供多个名字,经过使用id来提供 一个命名,而后在那么属性里有多个命名.这些名字等同于bean的别名,对于一些状况颇有用,例如容许应用中的每一个组件来经过一个特定于本组件的bean的名称来引用一个公共的依赖.

然而,别名和bean的实际命名是不同的.有时候会从给在其余地方定义的bean引入一个别名.这种状况通常为:在大系统中,会被分割为几个的子系统,每一个子系统都有本身的对象定义集.在基于xml配置元数据中,你可使用<alias/>元素来完成.

<alias name="fromName" alias="toName"/>

在此状况下,在同一容器中一个bean被命名为fromName,但在使用别名以后,也能够用toName来指向它.

例如,子系统A中配置元数据可能指向一个名为subsystemA-dataSource的数据源.而配置在子系统B中的经过subsystemB-dataSource来指向一个数据源.当你编译主系统时,主系统能够经过myApp-dataSource来指向这个数据源.这样,就有三个指向相同对象的名字被你添加到了MyApp配置元数据里了.其别名定义以下:

<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />

如今每一个组件和主应用均可以经过惟一的名字来引用一个相同的dataSource,并保证不与其余定义冲突(有效的建立命名空间).

7.3.2 bean的实例化

bean定义根本上是为了创造一个或多个对象的清单.当一个命名bean被请求时,容器就会查找该清单,使用被封装的bean定义的配置元数据来建立(获取)一个实际的对象.

若是你使用基于xml的配置元数据,你须要,在bean元素里class的属性里指定你要实例化对象的类型.这个class属性,本质上是BeanDefinition的class属性,通常是强制要求的.(固然有例外:用工程实例方法来实例化7.7 bean定义继承).你这样使用class特征:

  • 通常而言,指定bean的class是为了在构造时容器能经过反射调用它的构造器来直接新建bean,必定程度上如同java代码使用new 操纵符.

  • 调用实际上包含某种静态工厂方法能够建立对象的类,一些状况下容器能够调用静态工厂方法来建立bean.静态工厂方法调用的返回对象类型能够与class类型相同或不一样的类

  • 内部类的命名

加入你要配置一个静态内部类的bean定义,你必须使用内部类的二进制名称. 例如,若是你有一个类叫Foo在com.example包里,并且Foo有一个静态内部类叫Bar,那么Bar的bean定义中的class属性应该这样: com.example.Foo$Bar 记住$符号的使用,它能够从外部类的名字中分割内部类的名字;

用构造器来实例化

当你使用构造器方法来建立bean时,全部正常的类均可以被spring使用和兼容.这样的话,这些类就不须要实现特定的接口或者被编码到特定的快照中.简单指定class的类型就足够了.可是,取决于那种你具体使用的ioc容器的类型,你可能须要一个默认构造器.

Spring Ioc 容器能够管理任何你须要管理的类;它不止能管理javaBeans.大多数spring用户倾向于选择javaBean,它的定义以下:一个默认构造器(非参),其属性有合适的set和get方法模块的容器. 你也能够在你的容器里有其余非bean风格的类.例如,若是你使用一个逻辑链接池,它彻底不符合javaBean的定义,spring依然能管理它. 在基于xml的配置元数据中,你能够如此管理你的bean:

<bean id="exampleBean" class="examples.ExampleBean" />
	<bean  name="anotherExample" class="examples.ExampleBeanTwo"/>

关于如何将参数提供到构造器的机制和对象构造以后如何设置实例属性,参见 Injecting Dependencies ;

静态工厂方法实例化

当你要用静态方法工厂来建立的定义bean时,你须要在class属性里指定一个包含了静态工厂方法的类,在factory-method方法里指定该工厂方法.你可用调用该方法并返回一个实例,随后你能够像一个构造器造出的bean同样来使用它了.一我的使用这种bean的定义方式,主要是想在逻辑代码里调用静态工厂.

接下来的bean定义说明,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;
		}
	}

细节参照 Dependencied and configuration in detail

使用实例工厂方法进行实例化

与静态工厂方法类似,一个实例工厂方法的实例化会调用一个在容器中已存在的bean的非静态方法,并用来建立一个新的bean.以下使用:不填class属性,并在factory-bean属性里指定有实例工厂方法的并确实要调用类的bean的标识符.在factory-method方法里添上实例工厂方法.

<!-- the factory bean, which contains a method called createInstance() -->
	<bean id="serviceLocator" class="examples.DefaultServiceLocator">
		<!-- inject any dependencies required by this locator bean -->
	</bean>

	<!-- the bean to be created via the factory bean -->
	<bean id="clientService"
		factory-bean="serviceLocator"
		factory-method="createClientServiceInstance"/>

	public class DefaultServiceLocator {

		private static ClientService clientService = new ClientServiceImpl();
		private DefaultServiceLocator() {}

		public ClientService createClientServiceInstance() {
			return clientService;
		}
	}

一个工厂类能够包含多个工厂方法,以下使用

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
		<!-- inject any dependencies required by this locator bean -->
	</bean>

	<bean id="clientService"
		factory-bean="serviceLocator"
		factory-method="createClientServiceInstance"/>

	<bean id="accountService"
		factory-bean="serviceLocator"
		factory-method="createAccountServiceInstance"/>
		
	public class DefaultServiceLocator {

		private static ClientService clientService = new ClientServiceImpl();
		private static AccountService accountService = new AccountServiceImpl();

		private DefaultServiceLocator() {}

		public ClientService createClientServiceInstance() {
			return clientService;
		}

		public AccountService createAccountServiceInstance() {
			return accountService;
		}

	}

改方法显示工厂Bean自己也能够经过依赖注入被管理和配置.具体查看 [dependencies and configuration in detail].(http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-factory-properties-detailed)

  • 注意:在spring文档中,工厂Bean指的是能够被spring容器配置的,能用静态工厂方法或实例工厂方法来产生实例的Bean.相比较,FactoryBean(注意大写)指的是一个spring特定的FactoryBean.

7.4 依赖

一个典型的企业应用不是由单个对象组成的.即便是最简单的应用也有好几个对象协同工做,并展示给终端用户一个条例分明的应用.这章将讲述如何定义不少独立的bean,并使他们在一个良好运行的应用里相互协做并实现目标的.

7.4.1 依赖注入

依赖注入(DI)是一个过程,即对象定义他们的依赖关系,也就是说与它协做的对象,只有经过构造器参数,工厂方法参数,或者当构造器或工厂方法生成实例返回以后才能设置其属性.当容器建立这个bean时,它会注入这些依赖.这个过程是反转(控制反转)的基础,经过直接调用类构造器或者Service Locator pattern反转的bean本身控制自身的实例化或其依赖的定位

在DI原则下,代码会更加简洁,且由于对象同它们的依赖一块儿提供,解耦会更好实现.对象没必要查找他们的依赖,也没必要知道依赖的位置和他们的class.所以,你的class会更容易测试,特别是当这些依赖是接口或抽象类时会容许单元测试进行存根或实现模拟; DI存在两个变种:基于构造器的依赖注入和基于Setter的依赖注入

基于构造器的依赖注入

基于构造器的注入依赖是经过容器来调用有不少参数的构造器来完成,每一个构造器表明一种依赖.调用一个带有具体参数的静态工厂方法来构造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和Brz类之间无继承关系.所以在剩下的配置工做中,你不须要在<constructor-arg/>标签中指定构造器参数的加载顺序或者类型说明. <beans> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> </bean>

<bean id="bar" class="x.y.Bar"/>

		<bean id="baz" class="x.y.Baz"/>
	</beans

当其余类型被引用,且类型已知,那么匹配能够进行.当一个简单的类型被使用时,例如<value>true</value>,spring没法判断这个值的类型,因此没法在无辅助下进行类型匹配.以下例所示:

package examples;

		public class ExampleBean {

			// Number of years to calculate the Ultimate Answer
			private int years;

			// The Answer to Life, the Universe, and Everything
			private String ultimateAnswer;

			public ExampleBean(int years, String ultimateAnswer) {
				this.years = years;
				this.ultimateAnswer = ultimateAnswer;
			}

		}

在下面的场景里,容器可使用简单的类型进行类型匹配,若是你使用了type属性明确的指定构造器的参数类型.例如:

<bean id="exampleBean" class="examples.ExampleBean">
			<constructor-arg type="int" value="7500000"/>
			<constructor-arg type="java.lang.String" value="42"/>
		</bean>

使用index来指明构造器参数明确的加载顺序,如:

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg index="0" value="7500000"/>
	<constructor-arg index="1" value="42"/>
</bean>

另外,消除简单类型值的歧义,若是一个构造器有两个相同类型的参数,能够指定其下标index来消除歧义.记住下标从0开始. 你可用使用构造器参数名来消除歧义.

<bean id="exampleBean" class="examples.ExampleBean">
		<constructor-arg name="years" value="7500000"/>
		<constructor-arg name="ultimateAnswer" value="42"/>
	</bean>

请记住,为了可以开箱即用,你的代码编译时必须打开调试模式,因此spring能够从构造器里查找参数名字.若是你不想让你的代码同调试标志一块儿编译,你可用使用@ConstructorPropertiesJDK注解,来指定你的构造器参数的名称.例子以下:

package examples;

		public class ExampleBean {

			// Fields omitted

			@ConstructorProperties({"years", "ultimateAnswer"})
			public ExampleBean(int years, String ultimateAnswer) {
				this.years = years;
				this.ultimateAnswer = ultimateAnswer;
			}

		}

基于Setter的依赖注入

Setter-based DI是在容器调用无参构造器或者无参静态工厂方法实例化你的bean以后,在调用你bean里的setter方法来实现的. 下面的例子展现了一个只使用setter注入来完成依赖注入的class.这个是一个传统的java类.它是一个简单的java对象,不依赖于容器的特定的接口,基本类或者注解.

public class SimpleMovieLister {

			// the SimpleMovieLister has a dependency on the MovieFinder
			private MovieFinder movieFinder;

			// a setter method so that the Spring container can inject a MovieFinder
			public void setMovieFinder(MovieFinder movieFinder) {
				this.movieFinder = movieFinder;
			}

			// business logic that actually uses the injected MovieFinder is omitted...

		}

ApplicationContext为它管理的bean提供了基于构造器和基于Setter方法的DI.它还支持当你使用构造器方法注入一些依赖以后,还可使用基于Setter的依赖注入.你能够以BeanDefinition的形式来定义这些依赖,这样可使用PropertyEditor实例来将其属性从一种形式转化为另外一种.然而,spring用户并不直接与这些类打交道,然是经过XML bean定义,注解组件(用@Component,@Controller标记的类),或者基于Java的@Configuration类里的@Bean方法来使用它们.这些资源将被相互转化为BeanDefinition的实例,并能够用来加载一个完整的Spring ioc的实例.

基于构造器仍是基于setter的依赖注入

既然你能够同时使用基于构造器和基于setter的依赖注入,那么最好的作法就是使用构造器来加载强制依赖,用setter方法或配置方法来加载可选依赖.记住在setter方法上加上@Required注解,就可使一个属性变为强制依赖.

spring团队原则上主张构造器注入,它能够保证一个应用组件实现做为一个不可变对象并保证全部的强制依赖不为空.另外,构造器注入组件老是返回客户端(调用)代码在一个彻底初始化的状态.另外一方面也说明,大量的构造其参数是一个糟糕的气味,暗示这个类有太多的职责,应该分离一些其关注点并改造其为更好的专一.

Setter注入通常只用于可选依赖,这样它能在类里分配到合理的默认值.另外,在使用这些依赖的任何地方都必需要非空检验.好处之一,能够对该类的对象进行重构或从新注入.经过JMX MBean来管理是Setter注入一个很是好的实践.

使用UI的方式要具体分析.有时,咱们要使用没有资源的第三方的类,对你来讲选择以肯定.例如,若是一个第三方的类没有暴露set方法,那么构造器注入就是依赖注入惟一的形式.

依赖注入解决步骤(Dependency resolution process)

容器对bean依赖的解决步骤以下:

  • ApplicationContext读取描述因此beans的配置元数据进行新建和实例化.配置元数据能够经过xml,java代码,注解来定义

  • 对于每一个bean,若是你不用正常的构造器时,它的依赖能够以属性,构造器参数,或者静态工厂方法参数的形式表现.当bean被正式建立时,这些依赖会被提供给bean.

  • 每一个属性或构造器参数能够定义注入的具体值,也能够定义为容器中的一个引用.

  • 每一个属性或者构造器参数的值都是经过特定形式来转化为参数或构造器的实际类型.默认的spring会把string类型定义的值转化为各类内置类型,如int,long,String,boolean等.

在spring容器建立时spring就会验证各个bean的配置.单例或预安装bean会随容器一块儿建立.做用域查看7.5"bean Scope".不然,bean只有当被请求时才会建立.一个bean的建立潜在的会引发一组bean的建立,由于在此时bean的依赖和bean依赖的依赖都会建立.第一次建立bean时,这些依赖的解决会显稍后显示.

循环依赖

若是你主要使用了构造器依赖,这将会产生没法解决的循环依赖问题. 例如,类A须要类B的一个实例且类B须要类A的实例,且类B须要类A的实例.若是你配置类A和类B相互注入,那么spring ioc容器就会判断其在运行时为循环依赖.并会抛出一个BeanCurrentlyInCreationException.

一个解决方案是将一些类的资源代码配置为Setter注入而不是构造器注入.另外,避免只是用构造器依赖和只是用setter依赖.换而言之,尽管没有被明确指出,但你仍是能够把循环依赖配置为setter依赖.

不一样于其余典型状况(没有循环依赖),一个beanA和Bean B间的循环依赖会强制其中一个bean在注入到其余Bean以前先完成自身的实例化(一个典型的鸡和蛋的问题)

你能够认为spring所作的都是正确的.它能够在容器加载时检测配置问题,如未存在的bean的引用,以及循环依赖.当bean实际上已经建立了,那么spring会尽可能晚的去设置属性和解决依赖.当你请求一个对象在建立对象或其中一个依赖时出现问题,已正确加载的spring容器稍后会产生一个异常.例如,若是一个属性缺失或无效,这个bean就会抛出异常.由于ApplicationContext是由前置实例化单例化beans实现的,因为配置问题可能会潜在的下降可见性.有建立这些前置单例Bean会花费一些时间和内存,因此当ApplicationContext建立时你会发现配置问题,而不是以后.你也能够重写这个默认的行为,这样单例Bean就会懒加载,而不是前置实例化.

若是没循环依赖存在,当有一个或多个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的定义.

<bean id="exampleBean" class="examples.ExampleBean">
			<!-- setter injection using the nested ref element -->
			<property name="beanOne">
				<ref bean="anotherExampleBean"/>
			</property>

			<!-- setter injection using the neater ref attribute -->
			<property name="beanTwo" ref="yetAnotherBean"/>
			<property name="integerProperty" value="1"/>
		</bean>

		<bean id="anotherExampleBean" class="examples.AnotherBean"/>
		<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
		```
java代码以下:
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文件中指定的属性.下面的例子都是使用基于构造器的依赖注入:

<bean id="exampleBean" class="examples.ExampleBean">
			<!-- constructor injection using the nested ref element -->
			<constructor-arg>
				<ref bean="anotherExampleBean"/>
			</constructor-arg>

			<!-- constructor injection using the neater ref attribute -->
			<constructor-arg ref="yetAnotherBean"/>

			<constructor-arg type="int" value="1"/>
		</bean>

		<bean id="anotherExampleBean" class="examples.AnotherBean"/>
		<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

java代码以下

public class ExampleBean {

			private AnotherBean beanOne;
			private YetAnotherBean beanTwo;
			private int i;

			public ExampleBean(
				AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
				this.beanOne = anotherBean;
				this.beanTwo = yetAnotherBean;
				this.i = i;
			}

		}

bean定义里的构造器参数能够用于ExampleBean的构造器的参数. 如今能够认为是上面例子的变种,替换使用构造器,spring介绍了一种静态工厂方法来返回对象的实例

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
		<constructor-arg ref="anotherExampleBean"/>
		<constructor-arg ref="yetAnotherBean"/>
		<constructor-arg value="1"/>
		</bean>

		<bean id="anotherExampleBean" class="examples.AnotherBean"/>
		<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

静态工厂方法类:

public class ExampleBean {

			// a private constructor
			private ExampleBean(...) {
				...
			}

			// a static factory method; the arguments to this method can be
			// considered the dependencies of the bean that is returned,
			// regardless of how those arguments are actually used.
			public static ExampleBean createInstance (
				AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

				ExampleBean eb = new ExampleBean (...);
				// some other operations...
				return eb;
			}

		}

静态工厂方法的参数经过<constructor-arg>标签来提供,和构造器实际使用的是同样的.静态工厂方法返回的类型没有必要和拥有静态工厂方法的类的类型一致,虽然上个例子是一致的.实例工厂方法使用了一致的方法(除了使用factory-bean属性来替代class属性),因此细节不在这讨论了.

7.4.2 依赖和配置细节

如上节所示,你能够将bean的属性和构造器参数定义为其余被管理bean的引用,或者是内联的bean.spring 基于xml的配置元数据支持使用<property/>和<constructor-arg/>来实现目标.

直接定义(基本类型,String等)

经过<property/>标签中的value元素来指定属性或构造器参数为一个可读的string形式.spring的conversion service用来转换这些值从string类型到属性或参数的实际值.

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
			<!-- results in a setDriverClassName(String) call -->
			<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
			<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
			<property name="username" value="root"/>
			<property name="password" value="masterkaoli"/>
		</bean>

下面是用p-namespace来实现更加简洁的xml配置

<beans xmlns="http://www.springframework.org/schema/beans"
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			xmlns:p="http://www.springframework.org/schema/p"
			xsi:schemaLocation="http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans.xsd">

			<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
				destroy-method="close"
				p:driverClassName="com.mysql.jdbc.Driver"
				p:url="jdbc:mysql://localhost:3306/mydb"
				p:username="root"
				p:password="masterkaoli"/>

		</beans>

上面的例子更加的简洁.可是编码的错误只有在运行时才会发现而不是在编译时,除非你有好的IDE工具例如IntelliJ IDEA或者Spring Tool Suite ,当你建立bean定义时,它们支持自动属性补全.这些IDE工具的帮助是很是重要的.

<values/>标签

固然你能够这样定义java.util.Properties的属性实例:

<bean id="mappings"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

		<!-- typed as a java.util.Properties -->
		<property name="properties">
			<value>
				jdbc.driver.className=com.mysql.jdbc.Driver
				jdbc.url=jdbc:mysql://localhost:3306/mydb
			</value>
		</property>
	</bean>

spring容器经过JavaBeans的PropertyEditor机制将<value/>标签里的文本内容转化为java.util.Properties的实例.这是一个漂亮的简化,在不少地方spring团队用<value/>标签来代替value属性.

idref属性

idref元素用于将容器中其余bean的id(值是字符串,而不是引用)传给一个<constructor-arg/>或<property>标签.

<bean id="theTargetBean" class="..."/>

	<bean id="theClientBean" class="...">
		<property name="targetName">
			<idref bean="theTargetBean" />
		</property>
	</bean>

上面的bean定义片断很是等同于下面的片断;

<bean id="theTargetBean" class="..." />

	<bean id="client" class="...">
		<property name="targetName" value="theTargetBean" />
	</bean>

第一种形式优于第二种,由于使用idref标签容许容器在部署期间验证其引用的bean是否存在.第二种形式,不会对client Bean的targetName属性的值作验证.错误的代码只有在Client Bean被彻底初始化是才被发现(伴随很是糟糕的结果).若是client bean是原型bean的话,那么这个错误和异常可能只有再容器部署很长一段时间后才会被发现.

ref元素上的local属性在4.0 Bean xsd以后再也不支持,由于它不能为一个普通的bean提供值.若是你要升级到4.0,那么你就将你的idref local引用修改完idref bean属性.

一个共同的地方(至少早于spring2.0):<idref/>元素返回值是由ProxyFactoryBean的bean定义里的AOP interceptors配置的.当你使用<idref/>元素时,只要你指定拦截器的名字,即便拼错拦截器的id也没有问题.

对于其余bean的引用(协同)

ref元素是<constructor-arg/>或<property/>的最后一个元素.这里米能够设置一个bean里的特定元素,使其指向其余被管理的bean.引用bean是那个属性要设置的bean的依赖.在其属性设置以前它将初始化.若是该依赖是单例Bean,那么它在容器建立时就会初始化.全部的引用都指向其余的对象.做用域和验证依赖于你经过bean,local,或者parent指定的其余对象的id/name.

通常经过bean属性里的<ref/>标签来指定目标bean;能够指定在同一容器或父容器里的bean对象,而无论它是否在同一xml文件里.bean属性的值能够和目标bean的id元素同样,或者是目标bean里name属性的值之一.

<ref bean="someBean"/>

指定目标bean能够经过parent属性来建立一个关于在父容器或当前容器bean的引用.当你要使用父容器里的bean,且本身要定义的bean的id和父容器的bean相同时,你可使用parent标签.

这是父容器里的东西

<!-- in the parent context -->

		<bean id="accountService" class="com.foo.SimpleAccountService">
			<!-- insert dependencies as required as here -->
		</bean>

这是子容器里的东西

<!-- in the child (descendant) context -->

<bean id="accountService" <!-- bean name is the same as the parent bean -->
			class="org.springframework.aop.framework.ProxyFactoryBean">
			<property name="target">
				<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
			</property>
			<!-- insert other configuration and dependencies as required here -->
</bean>

内部bean

在<property/>或<constructor-arg/>里的元素<bean/>定义被称为内部bean.

<bean id="outer" class="...">
		<!-- instead of using a reference to a target bean, simply define the target bean inline -->
		<property name="target">
			<bean class="com.example.Person"> <!-- this is the inner bean -->
				<property name="name" value="Fiona Apple"/>
				<property name="age" value="25"/>
			</bean>
		</property>
	</bean>

一个内部bean不须要id或name属性,即便设置了,容器也会忽略.容器也会忽略其scope标志,内部bean通常是匿名的,它们和外部的bean一块儿建立.你不可能将内部bean注入到非外部bean以外的bean,你也不能单独访问内部bean.

做为特例,内部bean也可能从自定义做用域里收到销毁回调.例如,一个请求做用域的内部bean被一个单例bean持有,内部bean的建立会管理其持有bean,但销毁回调容许他参与到请求做用域的生命周期中.这只是个特例.通常内部bean的做用域和其持有bean相同.

集合

在<list/>,<set/>,<map/>,<props/>元素中,你可用直接设置属性和构造器参数中的集合类型,如List,Set,Map以及Properties.

<bean id="moreComplexObject" class="example.ComplexObject">
		<!-- results in a setAdminEmails(java.util.Properties) call -->
		<property name="adminEmails">
			<props>
				<prop key="administrator">administrator@example.org</prop>
				<prop key="support">support@example.org</prop>
				<prop key="development">development@example.org</prop>
			</props>
		</property>
		<!-- results in a setSomeList(java.util.List) call -->
		<property name="someList">
			<list>
				<value>a list element followed by a reference</value>
				<ref bean="myDataSource" />
			</list>
		</property>
		<!-- results in a setSomeMap(java.util.Map) call -->
		<property name="someMap">
			<map>
				<entry key="an entry" value="just some string"/>
				<entry key ="a ref" value-ref="myDataSource"/>
			</map>
		</property>
		<!-- results in a setSomeSet(java.util.Set) call -->
		<property name="someSet">
			<set>
				<value>just some string</value>
				<ref bean="myDataSource" />
			</set>
		</property>
	</bean>

map的key或value,set的value值,能够是如下元素:

bean,ref,idref,list,set,map,props,value,null

集合合并

spring 容器支持集合合并.应用开发者能够定义一个父风格的<list/>,<set/>,<map/>,<props>元素,能够有子风格的<list/>,<map/>,<set/>,<props/>元素继承并重写付集合的值.子集合的值是合并父集合和子集合值的结果,子集合的元素之能够重写指定的父集合的值. 这部分讨论关于父子bean的机制.

<beans>
		<bean id="parent" abstract="true" class="example.ComplexObject">
			<property name="adminEmails">
				<props>
					<prop key="administrator">administrator@example.com</prop>
					<prop key="support">support@example.com</prop>
				</props>
			</property>
		</bean>
		<bean id="child" parent="parent">
			<property name="adminEmails">
				<!-- the merge is specified on the child collection definition -->
				<props merge="true">
					<prop key="sales">sales@example.com</prop>
					<prop key="support">support@example.co.uk</prop>
				</props>
			</property>
		</bean>
	<beans>

能够看到子Bean的<props/>标签里添加了"merger=true"属性.当子Bean被容器释放和实例化,其结果实例中的adminEmails属性将包含子Bean中adminEmails集合和父Bean中adminEmails集合.

administrator=administrator@example.com
	sales=sales@example.com
	support=support@example.co.uk

子Bean中的Properties集合即支持从父Bean的<props/>继承属性,也支持子Bean重写父Bean的集合元素.
这个合并行为一样适用于<list/>,<map/>,<set/>等集合类型.可是有list的语法的做用,List里的父Bean的元素的顺序优先于子Bean的顺序.其余类型如Map,Set,Properties,由于没有排序的存在,因此也不会对Map,Set,Properties的实现类的排序形成影响.

集合合并的缺点

  • 1.不能合并不一样的集合类型
  • 2.merger属性只能用在子类,低级,继承的Bean的定义里.在父Bean里使用无效

强类型集合

1.5的泛型以后,咱们可使用强类型的集合.你能够申明一个只有String类型元素的集合.若是你要使用强类型的依赖注入的集合到一个bean里,你能够利用spring 的类型转化,在注入到集合以前将其转化合适的类型.

public class Foo {

		private Map<String, Float> accounts;

		public void setAccounts(Map<String, Float> accounts) {
			this.accounts = accounts;
		}
	}

xml配置:

<beans>
		<bean id="foo" class="x.y.Foo">
			<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>

当foo Bean里的account属性准备注入时,其属性的类型Map<String,Float>中的元素的泛型信息会经过反射得到.这样spring的类型转化会了解到其各类值元素都是Float类型,并把String类型的值9.99等转化为Float类型.

Null和空的字符串值

spring将空的属性参数视为空字符串.下面的xml配置元数据片断,将Email属性的值设为空的字符串.

<bean class="ExampleBean">
		<property name="email" value=""/>
	</bean>

等同于如下java代码:

exampleBean.setEmail("")

<null/>标签将会认为是null值.以下:

<bean class="ExampleBean">
		<property name="email">
			<null/>
		</property>
	</bean>

上面的配置等价于下面的代码:

exampleBean.setEmail(null)

XML关于p-namespace的简化

p-namespace能够替代<property/>标签,来描述某个属性值或协助bean. spring支持对namespacce的扩展配置格式,只要基于XML Schema定义.beans的配置格式会有专章介绍.可是p-namespace不存在与XSD文件里,只存在于spring 核心中.你须要设置P标签的引用(xmlns:p="http://www.springframework.org/schema/p") 下面的两个xml片断会有相同的结果:第一个使用XML格式,第二个用的是p-namespace.

<beans xmlns="http://www.springframework.org/schema/beans"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:p="http://www.springframework.org/schema/p"
		xsi:schemaLocation="http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans.xsd">

		<bean name="classic" class="com.example.ExampleBean">
			<property name="email" value="foo@bar.com"/>
		</bean>

		<bean name="p-namespace" class="com.example.ExampleBean"
			p:email="foo@bar.com"/>
	</beans>

这个例子说的是在bean定义中一个属性叫p-namespace.由于p-namespace没有一个schema定义,因此你能够把属性的名字设置其上. 下面的例子是多个bean且有其余bean的引用

<beans xmlns="http://www.springframework.org/schema/beans"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:p="http://www.springframework.org/schema/p"
		xsi:schemaLocation="http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans.xsd">

		<bean name="john-classic" class="com.example.Person">
			<property name="name" value="John Doe"/>
			<property name="spouse" ref="jane"/>
		</bean>

		<bean name="john-modern"
			class="com.example.Person"
			p:name="John Doe"
			p:spouse-ref="jane"/>

		<bean name="jane" class="com.example.Person">
			<property name="name" value="Jane Doe"/>
		</bean>
	</beans>

如你所见,p-namespace不只能够设置一个属性值,也能够设置bean引用(用p:name-ref属性).

  • 该格式不是标准的XML格式.例如,这种格式宣布属性引用和Ref标签引用冲突.所以,咱们建议你仔细选择方法,并与团队成员交流.

c-namespace带来的xml简化

同上一节相同,但本节是3.1以后引入的.运行用相同的方式,能够替代constructor-arg元素.你须要设置c标签的引用(xmlns:c="http://www.springframework.org/schema/c")

一个是用c:namespace的例子:

<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
			http://www.springframework.org/schema/beans/spring-beans.xsd">

		<bean id="bar" class="x.y.Bar"/>
		<bean id="baz" class="x.y.Baz"/>

		<!-- traditional declaration -->
		<bean id="foo" class="x.y.Foo">
			<constructor-arg ref="bar"/>
			<constructor-arg ref="baz"/>
			<constructor-arg value="foo@bar.com"/>
		</bean>

		<!-- c-namespace declaration -->
		<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

	</beans>

从上例中能够看出,使用c:name来设置属性值,使用c:name-ref来设置依赖. 有时构造器参数没法获取(一般是子节码没有调试信息的编译),你可使用参数顺序.

<!-- c-namespace index declaration -->
	<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

复合属性名称

你可使用复合或关联属性名称来设置bean的属性,只要左后全部须要的路径组件属性不为空便可.以下定义:

<bean id="foo" class="foo.Bar">
		<property name="fred.bob.sammy" value="123" />
	</bean>

解释:在foo.Bar类下,有一个名为fred的属性,fred里有个bob的属性,bob里有一个sammy的属性,要给sammy设置123的值.注意,实例化foo Bean以后,fred属性,和bob属性必须不为空才行.

使用depends-on

若是一个bean被某个bean做为依赖,那么表示该Bean是某个Bean的属性.通常你可用使用<ref/>标签来完成它.可是,有时候依赖间没有直接联系,例如,一个静态的初始器须要被触发,好比数据库引擎的注册.使用depends-on可用强迫一个或多个bean先于该Bean被初始化.下面的例子是用depends-on属性来变形对一个单例Bean的依赖:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
	<bean id="manager" class="ManagerBean" />

使用beanOne以前会强制加载manager Bean.若是依赖是多个bean,则用逗号,分号,空格隔开.

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
		<property name="manager" ref="manager" />
	</bean>

	<bean id="manager" class="ManagerBean" />
	<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

这样就强制依赖了两个bean.

depends-on属性还能够指定相应销毁时的依赖.该依赖只针对于singleton bean这样的depends-on,它们的依赖先于singleton bean销毁.

7.4.4 懒加载bean

通常全部的单例bean会在ApplicationContext实现容器初始化时加载.基本上,这种前置实例化是好的,由于在配置或周边环境里的错误能够当即发现,而不是几个小时甚至几天之后.当这个行为不理想时,咱们能够将该Bean定义为懒记载(lazy-initialized).懒加载的bean能够告诉容器在第一次被请求时建立该bean的实例,而不是启动时.

在xml里,如此在<bean/>里配置lazy-init属性:

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
	<bean name="not.lazy" class="com.foo.AnotherBean"/>

当该配置被ApplicationContext读取时,命名为lazy的bean不会急切的在容器加载时提早初始化. 可是当它被其余bean依赖时,该配置无效,由于容器须要初始化它来建立其余的bean. 咱们可使用default-lazy-init属性来控制整个容器的初始化;例如:

<beans default-lazy-init="true">
		<!-- no beans will be pre-instantiated... -->
	</beans>

7.4.5 自动装配协助者

spring容器能够自动装配协助beans之间的关系.你能够容许spring经过检测ApplicationContext的内容来为你的bean自动释放合做者.

自动装配有如下好处:

  • 1.明显减小对指定属性和构造器参数的须要.

  • 2.当你对象改变时,autowiring能够自动更新设置.在开发时,它会很是有用,当代码稳定时你可改成显示配置.

当使用基于xml的配置元数据时,你能够在<bean/>里的autowire来指定bean的自动装配的模式.自动装配有四种模式,简介以下;

no  不自动装配
byName  经过bean的name属性来自动装配,
byType  经过bean的Type来装配,若是有多个bean,会抛异常;没找到则不设置.
constructor  与byType类似,只适用于构造器参数.若是没准确的值,就会抛出错误.

在byType和constructor模式下,你能够装配数组或者强类型集合.这种状况下,容器里全部的匹配类型都会来适配这个依赖.若是你的期待类型是string,你也能够选择强类型Map.一个自动装配的Map的值能够由全部符合指望类型的实例组成,那么Maps的key值会包含相应的bean的名字. 你能够在自动装配完成后进行验证.

自动装配的局限和缺点

自动装配最好在你的项目里所有使用.若是自动装配基本不使用或只使用一两个bean定义,它会困扰开发者. 缺点及局限以下:

  • 1.属性和构造参数明确的依赖会重写自动装配.另外,你不能自动装配简单属性,例如原生的,String,或者class(或者简单类型的数组).这个是设计局限

  • 2.自动装配没有显示装配精确.spring已经很当心的避免有歧义带来的猜想,由于这会带来难以预料的后果.

  • 3.文档生成工具没法从spring容器中获取装配信息.

  • 4.容器中的混合bean定义可能要匹配构造参数或setter方法来注入.对于数组,集合,Maps来讲,这不是一个问题.但对于单个值的依赖来讲,这种歧义却没法消除.若是没有明确的bean定义,异常会抛出.

你有如下选择:

  • 放弃自动注入使用显示注入;
  • 在bean中设置autowire-candidate属性为false,避免该bean自动装配
  • 在<bean>的定义上添加primary属性为true,
  • 使用基于注解配置实现更加细粒度的控制

将bean排除在自动配置以外

在提早实例化bean的基础上,能够将bean排除自动装配.spring xml格式中,能够将autowire-candidate属性设置为false,容器会使特定的bean不能够用于自动装配(包括注解配置好比@Autowired)

你也能够对bean的名字进行匹配,在顶层<beans/> 元素中,使用default-autowire-candidates属性,接受一个或多个模式. 这个方式是在自动装配获取名单中排除该bean.该bean自己仍是能够用自动装配进行初始化的

7.4.6 方法注入

大多数状况,spring的bean都是单例bean.当一个单例bean同其余单例bean协做,一个非单例bean同非单例bean协做,你通常会把该依赖设置为bean的某个属性.可是当bean的生命周期不一样就会有问题,一个protype的bean不可能每次都给一个singleton的bean一个new 实例.

用如下方式解决

lookup 方法注入

lookup的原理是经过cglib动态代理产生子类,来覆盖父类的方法

建立一个抽象类,将工厂方法设为抽象

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();
	}

xml文件配置

<!-- a stateful bean deployed as a prototype (non-singleton) -->
	<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
		<!-- inject dependencies here as required -->
	</bean>

	<!-- commandProcessor uses statefulCommandHelper -->
	<bean id="commandManager" class="fiona.apple.CommandManager">
		<lookup-method name="createCommand" bean="command"/>
	</bean>

commandManager bean里使用lookup-method标签,name是要查找的方法,bean是返回的实例的依赖Bean.本例中每次调用createCommand方法都会返回一个新的command的bean.若是你把其做用域不设为prototype,每次都会返回新的command实例.

原理为spring ioc会自动编译,若是方法是抽象的,就会实现该方法;若是方法不抽象,就重写该方法.

任意方法替换

这个方法可能比lookup方法注入要稍微差点,但仍能够替换任意方法.用户能够忽略全部不须要的方法,只使用实际须要的功能.

基于xml的配置元数据,对于一个已部署的bean,你可使用replaced-method元素去替换一个已存在的方法实现.在下面的class中,一个方法computeValue,这个咱们想要重写的:

public class MyValueCalculator {

		public String computeValue(String input) {
			// some real code...
		}

		// some other methods...

	}

该接口的实现类以下:

/**
	 * 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 定义原生class和指定方法重写以下:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
		<!-- arbitrary method replacement -->
		<replaced-method name="computeValue" replacer="replacementComputeValue">
			<arg-type>String</arg-type>
		</replaced-method>
	</bean>

	<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

你能够用<arg-type/>元素来定义方法签名以指定哪一个方法你须要重写.当类里有方法重载且有变种时,参数签名会很是重要.为了方便,参数类型的字符串能够是其全路径名的一部分.例如,一下都适配java.lang.String: java.lang.String String Str 由于参数的数目一般足够用来区分每一个可能的选择,这个简化能够节省不少打字,你能够经过打更少的字来匹配参数类型.

相关文章
相关标签/搜索