欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring源码分析之与WEB服务器衔接(上)

程序员文章站 2022-07-15 10:10:56
...

web服务器的配置文件是\webapp\WEB-INF\web.xml,这里面会配置两个Spring容器,父容器是通过监听ServletContextListener上下文事件通过ContextLoaderListener初始化并加载所需要的容器环境,子容器就是当初始化Servlet时加载用于MVC功能的容器环境。


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="verification" version="2.5">
    <display-name>prototype</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:springMVC-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
 
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <context-param>
        <param-name>logbackConfigLocation</param-name>
        <param-value>classpath:logback.xml</param-value>
    </context-param>
    <filter>
        <filter-name>accessFilter</filter-name>
        <filter-class>com.ph3636.AccessFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>accessFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

上下文加载监听器ContextLoaderListener继承了web容器启动后调用的接口ServletContextListener,初始化web上下文


public void contextInitialized(ServletContextEvent event) {
  initWebApplicationContext(event.getServletContext());
}

首先判断上下文是否存在,记录开始时间


if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
  throw new IllegalStateException(
      "Cannot initialize context because there is already a root application context present - " +
      "check whether you have multiple ContextLoader* definitions in your web.xml!");
}

Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
  logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();

创建web上下文


// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
  this.context = createWebApplicationContext(servletContext);
}

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
  Class<?> contextClass = determineContextClass(sc);
  if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
        "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
  }
  return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

决定上下文类型,可以设置参数


public static final String CONTEXT_CLASS_PARAM = "contextClass";
protected Class<?> determineContextClass(ServletContext servletContext) {
  String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
  if (contextClassName != null) {
    try {
      return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
    }
    catch (ClassNotFoundException ex) {
      throw new ApplicationContextException(
          "Failed to load custom context class [" + contextClassName + "]", ex);
    }
  }
  else {
    contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
    try {
      return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
    }
    catch (ClassNotFoundException ex) {
      throw new ApplicationContextException(
          "Failed to load default context class [" + contextClassName + "]", ex);
    }
  }
}

如果没设置的话取默认的上下文类


org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

private static final Properties defaultStrategies;
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
static {
  // Load default strategy implementations from properties file.
  // This is currently strictly internal and not meant to be customized
  // by application developers.
  try {
    ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
    defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
  }
  catch (IOException ex) {
    throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
  }
}

初始化web上下文


public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
  Assert.notNull(clazz, "Class must not be null");
  if (clazz.isInterface()) {
    throw new BeanInstantiationException(clazz, "Specified class is an interface");
  }
  try {
    return instantiateClass(clazz.getDeclaredConstructor());
  }
  catch (NoSuchMethodException ex) {
    throw new BeanInstantiationException(clazz, "No default constructor found", ex);
  }
}

public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
  Assert.notNull(ctor, "Constructor must not be null");
  try {
    ReflectionUtils.makeAccessible(ctor);
    return ctor.newInstance(args);
  }
  catch (InstantiationException ex) {
    throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
  }
  catch (IllegalAccessException ex) {
    throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
  }
  catch (IllegalArgumentException ex) {
    throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
  }
  catch (InvocationTargetException ex) {
    throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
  }
}

判断web上下文类型和活跃标志


if (this.context instanceof ConfigurableWebApplicationContext) {
  ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
  if (!cwac.isActive()) {
    // The context has not yet been refreshed -> provide services such as
    // setting the parent context, setting the application context id, etc
    if (cwac.getParent() == null) {
      // The context instance was injected without an explicit parent ->
      // determine parent for root web application context, if any.
      ApplicationContext parent = loadParentContext(servletContext);
      cwac.setParent(parent);
    }
    configureAndRefreshWebApplicationContext(cwac, servletContext);
  }
}

加载并设置父容器


protected ApplicationContext loadParentContext(ServletContext servletContext) {
  ApplicationContext parentContext = null;
  String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
  String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);

  if (parentContextKey != null) {
    // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
    BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
    Log logger = LogFactory.getLog(ContextLoader.class);
    if (logger.isDebugEnabled()) {
      logger.debug("Getting parent context definition: using parent context key of '" +
          parentContextKey + "' with BeanFactoryLocator");
    }
    this.parentContextRef = locator.useBeanFactory(parentContextKey);
    parentContext = (ApplicationContext) this.parentContextRef.getFactory();
  }

  return parentContext;
}

启动web上下文,设置id,servlet上下文,获取配置路径,也就是web.xml文件里面配置的classpath*:applicationContext.xml

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
  if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
    // The application context id is still set to its original default value
    // -> assign a more useful id based on available information
    String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
    if (idParam != null) {
      wac.setId(idParam);
    }
    else {
      // Generate default id...
      wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
          ObjectUtils.getDisplayString(sc.getContextPath()));
    }
  }

  wac.setServletContext(sc);
  String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
  if (configLocationParam != null) {
    wac.setConfigLocation(configLocationParam);
  }

  // The wac environment's #initPropertySources will be called in any case when the context
  // is refreshed; do it eagerly here to ensure servlet property sources are in place for
  // use in any post-processing or initialization that occurs below prior to #refresh
  ConfigurableEnvironment env = wac.getEnvironment();
  if (env instanceof ConfigurableWebEnvironment) {
    ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
  }

  customizeContext(sc, wac);
  wac.refresh();
}

获取配置环境,初始化配置环境的servlet上下文及配置信息


public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
  WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}

public static void initServletPropertySources(
    MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) {

  Assert.notNull(propertySources, "'propertySources' must not be null");
  if (servletContext != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) &&
      propertySources.get(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
    propertySources.replace(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,
        new ServletContextPropertySource(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext));
  }
  if (servletConfig != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) &&
      propertySources.get(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
    propertySources.replace(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
        new ServletConfigPropertySource(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig));
  }
}

自定义上下文,可以对上下文进行自定义操作


protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
  List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
      determineContextInitializerClasses(sc);

  for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
    Class<?> initializerContextClass =
        GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
    if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
      throw new ApplicationContextException(String.format(
          "Could not apply context initializer [%s] since its generic parameter [%s] " +
          "is not assignable from the type of application context used by this " +
          "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
          wac.getClass().getName()));
    }
    this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
  }

  AnnotationAwareOrderComparator.sort(this.contextInitializers);
  for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
    initializer.initialize(wac);
  }
}

获取servlet上下文中配置的全局初始化类和上下文初始化类

protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
    determineContextInitializerClasses(ServletContext servletContext) {

  List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
      new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();

  String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
  if (globalClassNames != null) {
    for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
      classes.add(loadInitializerClass(className));
    }
  }

  String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
  if (localClassNames != null) {
    for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
      classes.add(loadInitializerClass(className));
    }
  }

  return classes;
}

加载对应的初始化类,实例化这些初始化类,并进行排序后对web上下文进行初始化操作


private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
  try {
    Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
    Assert.isAssignable(ApplicationContextInitializer.class, clazz);
    return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
  }
  catch (ClassNotFoundException ex) {
    throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
  }
}

启动容器初始化,完成后设置到servlet上下文中,父容器启动完成


try {
  servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
  ClassLoader ccl = Thread.currentThread().getContextClassLoader();
  if (ccl == ContextLoader.class.getClassLoader()) {
    currentContext = this.context;
  }
  else if (ccl != null) {
    currentContextPerThread.put(ccl, this.context);
  }

  if (logger.isDebugEnabled()) {
    logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
  }
  if (logger.isInfoEnabled()) {
    long elapsedTime = System.currentTimeMillis() - startTime;
    logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
  }

  return this.context;
}
catch (RuntimeException ex) {
  logger.error("Context initialization failed", ex);
  servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
  throw ex;
}
catch (Error err) {
  logger.error("Context initialization failed", err);
  servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
  throw err;
}

 

相关标签: Spring