【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(一)(上)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(一)(上)

前言


还记得我在这篇博文:【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)

里留了几个非常重要,但是没有解释的方法。其中有一个非常重要的方法ApplicationContext#refresh()方法就是提到两次但都暂时忽略了(因为有父子容器,所以会刷新两次容器~)


refresh()方法是Spring容器启动的核心中的核心,逻辑也是异常的复杂,因为准备分两篇文章来叙述他的过程,以及源码的分析


Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)

Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)

Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)


Spring Bean声明周期流程图


Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点。


image.png


image.png


我把这张图放在最开始的位置,初看可能觉得一脸懵逼,但是相信在接下来的阅读过程中,会一步一步的柳暗花明的~

简单分类如下:


  • 工厂后置处理器(BeanFactoryPostProcessor):这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器  接口的方法。工厂后处理器也是容器级的。
  • 容器级别生命周期处理器:包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”
  • Bean级生命周期接口方法(仅作用于某个Bean):这个包括了BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法
  • Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中<bean>的init-method和destroy-method指定的方法


初识


因为我们项目的案例是全注解驱动的,因此我们的容器实例为:AnnotationConfigWebApplicationContext

(若你是xml配置驱动,则为XmlWebApplicationContext)


为了更方便了解容器类的继承关系,贴出如下类继承图:

image.png


refresh()方法所有的ApplicationContext子类都没重写,只有AbstractApplicationContext里有实现过(接口定义在ConfigurableApplicationContext),因此我们看起来也容易了,直接上源码~

refresh()源码 宏观步骤说明


此部分源码,把容器的刷新步骤体现得非常的清晰,十足的面向对象编程。因此我会保留Spring 英文备注的同事,把一些备注写在源码上~~~ 详细的下面都会具体再解释


  @Override
  public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      //容器刷新前的准备,设置上下文状态,获取属性,验证必要的属性等
      prepareRefresh();
      // Tell the subclass to refresh the internal bean factory.
      // 获取新的beanFactory,销毁原有beanFactory、为每个bean生成BeanDefinition等  注意,此处是获取新的,销毁旧的,这就是刷新的意义
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      // Prepare the bean factory for use in this context.
      //配置标准的beanFactory,设置ClassLoader,设置SpEL表达式解析器等
      prepareBeanFactory(beanFactory);
      try {
        // Allows post-processing of the bean factory in context subclasses.
        //模板方法,允许在子类中对beanFactory进行后置处理。
        postProcessBeanFactory(beanFactory);
        // Invoke factory processors registered as beans in the context.
        //实例化并调用所有注册的beanFactory后置处理器(实现接口BeanFactoryPostProcessor的bean)。
        //在beanFactory标准初始化之后执行  例如:PropertyPlaceholderConfigurer(处理占位符)
        invokeBeanFactoryPostProcessors(beanFactory);
        // Register bean processors that intercept bean creation.
        //实例化和注册beanFactory中扩展了BeanPostProcessor的bean。
        //例如:
        //AutowiredAnnotationBeanPostProcessor(处理被@Autowired注解修饰的bean并注入)
        //RequiredAnnotationBeanPostProcessor(处理被@Required注解修饰的方法)
        //CommonAnnotationBeanPostProcessor(处理@PreDestroy、@PostConstruct、@Resource等多个注解的作用)等。
        registerBeanPostProcessors(beanFactory);
        // Initialize message source for this context.
        //初始化国际化工具类MessageSource
        initMessageSource();
        // Initialize event multicaster for this context.
        //初始化事件广播器
        initApplicationEventMulticaster();
        // Initialize other special beans in specific context subclasses.
        //模板方法,在容器刷新的时候可以自定义逻辑(子类自己去实现逻辑),不同的Spring容器做不同的事情
        onRefresh();
        // Check for listener beans and register them.
        //注册监听器,并且广播early application events,也就是早期的事件
        registerListeners();
        // Instantiate all remaining (non-lazy-init) singletons.
        //非常重要。。。实例化所有剩余的(非懒加载)单例Bean。(也就是我们自己定义的那些Bean们)
        //比如invokeBeanFactoryPostProcessors方法中根据各种注解解析出来的类,在这个时候都会被初始化  扫描的 @Bean之类的
        //实例化的过程各种BeanPostProcessor开始起作用~~~~~~~~~~~~~~
        finishBeanFactoryInitialization(beanFactory);
        // Last step: publish corresponding event.
        //refresh做完之后需要做的其他事情
        //清除上下文资源缓存(如扫描中的ASM元数据)
        //初始化上下文的生命周期处理器,并刷新(找出Spring容器中实现了Lifecycle接口的bean并执行start()方法)。
        //发布ContextRefreshedEvent事件告知对应的ApplicationListener进行响应的操作
        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.
        //如果刷新失败那么就会将已经创建好的单例Bean销毁掉
        destroyBeans();
        // Reset 'active' flag.
        //重置context的活动状态 告知是失败的
        cancelRefresh(ex);
        // Propagate exception to caller.
        //抛出异常
        throw ex;
      } finally {
        // Reset common introspection caches in Spring's core, since we
        // might not ever need metadata for singleton beans anymore...
        // 失败与否,都会重置Spring内核的缓存。因为可能不再需要metadata给单例Bean了。
        resetCommonCaches();
      }
    }
  }


通过上面的注释,已经能够比较宏观的了解到容器的一个初始化过程了,那么接下来,将针对每一个步骤,进行微观源码级别的解释说明。


refresh() 具体源码解析


refresh() 第一步:prepareRefresh()


  protected void prepareRefresh() {
    //记录容器启动时间,然后设立对应的标志位
    this.startupDate = System.currentTimeMillis();
    this.closed.set(false);
    this.active.set(true);
    // 打印info日志:开始刷新this此容器了
    if (logger.isInfoEnabled()) {
      logger.info("Refreshing " + this);
    }
    // Initialize any placeholder property sources in the context environment
    // 这是扩展方法,由子类去实现,可以在验证之前为系统属性设置一些值可以在子类中实现此方法
    // 因为我们这边是AnnotationConfigApplicationContext,可以看到不管父类还是自己,都什么都没做,所以此处先忽略
    initPropertySources();
    // Validate that all properties marked as required are resolvable
    // see ConfigurablePropertyResolver#setRequiredProperties
    //这里有两步,getEnvironment(),然后是是验证是否系统环境中有RequiredProperties参数值 如下详情
    // 然后管理Environment#validateRequiredProperties 后面在讲到环境的时候再专门讲解吧
    // 这里其实就干了一件事,验证是否存在需要的属性
    getEnvironment().validateRequiredProperties();
    // Allow for the collection of early ApplicationEvents,
    // to be published once the multicaster is available...
    // 初始化容器,用于装载早期的一些事件
    this.earlyApplicationEvents = new LinkedHashSet<>();
  }


AbstractApplicationContext#getEnvironment()

关于getEnvironment()的顶层接口位于:EnvironmentCapable,有如下实现(注意ConfigurableApplicationContext是接口,所以其实上容器的实现只有AbstractApplicationContext):

image.png


Demo代码如下:


    @Autowired
    private BeanFactory beanFactory;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    //web环境下
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private HttpServletResponse response;
    @Autowired
    private HttpSession session;
    @Autowired
    private WebRequest webRequest;
    @Override
    public Object hello() {
        System.out.println(beanFactory); //org.springframework.beans.factory.support.DefaultListableBeanFactory@3ff27f35
        System.out.println(applicationContext); //Root WebApplicationContext: startup date [T ...
        System.out.println(applicationEventPublisher); //Root WebApplicationContext: startup date [T ...
        // 我们发现的是同一个Bean
        System.out.println(System.identityHashCode(applicationEventPublisher) == System.identityHashCode(applicationContext)); //true
        //web环境
        // =================必须说明一点:这里注入的所有web对象,都是线程安全的=================
        // 请求N次,每次输出的HashCode都是一样的,那怎么还没有线程安全问题呢?具体看下面分解原因
        System.out.println(System.identityHashCode(request));
        System.out.println(request.getClass()); //class com.sun.proxy.$Proxy22  这是个代理对象哟~~~~
        System.out.println(request); //Current HttpServletRequest
        System.out.println(response); //Current HttpServletResponse
        System.out.println(session); //Current HttpSession
        System.out.println(webRequest); //Current ServletWebRequest
        return "service hello";
    }


@Autowire注入HttpServletRequest为何是线程安全的?


现状:我看到很多同事,还有小伙伴们在Controller层想要使用Servlet源生API比如HttpServletRequest的时候,让方法入参了去写,当你的Controller方法多了后(这是必然的),会让代码看起来十分的不优雅(重复工作太多)。


原因:把request放在方法入参里也不无道理。因为我们常识性的认为:由于Controller是单例的,所以直接放在全局属性上,理论上肯定是有线程安全问题的。


迷惑点:为了证明这一点,然后每次请求都输出System.identityHashCode(request),发现HashCode是不变,因此可以确定的是,注入的request,肯定是同一个实例


线程安全的原因分析解释:


其实从上面一句request.getClass()或许能看出端倪的,我们发现注入的是JDK的动态代理对象class com.sun.proxy.$Proxy22(至于为何注入的是代理对象,请参考上面博文:细说Spring IOC容器的自动装配这一篇有详细的分析讲解,代理类的处理器为:ObjectFactoryDelegatingInvocationHandler),因此我们主要看看invoke方法如下:

(图片所示为该处理器的调用地方和基本源码:)

image.png

  public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
    // 如果注入到的值为ObjectFactory类型(并且不是requiredType实例),就猪呢比下面的代理吧~~~
    if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
      ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
      //autowiringValue 显示是实现了Serializable 接口的
      // 并且requiredType是个接口(HttpServletRequest是接口,继承自ServletRequest)
      // 所以此处注意了,只能根据接口进行注入才是线程安全的,如果注入实现类,线程就是不安全的(因为无法创建代理了) 但是显然我们不可能注入实现类的
      if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
        // 创建出来的代理对象,才是最终要被注入进去的值====
        autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
            new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
      }
      else {
        return factory.getObject();
      }
    }
    return autowiringValue;
  }


ObjectFactoryDelegatingInvocationHandler#invoke:调用request的方法,都被代理到此处


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String methodName = method.getName();
      if (methodName.equals("equals")) {
        // Only consider equal when proxies are identical.
        return (proxy == args[0]);
      }
      else if (methodName.equals("hashCode")) {
        // Use hashCode of proxy.
        return System.identityHashCode(proxy);
      }
      else if (methodName.equals("toString")) {
        return this.objectFactory.toString();
      }
      try {
        // 核心在这里,每次调用的方法,实际上调用的是objectFactory.getObject()这个对象的对应方法,那么这个对象源码呢?
        //    beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
        // 可以看出他是一个RequestObjectFactory类型,所以看下面getObject方法
        return method.invoke(this.objectFactory.getObject(), args);
      }
      catch (InvocationTargetException ex) {
        throw ex.getTargetException();
      }
    }
  }
//RequestObjectFactory
  private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
    // 它每次返回的是和当前线程上下文绑定的一个request副本。至于怎么和上下文绑定的,下面贴出参考链接
    // 有了这些解释,那肯定的这样注入的是线程安全的,不用再担心了
    @Override
    public ServletRequest getObject() {
      return currentRequestAttributes().getRequest();
    }
    @Override
    public String toString() {
      return "Current HttpServletRequest";
    }
  }



相关文章
|
29天前
|
Java 测试技术 开发工具
ApplicationArguments读取应用程序参数并注入到IOC容器
ApplicationArguments读取应用程序参数并注入到IOC容器
ApplicationArguments读取应用程序参数并注入到IOC容器
|
1月前
|
XML 前端开发 Java
深入了解Spring MVC工作流程
深入了解Spring MVC工作流程
|
29天前
|
存储 前端开发 Java
springboot中的第二个IOC容器BootstrapContext
springboot中的第二个IOC容器BootstrapContext
springboot中的第二个IOC容器BootstrapContext
|
1月前
|
JSON Java 数据库连接
【spring(五)】SpringMvc总结 SSM整合流程
【spring(五)】SpringMvc总结 SSM整合流程
|
1月前
|
Java 容器 Spring
【spring(一)】核心容器总结
【spring(一)】核心容器总结
|
1月前
|
Java 开发者 容器
【Java】深入了解Spring容器的两个关键组件
【Java】深入了解Spring容器的两个关键组件
10 0
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (下)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
|
2月前
|
前端开发 Java 数据格式
10个知识点让你读懂spring MVC容器
随着 Spring Boot 逐步全面覆盖到我们的项目之中,我们已经基本忘却当年经典的 Servlet + Spring MVC 的组合,那让人熟悉的 web.xml 配置。而本文,我们想先抛开 Spring Boot 到一旁,回到从前,一起来看看 Servlet 是怎么和 Spring MVC 集成,怎么来初始化 Spring 容器的。
20 1
|
2月前
|
Dubbo Java 应用服务中间件
Dubbo 第四节: Spring与Dubbo整合原理与源码分析
DubboConfigConfigurationRegistrar的主要作⽤就是对propties⽂件进⾏解析并根据不同的配置项项⽣成对应类型的Bean对象。