2019独角兽企业重金招聘Python工程师标准>>>
7.核心容器
7.1spring ioc容器和beans介绍
本章简单介绍spring 对于ioc原则的实现.IOC也被称为DI.这是一个过程,通过对象来定义他们的依赖(指那些需要和他们一起工作的对象),并且只能通过构造器参数,工厂方法参数,或者被构造器或被工厂方法返回实例后进行属性设置.(其实是bean被设置)这个过程是反转的基础.控制反转,在于bean自己控制它的依赖的实例化或直接通过类的构造器定位他们,又或者通过Service Locator pattern(服务定位模式)的机制来定义.
org.springframework.beans和org.springframework.context包是spring ioc容器的基础.beanFactory接口提供了高级的配置来管理任何种类的对象.ApplicationContext是beanFactory的一个下属接口.它实现了对spring Aop功能的简单集成,消息资源的处理(国际化中使用),事件发布,以及应用层次的特定上下文(例如,WebApplicationContext就是在web应用中使用的).
简而言之,BeanFactory提供了配置框架和基础功能,ApplicationContext添加了许多企业定制的功能.ApplicationContext是beanFactory的完全超集,她的用法有特定的介绍.要使用BeanFactory来取代ApplicationContext,查阅7.16部分.
在spring中,bean如此定义:组成你应用基础并被spring ioC容器管理的对象;一个bean是一个可以被实例化,被组装,且能被Spring ioc 容器管理的一个对象.另外,一个bean是你应用中许多对象的一个简单抽象.beans,还有在Beans中的依赖,都通过一个容器在configuration元数据反应出来.
7.2 容器简介
org.springframework.context.ApplicationContext接口实现了spring ioc 容器,主要负责之前提到的beans实例化,配置,组装.容器得到自己的指令去实例化,配置,以及读取配置元数据进行组装. 配置元数据可以是xml,java注解,java代码.它允许你表达组成应用的对象,以及这些对象之间复杂的关系;
spring提供了ApplicationContext的几种开箱即用的实现.在单独的应用中,它可以轻易的创建ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext的实例.Xml是定义配置元数据的传统格式,然你可以使用java注解或代码来左右元数据格式来构造你的容器,当然这需要你提供少量的xml配置来申明对这些额外的元数据格式的支持.
在大部分的应用场景里,显示的用户代码不需要你去实例化一个或多个spring IOC 容器.例如,在一个web场景里在应用的web.xml文件里的一个简单的八行左右的典型的web描述XML将足够使用的.如果你使用STS的开发环境,那么这些配置将轻易的通过一些鼠标点击和键盘事件来实现.
下面的图表是spring工作流程的高水平视图.你的类可以通过配置元数据来进行组装,因为当你的ApplicationContext创建和初始化后,你将会拥有一个全面的配置和可执行的系统或应用;
7.2.1 配置元数据
如前面的流程图所示,spring ioc 容器消费了一系列的配置元数据.这个配置元数据体现出你作为一个应用开发者如何在你的应用中使spring容器来实例化,配置并装备你应用中的对象的.
配置元数据是在一个简单直观的xml格式中是传统的生产者,本章中大部分内容都在讲述这个spring ioc 容器的核心概念功能;
备注:XML-based 元数据不是配置元数据的唯一格式;spring IOC 容器的配置和书写方式实际上是完全解耦的.最近很多开发者使用 基于Java的配置来配置他们的spring应用.
- 对于使用其他形式来配置spring容器,查看 Annotaion-based configuration:spring 2.5以后支持, 一种是基于java配置,.
从spring3.0开始,许多Spring JavaConfig project提供的功能成为了spring 核心框架的一部分.因此你可以使用java而不是xml文件来定义你项目文件中额外的bean.要实现这些新功能,请查看@Configuration,@Bean,@Import和@DependsOn注解.
spring 配置至少由一个bean或者多个bean定义(他们由容器来管理);基于xml的配置会在
与这些bean定义的真正对象组成了你的应用.一般你会定义服务层对象,数据访问层对象(DAOs),表现层对象,例如Struts 的action实例,框架对象如Hibernate 的SessionFactories,JMS Queues等等.一般而言,应用不会再容器里配置细粒度的领域对象,因为创建和加载这些领域对象是DAOs和服务逻辑层的责任.然而,你可以使用spring 的AspectJ来在IOC 容器之外来配置对象.查看 Using AspectJ to dependency-inject domain objects with Spring.
- 这个是基本的基于XML配置的元数据
- 这个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";
-
下个例子是服务层对象的配置文件:
-
下个例子是在daos.xml文件里定义数据访问对象
- 在上述例子中,一个服务层是由PetStoreServiceImpl类,两个DAO类--JpaAccoutDao和JPaItemDao(基于JPA ORM映射标准)组成.这个property元素的name指向JavaBean属性里的name,其中的ref属性指的是其他bean定义类的名称.id和ref元素之间的关系表示协作对象之间的依赖.配置对象依赖的细节,查看Dependencies; ###组装 基于xml的配置元数据
- 跨越多个Xml文件来定义bean将非常有效.通常每个独立的XMl定义文件代表一个逻辑层或你架构中的一个模块. 你可以使用应用上下文构造器从所有的xml碎片中加载bean定义.这个构造器可以获得混合资源的位置.一种如上节所示.另一种,使用一种或多个
元素去从其他文件里加载bean定义.例如
在上述的例子中,额外的bean定义从以下三个额外文件加载--services.xml,messageSource.xml,themeSource.xml.所有的定位路径都与定义文件的引用相关,所以引用时services.xml文件必须在相同路径或者在classpath位置下,而messageSource.xml和themeSource.xml必须在resource位置下.如你所见,反斜杠被忽略了,你可以给定相对路径,这比以前要好的多.要引入的文件内容,必须包含最高级的
-
备注:有个可以但不推荐的做法,在父路径引用文件应该加上相对路径"../".这样做会创建一个独立于当前应用的依赖文件.特别,这个方法不推荐在classpath中使用,当运行方案过程选择最近的classpath根路径,然后查找它的父路径.classpath路径配置改变可能会导致错误的结果.
你可以使用全资源路径来替代相对路径.例如,使用"file:c:/config/services.xml"或者"classpath:/config/services.xml".但是你最好使用抽象位置路径,
7.2.3 使用容器
ApplicationContext是一个先进工厂来管理一系列不同的beans和她们的依赖的注册器.使用方法 T getBean(String name,Class
- 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 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行为的配置元素,它描述了在容器中这个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配置元数据中,你可以使用
在此情况下,在同一容器中一个bean被命名为fromName,但在使用别名之后,也可以用toName来指向它.
例如,子系统A中配置元数据可能指向一个名为subsystemA-dataSource的数据源.而配置在子系统B中的通过subsystemB-dataSource来指向一个数据源.当你编译主系统时,主系统可以通过myApp-dataSource来指向这个数据源.这样,就有三个指向相同对象的名字被你添加到了MyApp配置元数据里了.其别名定义如下:
现在每个组件和主应用都可以通过唯一的名字来引用一个相同的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:
关于如何将参数提供到构造器的机制和对象构造之后如何设置实例属性,参见 Injecting Dependencies ;
静态工厂方法实例化
当你要用静态方法工厂来创建的定义bean时,你需要在class属性里指定一个包含了静态工厂方法的类,在factory-method方法里指定该工厂方法.你可用调用该方法并返回一个实例,随后你可以像一个构造器造出的bean一样来使用它了.一个人使用这种bean的定义方式,主要是想在逻辑代码里调用静态工厂.
接下来的bean定义说明,bean会被通过调用工厂方法来创建.这个定义不会指定返回对象的类型,只有哪个类包含有工厂方法.在本例中,这个 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方法里添上实例工厂方法.
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private DefaultServiceLocator() {}
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类可以包含多个工厂方法,如下使用
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类之间无继承关系.因此在剩下的配置工作中,你不需要在
当其他类型被引用,且类型已知,那么匹配可以进行.当一个简单的类型被使用时,例如
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
在下面的场景里,容器可以使用简单的类型进行类型匹配,如果你使用了type属性明确的指定构造器的参数类型.例如:
使用index来指明构造器参数明确的加载顺序,如:
另外,消除简单类型值的歧义,如果一个构造器有两个相同类型的参数,可以指定其下标index来消除歧义.记住下标从0开始. 你可用使用构造器参数名来消除歧义.
请记住,为了能够开箱即用,你的代码编译时必须打开调试模式,所以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的定义.
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文件中指定的属性.下面的例子都是使用基于构造器的依赖注入:
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介绍了一种静态工厂方法来返回对象的实例
静态工厂方法类:
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;
}
}
静态工厂方法的参数通过
下面是用p-namespace来实现更加简洁的xml配置
上面的例子更加的简洁.但是编码的错误只有在运行时才会发现而不是在编译时,除非你有好的IDE工具例如IntelliJ IDEA或者Spring Tool Suite ,当你创建bean定义时,它们支持自动属性补全.这些IDE工具的帮助是非常重要的.
####
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
spring容器通过JavaBeans的PropertyEditor机制将
idref属性
idref元素用于将容器中其他bean的id(值是字符串,而不是引用)传给一个
上面的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):
一般通过bean属性里的标签来指定目标bean;可以指定在同一容器或父容器里的bean对象,而不管它是否在同一xml文件里.bean属性的值可以和目标bean的id元素一样,或者是目标bean里name属性的值之一.
指定目标bean可以通过parent属性来创建一个关于在父容器或当前容器bean的引用.当你要使用父容器里的bean,且自己要定义的bean的id和父容器的bean相同时,你可以使用parent标签.
这是父容器里的东西
这是子容器里的东西
class="org.springframework.aop.framework.ProxyFactoryBean">
###内部bean 在
一个内部bean不需要id或name属性,即使设置了,容器也会忽略.容器也会忽略其scope标志,内部bean一般是匿名的,它们和外部的bean一起创建.你不可能将内部bean注入到非外部bean之外的bean,你也不能单独访问内部bean.
作为特例,内部bean也可能从自定义作用域里收到销毁回调.例如,一个请求作用域的内部bean被一个单例bean持有,内部bean的创建会管理其持有bean,但销毁回调允许他参与到请求作用域的生命周期中.这只是个特例.一般内部bean的作用域和其持有bean相同.
####集合 在,
[email protected]
[email protected]
[email protected]
just some string
map的key或value,set的value值,可以是以下元素:
bean,ref,idref,list,set,map,props,value,null
####集合合并 spring 容器支持集合合并.应用开发者可以定义一个父风格的,
,,
[email protected]
[email protected]
[email protected]
[email protected]
可以看到子Bean的
[email protected]
[email protected]
[email protected]
子Bean中的Properties集合即支持从父Bean的
这个合并行为同样适用于,,
集合合并的缺点
- 1.不能合并不同的集合类型
- 2.merger属性只能用在子类,低级,继承的Bean的定义里.在父Bean里使用无效
####强类型集合 1.5的泛型之后,我们可以使用强类型的集合.你可以申明一个只有String类型元素的集合.如果你要使用强类型的依赖注入的集合到一个bean里,你可以利用spring 的类型转化,在注入到集合之前将其转化合适的类型.
public class Foo {
private Map accounts;
public void setAccounts(Map accounts) {
this.accounts = accounts;
}
}
xml配置:
当foo Bean里的account属性准备注入时,其属性的类型Map
Null和空的字符串值
spring将空的属性参数视为空字符串.下面的xml配置元数据片段,将Email属性的值设为空的字符串.
等同于以下java代码:
exampleBean.setEmail("")
上面的配置等价于下面的代码:
exampleBean.setEmail(null)
####XML关于p-namespace的简化
p-namespace可以替代
这个例子说的是在bean定义中一个属性叫p-namespace.因为p-namespace没有一个schema定义,所以你可以把属性的名字设置其上. 下面的例子是多个bean且有其他bean的引用
如你所见,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的例子:
从上例中可以看出,使用c:name来设置属性值,使用c:name-ref来设置依赖. 有时构造器参数无法获取(通常是子节码没有调试信息的编译),你可以使用参数顺序.
####复合属性名称 你可以使用复合或关联属性名称来设置bean的属性,只要左后所有需要的路径组件属性不为空即可.如下定义:
解释:在foo.Bar类下,有一个名为fred的属性,fred里有个bob的属性,bob里有一个sammy的属性,要给sammy设置123的值.注意,实例化foo Bean之后,fred属性,和bob属性必须不为空才行.
####使用depends-on 如果一个bean被某个bean作为依赖,那么表示该Bean是某个Bean的属性.一般你可用使用标签来完成它.但是,有时候依赖间没有直接联系,例如,一个静态的初始器需要被触发,比如数据库引擎的注册.使用depends-on可用强迫一个或多个bean先于该Bean被初始化.下面的例子是用depends-on属性来变形对一个单例Bean的依赖:
使用beanOne之前会强制加载manager Bean.如果依赖是多个bean,则用逗号,分号,空格隔开.
这样就强制依赖了两个bean.
depends-on属性还可以指定相应销毁时的依赖.该依赖只针对于singleton bean这样的depends-on,它们的依赖先于singleton bean销毁.
####7.4.4 懒加载bean
一般所有的单例bean会在ApplicationContext实现容器初始化时加载.基本上,这种前置实例化是好的,因为在配置或周边环境里的错误可以立即发现,而不是几个小时甚至几天以后.当这个行为不理想时,我们可以将该Bean定义为懒记载(lazy-initialized).懒加载的bean可以告诉容器在第一次被请求时创建该bean的实例,而不是启动时.
在xml里,如此在
当该配置被ApplicationContext读取时,命名为lazy的bean不会急切的在容器加载时提前初始化. 但是当它被其他bean依赖时,该配置无效,因为容器需要初始化它来创建其他的bean. 我们可以使用default-lazy-init属性来控制整个容器的初始化;例如:
7.4.5 自动装配协助者
spring容器可以自动装配协助beans之间的关系.你可以允许spring通过检测ApplicationContext的内容来为你的bean自动释放合作者. 自动装配有以下好处:
-
1.明显减少对指定属性和构造器参数的需要.
-
2.当你对象改变时,autowiring可以自动更新设置.在开发时,它会非常有用,当代码稳定时你可改为显示配置.
当使用基于xml的配置元数据时,你可以在
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自动装配
- 在
的定义上添加primary属性为true, - 使用基于注解配置实现更加细粒度的控制
####将bean排除在自动配置之外 在提前实例化bean的基础上,可以将bean排除自动装配.spring xml格式中,可以将autowire-candidate属性设置为false,容器会使特定的bean不可以用于自动装配(包括注解配置比如@Autowired)
你也可以对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文件配置
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和指定方法重写如下:
String
你可以用