Spring容器之BeanFactory
引言Spring提供了两种容器类型:BeanFactoryApplicationContext其中ApplicationContext间接继承自BeanFactory,两者最大的不同是容器初始化策略。BeanFactory采用懒加载(lazy-load)策略,即当客户端需要访问容器内的某个对象时,才对该对象进行初始化以及依赖注入操作。所以该模式下,启动速度较快,适用于资源有限,对功...
引言
Spring提供了两种容器类型:
- BeanFactory
- ApplicationContext
其中ApplicationContext间接继承自BeanFactory,两者最大的不同是容器初始化策略。
BeanFactory采用懒加载(lazy-load)策略,即当客户端需要访问容器内的某个对象时,才对该对象进行初始化以及依赖注入操作。所以该模式下,启动速度较快,适用于资源有限,对功能要求不是很严格的场景。
ApplicationContext所管理的对象,默认启动之后全部初始化并绑定完成,故启动速度较慢,但其除了拥有BeanFactory的所有支持,还提供事件发布、国际化信息支持等,所以适用于系统资源充足,并且要求更多功能的场景。
本文首先讲解BeanFactory。
BeanFactory、BeanDefinitionRegistry和Bean的关系
BeanFactory主要完成2项工作:
- 业务对象的初始化及注册;
- 对象间依赖关系的绑定。
看一下BeanFactory的源码:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String var1) throws BeansException;
<T> T getBean(String var1, @Nullable Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
boolean containsBean(String var1);
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, @Nullable Class<?> var2) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
String[] getAliases(String var1);
}
可以发现,BeanFactory接口只定义了查询相关的方法,例如: 取得某个对象的方法(getBean)、查询某个对象是否存在于容器中的方法(containsBean),或者取得某个bean的状态或者类型的方法等。
而对象的注册管理及依赖绑定则交给BeanFactory的接口实现类来完成,如常见的DefaultListableBeanFactory。
public class DefaultListableBeanFactory
extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
...
}
DefaultListableBeanFactory还实现了BeanDefinitionRegistry接口,来完成Bean的注册管理。
简单阐释一下BeanFactory、BeanDefinitionRegistry和Bean的关系:
Bean是图书,BeanFactory相当于图书馆,而BeanDefinitionRegistry则相当于图书馆的书架。虽然还书和借书均是跟图书馆(BeanFactory)打交道,但是图书馆实际存储书的地方是书架(BeanDefinitionRegistry)。
每个Bean交给BeanFactory管理时,均会包装成BeanDefination接口的实例,BeanDefination实例负责保存Bean所有必要的信息,包括Class类型、是否是抽象类、构造方法参数及其他属性等。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
void setParentName(@Nullable String var1);
@Nullable
String getParentName();
void setBeanClassName(@Nullable String var1);
@Nullable
String getBeanClassName();
void setScope(@Nullable String var1);
@Nullable
String getScope();
void setLazyInit(boolean var1);
boolean isLazyInit();
void setDependsOn(@Nullable String... var1);
@Nullable
String[] getDependsOn();
void setAutowireCandidate(boolean var1);
boolean isAutowireCandidate();
void setPrimary(boolean var1);
boolean isPrimary();
void setFactoryBeanName(@Nullable String var1);
@Nullable
String getFactoryBeanName();
void setFactoryMethodName(@Nullable String var1);
@Nullable
String getFactoryMethodName();
ConstructorArgumentValues getConstructorArgumentValues();
default boolean hasConstructorArgumentValues() {
return !this.getConstructorArgumentValues().isEmpty();
}
MutablePropertyValues getPropertyValues();
default boolean hasPropertyValues() {
return !this.getPropertyValues().isEmpty();
}
boolean isSingleton();
boolean isPrototype();
boolean isAbstract();
int getRole();
@Nullable
String getDescription();
@Nullable
String getResourceDescription();
@Nullable
BeanDefinition getOriginatingBeanDefinition();
}
RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要实现类。
BeanFactory的对象注册与依赖绑定方式
假设我们有一家饭店,需要有蔬菜采购和厨师做菜,则Restaurant类定义如下:
public class Restaurant {
// 厨师
private Chef chef;
// 蔬菜
private Vegetable vegetable;
构造方法...
get/set方法...
}
蔬菜存在多种,我们抽象出Vegetable接口,并存在购买行为。
public interface Vegetable {
public String buy();
}
编写2个Vegetable接口的实现类Tomato、Cabbage:
public class Tomato implements Vegetable {
@Override
public String buy() {
System.out.println("采购员买来了番茄!");
return "Tomato";
}
}
public class Cabbage implements Vegetable {
@Override
public String buy() {
System.out.println("采购员买来了卷心菜!");
return "Cabbage";
}
}
厨师也可能有多种,抽象出Chef接口如下:
public interface Chef {
public void cook(String vegetableName);
}
并编写2个Chef的实现类:
public class ChineseChef implements Chef {
@Override
public void cook(String vegetableName) {
System.out.println("中国厨师正在做" + vegetableName + "!");
}
}
public class ForeignChef implements Chef {
@Override
public void cook(String vegetableName) {
System.out.println("外国厨师正在做" + vegetableName + "!");
}
}
假设我们现在要开这样一家饭店,需要中国厨师和番茄,那么如何完成对象的注册管理和依赖绑定呢?
直接编码形式
所谓直接编码形式,即用Spring的底层容器类来进行Bean的注册和管理,虽然不常用,但却有助于我们了解Spring的IOC是如何运作的。
public class Test {
public static void main(String[] args) {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = bindViaCode(beanRegistry);
// 测试Bean的装配
Restaurant chineseRestaurant = (Restaurant) container.getBean("chineseRestaurant");
Vegetable vegetable = chineseRestaurant.getVegetable();
String vegetableName = vegetable.buy();
Chef chef = chineseRestaurant.getChef();
chef.cook(vegetableName);
// 测试Bean的管理
Chef chef1 = (Chef) container.getBean("chineseChef");
chef1.cook("干锅花菜");
}
public static BeanFactory bindViaCode(BeanDefinitionRegistry registry){
AbstractBeanDefinition restaurant = new RootBeanDefinition(Restaurant.class);
AbstractBeanDefinition chef = new RootBeanDefinition(ChineseChef.class);
AbstractBeanDefinition vegetable = new RootBeanDefinition(Tomato.class);
// 将Bean注册到容器中
registry.registerBeanDefinition("chineseRestaurant", restaurant);
registry.registerBeanDefinition("chineseChef", chef);
registry.registerBeanDefinition("tomato", vegetable);
// 1. 指定依赖关系(构造方法注入)
ConstructorArgumentValues argValues = new ConstructorArgumentValues();
argValues.addIndexedArgumentValue(0, chef);
argValues.addIndexedArgumentValue(1, vegetable);
restaurant.setConstructorArgumentValues(argValues);
// 2. 指定依赖关系(setter方法注入)
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue(new PropertyValue("chef", chef));
propertyValues.addPropertyValue(new PropertyValue("vegetable", vegetable));
restaurant.setPropertyValues(propertyValues);
// 绑定完成
return (BeanFactory) registry;
}
}
运行结果如下:
采购员买来了番茄!
中国厨师正在做Tomato!
中国厨师正在做干锅花菜!
外部配置文件形式
其实就是将Bean的依赖关系放在配置文件中,然后Spring解析配置文件,得到各对象的依赖关系,进而完成对象的注册和依赖管理。
整体的流程如下:
- BeanDefinitionReader实现类读取配置文件内容,并映射得到待管理Bean的BeanDefinition;
- BeanDefinitionReader根据配置文件内容定义的依赖关系,通过BeanDefinition指定Bean之间的依赖关系;
- 将映射后的BeanDefinition注册到BeanDefinitionRegistry中。
上述大部分的工作,如解析文件格式、装配BeanDefinition之类的工作,均由BeanDefinitionReader实现类来完成,BeanDefinitionRegistry仅是负责Bean的保管而已。
整个过程类似于如下代码:
BeanDefinitionRegistry beanRegistry = <某个 BeanDefinitionRegistry 实现类,通常为DefaultListableBeanFactory>;
BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry);
beanDefinitionReader.loadBeanDefinitions("配置文件路径");
// 现在我们就取得了一个可用的BeanDefinitionRegistry实例
看一下BeanDefinitionReader:
public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry();
@Nullable
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getBeanClassLoader();
BeanNameGenerator getBeanNameGenerator();
int loadBeanDefinitions(Resource var1) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... var1) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String var1) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... var1) throws BeanDefinitionStoreException;
}
可以看出,BeanDefinitionReader通过getRegistry()获取BeanDefinitionRegistry,getResourceLoader()加载配置文件资源用于解析Bean的注册和依赖关系,getBeanClassLoader()获取类加载器用于类的加载,loadBeanDefinitions(…)方法主要运用反射将待管理的Bean封装成BeanDefinition,最后注册到BeanDefinitionRegistry中。
由上图可以看出,Spring的IOC容器默认支持3种文件格式:
- Properties文件格式
- XML文件格式
- Groovy文件格式
本文主要讨论前2种文件格式:
Properties文件
首先在resources目录下创建beans.properties文件:
# bean的name.(class)=实现类的全路径
chef.(class)=com.ruanshubin.springboot.ioc.service.impl.ChineseChef
vegetable.(class)=com.ruanshubin.springboot.ioc.service.impl.Tomato
restaurant.(class)=com.ruanshubin.springboot.ioc.domain.Restaurant
# 通过构造方法注入
restaurant.$0(ref)=chef
restaurant.$1(ref)=vegetable
# 通过setter方法注入
# restaurant.setChef(ref)=chef
# restaurant.setVegetable(ref)=vegetable
然后编写主函数:
public class FileTest {
public static void main(String[] args) {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = bindViaPropertiesFile(beanRegistry);
// 测试Bean的装配
Restaurant chineseRestaurant = (Restaurant) container.getBean("restaurant");
Vegetable vegetable = chineseRestaurant.getVegetable();
String vegetableName = vegetable.buy();
Chef chef = chineseRestaurant.getChef();
chef.cook(vegetableName);
// 测试Bean的管理
Chef chef1 = (Chef) container.getBean("chef");
chef1.cook("蒜蓉空心菜!");
}
public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry){
PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(registry);
reader.loadBeanDefinitions("classpath:beans.properties");
return (BeanFactory) registry;
}
}
运行结果如下:
采购员买来了番茄!
中国厨师正在做Tomato!
中国厨师正在做蒜蓉空心菜!
XML文件
XML配置格式是Spring支持最完整,功能最强大的表达方式,主要得益于:
- XML良好的语意表达能力;
- Spring框架从开始就自始至终保持XML配置加载的统一性。
Spring 2.x之前,XML配置文件采用DTD(Document Type Definition)实现文档的格式约束。2.x之后,引入了基于XSD(XML Schema Definition)的约束方式。不过,原来的基于DTD的方式依然有效,因为从DTD转向XSD只是“形式”上的转变。
首先在resources目录下创建beans.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="restaurant" class="com.ruanshubin.springboot.ioc.domain.Restaurant">
<constructor-arg index="0">
<ref bean="chef"/>
</constructor-arg>
<constructor-arg index="1">
<ref bean="vegetable"/>
</constructor-arg>
</bean>
<bean id="chef" class="com.ruanshubin.springboot.ioc.service.impl.ForeignChef"/>
<bean id="vegetable" class="com.ruanshubin.springboot.ioc.service.impl.Cabbage"/>
</beans>
主函数:
public class FileTest {
public static void main(String[] args) {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = bindViaXmlFile(beanRegistry);
// 测试Bean的装配
Restaurant chineseRestaurant = (Restaurant) container.getBean("restaurant");
Vegetable vegetable = chineseRestaurant.getVegetable();
String vegetableName = vegetable.buy();
Chef chef = chineseRestaurant.getChef();
chef.cook(vegetableName);
// 测试Bean的管理
Chef chef1 = (Chef) container.getBean("chef");
chef1.cook("法式焗蜗牛");
}
public static BeanFactory bindViaXmlFile(BeanDefinitionRegistry registry){
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
reader.loadBeanDefinitions("classpath:beans.xml");
return (BeanFactory) registry;
// 或者直接如下:
// return new XmlBeanFactory(new ClassPathResource("beans.xml"));
}
}
运行结果如下:
采购员买来了卷心菜!
外国厨师正在做Cabbage!
外国厨师正在做法式焗蜗牛!
当然,如果你想使用其他格式的配置文件来进行Bean的注册和依赖管理,可以实现自定义的BeanDefinitionReader来达到自定义文件加载的目的。
注解形式
基于注解的方式,主要是通过@Autowired及@Component对相关类进行标记,然后通过AbstractApplicationContext借助classpath-scanning功能来完成Bean的注册和依赖管理。
首先为相关类添加注解:
@Component
public class Restaurant {
// 厨师
@Autowired
private Chef chef;
// 蔬菜
@Autowired
private Vegetable vegetable;
...
}
@Component
public class Tomato implements Vegetable {
...
}
@Component
public class ChineseChef implements Chef {
...
}
@Autowired用于依赖注入,@Component用于Bean注册,接下来配置注解扫描。
在resources目录下新建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"
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">
<context:component-scan base-package="com.ruanshubin.springboot.ioc"/>
</beans>
context:component-scan/ 会到指定的包(package)下面扫描标注有@Component的类,如果找到,则将它们添加到容器进行管理,并根据它们所标注的@Autowired为这些类注入符合条件的依赖对象。
主函数:
public class AnnotationTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
Restaurant chineseRestaurant = (Restaurant) ctx.getBean("restaurant");
Vegetable vegetable = chineseRestaurant.getVegetable();
String vegetableName = vegetable.buy();
Chef chef = chineseRestaurant.getChef();
chef.cook(vegetableName);
// 测试Bean的管理
Chef chef1 = (Chef) ctx.getBean("chineseChef");
chef1.cook("鸡蛋韭菜水饺");
}
}
运行结果为:
采购员买来了番茄!
中国厨师正在做Tomato!
中国厨师正在做鸡蛋韭菜水饺!
我们再配置Bean时,只是在实体类上加了@Component注解,并没有指定Bean的id或name,ApplicationContext默认采用实体类的小驼峰作为id,如ChineseChef的id为chineseChef,Tomato的id为tomato。
同时,在配置Bean依赖时,@Autowired加在接口之上,如:
@Autowired
private Chef chef;
由于我们只在ChineseChef添加了@Component注解,所以Spring容器里只有1个Chef的实现类,此时,ApplicationContext直接将ChineseChef注入到Restaurant中。
假如Spring容器里有2个Chef的实现类.会出现什么情况呢?
只需要在ForeignChef上添加@Component即可。
@Component
public class ForeignChef implements Chef {
...
}
重新运行主函数,报错:
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'restaurant': Unsatisfied dependency expressed through field 'chef'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.ruanshubin.springboot.ioc.service.Chef' available: expected single matching bean but found 2: chineseChef,foreignChef
...
错误信息很明显,说进行Restaurant依赖注入的时候,发现Chef接口有2个实现类:chineseChef、foreignChef,程序不知道该选择哪个进行注入。
解决上述错误的方式有很多,后面我们专门用1篇文章进行讲解。
参考文献:
- 王福强. Spring揭秘[M]. 2009.
更多推荐
所有评论(0)