spring-beans深入源码之Bean Definitio
我们在使用一个bean得时候,在不用任何框架的情况下都是需要自己new的,spring框架既然为我们提供bean容器使得我们把bean得管理权交给它,那么我们看看spring的bean是怎么装载的,先说xml,注解的方式其实是一样的。
接着上篇的test例子<b>CollectionMergingTests</b>其中最开始有个<b>setUp</b>方法,这是junit方法运行前的装配方法
public class CollectionMergingTests extends TestCase {
private DefaultListableBeanFactory beanFactory;
@Override
protected void setUp() throws Exception {
this.beanFactory = new DefaultListableBeanFactory();
BeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory);
reader.loadBeanDefinitions(new ClassPathResource("collectionMerging.xml", getClass()));
}
………………
}
这个方法就是在装配bean到容器中。
<b>DefaultListableBeanFactory </b>是spring 真正可以独立使用IOC容器的<b>BeanFactory</b>。继承和实现的传递性可知<b>DefaultListableBeanFactory </b>默认实现了<b>BeanFactory</b>和<b>BeanDefinitionRegistry</b>,<b>DefaultListableBeanFactory </b>类继承图(引用自software-architecture-design)
直接debug看下其初始化过程便基本知道继承关系。
DefaultListableBeanFactory初始化第一步可见先是到static初始化块
static {
try {
javaUtilOptionalClass =
ClassUtils.forName("java.util.Optional", DefaultListableBeanFactory.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// Java 8 not available - Optional references simply not supported then.
}
try {
javaxInjectProviderClass =
ClassUtils.forName("javax.inject.Provider", DefaultListableBeanFactory.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - Provider interface simply not supported then.
}
}
加载class java.util.Optional
(JAVA8新加入的)和interface javax.inject.Provider
<b>java.util.Optional</b>是JAVA8新加入的类,这个类专门用来解决空引用的问题,同时这个类和lambda表达式和函数式编程也可以比较好的整合在一起使用
java.util.Optional doc
<b>javax.inject.Provider</b>提供了一个 T的实例。 通常作为一个依赖注入容器器的父接口. 可以注入任何类型的 T, 当然也可以入 Provider<T>
相对于直接注入 T ,注入 Provider<T> 有如下作用(doc的英文大概翻译):
- 检索多个实例
- 延迟或者选择性的检索一个实例
- 打破循环依赖
- 抽象的scope,可以从一个包含scope的更小的scope中检索一个实例
javax.inject.Provider doc
在debug该类初始化的过程中我在类的属性初始化上打断点无意间发现了eclipse的断点类型
breakpoint第一处的是<b>Watchpoint</b>这个主要是关注变量的值得变化过程,第二处是<b>Line Breakpoint</b>最简单的断点,即执行到此处就断点,还有。eclipse 还有条件断点,方法断点 异常断点 远程调试等 这个完了再细化记录博客吧。
这里想说的是为什么 第一行是<b>Watchpoint</b>二处是<b>Line Breakpoint</b> 同样是属性是为什么? 其实是final关键字的作用,<b>Watchpoint</b>是观察值的变化情况 final的变量是不可变的 所以final修饰的参数断点都是<b>Line Breakpoint</b> 类似于方法断点,所以有时候要分清你可以在属性上设置的断点类型。
接下来到了属性的初始化
属性初始化继续往下走的时候可以看到 类初始化的关系 先是<b>DefaultListableBeanFactory</b>static静态初始化块和static属性,然后是SimpleAliasRegistry的属性
SimpleAliasRegistry初始化
再是SimpleAliasRegistry的子类<b>DefaultSingletonBeanRegistry</b>
DefaultSingletonBeanRegistry初始化
接着是<b>DefaultSingletonBeanRegistry</b>的子类<b>FactoryBeanRegistrySupport</b> Paste_Image.png
接着<b>FactoryBeanRegistrySupport</b>子类<b>AbstractBeanFactory</b>
AbstractBeanFactory初始化
接着是<b>AbstractBeanFactory</b>子类<b>AbstractAutowireCapableBeanFactory</b>
AbstractAutowireCapableBeanFactory初始化最后到<b>DefaultListableBeanFactory</b>自己
DefaultListableBeanFactory初始化由此我们基本可以得出类初始化的顺序(为什么要说这个 是因为我看到网上好多说法和我运行的得到的结果不一致):
先找到根类,根类static静态块和static属性的初始化 接着根类的子类static静态块和static属性初始化 最后回到类本身的类static静态块和static属性初始化然后再到根类 其他属性的初始化,接着根类子类的其他属性初始化 最后回到自己的其他属性初始化。
<b>DefaultListableBeanFactory</b>初始化完毕以后接着是<b>BeanDefinitionReader</b>的初始化 选择初始化的类实例是<b>XmlBeanDefinitionReader</b>初始化完毕后调用
reader.loadBeanDefinitions(new ClassPathResource("collectionMerging.xml", getClass()));
方法去加载collectionMerging.xml配置文件。
ClassPathResource类是Resource接口实现类的子类,如果没有指定相对的类名,该类将从类的根路径开始寻找某个resource,如果指定了相对的类名,则根据指定类的相对路径来查找某个resource。上面的还可以写成:
reader.loadBeanDefinitions(new ClassPathResource(
"org/springframework/beans/factory/xml/collectionMerging.xml"));
<b>XmlBeanDefinitionReader</b>初始化的时候先找到根类<b>AbstractBeanDefinitionReader</b>
其构造方法
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
}
else {
this.environment = new StandardEnvironment();
}
}
在构造方法中有instanceof ResourceLoader 判断 显然传入的registry 是<b>DefaultListableBeanFactory</b>的实例 没有实现ResourceLoader接口 所以走else 逻辑创建<b>PathMatchingResourcePatternResolver</b>类,在<b>PathMatchingResourcePatternResolver</b>初始化的过程中
public PathMatchingResourcePatternResolver() {
this.resourceLoader = new DefaultResourceLoader();
}
其创建的是<b>DefaultResourceLoader</b>类 该类实现了<b>ResourceLoader</b>接口。所以最后还是创建的是<b>ResourceLoader</b>的实例
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}
这个构造方法就是为了初始化private ClassLoader classLoader;
属性用于load xml资源。
好了<b>Resource</b>参数构造完毕后进入
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
方法,这里将<b>Resource</b>包装成了<b>EncodedResource</b> 其实EncodedResource是对Resource的包装 增加了encoding和charset属性而已 这里都为null 相当于默认的null。接着进入方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
这里会有一个比较经典的同步缓存的方式
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
this.resourcesCurrentlyBeingLoaded是
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded");
可以看到采用了<b>ThreadLocal</b>的存贮方式。在JDK1.2的时候就提供了<b>java.lang.ThreadLocal</b><b>ThreadLocal</b>为解决多线程问题提供了非常非常大帮助,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。这样就完美的解决了多线程的问题,不过对于计数的情况不适用 建议使用ATMOIC的变量。
在没有取到的情况下 将新的<b>EncodedResource</b>加入ThreadLocal变量的缓存中。
接着从<b>EncodedResource</b>获取输入流<b>InputStream</b>构造 <b>InputSource</b>,<b>InputSource</b>比较简单的一个类,封装了一下<b>InputStream</b>最后调用 doLoadBeanDefinitions
方法。部分源码:
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
这里的<b>finally</b>方法最后将<b>ThreadLocal</b>变量<b>resourcesCurrentlyBeingLoaded</b> remove掉了。看了好半天才看懂其中的奇妙之处:<b>这个方法本身存在多线程问题,最简单的做法就是同步语句块,但是大家知道这会影响性能所以借助<b>ThreadLocal</b>变量做了一个过程同步 在使用完即remove掉</b>。
<b>doLoadBeanDefinitions</b>是java dom加载xml的过程,大家都知道java解析xml的几种方式,基本的解析方式有两种,一种叫SAX,另一种叫DOM,再细一点有
-
DOM生成和解析XML文档
为 XML 文档的已解析版本定义了一组接口。解析器读入整个文档,然后构建一个驻留内存的树结构,然后代码就可以使用 DOM 接口来操作这个树结构。
优点:整个文档树在内存中,便于操作;支持删除、修改、重新排列等多种功能;
缺点:将整个文档调入内存(包括无用的节点),浪费时间和空间;使用场合:一旦解析了文档还需多次访问这些数据;硬件资源充足(内存、CPU)。 -
SAX生成和解析XML文档
为解决DOM的问题,出现了SAX。SAX ,事件驱动。当解析器发现元素开始、元素结束、文本、文档的开始或结束等时,发送事件,程序员编写响应这些事件的代码,保存数据。
优点:不用事先调入整个文档,占用资源少;SAX解析器代码比DOM解析器代码小,适于Applet,下载。
缺点:不是持久的;事件过后,若没保存数据,那么数据就丢了;无状态性;从事件中只能得到文本,但不知该文本属于哪个元素;使用场合:Applet;只需XML文档的少量内容,很少回头访问;机器内存少; -
DOM4J生成和解析XML文档
DOM4J 是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的 Java 软件都在使用 DOM4J 来读写 XML,特别值得一提的是连 Sun 的 JAXM 也在用 DOM4J。 -
JDOM生成和解析XML
为减少DOM、SAX的编码量,出现了JDOM;优点:20-80原则,极大减少了代码量。使用场合:要实现的功能简单,如解析、创建等,但在底层,JDOM还是使用SAX(最常用)、DOM、Xanan文档。
这里spring用java dom解析xml 我想原因就是需要加载的xml配置文件都不是大型的xml文件,都是比较小的 所以使用java dom反而效果会好一些。
这里定义了<b>DocumentLoader</b>接口,提供了默认的实现类<b>DefaultDocumentLoader</b>其中的方法:
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
解析过程可以看oracle jom的api文档
最后返回<b>Document</b>对象。
进入很重要的方法<b>registerBeanDefinitions</b>该方法将读取的dom转换成bean definition先进入方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
第一行BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
创建了<b>BeanDefinitionDocumentReader</b>对象 该对象是将document中包含的所偶bean定义解析出来。
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
第二行int countBefore = getRegistry().getBeanDefinitionCount();
获取到解析前的bean的数量
第三行documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
这里先根据传入的<b>Resource</b>参数创建<b>XmlReaderContext</b>对象,官方给出的解释是
Extension of {@link org.springframework.beans.factory.parsing.ReaderContext},
* specific to use with an {@link XmlBeanDefinitionReader}. Provides access to the
* {@link NamespaceHandlerResolver} configured in the {@link XmlBeanDefinitionReader}.
大概意思就是配合XmlBeanDefinitionReader使用的类。继续往下走到:
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
到方法<b>doRegisterBeanDefinitions(root)</b> 进入:
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
先创建<b>BeanDefinitionParserDelegate</b>
BeanDefinitionParserDelegate create开始解析的三部曲
解析三部曲- 先看<b>preProcessXml</b>
哈哈这真是个好方法 空实现 我喜欢,不过这算是解析的可扩展性 三部曲的步骤必需保证全 以免扩展需要 - 在看<b>parseBeanDefinitions</b>
这个方法就是最重要的啦,可以看到在遍历节点,其中方法<b>parseDefaultElement</b>protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
```
分了好几种情况去解析 看开头是import、alias、bean、beans这四种开头的 标签去解析 ,我们就找个bean开头的跟踪下情况 其它的标签也是一样的道理。
bean开头标签的解析
进入方法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
其中<b>parseBeanDefinitionElement</b>解析后获得<b>BeanDefinitionHolder</b>其中过程中比较中要的一个类<b>GenericBeanDefinition</b>每个bean标签都是解析成这样的beandefinition了。获得这个bean后对应解析各种
GenericBeanDefinition 各个部分的解析对应的bean部件解析parseMetaElements、parseLookupOverrideSubElements、parseReplacedMethodSubElements、parseConstructorArgElements、parsePropertyElements、parseQualifierElements 见名知意的方法没有多大难度就是对应的解析xml即可,解析完成 bean definition也就构造完成了。
- 最后看<b>postProcessXml</b>
哈哈这也真是个好方法 空实现 我喜欢,不过还是那句话这算是解析的可扩展性 三部曲的步骤必需保证全 以免扩展需要。
BeanDefinition装配过程基本完成 最后是装配到了最先初始化的<b>DefaultListableBeanFactory</b>的各个map属性中了。 好多东西很粗率的过了 后续补进。
不早了 go home!