Spring复杂的BeanFactory继承体系该如何理解? ----上

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring复杂的BeanFactory继承体系该如何理解? ----上相关的知识,希望对你有一定的参考价值。

Spring复杂的BeanFactory继承体系该如何理解? ----上



本篇文章想和大家一起来讨论一下BeanFacotory复杂的家族体系背后的理念所在,本篇文章针对的是对spring体系结构和源码有过一定了解的小伙伴


BeanFactory和ApplicationContext介绍

先给大家回顾一下这两个主要的IOC容器类型

Spring提供了两种容器类型:BeanFactory和ApplicationContext。

BeanFactory: 基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IoC容器选择。

ApplicationContext:ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等,这些会在后面详述。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器是比较合适的选择。

通过下图,我们可以对BeanFactory和ApplicationContext之间的关系有一个更清晰的认识。


ApplicationContext间接继承自BeanFactory,所以说它是构建于BeanFactory之上的IoC容器。并且ApplicationContext还继承了其他三个接口


闲谈BeanFactory

BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理,DefaultListableBeanFactory就是这么一个比较通用的BeanFactory实现类。

DefaultListableBeanFactory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFactory的实现中担当Bean注册管理的角色。

基本上,BeanFactory接口只定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。

BeanDefinitionRegistry接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFactory实现类会实现这个接口来管理Bean的注册。

打个比方说,BeanDefinitionRegistry就像图书馆的书架,所有的书是放在书架上的。

虽然你还书或者借书都是跟图书馆(也就是BeanFactory,或许BookFactory可能更好些)打交道,但书架才是图书馆存放各类图书的地方。

所以,书架相对于图书馆来说,就是它的“BookDefinitionRegistry”。 每一个受管的对象,在容器中都会有一个BeanDefinition的实例(instance)与之相对应,该BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象
类、构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory会通过这些信息为客户端返回一个完备可用的对象实例。


外部配置文件方式定义Bean依赖关系

Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式。当然,如果你愿意也可以引入自己的文件格式,前提是真的需要。

采用外部配置文件时,Spring的IoC容器有一个统一的处理方式。通常情况下,需要根据不同的外部配置文件格式,给出相应的BeanDefinitionReader实现类,由BeanDefinitionReader的相应实现类负责将相应的配置文件内容读取并映射到BeanDefinition,然后将映射后的BeanDefinition注册到一个BeanDefinitionRegistry,之后,BeanDefinitionRegistry即完成Bean的注册和加载。

当然,大部分工作,包括解析文件格式、装配BeanDefinition之类的工作,都是由BeanDefinitionReader的相应实现类来做的,BeanDefinitionRegistry只不过负责保管而已。整个过程类似于如下

代码:

BeanDefinitionRegistry beanRegistry = <某个BeanDefinitionRegistry实现类,通常为DefaultListableBeanFactory>; 
BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry); 
beanDefinitionReader.loadBeanDefinitions("配置文件路径"); 
// 现在我们就取得了一个可用的BeanDefinitionRegistry实例

1. Properties配置格式的加载

Spring提供了org.springframework.beans.factory.support.PropertiesBeanDefinitionReader类用于Properties格式配置文件的加载,所以,我们不用自己去实现BeanDefinitionReader,只要根据该类的读取规则,提供相应的配置文件即可。

Properties格式表达的依赖注入配置内容

djNewsProvider.(class)=..FXNewsProvider 
# ----------通过构造方法注入的时候------------- 
djNewsProvider.$0(ref)=djListener 
djNewsProvider.$1(ref)=djPersister 
# ----------通过setter方法注入的时候--------- 
# djNewsProvider.newsListener(ref)=djListener 
# djNewsProvider.newPersistener(ref)=djPersister 

大家不需要纠结为何要这样写,因为properties配置文件定义bean的依赖关系本身用的就少,所以目前大家看看,知道有这回事就行了

加载Properties配置的BeanFactory的使用演示

 
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory(); 
 BeanFactory container = (BeanFactory)bindViaPropertiesFile(beanRegistry); 
FXNewsProvider newsProvider = 
(FXNewsProvider)container.getBean("djNewsProvider"); 
 newsProvider.getAndPersistNews(); 
 

public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry) 
 
PropertiesBeanDefinitionReader reader = 
new PropertiesBeanDefinitionReader(registry); 
 reader.loadBeanDefinitions("classpath:../../binding-config.properties"); 
 return (BeanFactory)registry; 
 

基于Properties的加载方式就是这么简单,所有的信息配置到Properties文件即可,不用再通过冗长的代码来完成对象的注册和依赖绑定。这些工作就交给相应的BeanDefinitionReader来做吧!哦,我的意思是,让给PropertiesBeanDefinitionReader来做。

注意 Spring提供的PropertiesBeanDefinitionReader是按照Spring自己的文件配置规则进 行加载的,而同样的道理,你也可以按照自己的规则来提供相应的Properties配置文件。只不过,现在需要实现你自己的“PropertiesBeanDefinitionReader”来读取并解析。


2. XML配置格式的加载

这个应该是大家最熟悉不过的了

FX新闻系统相关类对应XML格式的配置内容

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ➥
"http://www.springframework.org/dtd/spring-beans.dtd"> 
<beans> 
 <bean id="djNewsProvider" class="..FXNewsProvider"> 
 <constructor-arg index="0"> 
 <ref bean="djNewsListener"/> 
 </constructor-arg> 
 <constructor-arg index="1"> 
 <ref bean="djNewsPersister"/> 
 </constructor-arg> 
 </bean> 
 
 <bean id="djNewsListener" class="..impl.DowJonesNewsListener"> 
 </bean> 
 <bean id="djNewsPersister" class="..impl.DowJonesNewsPersister"> 
 </bean> 
</beans> 

加载XML配置文件的BeanFactory的使用演示

public static void main(String[] args) 
 
 DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory(); 
 BeanFactory container = (BeanFactory)bindViaXMLFile(beanRegistry); 
 FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); 
newsProvider.getAndPersistNews(); 
 

public static BeanFactory bindViaXMLFile(BeanDefinitionRegistry registry) 
 
  XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); 
 reader.loadBeanDefinitions("classpath:../news-config.xml"); 
 return (BeanFactory)registry; 
 // 或者直接
 //return new XmlBeanFactory(new ClassPathResource("../news-config.xml")); 

与为Properties配置文件格式提供PropertiesBeanDefinitionReader相对应,Spring同样为XML格式的配置文件提供了现成的BeanDefinitionReader实现,即XmlBeanDefinitionReader。

XmlBeanDefinitionReader负责读取Spring指定格式的XML配置文件并解析,之后将解析后的文件内容映射到相应的BeanDefinition,并加载到相应的BeanDefinitionRegistry中(在这里是DefaultListableBeanFactory)。

这时,整个BeanFactory就可以放给客户端使用了。

除了提供XmlBeanDefinitionReader用于XML格式配置文件的加载,Spring还在DefaultListableBeanFactory的基础上构建了简化XML格式配置加载的XmlBeanFactory实现。从以上代码最后注释掉的一行,你可以看到使用了XmlBeanFactory之后,完成XML的加载和BeanFactory的初始化是多么简单.

注意 当然,如果你愿意,就像Properties方式可以扩展一样,XML方式的加载同样可以扩展。 虽然XmlBeanFactory基本上已经十分完备了,但如果出于某种目的,XmlBeanFactory或者默 认的XmlBeanDefinitionReader所使用的XML格式无法满足需要的话,你同样可以通过扩展 XmlBeanDefinitionReader或者直接实现自己的BeanDefinitionReader来达到自定义XML 配置文件加载的目的。Spring的可扩展性为你服务!


3.注解方式

使用指定注解标注后的FXNews相关类

@Component 
public class FXNewsProvider
 
 @Autowired 
 private IFXNewsListener newsListener; 
 @Autowired 
 private IFXNewsPersister newPersistener; 
 
 public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) 
  
 this.newsListener = newsListner; 
 this.newPersistener = newsPersister; 
  
 ... 
 
@Component 
public class DowJonesNewsListener implements IFXNewsListener 
 
 ... 
 
@Component 
public class DowJonesNewsPersister implements IFXNewsPersister 
 
 ... 
 

配置使用classpath-scanning功能

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:context="http://www.springframework.org/schema/context" 
xmlns:tx="http://www.springframework.org/schema/tx" 
xsi:schemaLocation="http://www.springframework.org/schema/beans ➥
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ➥
http://www.springframework.org/schema/context ➥
http://www.springframework.org/schema/context/spring-context-2.5.xsd ➥
http://www.springframework.org/schema/tx ➥
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> 
<context:component-scan base-package="cn.spring21.project.base.package"/> 
</beans> 

< context:component-scan />会到指定的包(package)下面扫描标注有@Component的类,如果找到,则将它们添加到容器进行管理,并根据它们所标注的@Autowired为这些类注入符合条件的依赖对象。

在以上所有这些工作都完成之后,我们就可以像通常那样加载配置并执行当前应用程序了,如以下代码所示:

public static void main(String[] args) 
 
 ApplicationContext ctx = new ClassPathXmlApplicationContext("配置文件路径"); 
 FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("FXNewsProvider");
  newsProvider.getAndPersistNews(); 


BeanFactory的分层性

BeanFactory可以分层次(通过实现HierarchicalBeanFactory接口),容器A在初始化的时候,可以首先加载容器B中的所有对象定义,然后再加载自身的对象定义,这样,容器B就成为了容器A的父容器,容器A可以引用容器B中的所有对象定义:

BeanFactory parentContainer = new XmlBeanFactory(new ClassPathResource("父容器配置文件路径"));
BeanFactory childContainer = new XmlBeanFactory(new ClassPathResource("子容器配置文件路径"),parentContainer);

childContainer中定义的对象,如果通过parent指定依赖,则只能引用parentContainer中的对象定义


Bean的scope

BeanFactory除了拥有作为IoC Service Provider的职责,作为一个轻量级容器,它还有着其他一些职责,其中就包括对象的生命周期管理。

scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。

Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0之后,又引入了另外三种scope类型,即request、session和global session类型。

不过这三种类型有所限制,只能在Web应用中使用。也就是说,只有在支持Web应用的ApplicationContext中使用这三个scope才是合理的。

1. singleton

配置中的bean定义可以看作是一个模板,容器会根据这个模板来构造对象。但是要根据这个模板构造多少对象实例,又该让这些构造完的对象实例存活多久,则由容器根据bean定义的scope语意来决定。标记为拥有singleton scope的对象定义,在Spring的IoC容器中只存在一个实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并因为第一次被请求而初始化之后,将一直存活到容器退出,也就是说,它与IoC容器“几乎”拥有相同的“寿命”。

2. prototype

针对声明为拥有prototype scope的bean定义,容器在接到该类型对象的请求的时候,会每次都重新生成一个新的对象实例给请求方。

虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每次返回给请求方一个新的对象实例之后,就任由这个对象实例“自生自灭”了

对于那些请求方不能共享使用的对象类型,应该将其bean定义的scope设置为prototype。这样,每个请求方可以得到自己对应的一个对象实例。通常,声明为prototype的scope的bean定义类型,都是一些有状态的,比如保存每个顾客信息的对象。


自定义scope

在Spring 2.0之后的版本中,容器提供了对scope的扩展点,这样,你可以根据自己的需要或者应用的场景,来添加自定义的scope类型。需要说明的是,默认的singleton和prototype是硬编码到代码中的,而request、session和global session,包括自定义scope类型,则属于可扩展的scope行列,它们都实现了 org.springframework.beans.factory.config.Scope接口,该接口定义如下:

public interface Scope 
    Object get(String var1, ObjectFactory<?> var2);

    @Nullable
    Object remove(String var1);

    void registerDestructionCallback(String var1, Runnable var2);

    @Nullable
    Object resolveContextualObject(String var1);

    @Nullable
    String getConversationId();

要实现自己的scope类型,首先需要给出一个Scope接口的实现类,接口定义中的4个方法并非都是必须的,但get和remove方法必须实现。

  • 自定义的ThreadScope的定义
public class ThreadScope implements Scope 
	
	private final ThreadLocal<Map<String, Object>> threadLoacal = new ThreadLocal<Map<String, Object>>() 
		@Override
		protected Map<String, Object> initialValue() 
			return new HashMap<String, Object>();
		
	;

	@Override
	public Object get(String name, ObjectFactory<?> objectFactory) 
		Map<String, Object> scope = threadLoacal.get();
		Object obj = scope.get(name);

		// 不存在则放入ThreadLocal
		if (obj == null) 
			obj = objectFactory.getObject();
			scope.put(name, obj);
			
			System.out.println("Not exists " + name + "; hashCode: " + obj.hashCode());
		 else 
			System.out.println("Exists " + name + "; hashCode: " + obj.hashCode());
		

		return obj;
	

	@Override
	public Object remove(String name) 
		Map<String, Object> scope = threadLoacal.get();
		System.out.println("bean被销毁");
		return scope.remove(name);
	

	@Override
	public String getConversationId() 
		return null;
	

	@Override
	public void registerDestructionCallback(String arg0, Runnable arg1) 
	

	@Override
	public Object resolveContextualObject(String arg0) 
		return null;
	


在上述代码中,threadLocal用于做线程之间的数据隔离。换言之,threadLocal实现了相同的线程相同名字的bean是同一个对象;不同的线程的相同名字的bean是不同的对象。

同时,我们将对象的hashCode打印了出来。如果他们是相同的对象,则hashCode是相同的。


有了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实现(有关BeanFactoryPostProcessor的更多细节稍后将详述),

即org.springframework.beans.factory.config.CustomScopeConfigurer。对于ApplicationContext来说,因为它可以自动识别并加载BeanFactoryPostProcessor,所以我们就可以直接在配置文件中,通过这个CustomScopeConfigurer注册来ThreadScope。


使用CustomScopeConfigurer注册自定义scope

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> 
 <property name="scopes"> 
 <map> 
 <entry key="thread" value="com.foo.ThreadScope"/> 
 </map> 
 </property> 
</bean> 

编码方式:

@Configuration
@ComponentScan
public class AppConfig 

	@Bean
	public static CustomScopeConfigurer customScopeConfigurer() 
		CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();

		Map<String, Object> map = new HashMap<String, Object>();
		map.put("threadScope", new ThreadScope());

		// 配置scope
		customScopeConfigurer.setScopes(map);
		return customScopeConfigurer;
	

在以上工作全部完成之后,我们就可以在自己的bean定义中使用这个新增加到容器的自定义scope“thread”了,如下代码演示了通常情况下“thread”自定义scope的使用:

<bean id="beanName" class="..." scope="thread"> 
</bean> 

注解方式使用:

@Scope("threadScope")
@Service
public class MessageServiceImpl implements MessageService 
	
	public String getMessage() 
		return "Hello World!";
	



这里先不对源码进行深入分析,只是跑一块砖,先让大家稍微了解一下底层是如何实现的


FactoryBean

FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,请不要将其与容器名称BeanFactory相混淆。FactoryBean,其主语是Bean,定语为Factory,也就是说,它本身与其他注册到容器的对象一样,只是一个Bean而已,只不过,这种类型的Bean本身就是生产对象的工厂(Factory)。

当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现org.springframework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码。

public interface FactoryBean<T> 
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() 
        return true;
    

getObject()方法会返回该FactoryBean“生产”的对象实例,我们需要实现该方法以给出自己的对象实例化逻辑;

getObjectType()方法仅返回getObject()方法所返回的对象的类型,如果预先无法确定,则返回null;

isSingleton()方法返回结果用于表明,工厂方法(getObject())所“生产”的对象是否要以singleton形式存在于容器中。如果以singleton形式存在,则返回true,否则返回false;


如果我们想每次得到的日期都是第二天,这个该如何解决呢?

@Component
@RequiredArgsConstructor
@Data
public class MyDay 
    private final LocalDateTime nextDay;

@Component
public class MyFactory implements FactoryBean<LocalDateTime> 
    @Override
    public LocalDateTime getObject() throws Exception 
        return LocalDateTime.now().plusDays(Spring复杂的BeanFactory继承体系该如何理解? ----中

Spring复杂的BeanFactory继承体系该如何理解?---下

spring的beanFactory继承体系

Spring源码学习之BeanFactory体系结构

Spring源码解析 - BeanFactory接口体系解读

Spring BeanFactory源码学习