图为Spring的IoC容器和IoC Service Provider之间的关系。我们以往都在强调IoC的意思,现在我们来看看***容器***。
容器
Spring提供了两种容器类型:
- BeanFactory:基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化(lazy-load)策略。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以相对来说容器启动初期较快,所需资源有限。
- ApplicationContext:ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如国际化信息支持、事件发布等等。该容器启动后,默认全部初始化绑定完成,所以需要的资源相对较多。
BeanFactory的定义:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
Object getBean(String name, Class requiredType) throws BeansException;
/**
* @since 2.5
*/
Object getBean(String name, Object[] args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
/**
* @since 2.0.3
*/
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
/**
* @since 2.0.1
*/
boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException;
Class getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
使用BeanFactory后的变化
不管是否引入BeanFactory之类的轻量级容器,应用的设计和开发流程实际上没有太大的变化。换句话说,针对系统和业务逻辑,该如何设计和实现当前系统不受是否引入轻量级容器变化的影响。
对于我们的FX新闻系统,前后唯一的不同是,对象之间的依赖关系的解决方式改变了:之前我们的系统业务对象需要自己的“拉”(Pull)所依赖的业务对象,有了BeanFactory类的IoC容器之后,需要依赖什么让BeanFactory为我们推过来(Push)就行了。
简单来说,就是拥有了BeanFactory之后,要使用IoC模式进行系统业务对象的开发。
BeanFactory以前:
1-设计FXNewsProvider类用于普遍的新闻处理
public class FXNewsProvider
{
...
}
2-设计IFXNewsListener接口抽象各个新闻社不同的新闻获取方式,并给出相应实现类
public interface IFXNewsListener
{
...
}
以及
public class DowJonesNewsListener implements IFXNewsListener
{
...
}
3-设计IFXNewsPersister接口抽象不同数据访问方式,并实现相应的实现类
public interface IFXNewsPersister
{
... }
以及
public class DowJonesNewsPersister implements IFXNewsPersister
{
...
}
BeanFactory以后:既然IoC模式开发的业务对象现在不用自己操心如何解决相互依赖的关系,那么肯定得找人来做这个工作。
<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出现以前,我们通常会直接在应用程序的入口类main方法中,自己实例化相应的对象并调用:
FXNewsProvider newsProvider = new FXNewsProvider();
newsProvider.getAndPersistNews();
既然有了BeanFactory,我们只需要将“生产线图纸”交给BeanFactory,让它为我们生产一个FXNewsProvider:
BeanFactory container = new XmlBeanFactory(new ClassPathResource("配置文件路径"));
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
或者使用ApplicationContext container=new ClassPathXmlApplicationContext("配置文件路径");
或者ApplicationContext container=new FileSystemXmlApplicationContext("配置文件路径");
BeanFactory的对象注册与依赖绑定的方式
BeanFactory作为一个IoC Service Provider,支持我们上一章介绍的3种注入方式:
直接编码方式:
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 PropertyValue("newsListener",newsListener));
propertyValues.addPropertyValue(new PropertyValue("newPersistener",newsPersister));
newsProvider.setPropertyValues(propertyValues);
// 绑定完成
return (BeanFactory)registry;
}
BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行Bean的管理,DefaultListableBeanFactory就是这样一个比较通用的BeanFactory实现类。
DefaultListableBeanFactory除了简介实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFactory的实现中担当Bean注册管理的角色。
打个比方说,BeanDefinitionRegistry就像图书馆的书架,所有书是放在书架上的。虽然你结束和还书都是跟图书馆(也就是BeanFactory)打交道,但是书架才是存放各类书的地方。
而这些书,可以说是每一个受管对象,在容器中都会有一个BeanDefinition的实例与之对应,该BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等。
所以RootBeanDefinition和ChildBeanDefinition都是BeanDefinition的两个主要实现类。
那么,我们再来看这一段绑定代码(上面那段):
- 在Main方法中,首先构造一个DefaultListableBeanFactory作为BeanDefinitionRegistry,然后将其交给bindViaCode方法进行具体的对象注册和相关依赖管理,然后通过bindViaCode返回的BeanFactory取得需要的对象,最后执行相应的逻辑。
- 在bindViaCode方法中,首先针对相应的业务对象构造与其相对应的BeanDefinition,使用了RootBeanDefinition作为BeanDefinition的实现类。构造完成后,将这些BeanDefinition注册到通过方法参数传进来的BeanDefinitionRegistry中。之后,因为我们的FXNewsProvider是采用构造方法注入,所以需要通过ConstructorArgumentValues为其注入相关依赖。在这里为了同时说明setter方法注入,也写了一段,实际运行要注释掉。最后以BeanFactory的形式返回已经注册并绑定了所有相关业务对象的BeanDefinitionRegistry实例。
return (BeanFactory)registry;
这个强制转换因为registry是DefaultListableBeanFactory类型的,它同时实现了BeanFactory和BeanDefinitionRegistry接口。
外部配置文件方式
Spring IoC容器支持两种配置方式:Properties文件格式和XML文件格式。通常,给出相应的BeanDefinitionReader实现类,它负责将相应的配置文件内容读取并映射到BeanDefinition中,然后将映射后的BeanDefinition注册到一个BeanDefinitionRegistry,之后,BeanDefinitionRegistry即完成Bean的注册和加载。
BeanDefinitionRegistry beanRegistry = <某个BeanDefinitionRegistry实现类,通常为DefaultListableBeanFactory>;
BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry);
beanDefinitionReader.loadBeanDefinitions("配置文件路径");
// 现在我们就取得了一个可用的BeanDefinitionRegistry实例
下面写一段Properties配置的BeanFactory的使用演示:
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;
}
其中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.DowJon esNewsPersister
下面写一段由XML配置格式的加载:
XML配置格式是Spring支持最完整,功能最强大的表达方式。
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"));
}
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>
注解方式
使用指定注解标注后的FXNewsProvider:
@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
{
...
}
@Autowired
是这里的主角,它的存在将告之Spring容器需要为当前对象注入哪些依赖对象。而@Component
则是配合Spring中新的classpath-scanning功能使用。现在我们只要再向Spring的配置文件中增加一个“触发器”,使用这个量标注就可以获得依赖对象的注入。
配置使用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/>
会到指定的包下面扫描标注有Component的类,如果找到,则将它们添加到容器里进行管理,并根据它们所标注的@Autowired
为这些类注入符合条件的依赖对象。
以上工作完成后,就直接使用下列代码就可以加载配置并执行当前应用程序了:
public static void main(String[] args)
{
ApplicationContext ctx = new ClassPathXmlApplicationContext("配置文件路径");
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("FXNewsProvider");
newsProvider.getAndPersistNews();
}