Spring-Ioc容器
IoC是随着近年来轻量级容器(Lightweight Container)的兴起而逐渐被很多人提起的一个名词,它 的全称为Inversion of Control,中文通常翻译为“控制反转”,它还有一个别名叫做依赖注入(Dependency Injection) 简单点儿说,IoC的理念就是,让别人为你服务!
-
为什么需要IoC?
-
IoC的具体意义是什么?
-
它到底有什么独到之处?
IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式!
IoC的理念就是,让别人为你服务
通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC的场景中,二者之间通过IoC Service Provider来打交道,所有的被注入对象和依赖对象现在由IoC Service Provider统一管理。被注入对象需要 什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而 达到IoC Service Provider为被注入对象服务的目的。IoC Service Provider在这里就是通常的IoC容器所充 当的角色。从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了IoC Service Provider那里
出门之前得先穿件外套吧? 以前,你得自己跑到衣柜前面取出衣服这一依赖对象,然后自己穿上 再出门。而现在,你只要跟你的“另一半”使个眼色或说一句“Honey,衣服拿来。”她就会心领神 会地到衣柜那里为你取出衣服,然后再给你穿上。现在,你就可以出门了。(此时此刻,你心里肯定 窃喜,“有人照顾的感觉真好!”) 对你来说,到底哪种场景比较惬意,我想已经不言自明了吧?
手语,呼喊,还是心有灵犀
“伙计,来杯啤酒!”当你来到酒吧,想要喝杯啤酒的时候,通常会直接招呼服务生,让他为你 送来一杯清凉解渴的啤酒。同样地,作为被注入对象,要想让IoC Service Provider为其提供服务,并 将所需要的被依赖对象送过来,也需要通过某种方式通知对方。
- 如果你是酒吧的常客,或许你刚坐好,服务生已经将你最常喝的啤酒放到了你面前;
- 如果你是初次或偶尔光顾,也许你坐下之后还要招呼服务生,“Waiter,Tsingdao, please.”;
- 还有一种可能,你根本就不知道哪个牌子是哪个牌子,这时,你只能打手势或干脆画出商标图来告诉服务生你到底想要什么了吧!
不管怎样,你终究会找到一种方式来向服务生表达你的需求,以便他为你提供适当的服务。那么,在IoC模式中,被注入对象又是通过哪些方式来通知IoC Service Provider为其提供适当服务的呢?
IoC模式最权威的总结和解释,应该是Martin Fowler的那篇文章“Inversion of Control Containers and
the Dependency Injection pattern(https://martinfowler.com/articles/injection.html)”,其中提到了三种依赖注入的方式,即构造方法注入(constructor injection)、setter方法注入(setter injection)以及接口注入(interface injection)。
相对于前两种注入方式来说,接口注入没有那么简单明了。被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。 IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。图2-3演示了如何 使用接口注入为FXNewsProvider注入依赖对象。
FXNewsProvider为了让IoC Service Provider为其注入所依赖的IFXNewsListener,首先需要实现 IFXNewsListenerCallable接口,这个接口会声明一个injectNewsListner方法(方法名随意), 该方法的参数,就是所依赖对象的类型。这样,InjectionServiceContainer对象,即对应的IoC Service Provider就可以通过这个接口方法将依赖对象注入到被注入对象FXNewsProvider当中。
三种注入方式的比较
-
接口注入。从注入方式的使用上来说,接口注入是现在不甚提倡的一种方式,基本处于“退 役状态”。因为它强制被注入对象实现不必要的接口,带有侵入性。而构造方法注入和setter 方法注入则不需要如此。
-
构造方法注入。这种注入方式的优点就是,对象在构造完成之后,即已进入就绪状态,可以 马上使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反 射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且 在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多 个构造方法,而参数数量的变动可能造成维护上的不便。
-
setter方法注入。因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入好一些。另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。缺点当然就是对象无法在构造完成后马上进入就绪状态。
综上所述,构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式;而接口注入因为侵入性较强,近年来已经不流行了。
掌管大局的IoC Service Provider
虽然业务对象可以通过IoC方式声明相应的依赖,但是最终仍然需要通过某种角色或者服务将这 些相互依赖的对象绑定到一起,而IoC Service Provider就对应IoC场景中的这一角色。IoC Service Provider在这里是一个抽象出来的概念,它可以指代任何将IoC场景中的业务对象绑定 到一起的实现方式。它可以是一段代码,也可以是一组相关的类,甚至可以是比较通用的IoC框架或 者IoC容器实现。
IoC Service Provider 的职责
- 业务对象的构建管理。在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做。所以,IoC Service Provider需要将对象的构建逻辑从客户端对 象1那里剥离出来,以免这部分逻辑污染业务对象的实现。
- 业务对象间的依赖绑定。对于IoC Service Provider来说,这个职责是最艰巨也是最重要的,这 是它的最终使命之所在。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不 会得到依赖对象的任何响应(最常见的倒是会收到一个NullPointerException)。IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。
运筹帷幄的秘密——IoC Service Provider 如何管理对象间的依赖关系
前面我们说过,被注入对象可以通过多种方式通知IoC Service Provider为其注入相应依赖。但问 3 题在于,收到通知的IoC Service Provider是否就一定能够完全领会被注入对象的意图,并及时有效地为其提供想要的依赖呢?有些时候,事情可能并非像我们所想象的那样理所当然。
还是拿酒吧的例子说事儿,不管是常客还是初次光顾,你都可以点自己需要的饮料,以任何方式通知服务生都可以。要是侍者经验老道,你需要的任何饮品他都知道如何为你调制并提供给你。可是, 如果服务生刚入行又会如何呢? 当他连啤酒、鸡尾酒都分不清的时候,你能指望他及时地将你需要的 饮品端上来吗?
服务生最终必须知道顾客点的饮品与库存饮品的对应关系,才能为顾客端上适当的饮品。对于为 被注入对象提供依赖注入的IoC Service Provider来说,它也同样需要知道自己所管理和掌握的被注入 对象和依赖对象之间的对应关系。
IoC Service Provider不是人类,也就不能像酒吧服务生那样通过大脑来记忆和存储所有的相关信 息。所以,它需要寻求其他方式来记录诸多对象之间的对应关系。比如:
- 它可以通过最基本的文本文件来记录被注入对象和其依赖对象之间的对应关系;
- 它也可以通过描述性较强的XML文件格式来记录对应信息;
- 它还可以通过编写代码的方式来注册这些对应信息;
- 甚至,如果愿意,它也可以通过语音方式来记录对象间的依赖注入关系(“嗨,它要一个这种类型的对象,拿这个给它”)。
当前流行的IoC Service Provider产品使用的注册对象管理信息的方式
直接编码方式
当前大部分的IoC容器都应该支持直接编码方式,比如PicoContainer1、Spring、Avalon等。在容 器启动之前,我们就可以通过程序编码的方式将被注入对象和依赖对象注册到容器中,并明确它们相 互之间的依赖注入关系
IoContainer container = ...;
container.register(FXNewsProvider.class,new FXNewsProvider()); container.register(IFXNewsListener.class,new DowJonesNewsListener());
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class); newProvider.getAndPersistNews();
通过为相应的类指定对应的具体实例,可以告知IoC容器,当我们要这种类型的对象实例时,请 将容器中注册的、对应的那个具体实例返回给我们。
如果是接口注入,可能伪代码看起来要多一些。不过,道理上是一样的,只不过除了注册相应对 象,还要将“注入标志接口”与相应的依赖对象绑定一下,才能让容器最终知道是一个什么样的对应 关系,如代码清单3-3所演示的那样。
IoContainer container = ...;
container.register(FXNewsProvider.class,new FXNewsProvider()); container.register(IFXNewsListener.class,new DowJonesNewsListener());
...
container.bind(IFXNewsListenerCallable.class, container.get(IFXNewsListener.class));
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class); newProvider.getAndPersistNews();
通过bind方法将“被注入对象”(由IFXNewsListenerCallable接口添加标志)所依赖的对象, 绑定为容器中注册过的IFXNewsListener类型的对象实例。容器在返回FXNewsProvider对象实例之 前,会根据这个绑定信息,将IFXNewsListener注册到容器中的对象实例注入到“被注入对象”—— FXNewsProvider中,并最终返回已经组装完毕的FXNewsProvider对象。
所以,通过程序编码让最终的IoC Service Provider(也就是各个IoC框架或者容器实现)得以知晓 服务的“奥义”,应该是管理依赖绑定关系的最基本方式。
配置文件方式
这是一种较为普遍的依赖注入关系管理方式。像普通文本文件、properties文件、XML文件等,都 可以成为管理依赖注入关系的载体。
<bean id="newsProvider" class="..FXNewsProvider">
<property name="newsListener">
<ref bean="djNewsListener"/>
</property>
<property name="newPersistener">
<ref bean="djNewsPersister"/>
</property>
</bean>
<bean id="djNewsListener"
class="..impl.DowJonesNewsListener">
</bean>
<bean id="djNewsPersister"
class="..impl.DowJonesNewsPersister">
</bean>
最后,我们就可以像代码清单3-5所示的那样,通过“newsProvider”这个名字,从容器中取得 已经组装好的FXNewsProvider并直接使用。
元数据方式
这种方式的代表实现是Google Guice,这是Bob Lee在Java 5的注解和Generic的基础上开发的一套 IoC框架。我们可以直接在类中使用元数据信息来标注各个对象之间的依赖关系,然后由Guice框架根 据这些注解所提供的信息将这些对象组装后,交给客户端对象使用
public class FXNewsProvider {
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
@Inject
public FXNewsProvider(IFXNewsListener listener,IFXNewsPersister persister) {
this.newsListener = listener;
this.newPersistener = persister;
}
...
}
通过@Inject,我们指明需要IoC Service Provider通过构造方法注入方式,为FXNewsProvider注入其所依赖的对象。当然,注解最终也要通过代码处理来确定最终的注入关系,从这点儿来说,注解方式可以算作编 码方式的一种特殊情况。
Spring的IoC容器之BeanFactory
软件技术中每一项技术出现的必然是用来解决某个问题的痛点,哪里痛就把哪里剥离出来,把它放到应该放的位置上去。通过上文,可以知道Ioc的理念是让别人为你服务,解决的痛点业务对象之间的强耦合关系,就是帮助我们解耦各业务对象间依赖关系的对象绑定方式。
技术必然存在着概念的指导,我们先掌握概念就等于是掌握技术的方向,顺着方向我们去学习Sping的源码就会清晰明了,知道会整体的概念,而不至于盲人摸象,迷失在代码堆中。同时本书作者能浅显生动的将概念讲述清楚,着实能看出作者对源码理解的内功。通过上文,我们知道Ioc需要提供两大功
- IoC Service Provider(Ioc服务提供者):存储并管理对象间依赖关系,业务对象间依赖关系绑定。
- 管理对象间的依赖关系方式:通过什么手段告诉Ioc Service Provider自己需要什么类型的对象,并通过什么手段把自己注册到Ioc Service Provider中。
因此我们需要弄清楚在Spring中的Ioc Service Provider是哪个类,如何存储并管理对象间依赖关系,如何绑定业务对象间的依赖
前面说过,Spring的IoC容器是一个IoC Service Provider,但是,这只是它被冠以IoC之名的部分原因,我们不能忽略的是“容器”。Spring的IoC容器是一个提供IoC支持的轻量级容器,除了基本 的IoC支持,它作为轻量级容器还提供了IoC之外的支持。如在Spring的IoC容器之上,Spring还提供了 相应的AOP框架支持、企业级服务集成等服务。Spring的IoC容器和IoC Service Provider所提供的服务 之间存在一定的交集,二者的关系如图4-1所示。
Spring提供了两种容器类型:BeanFactory和ApplicationContext
-
BeanFactory。基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延
迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对 该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需 要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的 IoC容器选择。
-
ApplicationContext。ApplicationContext在BeanFactory的基础上构建,是相对比较高 级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等,这些会在后面详述。ApplicationContext所管理 的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来 说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容 器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中, ApplicationContext类型的容器是比较合适的选择。
ApplicationContext间接继承自BeanFactory,所以说它是构建于BeanFactory之上 的IoC容器。此外,你应该注意到了,ApplicationContext还继承了其他三个接口,它们之间的关系,我们将在第5章中详细说明。 另外,在没有特殊指明的情况下,以BeanFactory为中心所讲述的内容同样适用于ApplicationContext,这一点需要明确一下,二者有差别的地方会在合适的位置给出解释。
BeanFactory,顾名思义,就是生产Bean的工厂。当然,严格来说,这个“生产过程”可能不像 说起来那么简单。既然Spring框架提倡使用POJO,那么把每个业务对象看作一个JavaBean对象,或许 更容易理解为什么Spring的IoC基本容器会起这么一个名字。作为Spring提供的基本的IoC容器, BeanFactory可以完成作为IoC Service Provider的所有职责,包括业务对象的注册和对象间依赖关系的绑定。
/**
* The root interface for accessing a Spring bean container.
*
* 用于访问Spring bean容器的根接口
*
* <p>This is the basic client view of a bean container;
* further interfaces such as {@link ListableBeanFactory} and
* {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}
* are available for specific purposes.
*
* 这是bean容器的基本访问方式,更多的例如ListableBeanFactory和ConfigurableBeanFactory可用于特定目的
*
* <p>This interface is implemented by objects that hold a number of bean definitions,
* each uniquely identified by a String name. Depending on the bean definition,
* the factory will return either an independent instance of a contained object
* (the Prototype design pattern), or a single shared instance (a superior
* alternative to the Singleton design pattern, in which the instance is a
* singleton in the scope of the factory). Which type of instance will be returned
* depends on the bean factory configuration: the API is the same. Since Spring
* 2.0, further scopes are available depending on the concrete application
* context (e.g. "request" and "session" scopes in a web environment).
*
* 该接口由保存许多BeanDefinitions的对象实现,每个BeanDefinitions定义由一个字符串的名称作为唯一标识。
* 根据BeanDefinitions的定义,工厂会返回一个包含对象的实例(原型设计模式)或者返回一个共享的实例,也就是我们说的单例对象
* (是Singleton设计模式的替代品,并不是真正的将bean实例包装成单例模式,而是在bean容器中每次获取都是同一个对象,从而保证单例)
* 返回哪种类型,根据bean工厂的配置,API是一样的。在Spring2.0以后,根据应用环境,增加新的scope(在Web环境增加 request 和 session)
*
* <p>The point of this approach is that the BeanFactory is a central registry
* of application components, and centralizes configuration of application
* components (no more do individual objects need to read properties files,
* for example). See chapters 4 and 11 of "Expert One-on-One J2EE Design and
* Development" for a discussion of the benefits of this approach.
*
* 这个BeanFactory的重点方法是一个集中的应用registry(注册)组件,并且集中应用程序配置组件。
* (例如不再有单独的对象去读取配置文件)
*
* <p>Note that it is generally better to rely on Dependency Injection
* ("push" configuration) to configure application objects through setters
* or constructors, rather than use any form of "pull" configuration like a
* BeanFactory lookup. Spring's Dependency Injection functionality is
* implemented using this BeanFactory interface and its subinterfaces.
*
* 注意依赖最好通过setters或者constructors来依赖注入到对象中去,而不是主动的去查找配置,例如BeanFactory的查找方法。
* 使用Spring的依赖注入,可以使用BeanFactory的接口实现类或其子类。
*
* <p>Normally a BeanFactory will load bean definitions stored in a configuration
* source (such as an XML document), and use the {@code org.springframework.beans}
* package to configure the beans. However, an implementation could simply return
* Java objects it creates as necessary directly in Java code. There are no
* constraints on how the definitions could be stored: LDAP, RDBMS, XML,
* properties file, etc. Implementations are encouraged to support references
* amongst beans (Dependency Injection).
*
* 通常BeanFactory会加载存储在配置源中的Bean定义,并使用org.springframework.beans包来配置bean。
* 然而,一个实现可以简单地直接返回它在Java代码中根据需要创建的Java对象。
* Bean定义的存储方式没有限制。鼓励实现依赖注入。
*
* <p>In contrast to the methods in {@link ListableBeanFactory}, all of the
* operations in this interface will also check parent factories if this is a
* {@link HierarchicalBeanFactory}. If a bean is not found in this factory instance,
* the immediate parent factory will be asked. Beans in this factory instance
* are supposed to override beans of the same name in any parent factory.
*
* 与ListableBeanFactory的方法相比HierarchicalBeanFactory中所有的方法会检查父工厂。
* 如果一个bean在子工厂咋不到会直接访问父工厂。如果父子工厂有同名的bean,则子工厂会覆盖父工厂。
*
* <p>Bean factory implementations should support the standard bean lifecycle interfaces
* as far as possible. The full set of initialization methods and their standard order is:
*
* bean工厂实现应尽可能支持标准Bean生命周期接口,全套初始化方法及其标准顺序为。
* <ol>
* <li>BeanNameAware's {@code setBeanName}
* <li>BeanClassLoaderAware's {@code setBeanClassLoader}
* <li>BeanFactoryAware's {@code setBeanFactory}
* <li>EnvironmentAware's {@code setEnvironment}
* <li>EmbeddedValueResolverAware's {@code setEmbeddedValueResolver}
* <li>ResourceLoaderAware's {@code setResourceLoader}
* (only applicable when running in an application context)
* <li>ApplicationEventPublisherAware's {@code setApplicationEventPublisher}
* (only applicable when running in an application context)
* <li>MessageSourceAware's {@code setMessageSource}
* (only applicable when running in an application context)
* <li>ApplicationContextAware's {@code setApplicationContext}
* (only applicable when running in an application context)
* <li>ServletContextAware's {@code setServletContext}
* (only applicable when running in a web application context)
* <li>{@code postProcessBeforeInitialization} methods of BeanPostProcessors
* <li>InitializingBean's {@code afterPropertiesSet}
* <li>a custom init-method definition
* <li>{@code postProcessAfterInitialization} methods of BeanPostProcessors
* </ol>
*
* <p>On shutdown of a bean factory, the following lifecycle methods apply:
*
* 在关闭bean工厂时,以下生命周期方法适用:
*
* <ol>
* <li>{@code postProcessBeforeDestruction} methods of DestructionAwareBeanPostProcessors
* <li>DisposableBean's {@code destroy}
* <li>a custom destroy-method definition
* </ol>
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @since 13 April 2001
* @see BeanNameAware#setBeanName
* @see BeanClassLoaderAware#setBeanClassLoader
* @see BeanFactoryAware#setBeanFactory
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher
* @see org.springframework.context.MessageSourceAware#setMessageSource
* @see org.springframework.context.ApplicationContextAware#setApplicationContext
* @see org.springframework.web.context.ServletContextAware#setServletContext
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization
* @see InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization
* @see DisposableBean#destroy
* @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName
*/
public interface BeanFactory {
/**
* Used to dereference a {@link FactoryBean} instance and distinguish it from
* beans <i>created</i> by the FactoryBean. For example, if the bean named
* {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
* will return the factory, not the instance returned by the factory.
*
* 用于取消引用一个FactoryBean实例并区分它创建的bean。例如如果bean名称为myJndiObject,它是一个FactoryBean
* 使用&myJndiObject将会返回这个工厂,而不是返回这个工厂创建的bean。
*/
String FACTORY_BEAN_PREFIX = "&";
/**
* Return an instance, which may be shared or independent, of the specified bean.
* <p>This method allows a Spring BeanFactory to be used as a replacement for the
* Singleton or Prototype design pattern. Callers may retain references to
* returned objects in the case of Singleton beans.
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
*
* 返回指定的bean实例,可以是共享的或单独的。
* 这个方法容许Spring BeanFactory去使用单例或原型设计模式。对于Singleton Bean,调用者可以保留对返回对象的引用。
* 将别名转换回相应的规范bean名称
* 如果找不到则会去父工厂进行查找
*
* @param name the name of the bean to retrieve
* @return an instance of the bean
* @throws NoSuchBeanDefinitionException if there is no bean with the specified name
* @throws BeansException if the bean could not be obtained
*/
Object getBean(String name) throws BeansException;
/**
* Return an instance, which may be shared or independent, of the specified bean.
* <p>Behaves the same as {@link #getBean(String)}, but provides a measure of type
* safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the
* required type. This means that ClassCastException can't be thrown on casting
* the result correctly, as can happen with {@link #getBean(String)}.
*
* 于getBean相同,但如果Bean不是传入的类型,则抛出BeanNotOfRequiredTypeException
*
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to retrieve
* @param requiredType type the bean must match; can be an interface or superclass
* @return an instance of the bean
* @throws NoSuchBeanDefinitionException if there is no such bean definition
* @throws BeanNotOfRequiredTypeException if the bean is not of the required type
* @throws BeansException if the bean could not be created
*/
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
/**
* Return an instance, which may be shared or independent, of the specified bean.
* <p>Allows for specifying explicit constructor arguments / factory method arguments,
* overriding the specified default arguments (if any) in the bean definition.
*
* 允许指定显式构造函数参数 / 工厂方法参数。覆盖Bean定义中指定的默认参数(如果有)。
*
* @param name the name of the bean to retrieve
* @param args arguments to use when creating a bean instance using explicit arguments
* (only applied when creating a new instance as opposed to retrieving an existing one)
* @return an instance of the bean
* @throws NoSuchBeanDefinitionException if there is no such bean definition
* @throws BeanDefinitionStoreException if arguments have been given but
* the affected bean isn't a prototype
* @throws BeansException if the bean could not be created
* @since 2.5
*/
Object getBean(String name, Object... args) throws BeansException;
/**
* Return the bean instance that uniquely matches the given object type, if any.
* <p>This method goes into {@link ListableBeanFactory} by-type lookup territory
* but may also be translated into a conventional by-name lookup based on the name
* of the given type. For more extensive retrieval operations across sets of beans,
* use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
*
* 如果有的话,返回与给定对象类型唯一匹配的bean实例。
* 此方法进入ListableBeanFactory按类型查找区域,但也可以根据给定类型的名称转换为常规的按名称查找
* 对于跨多个集合的更广泛的检索操作,使用ListableBeanFactory 或 BeanFactoryUtils。
* @param requiredType type the bean must match; can be an interface or superclass
* @return an instance of the single bean matching the required type
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
* @throws BeansException if the bean could not be created
* @since 3.0
* @see ListableBeanFactory
*/
<T> T getBean(Class<T> requiredType) throws BeansException;
/**
* Return an instance, which may be shared or independent, of the specified bean.
* <p>Allows for specifying explicit constructor arguments / factory method arguments,
* overriding the specified default arguments (if any) in the bean definition.
* <p>This method goes into {@link ListableBeanFactory} by-type lookup territory
* but may also be translated into a conventional by-name lookup based on the name
* of the given type. For more extensive retrieval operations across sets of beans,
* use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
* @param requiredType type the bean must match; can be an interface or superclass
* @param args arguments to use when creating a bean instance using explicit arguments
* (only applied when creating a new instance as opposed to retrieving an existing one)
* @return an instance of the bean
* @throws NoSuchBeanDefinitionException if there is no such bean definition
* @throws BeanDefinitionStoreException if arguments have been given but
* the affected bean isn't a prototype
* @throws BeansException if the bean could not be created
* @since 4.1
*/
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
/**
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
* of instances, including availability and uniqueness options.
* @param requiredType type the bean must match; can be an interface or superclass
* @return a corresponding provider handle
* @since 5.1
* @see #getBeanProvider(ResolvableType)
*/
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
/**
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
* of instances, including availability and uniqueness options.
* @param requiredType type the bean must match; can be a generic type declaration.
* Note that collection types are not supported here, in contrast to reflective
* injection points. For programmatically retrieving a list of beans matching a
* specific type, specify the actual bean type as an argument here and subsequently
* use {@link ObjectProvider#orderedStream()} or its lazy streaming/iteration options.
* @return a corresponding provider handle
* @since 5.1
* @see ObjectProvider#iterator()
* @see ObjectProvider#stream()
* @see ObjectProvider#orderedStream()
*/
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
/**
* Does this bean factory contain a bean definition or externally registered singleton
* instance with the given name?
* <p>If the given name is an alias, it will be translated back to the corresponding
* canonical bean name.
* <p>If this factory is hierarchical, will ask any parent factory if the bean cannot
* be found in this factory instance.
* <p>If a bean definition or singleton instance matching the given name is found,
* this method will return {@code true} whether the named bean definition is concrete
* or abstract, lazy or eager, in scope or not. Therefore, note that a {@code true}
* return value from this method does not necessarily indicate that {@link #getBean}
* will be able to obtain an instance for the same name.
* @param name the name of the bean to query
* @return whether a bean with the given name is present
*/
boolean containsBean(String name);
/**
* Is this bean a shared singleton? That is, will {@link #getBean} always
* return the same instance?
* <p>Note: This method returning {@code false} does not clearly indicate
* independent instances. It indicates non-singleton instances, which may correspond
* to a scoped bean as well. Use the {@link #isPrototype} operation to explicitly
* check for independent instances.
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to query
* @return whether this bean corresponds to a singleton instance
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
* @see #getBean
* @see #isPrototype
*/
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
/**
* Is this bean a prototype? That is, will {@link #getBean} always return
* independent instances?
* <p>Note: This method returning {@code false} does not clearly indicate
* a singleton object. It indicates non-independent instances, which may correspond
* to a scoped bean as well. Use the {@link #isSingleton} operation to explicitly
* check for a shared singleton instance.
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to query
* @return whether this bean will always deliver independent instances
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
* @since 2.0.3
* @see #getBean
* @see #isSingleton
*/
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
/**
* Check whether the bean with the given name matches the specified type.
* More specifically, check whether a {@link #getBean} call for the given name
* would return an object that is assignable to the specified target type.
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to query
* @param typeToMatch the type to match against (as a {@code ResolvableType})
* @return {@code true} if the bean type matches,
* {@code false} if it doesn't match or cannot be determined yet
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
* @since 4.2
* @see #getBean
* @see #getType
*/
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
/**
* Check whether the bean with the given name matches the specified type.
* More specifically, check whether a {@link #getBean} call for the given name
* would return an object that is assignable to the specified target type.
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to query
* @param typeToMatch the type to match against (as a {@code Class})
* @return {@code true} if the bean type matches,
* {@code false} if it doesn't match or cannot be determined yet
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
* @since 2.0.1
* @see #getBean
* @see #getType
*/
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
/**
* Determine the type of the bean with the given name. More specifically,
* determine the type of object that {@link #getBean} would return for the given name.
* <p>For a {@link FactoryBean}, return the type of object that the FactoryBean creates,
* as exposed by {@link FactoryBean#getObjectType()}. This may lead to the initialization
* of a previously uninitialized {@code FactoryBean} (see {@link #getType(String, boolean)}).
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to query
* @return the type of the bean, or {@code null} if not determinable
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
* @since 1.1.2
* @see #getBean
* @see #isTypeMatch
*/
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
/**
* Determine the type of the bean with the given name. More specifically,
* determine the type of object that {@link #getBean} would return for the given name.
* <p>For a {@link FactoryBean}, return the type of object that the FactoryBean creates,
* as exposed by {@link FactoryBean#getObjectType()}. Depending on the
* {@code allowFactoryBeanInit} flag, this may lead to the initialization of a previously
* uninitialized {@code FactoryBean} if no early type information is available.
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to query
* @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized
* just for the purpose of determining its object type
* @return the type of the bean, or {@code null} if not determinable
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
* @since 5.2
* @see #getBean
* @see #isTypeMatch
*/
@Nullable
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
/**
* Return the aliases for the given bean name, if any.
* <p>All of those aliases point to the same bean when used in a {@link #getBean} call.
* <p>If the given name is an alias, the corresponding original bean name
* and other aliases (if any) will be returned, with the original bean name
* being the first element in the array.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the bean name to check for aliases
* @return the aliases, or an empty array if none
* @see #getBean
*/
String[] getAliases(String name);
}
上面代码中的方法基本上都是查询相关的方法,例如,取得某个对象的方法(getBean)、查询 某个对象是否存在于容器中的方法(containsBean),或者取得某个bean的状态或者类型的方法等。 因为通常情况下,对于独立的应用程序,只有主入口类才会跟容器的API直接耦合。
拥有BeanFactory之后的生活
//在BeanFactory出现之前,我们通常会直接在应用程序的入口类的main方法中, 自己实例化相应的对象并调用之,如以下代码所示:
FXNewsProvider newsProvider = new FXNewsProvider();
newsProvider.getAndPersistNews();
//不过,现在既然有了BeanFactory,我们通常只需将“生产线图纸”交给BeanFactory,让 BeanFactory为我们生产一个FXNewsProvider,如以下代码所示:
//xml
BeanFactory container = new XmlBeanFactory(new ClassPathResource("配置文件路径"));
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); newsProvider.getAndPersistNews();
//类路径下xml
ApplicationContext container = new ClassPathXmlApplicationContext("配置文件路径");
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); newsProvider.getAndPersistNews();
//绝对路径下xml
ApplicationContext container = new FileSystemXmlApplicationContext("配置文件路径");
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); newsProvider.getAndPersistNews();
BeanFactory的对象注册与依赖绑定方式
BeanFactory作为一个IoC Service Provider,为了能够明确管理各个业务对象以及业务对象之间的依赖绑定关系,同样需要某种途径来记录和管理这些信息。上文在介绍IoC Service Provider时,我们提到通常会有三种方式来管理这些信息。而BeanFactory几乎支持所有这些方式。
直接编码方式
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
/**
* 通过直接编码的方式将对象注册到IOC容器中,并将对象依赖注入到其它需要的对象中去
*/
public class DirectCodingBeanFactoryDemo {
public static void main(String[] args) {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = (BeanFactory) bindViaCode(beanRegistry);
Dao dao = (Dao) container.getBean("dao");
ServiceA serviceA = container.getBean(ServiceA.class);
ServiceB serviceB = container.getBean("serviceB", ServiceB.class);
System.out.println(dao.getDbInfo("Main方法"));
System.out.println(serviceA.getUserInfo());
System.out.println(serviceB.getUserInfo());
}
private static Object bindViaCode(DefaultListableBeanFactory beanRegistry) {
//AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR 自动通过构造函数注入
//AbstractBeanDefinition.AUTOWIRE_NO 不自动注入
AbstractBeanDefinition serviceA_BeanDefine = new RootBeanDefinition(ServiceA.class, AbstractBeanDefinition.AUTOWIRE_NO, true);
AbstractBeanDefinition serviceB_BeanDefine = new RootBeanDefinition(ServiceB.class, AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR, true);
AbstractBeanDefinition dao = new RootBeanDefinition(Dao.class, AbstractBeanDefinition.AUTOWIRE_NO, true);
// 将bean定义注册到容器中
beanRegistry.registerBeanDefinition("serviceA", serviceA_BeanDefine);
beanRegistry.registerBeanDefinition("serviceB", serviceB_BeanDefine);
beanRegistry.registerBeanDefinition("dao", dao);
// 指定依赖关系
// 1. 或者通过setter方法注入方式
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue(new PropertyValue("dao", dao));
serviceA_BeanDefine.setPropertyValues(propertyValues);
// 2. 可以通过构造方法注入方式
ConstructorArgumentValues argValues = new ConstructorArgumentValues();
argValues.addIndexedArgumentValue(0, dao);
serviceB_BeanDefine.setConstructorArgumentValues(argValues);
// 绑定完成
return (BeanFactory) beanRegistry;
}
public static class Dao {
public Dao() {
System.out.println("初始化Dao");
}
public String getDbInfo(String id) {
return "获取DB中ID=" + id + "的信息";
}
}
public static class ServiceA {
public ServiceA() {
System.out.println("初始化ServiceA");
}
private Dao dao;
public Dao getDao() {
return dao;
}
public void setDao(Dao dao) {
this.dao = dao;
}
public String getUserInfo() {
return dao.getDbInfo("服务A");
}
}
public static class ServiceB {
private final Dao dao;
public ServiceB(Dao dao) {
System.out.println("初始化ServiceB");
this.dao = dao;
}
public String getUserInfo() {
return dao.getDbInfo("服务B");
}
}
}
//-----------------------
//输出
初始化Dao
初始化ServiceA
初始化Dao
初始化Dao
初始化ServiceB
获取DB中ID=Main方法的信息
获取DB中ID=服务A的信息
获取DB中ID=服务B的信息
BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理,DefaultListableBeanFactory就是这么一个比较通用的BeanFactory实现类。DefaultListableBeanFactory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFactory的实现中担当Bean注册管理的角色。基本上,BeanFactory接口只定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。 BeanDefinitionRegistry接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFactory实现类会实现这个接口来管理Bean的注册。它们之间的关系如图4-3所示。
打个比方说,BeanDefinitionRegistry就像图书馆的书架,所有的书是放在书架上的。虽然你还书或者借书都是跟图书馆(也就是BeanFactory,或许BookFactory可能更好些)打交道,但书架才是图书馆存放各类图书的地方。所以,书架相对于图书馆来说,就是它的“BookDefinitionRegistry”。每一个受管的对象,在容器中都会有一个BeanDefinition的实例(instance)与之相对应,该 BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory会通过这些信息为客户端返回一个完备可用的对象实例。RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要实现类。
由此可以知道BeanFacory接口负责提供获取依赖对象的能力,BeanDefinitionRegistry接口则负责提供注册并管理对象对象能力。将对象信息进行封装后得到BeanDefinition记录对象的各种属性,方便依赖关系中获取对应属性的对象。
由本类图可以看出并不是直接实现的接口而是通过集成抽象类而获得的beanfactory的接口能力。
外部配置文件方式
Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式。当然,如果你愿 意也可以引入自己的文件格式,前提是真的需要。由于现在基本已经不再使用外部配置文件进行依赖管理,此处就不再继续深入讲如何使用文件进行配置。
注解方式
注解是Java 5之后才引入的,所以只适用于应用程序使用了Spring 2.5以及Java 5或者更高版本的情况之下。如果要通过注解标注的方式为FXNewsProvider注入所需要的依赖,现在可以使用@Autowired以及@Component对相关类进行标记。并且需要Spring可以扫描到才可以。既然你都看源码了这种方式你肯定知道就不细说了。
配置Bean所支持的能力
-
default-lazy-init。其值可以指定为true或者false,默认值为false。用来标志是否对所 有的bean进行延迟初始化。
-
default-autowire。可以取值为no、byName、byType、constructor以及autodetect。默认值为no,如果使用自动绑定的话,用来标志全体bean使用哪一种默认绑定方式。
-
default-dependency-check。可以取值none、objects、simple以及all,默认值为none, 即不做依赖检查。
-
default-init-method。如果所管辖的bean按照某种规则,都有同样名称的初始化方法的 话,可以在这里统一指定这个初始化方法名,而不用在每一个bean上都重复单独指定。
-
default-destroy-method。与default-init-method相对应,如果所管辖的bean有按照某种 规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定。
-
alias 为某些bean起一些"外号"(别名),通常情况下是为了减少输入。你可以为其添加一个alias,像 这样
<alias name="dataSourceForMasterDatabase" alias="masterDataSource"/>
以后通过dataSourceForMasterDatabase或者masterDataSource来引用这个bean都可以,只要你觉得方便就行。
-
属性构造方法注入与Setter注入。
-
Bean延迟加载。
-
Bean的Scope。scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其 相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0之后,又引入了另 外三种scope类型,即request、session和global session类型。不过这三种类型有所限制,只能在Web应 用中使用。也就是说,只有在支持W eb应用的ApplicationContext中使用这三个scope才是合理的。
自定义scope类型
在Spring 2.0之后的版本中,容器提供了对scope的扩展点,这样,你可以根据自己的需要或者应 用的场景,来添加自定义的scope类型。需要说明的是,默认的singleton和prototype是硬编码到代码中的,而request、session和global session,包括自定义scope类型,则属于可扩展的scope行列,它们都实 现了org.springframework.beans.factory.config.Scope接口
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.config;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.lang.Nullable;
/**
* Strategy interface used by a {@link ConfigurableBeanFactory},
* representing a target scope to hold bean instances in.
* This allows for extending the BeanFactory's standard scopes
* {@link ConfigurableBeanFactory#SCOPE_SINGLETON "singleton"} and
* {@link ConfigurableBeanFactory#SCOPE_PROTOTYPE "prototype"}
* with custom further scopes, registered for a
* {@link ConfigurableBeanFactory#registerScope(String, Scope) specific key}.
*
* <p>{@link org.springframework.context.ApplicationContext} implementations
* such as a {@link org.springframework.web.context.WebApplicationContext}
* may register additional standard scopes specific to their environment,
* e.g. {@link org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST "request"}
* and {@link org.springframework.web.context.WebApplicationContext#SCOPE_SESSION "session"},
* based on this Scope SPI.
*
* <p>Even if its primary use is for extended scopes in a web environment,
* this SPI is completely generic: It provides the ability to get and put
* objects from any underlying storage mechanism, such as an HTTP session
* or a custom conversation mechanism. The name passed into this class's
* {@code get} and {@code remove} methods will identify the
* target object in the current scope.
*
* <p>{@code Scope} implementations are expected to be thread-safe.
* One {@code Scope} instance can be used with multiple bean factories
* at the same time, if desired (unless it explicitly wants to be aware of
* the containing BeanFactory), with any number of threads accessing
* the {@code Scope} concurrently from any number of factories.
*
* @author Juergen Hoeller
* @author Rob Harrop
* @since 2.0
* @see ConfigurableBeanFactory#registerScope
* @see CustomScopeConfigurer
* @see org.springframework.aop.scope.ScopedProxyFactoryBean
* @see org.springframework.web.context.request.RequestScope
* @see org.springframework.web.context.request.SessionScope
*/
public interface Scope {
/**
* Return the object with the given name from the underlying scope,
* {@link org.springframework.beans.factory.ObjectFactory#getObject() creating it}
* if not found in the underlying storage mechanism.
* <p>This is the central operation of a Scope, and the only operation
* that is absolutely required.
* @param name the name of the object to retrieve
* @param objectFactory the {@link ObjectFactory} to use to create the scoped
* object if it is not present in the underlying storage mechanism
* @return the desired object (never {@code null})
* @throws IllegalStateException if the underlying scope is not currently active
*/
Object get(String name, ObjectFactory<?> objectFactory);
/**
* Remove the object with the given {@code name} from the underlying scope.
* <p>Returns {@code null} if no object was found; otherwise
* returns the removed {@code Object}.
* <p>Note that an implementation should also remove a registered destruction
* callback for the specified object, if any. It does, however, <i>not</i>
* need to <i>execute</i> a registered destruction callback in this case,
* since the object will be destroyed by the caller (if appropriate).
* <p><b>Note: This is an optional operation.</b> Implementations may throw
* {@link UnsupportedOperationException} if they do not support explicitly
* removing an object.
* @param name the name of the object to remove
* @return the removed object, or {@code null} if no object was present
* @throws IllegalStateException if the underlying scope is not currently active
* @see #registerDestructionCallback
*/
@Nullable
Object remove(String name);
/**
* Register a callback to be executed on destruction of the specified
* object in the scope (or at destruction of the entire scope, if the
* scope does not destroy individual objects but rather only terminates
* in its entirety).
* <p><b>Note: This is an optional operation.</b> This method will only
* be called for scoped beans with actual destruction configuration
* (DisposableBean, destroy-method, DestructionAwareBeanPostProcessor).
* Implementations should do their best to execute a given callback
* at the appropriate time. If such a callback is not supported by the
* underlying runtime environment at all, the callback <i>must be
* ignored and a corresponding warning should be logged</i>.
* <p>Note that 'destruction' refers to automatic destruction of
* the object as part of the scope's own lifecycle, not to the individual
* scoped object having been explicitly removed by the application.
* If a scoped object gets removed via this facade's {@link #remove(String)}
* method, any registered destruction callback should be removed as well,
* assuming that the removed object will be reused or manually destroyed.
* @param name the name of the object to execute the destruction callback for
* @param callback the destruction callback to be executed.
* Note that the passed-in Runnable will never throw an exception,
* so it can safely be executed without an enclosing try-catch block.
* Furthermore, the Runnable will usually be serializable, provided
* that its target object is serializable as well.
* @throws IllegalStateException if the underlying scope is not currently active
* @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.beans.factory.support.AbstractBeanDefinition#getDestroyMethodName()
* @see DestructionAwareBeanPostProcessor
*/
void registerDestructionCallback(String name, Runnable callback);
/**
* Resolve the contextual object for the given key, if any.
* E.g. the HttpServletRequest object for key "request".
* @param key the contextual key
* @return the corresponding object, or {@code null} if none found
* @throws IllegalStateException if the underlying scope is not currently active
*/
@Nullable
Object resolveContextualObject(String key);
/**
* Return the <em>conversation ID</em> for the current underlying scope, if any.
* <p>The exact meaning of the conversation ID depends on the underlying
* storage mechanism. In the case of session-scoped objects, the
* conversation ID would typically be equal to (or derived from) the
* {@link javax.servlet.http.HttpSession#getId() session ID}; in the
* case of a custom conversation that sits within the overall session,
* the specific ID for the current conversation would be appropriate.
* <p><b>Note: This is an optional operation.</b> It is perfectly valid to
* return {@code null} in an implementation of this method if the
* underlying storage mechanism has no obvious candidate for such an ID.
* @return the conversation ID, or {@code null} if there is no
* conversation ID for the current scope
* @throws IllegalStateException if the underlying scope is not currently active
*/
@Nullable
String getConversationId();
}
更多Scope相关的实例,可以参照同一站点的一篇文章“More fun with Spring scopes”(https://java-career.blogspot.com/2012/07/more-fun-with-spring-scopes.html?view=flipcard),其中提到PageScope的实现。
有了Scope的实现类之后,我们需要把这个Scope注册到容器中,才能供相应的bean定义使用。通 常情况下,我们可以使用ConfigurableBeanFactory的以下方法注册自定义scope:
void registerScope(String scopeName, Scope scope);
其中,参数scopeName就是使用的bean定义可以指定的名称,比如Spring框架默认提供的自定义scope 类型request或者session。
参数scope即我们提供的Scope实现类实例。
对于以上的ThreadScope,如果容器为BeanFactory类型(当然,更应该实现ConfigurableBeanFactory),我们可以通过如下方式来注册该Scope:
Scope threadScope = new ThreadScope();
beanFactory.registerScope("thread",threadScope);
之后,我们就可以在需要的bean定义中直接通过“thread”名称来指定该bean定义对应的scope 为以上注册的ThreadScope了,如以下代码所示:
<bean id="beanName" class="..." scope="thread"/>
除了直接编码调用ConfigurableBeanFactory的registerScope来注册scope,Spring还提供了一个专门用于统一注册自定义scope的BeanFactoryPostProcessor实现,即org.springframework.beans.factory.config.CustomScopeConfigurer。对于ApplicationContext来说,因为它可以自动识别并加载BeanFactoryPostProcessor,所以我们就可以直接在配置文件中,通过这个CustomScopeConfigurer注册来ThreadScope。
- 通过BeanFactory方式注册Scope
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
/**
* 通过DefaultListableBeanFactory来加载自定义Scope
*/
public class ThreadScopeXmlMain {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerScope("thread", new ThreadScope());
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions("classpath:ThreadScopeXmlMain-ApplicationContext.xml");
BeanFactory container = (BeanFactory) beanFactory;
Runnable runnable = () -> {
SingleService singleService = container.getBean(SingleService.class);
ThreadService threadService1 = container.getBean(ThreadService.class);
ThreadService threadService2 = container.getBean(ThreadService.class);
System.out.println(singleService.getInfo());
System.out.println(threadService1.getInfo());
System.out.println(threadService2.getInfo());
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
//---------------
//输出
单例Scope=Thread-2-com.mm.demo.factory.scope.SingleService@658c4ffa
单例Scope=Thread-3-com.mm.demo.factory.scope.SingleService@658c4ffa
单例Scope=Thread-4-com.mm.demo.factory.scope.SingleService@658c4ffa
单例Scope=Thread-1-com.mm.demo.factory.scope.SingleService@658c4ffa
ThreadScope=Thread-2-com.mm.demo.factory.scope.ThreadService@636ef02a
ThreadScope=Thread-1-com.mm.demo.factory.scope.ThreadService@69eaf74f
ThreadScope=Thread-4-com.mm.demo.factory.scope.ThreadService@70dfae2a
ThreadScope=Thread-3-com.mm.demo.factory.scope.ThreadService@7a253d0f
ThreadScope=Thread-4-com.mm.demo.factory.scope.ThreadService@70dfae2a
ThreadScope=Thread-1-com.mm.demo.factory.scope.ThreadService@69eaf74f
ThreadScope=Thread-2-com.mm.demo.factory.scope.ThreadService@636ef02a
ThreadScope=Thread-3-com.mm.demo.factory.scope.ThreadService@7a253d0f
ThreadScopeXmlMain-ApplicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean class="com.mm.demo.factory.scope.SingleService"></bean>
<bean class="com.mm.demo.factory.scope.ThreadService" scope="thread">
<aop:scoped-proxy/>
</bean>
</beans>
ThreadScope
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.HashMap;
import java.util.Map;
/**
* 线程生命周期对象
*/
public class ThreadScope implements Scope {
private final ThreadLocal threadScope = new ThreadLocal() {
protected Object initialValue() {
return new HashMap();
}
};
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map scope = (Map) threadScope.get();
Object object = scope.get(name);
if (object == null) {
object = objectFactory.getObject();
scope.put(name, object);
}
return object;
}
@Override
public Object remove(String name) {
Map scope = (Map) threadScope.get();
return scope.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}
SingleService
public class SingleService {
public String getInfo() {
return "单例Scope="+Thread.currentThread().getName() + "-" + this.toString();
}
}
ThreadService
public class ThreadService {
public String getInfo() {
return "ThreadScope=" + Thread.currentThread().getName() + "-" + this.toString();
}
}
可以看到每个Thread内获取的ThreadService的对象是不同的,在多次获取时是相同的。
- 通过Application自动注册BeanFactoryPostProcessor机制将自定义Scope注册进Spring
package com.mm.demo.factory.scope;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 通过DefaultListableBeanFactory来加载自定义Scope
*/
public class ApplicationThreadScopeXmlMain {
public static void main(String[] args) {
ApplicationContext beanFactory = new ClassPathXmlApplicationContext("ApplicationThreadScopeXmlMain-ApplicationContext.xml");
BeanFactory container = (BeanFactory) beanFactory;
Runnable runnable = () -> {
SingleService singleService = container.getBean(SingleService.class);
ThreadService threadService1 = container.getBean(ThreadService.class);
ThreadService threadService2 = container.getBean(ThreadService.class);
System.out.println(singleService.getInfo());
System.out.println(threadService1.getInfo());
System.out.println(threadService2.getInfo());
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
//---------
//输出
单例Scope=Thread-4-com.mm.demo.factory.scope.SingleService@30556425
单例Scope=Thread-1-com.mm.demo.factory.scope.SingleService@30556425
单例Scope=Thread-2-com.mm.demo.factory.scope.SingleService@30556425
单例Scope=Thread-3-com.mm.demo.factory.scope.SingleService@30556425
ThreadScope=Thread-3-com.mm.demo.factory.scope.ThreadService@20e89346
ThreadScope=Thread-1-com.mm.demo.factory.scope.ThreadService@3aad0f55
ThreadScope=Thread-2-com.mm.demo.factory.scope.ThreadService@24a75fd6
ThreadScope=Thread-4-com.mm.demo.factory.scope.ThreadService@cf3fa40
ThreadScope=Thread-4-com.mm.demo.factory.scope.ThreadService@cf3fa40
ThreadScope=Thread-2-com.mm.demo.factory.scope.ThreadService@24a75fd6
ThreadScope=Thread-1-com.mm.demo.factory.scope.ThreadService@3aad0f55
ThreadScope=Thread-3-com.mm.demo.factory.scope.ThreadService@20e89346
ApplicationThreadScopeXmlMain-ApplicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread" value="com.mm.demo.factory.scope.ThreadScope"/>
</map>
</property>
</bean>
<bean class="com.mm.demo.factory.scope.SingleService"></bean>
<bean class="com.mm.demo.factory.scope.ThreadService" scope="thread">
<aop:scoped-proxy/>
</bean>
</beans>
工厂方法与 FactoryBean
在强调“面向接口编程”的同时,有一点需要注意:虽然对象可以通过声明接口来避免对特定接 口实现类的过度耦合,但总归需要一种方式将声明依赖接口的对象与接口实现类关联起来。否则,只 依赖一个不做任何事情的接口是没有任何用处的。如果该类是由我们设计并开发的,那么还好说,我们可以通过依赖注入,让容器帮助我们解除接 口与实现类之间的耦合性。但是,有时,我们需要依赖第三方库,需要实例化并使用第三方库中的相 关类,这时,接口与实现类的耦合性需要其他方式来避免。通常的做法是通过使用工厂方法(Factory Method)模式,提供一个工厂类来实例化具体的接口 实现类,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何变动。
public class Foo {
private BarInterface barInterface; public Foo()
{
// barInterface = BarInterfaceFactory.getInstance();
// 或者
// barInterface = new BarInterfaceFactory().getInstance();
} ...
}
针对使用工厂方法模式实例化对象的方式,Spring的IoC容器同样提供了对应的集成支持。我们所 要做的,只是将工厂类所返回的具体的接口实现类注入给主体对象。
-
静态工厂方法(Static Factory Method)
接口实现类提供工厂方法并提供静态方法,通过将工厂交给spring,并指明需要调用的方法,在需要获取对象时通过通过工厂方法获取对象。
public class StaticBarInterfaceFactory { public static BarInterface getInstance() { return new BarInterfaceImpl(); } }
<bean id="foo" class="...Foo"> <property name="barInterface"> <ref bean="bar"/> </property> </bean> <bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance"/>
-
非静态工厂方法(Instance Factory Method)
接口实现类并不提供静态方法
public class StaticBarInterfaceFactory { public BarInterface getInstance() { return new BarInterfaceImpl(); } }
<bean id="foo" class="...Foo"> <property name="barInterface"> <ref bean="bar"/> </property> </bean> <bean id="barFactory" class="...NonStaticBarInterfaceFactory"/> <bean id="bar" factory-bean="barFactory" factory-method="getInstance"/>
NonStaticBarInterfaceFactory是作为正常的bean注册到容器的,而bar的定义则与静态工厂方 法的定义有些不同。现在使用factory-bean属性来指定工厂方法所在的工厂类实例,而不是通过 class属性来指定工厂方法所在类的类型。指定工厂方法名则相同,都是通过factory-method属性进行的。
-
FactoryBean
FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,请不要将其与容器名称BeanFactory相混淆。FactoryBean,其主语是Bean,定语为Factory,也就是说,它本身与其他注册到容器的对象一样,只是一个Bean而已,只不过,这种类型的Bean本身就是生产对象的工厂(Factory)。
-
BeanFactory是一个接口是基础类型IoC容器,提供完整的IoC服务支持。一个是生产bean的工厂。
-
FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口。一个是工厂类型的bean。
-
FactoryBean的提出就是为了解决当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候。
-
或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现org.spring- framework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码。
-
当然,不使用FactoryBean,而像通常那样实现自定义的工厂方法类也是可以的。不过,FactoryBean可是Spring提供 的对付这种情况的“制式装备”哦
心得:也就是说可以将FactoryBean注册到BeanFactory中当作一个bean或者当作一个生产bean的bean。
FactoryBean
package org.springframework.beans.factory; import org.springframework.lang.Nullable; public interface FactoryBean<T> { String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType"; /** * 返回该FactoryBean“生产”的对象实例,我们需要实现该方法以给出自己的对象实例化逻辑 */ @Nullable T getObject() throws Exception; /** * 回getObject()方法所返回的对象的类型,如果预先 无法确定,则返回null */ @Nullable Class<?> getObjectType(); /** * 返回结果用于表明,工厂方法(getObject())所生产的对象是否要以singleton形式存在于容器中。 * 如果以singleton形式存在,则返回true,否则返回false */ default boolean isSingleton() { return true; } }
案例
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class FactoryBeanDemoMain { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("FactoryBeanDemoMain-ApplicationContext.xml"); Controller bean = context.getBean(Controller.class); bean.getInfo(); } } //------ //输出 单例bean创建 非单例bean创建 非单例bean创建 NoSingleton=com.mm.demo.factory.factorybean.NoSingletonService@4534b60d NoSingleton=com.mm.demo.factory.factorybean.NoSingletonService@3fa77460 Singleton=com.mm.demo.factory.factorybean.SingletonService@bebdb06 Singleton=com.mm.demo.factory.factorybean.SingletonService@bebdb06
import org.springframework.beans.factory.FactoryBean; public class NoSingletonServiceFactory implements FactoryBean<NoSingletonService> { @Override public NoSingletonService getObject() throws Exception { System.out.println("非单例bean创建"); return new NoSingletonService(); } @Override public Class<?> getObjectType() { return NoSingletonService.class; } @Override public boolean isSingleton() { return false; } }
public class NoSingletonService { private String name; public NoSingletonService() { name = this.toString(); } public String getInfo() { return "NoSingleton=" + name; } }
import org.springframework.beans.factory.FactoryBean; public class SingletonServiceFactory implements FactoryBean<SingletonService> { @Override public SingletonService getObject() throws Exception { System.out.println("单例bean创建"); return new SingletonService(); } @Override public Class<?> getObjectType() { return SingletonService.class; } @Override public boolean isSingleton() { return true; } }
public class SingletonService { private String name; public SingletonService() { name = this.toString(); } public String getInfo() { return "Singleton=" + name; } }
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <bean id="controller" class="com.mm.demo.factory.factorybean.Controller"> <property name="singletonService1" ref="singletonService"/> <property name="singletonService2" ref="singletonService"/> <property name="noSingletonService1" ref="noSingletonService"/> <property name="noSingletonService2" ref="noSingletonService"/> </bean> <bean id="noSingletonService" class="com.mm.demo.factory.factorybean.NoSingletonServiceFactory"/> <bean id="singletonService" class="com.mm.demo.factory.factorybean.SingletonServiceFactory"/> </beans>
-
可以看到FactoryBean并没有直接注入到Controller中而是将工厂类生产的对应类注入到Controller中。
如果一定要取得FactoryBean本身的话,可以通过在bean定义的id之前加前缀&来达到目的。其实在BeanFactory中有定义一个常量FACTORY_BEAN_PREFIX="&" 这个常量就是用来表明获取FactoryBean的。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FactoryBeanDemoMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("FactoryBeanDemoMain-ApplicationContext.xml");
Object noSingletonService = context.getBean("&noSingletonService");
System.out.println(noSingletonService instanceof NoSingletonServiceFactory);
System.out.println(noSingletonService instanceof NoSingletonService);
Object singletonService = context.getBean("&singletonService");
System.out.println(singletonService instanceof SingletonServiceFactory);
System.out.println(singletonService instanceof SingletonService);
}
}
//----------
//输出
true
false
true
false
Spring容器内部许多地方了使用FactoryBean。你可以参照FactoryBean的Javadoc以了解更多内容。
通过FactoryBean接口,Spring将复杂bean创建过程开放给开发者,提供灵活的接口,对内部创建流程进行封闭,对改动进行开放给出接口。
偷梁换柱之术
我们知道,拥有prototype类型scope的bean,在请求方每次向容器请求该类型对象的时候,容器都会返回一个全新的该对象实例。
-
通过setter注入方式
-
其它特殊方法
- 使用BeanFactoryAware接口,会将BeanFactory交给用户,这样想要什么对象都可以了,因此仅仅获取一个新对象也可以。
- 使用ObjectFactoryCreatingFactoryBean,ObjectFactoryCreatingFactoryBean 是 Spring 提 供 的 一 个 FactoryBean 实 现 , 它 返 回 一 个ObjectFactory实例。从ObjectFactoryCreatingFactoryBean返回的这个ObjectFactory实例可以 为我们返回容器管理的相关对象。实际上,ObjectFactoryCreatingFactoryBean实现了 BeanFactoryAware接口,它返回的ObjectFactory实例只是特定于与Spring容器进行交互的一个实现 而已。使用它的好处就是,隔离了客户端对象对BeanFactory的直接引用。
-
方法替换,直接通过AOP方式替换方法
以上我们学习了SpringIoc容器提供的能力,以能力为突破口去学习源码中是如何去实现是很好的方法。
梳理一下就目前为止Spring中Ioc Service Provider实现类 BeanFactory Application 提供的能力
- 业务对象的构建管理
- 依赖绑定方式?setter方法、构造方法、接口、注解
- 业务对象间的依赖绑定关系
- 依赖关系存储方式?代码直接编写、xml配置、注解
- 注册对象到Spring中方式?代码直接编写、xml配置、注解
- 延迟初始化
- 在初始化容器时不初始化对象,在对象需要使用时在初始化
- 自动绑定
- 为需要的注入的对象自动注入所依赖的对象
- 依赖检查
- 检查依赖的bean是否存在
- 对象初始化回调方法
- 对象销毁回调方法
- 为某些bean起别名
- Bean延迟加载
- Bean的Scope
- 自定义Scope加载与注册
- 工厂方法 FactoryBean
容器背后的秘密
子曰:学而不思则罔。除了了解Spring的IoC容器如何使用,了解Spring的IoC容器都提供了哪些功 能,我们也应该想一下,Spring的IoC容器内部到底是如何来实现这些的呢?虽然我们不太可能“重新 发明轮子”。但是,如图4-7(该图摘自Spring官方参考文档)所示的那样,只告诉你“Magic Happens Here”,你是否就能心满意足呢?
“战略性观望”
Spring的IoC容器所起的作用,就像图4-7所展示的那样,它会以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成 一个可用的基于轻量级容器的应用系统。
Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段和Bean实例化阶段,如图4-8所示。
Spring的IoC容器在实现的时候,充分运用了这两个实现阶段的不同特点,在每个阶段都加入了相 应的容器扩展点,以便我们可以根据具体场景的需要加入自定义的扩展逻辑。
- 容器启动阶段
容器启动伊始,首先会通过某种途径加载Configuration MetaData。除了代码方式比较直接,在大 部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必 要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。 图4-9演示了这个阶段的主要工作。总地来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。当然, 一些验证性或者辅助性的工作也可以在这个阶段完成。
- Bean实例化阶段
经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefini- tionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器 需要隐式地调用getBean方法时,就会触发第二阶段的活动。
该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的 BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接 口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。 如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。
插手“容器的启动”
Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修 改其中bean定义的某些属性,为bean定义增加其他信息等。
-
如果要自定义实现BeanFactoryPostProcessor,通常我们需要实现org.springframework. beans.factory.config.BeanFactoryPostProcessor接口。同时,因为一个容器可能拥有多个BeanFactoryPostProcessor,这个时候可能需要实现类同时实现Spring的org.springframework.core. Ordered接口,以保证各个BeanFactoryPostProcessor可以按照预先设定的顺序执行(如果顺序紧要的话)。
-
但是,因为Spring已经提供了几个现成的BeanFactoryPostProcessor实现类,所以,大多时候,我们很少自己去实现某个BeanFactoryPostProcessor。其中
- org.springframework.beans. factory.config.PropertyPlaceholderConfigurer 解析$ 占位符
- org.springframework.beans.factory. config.PropertyOverrideConfigurer 在应用程序上下文定义覆盖bean的属性值, 例如,加密的值可以在使用之前对它们相应地解密。配置在properties文件中的信息通常都以明文表示,PropertyOverrideConfigurer的父类 PropertyResourceConfigurer提供了一个protected类型的方法convertPropertyValue,允许子类 覆盖这个方法对相应的配置项进行转换,如对加密后的字符串解密之后再覆盖到相应的bean定义中。 当然,既然PropertyPlaceholderConfigurer也同样继承了PropertyResourceConfigurer,我们也可以针对PropertyPlaceholderConfigurer应用类似的功能。
是两个比较常用的BeanFactoryPostProcessor。
-
为了处理配置文件中的数据类型与真正的业务对象所定义的数据类型转换,Spring还允许我们通过 org.springframework.beans.factory.config.CustomEditorConfigurer 来注册自定义的PropertyEditor以补助容器中默认的PropertyEditor。可以参考BeanFactoryPostProcessor的Javadoc来了解更多其实现子类的情况。
我们可以通过两种方式来应用BeanFactoryPostProcessor,分别针对基本的IoC容器 BeanFactory和较为先进的容器ApplicationContext。
对于BeanFactory来说,我们需要用手动方式应用所有的BeanFactoryPostProcessor。对于ApplicationContext来说,情况看起来要好得多。因为ApplicationContext会自动识别配 置文件中的BeanFactoryPostProcessor并应用它,所以,相对于BeanFactory,在ApplicationContext 中加载并应用BeanFactoryPostProcessor,仅需要在XML配置文件中将这些BeanFactoryPost- Processor简单配置一下即可。
BeanFactory默认延迟初始化所有类,在那个资源贫瘠的时候使用此种方式最好不过,但在现代这点资源已经忽略,用户更需要的是快速ApplicationContext在默认会初始化所有的类,并且增加了很多功能。可以理解ApplicationContext是更加强壮的BeanFactory
PropertyPlaceholderConfigurer
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanFactoryPostProcessorDemoMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("BeanFactoryPostProcessor-ApplicationContext.xml");
DataSource bean = context.getBean(DataSource.class);
System.out.println(bean.toString());
}
}
//-----
//输出
原始属性集合方法
开始替换 -> jdbc.url=mysql://xxxxx
开始名称+占位符替换方法 -> jdbc.url=mysql://xxxxx
开始属性替换方法 -> mysql://xxxxx+名称替换
替换完毕 -> jdbc.url=mysql://xxxxx+名称替换+属性值替换
开始替换 -> jdbc.username=mysql
开始名称+占位符替换方法 -> jdbc.username=mysql
开始属性替换方法 -> mysql+名称替换
替换完毕 -> jdbc.username=mysql+名称替换+属性值替换
开始替换 -> jdbc.password=123123
开始名称+占位符替换方法 -> jdbc.password=123123
开始属性替换方法 -> 123123+名称替换
替换完毕 -> jdbc.password=123123+名称替换+属性值替换
DataSource{url='mysql://xxxxx+名称替换+属性值替换', userName='mysql+名称替换+属性值替换', password='123123+名称替换+属性值替换'}
public class DataSource {
private String url;
private String userName;
private String password;
public void setUrl(String url) {
this.url = url;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "DataSource{" +
"url='" + url + '\'' +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import java.util.Properties;
public class MyPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
@Override
protected String convertProperty(String propertyName, String propertyValue) {
System.out.println("开始名称+占位符替换方法 -> "+propertyName+"="+propertyValue);
return super.convertProperty(propertyName, propertyValue+"+名称替换");
}
/**
*
* @param props the Properties to convert
*/
@Override
protected void convertProperties(Properties props) {
System.out.println("原始属性集合方法");
super.convertProperties(props);
}
@Override
protected String convertPropertyValue(String originalValue) {
System.out.println("开始属性替换方法 -> "+originalValue);
return super.convertPropertyValue(originalValue+"+属性值替换");
}
}
//super.convertProperties(props); 方法做了修改
protected void convertProperties(Properties props) {
Enumeration<?> propertyNames = props.propertyNames();
while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
String propertyValue = props.getProperty(propertyName);
System.out.println("开始替换 -> "+propertyName+"="+propertyValue);
String convertedValue = convertProperty(propertyName, propertyValue);
System.out.println("替换完毕 -> "+propertyName+"="+convertedValue);
System.out.println();
if (!ObjectUtils.nullSafeEquals(propertyValue, convertedValue)) {
props.setProperty(propertyName, convertedValue);
}
}
}
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean class="com.mm.demo.factory.feanfactorypostprocessor.MyPropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>conf/jdbc.properties</value>
</list>
</property>
</bean>
<bean class="com.mm.demo.factory.feanfactorypostprocessor.DataSource">
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="userName">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
</bean>
</beans>
此处仅仅是介绍了几个BeanFactoryPostProcessor的实现类,足以说明在容器启动阶段去干预
了解 bean 的一生
在已经可以借助于BeanFactoryPostProcessor来干预Magic实现的第一个阶段(容器启动阶段) 的活动之后,我们就可以开始探索下一个阶段,即bean实例化阶段的实现逻辑了。
容器启动之后,并不会马上就实例化相应的bean定义。我们知道,容器现在仅仅拥有所有对象的 BeanDefinition来保存实例化阶段将要用的必要信息。只有当请求方通过BeanFactory的getBean() 方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。BeanFactory的getBean方 法可以被客户端对象显式调用,也可以在容器内部隐式地被调用。隐式调用有如下两种情况。
- 对于BeanFactory来说,对象实例化默认采用延迟初始化。通常情况下,当对象A被请求而需 要第一次实例化的时候,如果它所依赖的对象B之前同样没有被实例化,那么容器会先实例化 对象A所依赖的对象。这时容器内部就会首先实例化对象B,以及对象 A依赖的其他还没有被 实例化的对象。这种情况是容器内部调用getBean(),对于本次请求的请求方是隐式的。
- ApplicationContext启动之后会实例化所有的bean定义,这个特性在本书中已经多次提到。 但ApplicationContext在实现的过程中依然遵循Spring容器实现流程的两个阶段,只不过它 会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有bean定义的实例化方法 getBean()。这就是为什么当你得到ApplicationContext类型的容器引用时,容器内所有对 象已经被全部实例化完成。不信你查一下类org.springframework.context.support. AbstractApplicationContext的refresh()方法。
之所以说getBean()方法是有可能触发Bean实例化阶段的活动,是因为只有当对应某个bean定义的getBean()方法第一次被调用时,不管是显式的还是隐式的,Bean实例化阶段的活动才会被触发,第二次被调用则会直接返回容器缓存的第一次实例化完的对象实例(prototype类型bean除外)。当 getBean()方法内部发现该bean定义之前还没有被实例化之后,会通过createBean()方法来进行具体 的对象实例化,实例化过程如图4-10所示。
Spring容器将对其所管理的对象全部给予统一的生命周期管理,这些被管理的对象完全摆脱了原 来那种“new完后被使用,脱离作用域后即被回收”的命运。下面我们将详细看一看现在的每个bean 在容器中是如何走过其一生的。
可以在org.springframework.beans.factory.support.AbstractBeanFactory类的代 码中查看到getBean()方法的完整实现逻辑,可以在其子类org.springframework.beans. factory.support.AbstractAutowireCapableBeanFactory的代码中一窥createBean()方 法的全貌。
Bean的实例化与BeanWrapper
容器在内部实现的时候,采用“策略模式(Strategy Pattern)”来决定采用何种方式初始化bean实例。 通常,可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。
org.springframework.beans.factory.support.InstantiationStrategy定义是实例化策略 的抽象接口,其直接子类SimpleInstantiationStrategy实现了简单的对象实例化功能,可以通过反射来实例化对象实例,但不支持方法注入方式的对象实例化。CglibSubclassingInstantiationStrategy继承了SimpleInstantiationStrategy的以反射方式实例化对象的功能,并且通过CGLIB 的动态字节码生成功能,该策略实现类可以动态生成某个类的子类,进而满足了方法注入所需的对象实例化需求。默认情况下,容器内部采用的是CglibSubclassingInstantiationStrategy。
容器只要根据相应bean定义的BeanDefintion取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。但是,返回方式上有些“点缀”。不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的对象实例 进行包裹,返回相应的BeanWrapper实例。
至此,第一步结束。
BeanWrapper接口通常在Spring框架内部使用,它有一个实现类org.springframework.beans.
BeanWrapperImpl。其作用是对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。而在第一步结束后返回BeanWrapper实例而不是原先的对象实例, 就是为了第二步“设置对象属性”。
BeanWrapper定义继承了org.springframework.beans.PropertyAccessor接口,可以以统一的 方式对对象属性进行访问;BeanWrapper定义同时又直接或者间接继承了PropertyEditorRegistry 和TypeConverter接口。不知你是否还记得CustomEditorConfigurer?当把各种PropertyEditor注 册给容器时,知道后面谁用到这些PropertyEditor吗?对,就是BeanWrapper!在第一步构造完成 对象之后,Spring会根据对象实例构造一个BeanWrapperImpl实例,然后将之前CustomEditor- Configurer注册的PropertyEditor复制一份给BeanWrapperImpl实例(这就是BeanWrapper同时又 是PropertyEditorRegistry的原因)。这样,当BeanWrapper转换类型、设置对象属性值时,就不 会无从下手了。
使用BeanWrapper对bean实例操作很方便,可以免去直接使用Java反射API(Java Reflection API) 操作对象实例的烦琐。来看一段代码(见代码清单4-49),之后我们就会更加清楚Spring容器内部是如何设置对象属性的了!
各色的Aware接口
当对象实例化完成并且相关属性以及依赖设置完成之后,Spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义。如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。
这些Aware接口为如下几个。
-
org.springframework.beans.factory.BeanNameAware。如果Spring容器检测到当前对象实 例实现了该接口,会将该对象实例的bean定义对应的beanName设置到当前对象实例。
-
org.springframework.beans.factory.BeanClassLoaderAware。如果容器检测到当前对 象实例实现了该接口,会将对应加载当前bean的Classloader注入当前对象实例。默认会使用 加载org.springframework.util.ClassUtils类的Classloader。
-
org.springframework.beans.factory.BeanFactoryAware。在介绍方法注入的时候,我们 提到过使用该接口以便每次获取prototype类型bean的不同实例。如果对象声明实现了 BeanFactoryAware接口,BeanFactory容器会将自身设置到当前对象实例。这样,当前对象 实例就拥有了一个BeanFactory容器的引用,并且可以对这个容器内允许访问的对象按照需要 进行访问。
以上几个Aware接口只是针对BeanFactory类型的容器而言,对于ApplicationContext类型的容 器,也存在几个Aware相关接口。不过在检测这些接口并设置相关依赖的实现机理上,与以上几个接 口处理方式有所不同,使用的是下面将要说到的BeanPostProcessor方式。不过,设置Aware接口这 一步与BeanPostProcessor是相邻的,把这几个接口放到这里一起提及,也没什么不可以的。
对于ApplicationContext类型容器,容器在这一步还会检查以下几个Aware接口并根据接口定义 设置相关依赖。
-
org.springframework.context.ResourceLoaderAware 。 ApplicationContext 实 现 了 Spring的ResourceLoader接口(后面会提及详细信息)。当容器检测到当前对象实例实现了 ResourceLoaderAware接口之后,会将当前ApplicationContext自身设置到对象实例,这样 当前对象实例就拥有了其所在ApplicationContext容器的一个引用。
-
org.springframework.context.ApplicationEventPublisherAware。ApplicationContext 作为一个容器,同时还实现了ApplicationEventPublisher接口,这样,它就可以作为Appli- cationEventPublisher来使用。所以,当前ApplicationContext容器如果检测到当前实例 化的对象实例声明了ApplicationEventPublisherAware接口,则会将自身注入当前对象。
-
org.springframework.context.MessageSourceAware。ApplicationContext通过Message- Source接口提供国际化的信息支持,即I18n(Internationalization)。它自身就实现了Message- Source接口,所以当检测到当前对象实例实现了MessageSourceAware接口,则会将自身注入 当前对象实例。
-
org.springframework.context.ApplicationContextAware。如果ApplicationContext 容器检测到当前对象实现了ApplicationContextAware接口,则会将自身注入当前对象实例。
BeanPostProcessor
BeanPostProcessor的概念容易与BeanFactoryPostProcessor的概念混淆。但只要记住BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段, 这两个概念就比较容易区分了。
与BeanFactoryPostProcessor通常会处理容器内所有符合条件的BeanDefinition类似,BeanPostProcessor会处理容器内所有符合条件的实例化后的对象实例。
/**
* Factory hook that allows for custom modification of new bean instances —
* for example, checking for marker interfaces or wrapping beans with proxies.
*
* 工厂钩子方法,可以自定义修改new出来的bean实例
* 例如:检查标记接口或对bean进行代理
*
* <p>Typically, post-processors that populate beans via marker interfaces
* or the like will implement {@link #postProcessBeforeInitialization},
* while post-processors that wrap beans with proxies will normally
* implement {@link #postProcessAfterInitialization}.
*
* 通常:通过标记接口或类似对象为bean赋值实现postProcessBeforeInitialization方法
* 对bean进行包装代理通常实现postProcessAfterInitialization方法
*
* <h3>Registration</h3>
* <p>An {@code ApplicationContext} can autodetect {@code BeanPostProcessor} beans
* in its bean definitions and apply those post-processors to any beans subsequently
* created. A plain {@code BeanFactory} allows for programmatic registration of
* post-processors, applying them to all beans created through the bean factory.
*
* ApplicationContext可以自动注册BeanPostProcessor实现类到Spring中
* BeanFactory则需要手动注册到Spring中
* 他们都会在所有bean创建的过程中被调用
*
* <h3>Ordering</h3>
* <p>{@code BeanPostProcessor} beans that are autodetected in an
* {@code ApplicationContext} will be ordered according to
* {@link org.springframework.core.PriorityOrdered} and
* {@link org.springframework.core.Ordered} semantics. In contrast,
* {@code BeanPostProcessor} beans that are registered programmatically with a
* {@code BeanFactory} will be applied in the order of registration; any ordering
* semantics expressed through implementing the
* {@code PriorityOrdered} or {@code Ordered} interface will be ignored for
* programmatically registered post-processors. Furthermore, the
* {@link org.springframework.core.annotation.Order @Order} annotation is not
* taken into account for {@code BeanPostProcessor} beans.
*
* BeanPostProcessor的bean实例将将根据接口实现排序进行自动注册到ApplicationContext中
* - 实现PriorityOrdered接口最优先
* - 实现Ordered其次
* - 没有的最后
* 对比BeanFactory,编程方式注册BeanPostProcessor则排序不生效
* 此外@Order注解对于BeanPostProcessor的注册顺序并没有用
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 10.10.2003
* @see InstantiationAwareBeanPostProcessor
* @see DestructionAwareBeanPostProcessor
* @see ConfigurableBeanFactory#addBeanPostProcessor
* @see BeanFactoryPostProcessor
*/
public interface BeanPostProcessor {
/**
* Apply this {@code BeanPostProcessor} to the given new bean instance <i>before</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* <p>The default implementation returns the given {@code bean} as-is.
*
* 应用这个方法会在bean初始化前进行回调,这个bean已经填充好了属性值,可以将这个原始bean进行包裹返回。
* 如果方法返回null则后续的postProcessBeforeInitialization方法将不会被调用
*
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one;
* if {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* Apply this {@code BeanPostProcessor} to the given new bean instance <i>after</i> any bean
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
* instance and the objects created by the FactoryBean (as of Spring 2.0). The
* post-processor can decide whether to apply to either the FactoryBean or created
* objects or both through corresponding {@code bean instanceof FactoryBean} checks.
* <p>This callback will also be invoked after a short-circuiting triggered by a
* {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
* in contrast to all other {@code BeanPostProcessor} callbacks.
* <p>The default implementation returns the given {@code bean} as-is.
*
* 应用这个方法会在bean初始化之后进行回调,这个bean已经填充好了属性值,可以将这个原始bean进行包裹返回。
* 如果是FactoryBean,在Spring2.0时代这个回调会在 创建FactoryBean和FactoryBean创建对象时都进行回调。
* 后处理器可以通过相应的FactoryBean检查来决定是应用到FactoryBean还是创建的对象,还是两者都应用。
* 与其它在BeanPostProcessor回调不同的是在InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation方法执行短路以后,这个方法依然会被回调。
* 如果方法返回null则后续的postProcessAfterInitialization方法将不会被调用
*
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one;
* if {@code null}, no subsequent BeanPostProcessors will be invoked
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.FactoryBean
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
postProcessBeforeInitialization()方法是图4-10中BeanPostProcessor前置处理这一步将 会执行的方法,postProcessAfterInitialization()则是对应图4-10中BeanPostProcessor后置处 理那一步将会执行的方法。BeanPostProcessor的两个方法中都传入了原来的对象实例的引用,这为 我们扩展容器的对象实例化过程中的行为提供了极大的便利,我们几乎可以对传入的对象实例执行任 何的操作。
通常比较常见的使用BeanPostProcessor的场景,是处理标记接口实现类,或者为当前对象提供 代理实现。
实际上,有一种特殊类型的BeanPostProcessor没有提到,它的执行时机与通常的 BeanPostProcessor不同。 org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor 接口可以在对象的实例化过程中导致某种类似于电路“短路”的效果。实际上,并非所有注册 到Spring容器内的bean定义都是按照图4-10的流程实例化的。在所有的步骤之前,也就是实例 化bean对象步骤之前,容器会首先检查容器中是否注册有InstantiationAwareBeanPostProcessor类型的BeanPostProcessor。如果有,首先使用相应的InstantiationAwareBeanPostProcessor来构造对象实例。构造成功后直接返回构造完成的对象实例,而不会按照“正 规的流程”继续执行。这就是它可能造成“短路”的原因。 不过,通常情况下都是Spring容器内部使用这种特殊类型的BeanPostProcessor做一些动态对 象代理等工作,我们使用普通的BeanPostProcessor实现就可以。这里简单提及一下,目的是让大家有所了解。
InitializingBean和init-method
org.springframework.beans.factory.InitializingBean是容器内部广泛使用的一个对象生命周期标识接口该接口定义很简单,其作用在于,在对象实例化过程调用过“BeanPostProcessor的前置处理” 之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用其afterProper- tiesSet()方法进一步调整对象实例的状态。比如,在有些情况下,某个业务对象实例化完成后,还 不能处于可以使用状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet() 中完成对该业务对象的后续处理。
DisposableBean与destroy-method
当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类 型的bean实例,看其是否实现了org.springframework.beans.factory.DisposableBean接口。或 者其对应的bean定义是否通过
通过对BeanFactory进一步发掘我们发现了除ioc能力外还提供的能力
容器启动阶段
通过BeanFactoryPostProcessor来扩展容器启动阶段,到达对BeanDefinition的定制
Bean实例化阶段
BeanWrapper,通过包装原对象,达到便利的操作属性的目的等
Aware接口,通过接口为应用注入Spring内部类
- org.springframework.beans.factory.BeanNameAware
- org.springframework.beans.factory.BeanClassLoaderAware
- org.springframework.beans.factory.BeanFactoryAware
- org.springframework.context.ResourceLoaderAware
- org.springframework.context.ApplicationEventPublisherAware
- org.springframework.context.MessageSourceAware
- org.springframework.context.ApplicationContextAware
BeanPostProcessor接口,在bean初始化前后进行扩展
init-method
destroy-method