spring揭秘 读书笔记 二 BeanFactory的对象注册与依赖绑定

时间:2023-03-08 18:45:58

本文是王福强所著<<spring揭秘>>一书的读书笔记

我们前面就说过,Spring的IoC容器时一个IoC Service Provider,而且IoC Service Provider提供两个功能对象的创建,依赖关系的管理。

不过,IoC容器这个词中,我们还得关注容器二字。它还包含了一些别的功能,如下图

spring揭秘 读书笔记 二 BeanFactory的对象注册与依赖绑定





Spring提供了两种类型的容器,分别是BeanFactory与ApplicationContext。

它们的区别在于:

BeanFactory:对于它所管理的bean,采取的是延迟加载模式,也就是说等用户使用某个bean的时候,采取加载它;另外,它启动所需要的资源也比较少。

ApplciationContext:除了拥有BeanFctory的全部功能外,它还提供了一些高级特性。对应它所管理的对象,在容器启动的时候就把所有的bean都加载了,而且启动时需要的资源也比较多。

两个容器的关系如下:

spring揭秘 读书笔记 二 BeanFactory的对象注册与依赖绑定





我们看看BeanFactory的接口说明,都是些跟查询相关的方法。

spring揭秘 读书笔记 二 BeanFactory的对象注册与依赖绑定

拥有BeanFactory之后的生活

FXNewsProvider与DowJonesNewsListener等等的代码,我就不写了。

在采用IoC模式之前,我们是这么写代码的

FXNewsProvider newsProvider = new FXNewsProvider();
newsProvider.getAndPersistNews(); 

在之前的章节里,我们就说了IoC模式的好处就是用户不用处理各个组件间的依赖问题了。

那谁来处理呢?丢给BeanFactory。

首先,我们用xml来说明依赖关系

<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>
  ...
</beans> 

看看客户端的代码

BeanFactory container = new XmlBeanFactory(new ClassPathResource("配置文件路径"));
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews(); 

至于XmlBeanFactory是个什么东西,大家看名字猜也能猜出来,就是一个实现了BeanFactoy且能从xml中读取依赖关系的对象嘛。

或者

ApplicationContext container = new ClassPathXmlApplicationContext("配置文件路径");
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews(); 

OK,这次使用了ClassPathXmlApplicationContext,就是一个实现了ApplicationContext且能从xml中读取依赖关系的对象嘛。

再或者

ApplicationContext container = new FileSystemXmlApplicationContext("配置文件路径");
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();  

后两者有什么区别?

1、ClassPathXmlApplicationContext

这个方法是从classpath下加载配置文件(适合于相对路径方式加载),例如:

ApplicationContext ctx = new ClassPathXmlApplicationContext( "/applicationcontext.xml ");

该方法参数中classpath: 前缀是不需要的,默认就是指项目的classpath路径下面;这也就是说用ClassPathXmlApplicationContext时默认的根目录是在WEB-INF/classes下面,而不是项目根目录。这个需要注意!





2、FileSystemXmlApplicationContext

这个方法是从文件绝对路径加载配置文件,例如:

ApplicationContext ctx = new FileSystemXmlApplicationContext( "G:/Test/applicationcontext.xml ");

如果在参数中写的不是绝对路径,那么方法调用的时候也会默认用绝对路径来找,我测试的时候发现默认的绝对路径是eclipse所在的路径。

采用绝对路径的话,程序的灵活性就很差了,所以这个方法一般不推荐。

(如果要使用classpath路径,需要加入前缀classpath:   )

BeanFactory的对象注册与依赖绑定方式

在上一章,我们就提到spring绑定依赖关系的方式有三种:直接编码,通过配置文件,通过注解方式。

我们一个一个来看

直接编码

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

public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) {
    AbstractBeanDefinition newsProvider =
    new RootBeanDefinition(FXNewsProvider.class,true);
    AbstractBeanDefinition newsListener =
    new RootBeanDefinition(DowJonesNewsListener.class,true);
    AbstractBeanDefinition newsPersister =
    new RootBeanDefinition(DowJonesNewsPersister.class,true); 

    // 将bean定义注册到容器中
    registry.registerBeanDefinition("djNewsProvider", newsProvider);
    registry.registerBeanDefinition("djListener", newsListener);
    registry.registerBeanDefinition("djPersister", newsPersister); 

    // 指定依赖关系
    // 1. 可以通过构造方法注入方式
    ConstructorArgumentValues argValues = new ConstructorArgumentValues();
    argValues.addIndexedArgumentValue(0, newsListener);
    argValues.addIndexedArgumentValue(1, newsPersister);
    newsProvider.setConstructorArgumentValues(argValues); 

    // 2. 或者通过setter方法注入方式
    MutablePropertyValues propertyValues = new MutablePropertyValues();
    propertyValues.addPropertyValue(new ropertyValue("newsListener",newsListener));
    propertyValues.addPropertyValue(new PropertyValue("newPersistener",newsPersister));
    newsProvider.setPropertyValues(propertyValues);
    // 绑定完成
    return (BeanFactory)registry;
}  

我们看看上面几个对象的uml类图。

spring揭秘 读书笔记 二 BeanFactory的对象注册与依赖绑定

一个一个说,BeanDefinition,见名知意,是一个对bean的描述对象。RootBeanDefinition与ChildBeanDefinition都是BeanDefinition的实现类。

RootBeanDefinition与ChildBeanDefinition有什么区别?

java中的类是有继承关系,在xml中,bean包含一个parent属性。

RootBeanDefinition:一个bean就是一个*对象(没有parent)

ChildBeanDefinition:对应于一个子Bean定义,他是从一个已有的Bean继承而来

ChildBeanDefinitionl里面有一个私有变量parentName。





下来就是BeanDefinitionRegistry

BeanDefinitionRegistry也是一个接口,其方法如下:

spring揭秘 读书笔记 二 BeanFactory的对象注册与依赖绑定

registerBeanDefinition干的事情就是把bean与name对应起来,并且把bean的实例与name都存储起来,以后再用。

DefaultListableBeanFactory实现了BeanDefinitionRegistry接口:

里面有这个两个私有属性,看看名字就知道它是干什么的了

        /** Map of bean definition objects, keyed by bean name */
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);

	/** List of bean definition names, in registration order */
	private final List<String> beanDefinitionNames = new ArrayList<String>();

说实话,我不明白为什么还需要有一个beanDefinitionNames,beanDefinitionMap的key不就是beanname吗?





解释了这么多了,我想上面使用硬代码绑定依赖关系的例子,我就不解释了,大家都能看懂。

外部配置文件方式

通过xml或者Properties文件加载依赖信息的情况还是比通过硬代码加载依赖关系普遍(或者说通过硬代码加载,本身就是为了说明问题,实际中倒是不会这么用)。

加载外部文件,那么首先就得介绍一个接口。

BeanDefinitionReader。

看名字就知道,这个接口管的是读取BeanDefinition的信息(准确的说是从外部文件读取信息,填充到BeanDefinition中)。

它有两个实现类 PropertiesBeanDefinitionReader, XmlBeanDefinitionReader

看名字就知道,一个从Properties中读,一个从xml中读。

从properties

Properties文件如下:

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

djListener.(class)=..impl.DowJonesNewsListener 

djPersister.(class)=..impl.DowJonesNewsPersister 

调用例子如下:

public static void main(String[] args)  {
  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;
} 

从xml

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>     

调用代码如下:

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每什么区别,就是多了一个XmlBeanFactory。

通过查看api文档,我们知道XmlBeanFactory直接继承自DefaultListableBeanFactory。

注解方式

自从sping2.5之后,有了注解方式,它就一下子收到了广大程序员的热烈欢迎。

bean示例:

@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  {
  ...
}   

@Component 是告诉spring的扫描器:这是一个bean。

@Autowired 是告诉spring的扫描器:这里需要一个注入对象。

示例xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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 base-package="cn.spring21.project.base.package"/>就是告诉spring容器,扫描cn.spring21.project.base.package这个包,看哪里有 @Component @Autowired等等。





下面就是大家看到的第一spring程序。

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

感谢glt

参考资料

http://blog.****.net/souichiro/article/details/6068552

http://blog.****.net/turkeyzhou/article/details/2910888