zoukankan      html  css  js  c++  java
  • interface21

    前言 

    最近打算花点时间好好看看spring的源码,然而现在Spring的源码经过迭代的版本太多了,比较庞大,看起来比较累,所以准备从最初的版本(interface21)开始入手,仅用于学习,理解其设计思想,后续慢慢研究其每次版本变更的内容。。。

    先从interface21的一个典型web工程例子看起,宠物诊所 - petclinic,因为该工程基本涵盖了Spring的APO、IOC、JDBC、Web MVC、事务、国际化、主题切换、参数校验等主要功能。。。

    继上一篇,了解完Log4jConfigListener(加载Log4j日志)的流程后,看看ContextLoaderListener(加载Spring Web Application Context)流程是如何的~~~~~~~

    对应的web.xml配置

        <listener>
            <listener-class>com.interface21.web.context.ContextLoaderListener</listener-class>
        </listener>

    执行时序图(看不清的话可以点击查看原图)

    时序图中的各个步骤简要分析

    执行的入口在ContextLoaderListener类的contextInitialized方法,由于ContextLoaderListener类实现了ServletContextListener接口,所以在Servlet容器(tomcat)启动时,会自动调用contextInitialized方法。

    步骤描述:

    1. 进入ContextLoaderListener类的contextInitialized方法,该类只有一句代码,执行ContextLoader.initContext(event.getServletContext())方法;
      public void contextInitialized(ServletContextEvent event) {
              ContextLoader.initContext(event.getServletContext());
          }
    2. 进入ContextLoader类的initContext方法,首先,从servletContext中获取contextClass参数,如果配置了该参数,则创建该实例对象,否则创建默认的XmlWebApplicationContext实例对象,接下来调用XmlWebApplicationContext的setServletContext方法;
      public static WebApplicationContext initContext(ServletContext servletContext) throws ApplicationContextException {
              servletContext.log("Loading root WebApplicationContext");
              String contextClass = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
      
              // Now we must load the WebApplicationContext.
              // It configures itself: all we need to do is construct the class with a no-arg
              // constructor, and invoke setServletContext.
              try {
                  Class clazz = (contextClass != null ? Class.forName(contextClass) : DEFAULT_CONTEXT_CLASS);
                  logger.info("Loading root WebApplicationContext: using context class '" + clazz.getName() + "'");
      
                  if (!WebApplicationContext.class.isAssignableFrom(clazz)) {
                      throw new ApplicationContextException("Context class is no WebApplicationContext: " + contextClass);
                  }
      
                  WebApplicationContext webApplicationContext = (WebApplicationContext) clazz.newInstance();
                  webApplicationContext.setServletContext(servletContext);
                  return webApplicationContext;
      
              } catch (ApplicationContextException ex) {
                  handleException("Failed to initialize application context", ex);
      
              } catch (BeansException ex) {
                  handleException("Failed to initialize beans in application context", ex);
      
              } catch (ClassNotFoundException ex) {
                  handleException("Failed to load config class '" + contextClass + "'", ex);
      
              } catch (InstantiationException ex) {
                  handleException("Failed to instantiate config class '" + contextClass + "': does it have a public no arg constructor?", ex);
      
              } catch (IllegalAccessException ex) {
                  handleException("Illegal access while finding or instantiating config class '" + contextClass + "': does it have a public no arg constructor?", ex);
      
              } catch (Throwable ex) {
                  handleException("Unexpected error loading context configuration", ex);
              }
      
              return null;
          }
    3. 进入XmlWebApplicationContext类的setServletContext方法,首先,调用initConfigLocation方法从servletContext中获取contextConfigLocation参数(Spring Application配置文件),如果没配置该参数,则默认获取/WEB-INF/applicationContext.xml该文件;
          public void setServletContext(ServletContext servletContext) throws ApplicationContextException {
              this.servletContext = servletContext;
              this.configLocation = initConfigLocation();
              logger.info("Using config location '" + this.configLocation + "'");
              refresh();
      
              if (this.namespace == null) {
                  // We're the root context
                  WebApplicationContextUtils.publishConfigObjects(this);
                  // Expose as a ServletContext object
                  WebApplicationContextUtils.publishWebApplicationContext(this);
              }
          }
    4. 迎来了非常关键的一步操作,调用AbstractApplicationContext类的refresh()方法,该方法具体如下,每个阶段的英文注释已经比较清晰了,下面步骤也会做个详细描述:
          public final void refresh() throws ApplicationContextException {
              if (this.contextOptions != null && !this.contextOptions.isReloadable())
                  throw new ApplicationContextException("Forbidden to reload config");
      
              this.startupTime = System.currentTimeMillis();
      
              refreshBeanFactory();
      
              if (getBeanDefinitionCount() == 0)
                  logger.warn("No beans defined in ApplicationContext [" + getDisplayName() + "]");
              else
                  logger.info(getBeanDefinitionCount() + " beans defined in ApplicationContext [" + getDisplayName() + "]");
      
              // invoke configurers that can override values in the bean definitions
              invokeContextConfigurers();
      
              // load options bean for this context
              loadOptions();
      
              // initialize message source for this context
              initMessageSource();
      
              // initialize other special beans in specific context subclasses
              onRefresh();
      
              // check for listener beans and register them
              refreshListeners();
      
              // instantiate singletons this late to allow them to access the message source
              preInstantiateSingletons();
      
              // last step: publish respective event
              publishEvent(new ContextRefreshedEvent(this));
          }
    5. 首先,调用AbstractXmlApplicationContext类的refreshBeanFactory方法,该方法如下,具体完成的操作内容下面步骤会详细描述:
        protected void refreshBeanFactory() throws ApplicationContextException {
              String identifier = "application context with display name [" + getDisplayName() + "]";
              InputStream is = null;
              try {
                  // Supports remote as well as local URLs
                  is = getInputStreamForBeanFactory();
                  this.xmlBeanFactory = new XmlBeanFactory(getParent());
                  this.xmlBeanFactory.setEntityResolver(new ResourceBaseEntityResolver(this));
                  this.xmlBeanFactory.loadBeanDefinitions(is);
                  if (logger.isInfoEnabled()) {
                      logger.info("BeanFactory for application context: " + this.xmlBeanFactory);
                  }
              } catch (IOException ex) {
                  throw new ApplicationContextException("IOException parsing XML document for " + identifier, ex);
              } catch (NoSuchBeanDefinitionException ex) {
                  throw new ApplicationContextException("Cannot load configuration: missing bean definition [" + ex.getBeanName() + "]", ex);
              } catch (BeansException ex) {
                  throw new ApplicationContextException("Cannot load configuration: problem instantiating or initializing beans", ex);
              } finally {
                  try {
                      if (is != null)
                          is.close();
                  } catch (IOException ex) {
                      throw new ApplicationContextException("IOException closing stream for XML document for " + identifier, ex);
                  }
              }
          }
    6. 调用XmlWebApplicationContext类的getInputStreamForBeanFactory方法,读取阶段3获取到的配置文件为输入流InputStream
          protected InputStream getInputStreamForBeanFactory() throws IOException {
              InputStream in = getResourceAsStream(this.configLocation);
              if (in == null) {
                  throw new FileNotFoundException("Config location not found: " + this.configLocation);
              }
              return in;
          }
    7. 返回配置文件输入流InputStream
    8. 回到AbstractXmlApplicationContext的refreshBeanFactory方法,new出一个XmlBeanFactory对象
    9. 设置xmlBeanFactory.setEntityResolver,这里的EntityResolver主要用于寻找DTD声明
    10. 调用xmlBeanFactory的loadBeanDefinitions方法加载bean定义声明
    11. 进入xmlBeanFactory类的loadBeanDefinitions方法,解析读取的配置文件流InputStream为org.w3c.dom.Document对象,然后调用loadBeanDefinitions方法依次解析各个bean元素节点信息
          public void loadBeanDefinitions(InputStream is) throws BeansException {
              if (is == null)
                  throw new BeanDefinitionStoreException("InputStream cannot be null: expected an XML file", null);
      
              try {
                  logger.info("Loading XmlBeanFactory from InputStream [" + is + "]");
                  DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                  logger.debug("Using JAXP implementation [" + factory + "]");
                  factory.setValidating(true);
                  DocumentBuilder db = factory.newDocumentBuilder();
                  db.setErrorHandler(new BeansErrorHandler());
                  db.setEntityResolver(this.entityResolver != null ? this.entityResolver : new BeansDtdResolver());
                  Document doc = db.parse(is);
                  loadBeanDefinitions(doc);
              } catch (ParserConfigurationException ex) {
                  throw new BeanDefinitionStoreException("ParserConfiguration exception parsing XML", ex);
              } catch (SAXException ex) {
                  throw new BeanDefinitionStoreException("XML document is invalid", ex);
              } catch (IOException ex) {
                  throw new BeanDefinitionStoreException("IOException parsing XML document", ex);
              } finally {
                  try {
                      if (is != null)
                          is.close();
                  } catch (IOException ex) {
                      throw new FatalBeanException("IOException closing stream for XML document", ex);
                  }
              }
          }
    12. 寻找Document中声明为bean的Element节点,依次解析
          public void loadBeanDefinitions(Document doc) throws BeansException {
              Element root = doc.getDocumentElement();
              logger.debug("Loading bean definitions");
              NodeList nl = root.getElementsByTagName(BEAN_ELEMENT);
              logger.debug("Found " + nl.getLength() + " <" + BEAN_ELEMENT + "> elements defining beans");
              for (int i = 0; i < nl.getLength(); i++) {
                  Node n = nl.item(i);
                  loadBeanDefinition((Element) n);
              }
          }
    13. 解析Element节点内容,获取class声明信息、PropertyValues等信息,封装成AbstractBeanDefinition对象,添加到beanDefinitionMap中
          private void loadBeanDefinition(Element el) throws BeansException {
              // The DTD guarantees an id attribute is present
              String id = el.getAttribute(ID_ATTRIBUTE);
              logger.debug("Parsing bean definition with id '" + id + "'");
      
              // Create BeanDefinition now: we'll build up PropertyValues later
              AbstractBeanDefinition beanDefinition;
      
              PropertyValues pvs = getPropertyValueSubElements(el);
              beanDefinition = parseBeanDefinition(el, id, pvs);
              registerBeanDefinition(id, beanDefinition);
      
              String name = el.getAttribute(NAME_ATTRIBUTE);
              if (name != null && !"".equals(name)) {
                  // Automatically create this alias. Used for
                  // names that aren't legal in id attributes
                  registerAlias(id, name);
              }
          }
    14. 判断是否需要注册alias,放到aliasMap中,实际上就是维护了bean的name和id关系
    15. 返回到AbstractXmlApplicationContext类refreshBeanFactory方法中
    16. 返回到AbstractApplicationContext类refresh方法中
    17. 执行AbstractApplicationContext的invokeContextConfigurers方法,实际上内部是执行所有实现了BeanFactoryPostProcessor接口的bean的postProcessBeanFactory方法
          private void invokeContextConfigurers() {
              String[] beanNames = getBeanDefinitionNames(BeanFactoryPostProcessor.class);
              for (int i = 0; i < beanNames.length; i++) {
                  String beanName = beanNames[i];
                  BeanFactoryPostProcessor configurer = (BeanFactoryPostProcessor) getBean(beanName);
                  configurer.postProcessBeanFactory(getBeanFactory());
              }
          }
    18. 执行AbstractApplicationContext的loadOptions方法,获取contextOptions bean,首先,查看配置文件是否已经配置contextOptions bean,没有则自己创建一个new ContextOptions()对象,主要用于当应用运行时,是否可以重新加载该配置,如果配置成false的话,会在调用refresh方法时,抛出一个ApplicationContextException("Forbidden to reload config")异常;
          private void loadOptions() {
              try {
                  this.contextOptions = (ContextOptions) getBean(OPTIONS_BEAN_NAME);
              } catch (NoSuchBeanDefinitionException ex) {
                  logger.info("No options bean ("" + OPTIONS_BEAN_NAME + "") found: using default");
                  this.contextOptions = new ContextOptions();
              }
          }
    19. 执行AbstractApplicationContext的initMessageSource方法,获取messageSource bean,首先,查看配置文件是否已经配置messageSource bean,没有则自己创建一个StaticMessageSource对象,注意如果Parent context不为null的话,需要设置Parent MessageSource
          private void initMessageSource() {
              try {
                  this.messageSource = (MessageSource) getBean(MESSAGE_SOURCE_BEAN_NAME);
                  // set parent message source if applicable,
                  // and if the message source is defined in this context, not in a parent
                  if (this.parent != null && (this.messageSource instanceof NestingMessageSource) &&
                          Arrays.asList(getBeanDefinitionNames()).contains(MESSAGE_SOURCE_BEAN_NAME)) {
                      ((NestingMessageSource) this.messageSource).setParent(this.parent);
                  }
              } catch (NoSuchBeanDefinitionException ex) {
                  logger.info("No MessageSource found for [" + getDisplayName() + "]: using empty StaticMessageSource");
                  // use empty message source to be able to accept getMessage calls
                  this.messageSource = new StaticMessageSource();
              }
          }
    20. 执行AbstractXmlUiApplicationContext的onRefresh方法,获取themeSource bean, 主题相关(如应用可配置暗色主题或亮色主题功能),同样,这里也首先查看配置文件是否已经配置themeSource bean,没有则自己创建一个ResourceBundleThemeSource对象,注意这里还需要根据判断条件设置Parent ThemeSource
          protected void onRefresh() {
              this.themeSource = UiApplicationContextUtils.initThemeSource(this);
          }
          public static ThemeSource initThemeSource(ApplicationContext applicationContext) {
              ThemeSource themeSource;
              try {
                  themeSource = (ThemeSource) applicationContext.getBean(THEME_SOURCE_BEAN_NAME);
                  // set parent theme source if applicable,
                  // and if the theme source is defined in this context, not in a parent
                  if (applicationContext.getParent() instanceof ThemeSource && themeSource instanceof NestingThemeSource &&
                          Arrays.asList(applicationContext.getBeanDefinitionNames()).contains(THEME_SOURCE_BEAN_NAME)) {
                      ((NestingThemeSource) themeSource).setParent((ThemeSource) applicationContext.getParent());
                  }
              } catch (NoSuchBeanDefinitionException ex) {
                  logger.info("No ThemeSource found for [" + applicationContext.getDisplayName() + "]: using ResourceBundleThemeSource");
                  themeSource = new ResourceBundleThemeSource();
              }
              return themeSource;
          }
    21. 执行AbstractApplicationContext的refreshListeners方法,寻找所有ApplicationListener bean,将其放到ApplicationEventMulticaster对象的Set集合中
          private void refreshListeners() {
              logger.info("Refreshing listeners");
              List listeners = BeanFactoryUtils.beansOfType(ApplicationListener.class, this);
              logger.debug("Found " + listeners.size() + " listeners in bean factory");
              for (int i = 0; i < listeners.size(); i++) {
                  ApplicationListener listener = (ApplicationListener) listeners.get(i);
                  addListener(listener);
                  logger.info("Bean listener added: [" + listener + "]");
              }
          }
    22. 执行AbstractApplicationContext的preInstantiateSingletons方法,创建单例的bean实例,创建bean对象是在调用getBean方法时创建的,具体创建逻辑在getSharedInstance方法里;另外,对实现了ApplicationContextAware接口的bean,会调用对应的接口setApplicationContext方法,这里涉及的细节比较多,后续有时间可以具体详细分析;
          private void preInstantiateSingletons() {
              logger.info("Configuring singleton beans in context");
              String[] beanNames = getBeanDefinitionNames();
              logger.debug("Found " + beanNames.length + " listeners in bean factory: names=[" +
                      StringUtils.arrayToDelimitedString(beanNames, ",") + "]");
              for (int i = 0; i < beanNames.length; i++) {
                  String beanName = beanNames[i];
                  if (isSingleton(beanName)) {
                      getBean(beanName);
                  }
              }
          }
          public Object getBean(String name) throws BeansException {
              Object bean = getBeanFactory().getBean(name);
              configureManagedObject(name, bean);
              return bean;
          }
          private final synchronized Object getSharedInstance(String pname, Map newlyCreatedBeans) throws BeansException {
              // Get rid of the dereference prefix if there is one
              String name = transformedBeanName(pname);
      
              Object beanInstance = this.singletonCache.get(name);
              if (beanInstance == null) {
                  logger.info("Creating shared instance of singleton bean '" + name + "'");
                  beanInstance = createBean(name, newlyCreatedBeans);
                  this.singletonCache.put(name, beanInstance);
              } else {
                  if (logger.isDebugEnabled())
                      logger.debug("Returning cached instance of singleton bean '" + name + "'");
              }
      
              // Don't let calling code try to dereference the
              // bean factory if the bean isn't a factory
              if (isFactoryDereference(pname) && !(beanInstance instanceof FactoryBean)) {
                  throw new BeanIsNotAFactoryException(name, beanInstance);
              }
      
              // Now we have the beanInstance, which may be a normal bean
              // or a FactoryBean. If it's a FactoryBean, we use it to
              // create a bean instance, unless the caller actually wants
              // a reference to the factory.
              if (beanInstance instanceof FactoryBean) {
                  if (!isFactoryDereference(pname)) {
                      // Configure and return new bean instance from factory
                      FactoryBean factory = (FactoryBean) beanInstance;
                      logger.debug("Bean with name '" + name + "' is a factory bean");
                      beanInstance = factory.getObject();
      
                      // Set pass-through properties
                      if (factory.getPropertyValues() != null) {
                          logger.debug("Applying pass-through properties to bean with name '" + name + "'");
                          new BeanWrapperImpl(beanInstance).setPropertyValues(factory.getPropertyValues());
                      }
                      // Initialization is really up to factory
                      //invokeInitializerIfNecessary(beanInstance);
                  } else {
                      // The user wants the factory itself
                      logger.debug("Calling code asked for BeanFactory instance for name '" + name + "'");
                  }
              }    // if we're dealing with a factory bean
      
              return beanInstance;
          }
          private void configureManagedObject(String name, Object bean) {
              if (bean instanceof ApplicationContextAware &&
                      (!isSingleton(name) || !this.managedSingletons.contains(bean))) {
                  logger.debug("Setting application context on ApplicationContextAware object [" + bean + "]");
                  ApplicationContextAware aca = (ApplicationContextAware) bean;
                  aca.setApplicationContext(this);
                  this.managedSingletons.add(bean);
              }
          }
    23. 执行AbstractApplicationContext的publishEvent方法,发布ContextRefreshedEvent事件,如果parent不为空,一起发布,内部的逻辑是执行对应eventListeners的onApplicationEvent方法
          public final void publishEvent(ApplicationEvent event) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Publishing event in context [" + getDisplayName() + "]: " + event.toString());
              }
              this.eventMulticaster.onApplicationEvent(event);
              if (this.parent != null) {
                  parent.publishEvent(event);
              }
          }
    24. 回到XmlWebApplicationContext类
    25. 执行WebApplicationContextUtils.publishConfigObjects方法,寻找所有config bean,将其设置到ServletContext的属性中
          public static void publishConfigObjects(WebApplicationContext wac) throws ApplicationContextException {
              logger.info("Configuring config objects");
              String[] beanNames = wac.getBeanDefinitionNames();
              for (int i = 0; i < beanNames.length; i++) {
                  String name = beanNames[i];
                  if (name.startsWith(CONFIG_OBJECT_PREFIX)) {
                      // Strip prefix
                      String strippedName = name.substring(CONFIG_OBJECT_PREFIX.length());
                      try {
                          Object configObject = wac.getBean(name);
                          wac.getServletContext().setAttribute(strippedName, configObject);
                          logger.info("Config object with name [" + name + "] and class [" + configObject.getClass().getName() +
                                  "] initialized and added to ServletConfig");
                      } catch (BeansException ex) {
                          throw new ApplicationContextException("Couldn't load config object with name '" + name + "': " + ex, ex);
                      }
                  }
              }
          }
    26. 执行WebApplicationContextUtils.publishWebApplicationContext,将WebApplicationContext设置到ServletContext属性中
          public static void publishWebApplicationContext(WebApplicationContext wac) {
              // Set WebApplicationContext as an attribute in the ServletContext so
              // other components in this web application can access it
              ServletContext sc = wac.getServletContext();
              if (sc == null)
                  throw new IllegalArgumentException("ServletContext can't be null in WebApplicationContext " + wac);
      
              sc.setAttribute(WebApplicationContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE_NAME, wac);
              logger.info(
                      "Loader initialized on server name "
                              + wac.getServletContext().getServerInfo()
                              + "; WebApplicationContext object is available in ServletContext with name '"
                              + WebApplicationContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE_NAME
                              + "'");
          }
    27. 返回webApplicationContext到ContextLoader类
    28. ContextLoaderListener.contextInitialized方法执行结束

    就这样,Spring Web Application Context加载完成了,是不是感觉也挺简单的,主要就是读取xml配置文件中bean的配置信息,创建bean实例放到一个map中维护,当然,中间还穿插了各种逻辑;

    另外补充下,当Servlet容器销毁时,会调用ContextLoaderListener的contextDestroyed方法,最终是调用ContextLoader.closeContext(event.getServletContext(),执行一些资源销毁等操作,销毁工厂创建的bean对象,发布ContextClosedEvent事件等;

      public void close() {
            logger.info("Closing application context [" + getDisplayName() + "]");
    
            // destroy all cached singletons in this context,
            // invoking DisposableBean.destroy and/or "destroy-method"
            getBeanFactory().destroySingletons();
    
            // publish respective event
            publishEvent(new ContextClosedEvent(this));
        }

    interface21代码参考

     https://github.com/peterchenhdu/interface21

  • 相关阅读:
    [BJOI2019] 光线
    C# 从零开始写 SharpDx 应用 笔刷
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    PowerShell 拿到显卡信息
    PowerShell 拿到显卡信息
    win10 uwp 如何使用DataTemplate
    win10 uwp 如何使用DataTemplate
  • 原文地址:https://www.cnblogs.com/chenpi/p/9527304.html
Copyright © 2011-2022 走看看