上一篇博客讲了bean的创建过程。这次跟大家分享BeanDefinition的注册过程。
一、什么是BeanDefinition
BeanDefinition:就是bean的定义信息,比如bean的名称,对应的class,bean的属性值,bean是否是单列等等,一般是通过xml来定义的,如下所示:
<bean id="test" class="org.springframework.beans.TestBean">
<property name="name"><value>custom</value></property>
<property name="age"><value>25</value></property>
</bean>
二、怎么注册BeanDefinition
我们知道bean的实例是根据BeanDefinition的信息来创建的,而BeanDefinition是通过xml或者Properties形式的配置文件定义的,注册bean主要解决下面几个问题:
1.BeanDefinition定义的文件存在哪,怎么去加载?
2.怎么去解析xml或者其他格式的文件成BeanDefinition的实例?
3.BeanDefinition是怎么在工厂里存放和获取的?
下面我们来一条条解答:
1.BeanDefinition定义的文件存在哪,怎么去加载?
spring专门定义了InputStreamSource和Resource来解决这个问题
public interface InputStreamSource {
/**
* 返回流
* @return
* @throws IOException
*/
InputStream getInputStream() throws IOException;
}
Resource接口继承InputStreamSource,做了扩展
public interface Resource extends InputStreamSource {
/**
* Return whether this resource actually exists in physical form.
*/
boolean exists();
/**
* Return whether this resource represents a handle with an open
* stream. If true, the InputStream cannot be read multiple times,
* and must be read and closed to avoid resource leaks.
* <p>Will be false for all usual resource descriptors.
*/
boolean isOpen();
/**
* Return a URL handle for this resource.
* @throws IOException if the resource cannot be resolved as URL,
* i.e. if the resource is not available as descriptor
*/
URL getURL() throws IOException;
/**
* Return a File handle for this resource.
* @throws IOException if the resource cannot be resolved as absolute
* file path, i.e. if the resource is not available in a file system
*/
File getFile() throws IOException;
/**
* Return a description for this resource,
* to be used for error output when working with the resource.
* <p>Implementations are also encouraged to return this value
* from their toString method.
* @see java.lang.Object#toString
*/
String getDescription();
}
我们知道配置文件可能存在磁盘某个目录下,或者类classpath目录下,甚至来自网络。所以Resource有如下几个实现类:
ClassPathResource 通过类路径获取资源文件
FileSystemResource 通过文件系统获取资源
UrlResource 通过URL地址获取资源
ByteArrayResource 获取字节数组封装的资源
ServletContextResource 获取ServletContext环境下的资源
InputStreamResource 获取输入流封装的资源
2.怎么去解析xml或者其他格式的文件成BeanDefinition的实例?
一般bean是通过xml或者Properties配置文件类定义的,所以我们要解析xml或者Properties来生成bean的定义信息,Spring是通过AbstractBeanDefinitionReader的解析的,AbstractBeanDefinitionReader有两个实现类:
PropertiesBeanDefinitionReader 通过Properties配置文件方式解析
XmlBeanDefinitionReader 通过xml方式解析
3.BeanDefinition是怎么在工厂里存放和获取的?
首先我们看一下工厂的BeanDefinition注册的接口:
public interface BeanDefinitionRegistry {
/**
* 获取bean定义的数量
* @return
*/
int getBeanDefinitionCount();
/**
* 获取bean定义的名称
* @return
*/
String[] getBeanDefinitionNames();
/**
* 通过名称判断是否包含bean的定义
* @param name
* @return
*/
boolean containsBeanDefinition(String name);
/**
* 通过名称获取bean的定义
* @param name
* @return
* @throws BeansException
*/
BeanDefinition getBeanDefinition(String name) throws BeansException;
/**
* 注册bean的定义
* @param name
* @param beanDefinition
* @throws BeansException
*/
void registerBeanDefinition(String name, BeanDefinition beanDefinition)
throws BeansException;
/**
* 果果名称获取别名
* @param name
* @return
* @throws NoSuchBeanDefinitionException
*/
String[] getAliases(String name) throws NoSuchBeanDefinitionException;
/**
* 注册别名
* @param name
* @param alias
* @throws BeansException
*/
void registerAlias(String name, String alias) throws BeansException;
}
该接口的实现类是DefaultListableBeanFactory,DefaultListableBeanFactory里有个map专门存放BeanDefinition
/** Map of bean definition objects, keyed by bean name */
private Map beanDefinitionMap = new HashMap();
注册的过程其实就是讲BeanDefinition放到map里,name作为key,BeanDefinition是value。
XmlBeanFactory继承DefaultListableBeanFactory,注册通过xml的定义的bean
XmlBeanFactory的源码:
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* Create a new XmlBeanFactory with the given resource,
* which must be parsable using DOM.
* @param resource XML resource to load bean definitions from
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* Create a new XmlBeanFactory with the given InputStream,
* which must be parsable using DOM.
* <p>It's preferable to use a Resource argument instead of an
* InputStream, to retain location information. This constructor
* is mainly kept for backward compatibility.
* @param is XML InputStream to load bean definitions from
* @throws BeansException in case of loading or parsing errors
* @see #XmlBeanFactory(Resource)
*/
public XmlBeanFactory(InputStream is) throws BeansException {
this(new InputStreamResource(is, "(no description)"), null);
}
/**
* Create a new XmlBeanFactory with the given input stream,
* which must be parsable using DOM.
* @param resource XML resource to load bean definitions from
* @param parentBeanFactory parent bean factory
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
其实源码很简单,创建一个bean的解析器,这里当然是xml的解析器,Resource(获取xml配置文件)通过构造方法传入,调用reader.loadBeanDefinitions(resource)来注册BeanDefinition
三、设计理念:正交性
阅读源码我们不但要学习里面的逻辑和模式,更要学习里面的设计理念,这里先给大家介绍一个重要的设计理念:正交性。
“正交”在数学上指的是线性无关,最常见的例子就是坐标系下的x 轴和y轴,对于一个点来讲,它的x值的变化不会影响到y, y值得变化不会影响到x ,即x和y是正交的。
正交的威力在于互不影响,扩展方便,单用一个坐标轴可以表示一个直线上的所有的点, 再加一个y 轴就能表示平面上的所有的点, 再加一个z轴 3维空间中的所有点都能表示出来了!
软件编程也是如此,无论你使用何种方法, 就是让程序形成互不影响、可以独立变化的多个维度, 这样的程序不仅优雅漂亮,更是容易理解,容易维护。
BeanDefinition的Resource和AbstractBeanDefinitionReader体现的正交性,bean定义获取和解析可以独立变化,相互不影响。