【小家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";
    }
  }



相关文章
|
4月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
132 1
|
1月前
|
人工智能 自然语言处理 Java
Spring 集成 DeepSeek 的 3大方法(史上最全)
DeepSeek 的 API 接口和 OpenAI 是兼容的。我们可以自定义 http client,按照 OpenAI 的rest 接口格式,去访问 DeepSeek。自定义 Client 集成DeepSeek ,可以通过以下步骤实现。步骤 1:准备工作访问 DeepSeek 的开发者平台,注册并获取 API 密钥。DeepSeek 提供了与 OpenAI 兼容的 API 端点(例如),确保你已获取正确的 API 地址。
Spring 集成 DeepSeek 的 3大方法(史上最全)
|
3月前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
209 73
|
2月前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
63 6
|
3月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
5月前
|
存储 安全 Java
|
4月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
77 1
|
4月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
63 1
|
4月前
|
前端开发 Java Docker
使用Docker容器化部署Spring Boot应用程序
使用Docker容器化部署Spring Boot应用程序
|
4月前
|
Java Docker 微服务
利用Docker容器化部署Spring Boot应用
利用Docker容器化部署Spring Boot应用
91 0