美文网首页
Spring IoC之原理解析(三)

Spring IoC之原理解析(三)

作者: 萌妈码码 | 来源:发表于2018-10-06 15:37 被阅读0次

容器的启动核心方法refresh():

@Override
public void refresh() throws BeansException, IllegalStateException {
   // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛
   synchronized (this.startupShutdownMonitor) {

      // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
      prepareRefresh();

      // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
      // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
      // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
      // 这块待会会展开说
      prepareBeanFactory(beanFactory);

      try {
         // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
         // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】

         // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
         // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
         postProcessBeanFactory(beanFactory);
         // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 回调方法
         invokeBeanFactoryPostProcessors(beanFactory);          



         // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
         // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
         // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。这里仅仅是注册,之后会看到回调这两方法的时机
         registerBeanPostProcessors(beanFactory);

         // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了
         initMessageSource();

         // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了
         initApplicationEventMulticaster();

         // 从方法名就可以知道,典型的模板方法(钩子方法),不展开说
         // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
         onRefresh();

         // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过
         registerListeners();

         // 重点,重点,重点
         // 初始化所有的 singleton beans
         //(lazy-init 的除外)
         finishBeanFactoryInitialization(beanFactory);

         // 最后,广播事件,ApplicationContext 初始化完成,不展开
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // 把异常往外抛
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

最重要的三个步骤:

  • obtainFreshBeanFactory(); 初始化容器,解析<bean/>得到一个个BeanDefinition,并注册。
  • registerBeanPostProcessors(beanFactory); 找到所有实现了BeanPostProcessor接口的bean,以供后面初始化非懒加载bean时使用。
  • finishBeanFactoryInitialization(beanFactory); 初始化所有的单例Bean,懒加载的除外。

大体的逻辑就是这样的,refresh方法中调用的每个方法中都包含了很多细节,值得反复挖掘。详细的解析可以查看引用中的两篇文章。

几个重要主题:

XmlBeanDefinitionReader负责读取加载配置,解析。

解析过程:
首先将XML配置文件转换成一棵DOM树,然后从根节点开始解析。
对XML节点的解析分成两大类:default namespace和custom namespace。其中,default namespace只涉及四类标签:<import />, <alias />, <bean /> <beans />。其他的如context, mvc, aop等都属于custom namspace。对它们的解析需要提供相应的parser,如MvcNamespaceHandler、TaskNamespaceHandler、ContextNamespaceHandler、AopNamespaceHandler 等。这些Handler定义在各自jar包中spring.handlers文件中。如aop包中,spring.handlers内容如下:

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

解析<bean /> 配置相应地转换为了一个个 BeanDefinition,然后注册了各个 BeanDefinition 到注册中心。

配置是否允许 Bean 覆盖、是否允许循环依赖

BeanDefinition的覆盖问题:如果在配置文件中定义 bean 时使用了相同的 id 或 name,默认情况下,allowBeanDefinitionOverriding 属性为 null,如果在同一配置文件中重复了,会抛错,但是如果不是同一配置文件中,会发生覆盖。

循环引用: A 依赖 B,而 B 依赖 A。或 A 依赖 B,B 依赖 C,而 C 依赖 A。
默认情况下,Spring 允许循环依赖,当然如果在 A 的构造方法中依赖 B,在 B 的构造方法中依赖 A 是不行的。

怎么配置这两个属性呢?参考如下方法:

public class NoBeanOverridingContextLoader extends ContextLoader {

  @Override
  protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
    super.customizeContext(servletContext, applicationContext);
    AbstractRefreshableApplicationContext arac = (AbstractRefreshableApplicationContext) applicationContext;
    arac.setAllowBeanDefinitionOverriding(false);
  }
}
public class MyContextLoaderListener extends org.springframework.web.context.ContextLoaderListener {

  @Override
  protected ContextLoader createContextLoader() {
    return new NoBeanOverridingContextLoader();
  }
<listener>
    <listener-class>com.javadoop.MyContextLoaderListener</listener-class>  
</listener>
}
Singleton依赖Prototype

一般来说,我们的应用中大多数的 Bean 都是 singleton 的。singleton 依赖 singleton,或者 prototype 依赖 prototype 都很好解决,直接设置属性依赖就可以了。
但是,如果是 singleton 依赖 prototype 呢?这个时候不能用属性依赖,因为如果用属性依赖的话,我们每次其实拿到的还是第一次初始化时候的 bean。

一种解决方案就是不要用属性依赖,每次获取依赖的 bean 的时候从 BeanFactory 中取。这个也是大家最常用的方式了吧。怎么取,我就不介绍了,大部分 Spring 项目大家都会定义那么个工具类的。

另一种解决方案就是这里要介绍的通过使用 Lookup method (lookup-method)。
来看一个Spring Reference 中提供的一个例子:

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

xml 配置 <lookup-method />:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

Spring 采用 CGLIB 生成字节码的方式来生成一个子类。我们定义的类不能定义为 final class,抽象方法上也不能加 final。

lookup-method 上的配置也可以采用注解来完成,这样就可以不用配置 <lookup-method /> 了,其他不变:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

注意,既然用了注解,要配置注解扫描:<context:component-scan base-package="com.javadoop" />

甚至,我们可以像下面这样:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

上面的返回值用了 MyCommand,当然,如果 Command 只有一个实现类,那返回值也可以写 Command。

引用
Spring IOC源码解读
Spring IOC 容器源码分析

相关文章

网友评论

      本文标题:Spring IoC之原理解析(三)

      本文链接:https://www.haomeiwen.com/subject/fpecaftx.html