前言

典型的SpringMVC在Web.xml中的配置如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://java.sun.com/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  5. http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  6. version="3.0">
  7. <display-name>springmvc-demo</display-name>
  8. <context-param>
  9. <param-name>appName</param-name>
  10. <param-value>SpringMVCDemo</param-value>
  11. </context-param>
  12. <!-- 请求参数编码过滤器 -->
  13. <filter>
  14. <filter-name>SetCharacterEncoding</filter-name>
  15. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  16. <init-param>
  17. <param-name>encoding</param-name>
  18. <param-value>UTF-8</param-value>
  19. </init-param>
  20. <init-param>
  21. <param-name>forceEncoding</param-name>
  22. <param-value>true</param-value>
  23. </init-param>
  24. </filter>
  25. <filter-mapping>
  26. <filter-name>SetCharacterEncoding</filter-name>
  27. <url-pattern>/*</url-pattern>
  28. </filter-mapping>
  29. <!-- 加载spring根环境 -->
  30. <listener>
  31. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  32. </listener>
  33. <context-param>
  34. <param-name>contextConfigLocation</param-name>
  35. <param-value>classpath:spring/applicationContext.xml</param-value>
  36. </context-param>
  37. <!-- servlet映射到springmvc -->
  38. <servlet>
  39. <servlet-name>frontend</servlet-name>
  40. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  41. <init-param>
  42. <param-name>contextConfigLocation</param-name>
  43. <param-value>classpath:web/webmvc-frontend.xml</param-value>
  44. </init-param>
  45. </servlet>
  46. <servlet-mapping>
  47. <servlet-name>frontend</servlet-name>
  48. <url-pattern>/frontend/*</url-pattern>
  49. </servlet-mapping>
  50. <servlet>
  51. <servlet-name>backend</servlet-name>
  52. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  53. <init-param>
  54. <param-name>contextConfigLocation</param-name>
  55. <param-value>classpath:web/webmvc-backend.xml</param-value>
  56. </init-param>
  57. <load-on-startup>0</load-on-startup>
  58. </servlet>
  59. <servlet-mapping>
  60. <servlet-name>backend</servlet-name>
  61. <url-pattern>/backend/*</url-pattern>
  62. </servlet-mapping>
  63. </web-app>

这里通过ContextLoadListender指定了一个Spring上下文文件,一般称为Root AC,在配置Servlet也指可指定属于此Servlet的Spring上下文,一般称为Web AC,依赖关系为:容器启动后,WebAC会设置其parent为RootAC,因此整个WebAC环境里用到的所有Bean都可通过RootAC获得。如图为:
image.png

实例化ROOT Application

Servlet容器加载web.xml时,首先执行org.springframework.web.context.ContextLoaderListener,其内部实现逻辑为:获取contextParam中的配置,如:

  1. <context-param>
  2. <param-name>contextConfigLocation</param-name>
  3. <param-value>classpath:spring/applicationContext.xml</param-value>
  4. </context-param>

然后初始化一个WebXmlApplicationContext,然后设置到ServletContext中,放到容器环境里。
ServletContext的属性为:

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + “.ROOT”;

org.springframework.web.context.WebApplicationContext.ROOT

代码示例:

  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  2. if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
  3. throw new IllegalStateException(
  4. "Cannot initialize context because there is already a root application context present - " +
  5. "check whether you have multiple ContextLoader* definitions in your web.xml!");
  6. }
  7. Log logger = LogFactory.getLog(ContextLoader.class);
  8. servletContext.log("Initializing Spring root WebApplicationContext");
  9. if (logger.isInfoEnabled()) {
  10. logger.info("Root WebApplicationContext: initialization started");
  11. }
  12. long startTime = System.currentTimeMillis();
  13. try {
  14. // Store context in local instance variable, to guarantee that
  15. // it is available on ServletContext shutdown.
  16. if (this.context == null) {
  17. this.context = createWebApplicationContext(servletContext);
  18. }
  19. if (this.context instanceof ConfigurableWebApplicationContext) {
  20. ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
  21. if (!cwac.isActive()) {
  22. // The context has not yet been refreshed -> provide services such as
  23. // setting the parent context, setting the application context id, etc
  24. if (cwac.getParent() == null) {
  25. // The context instance was injected without an explicit parent ->
  26. // determine parent for root web application context, if any.
  27. ApplicationContext parent = loadParentContext(servletContext);
  28. cwac.setParent(parent);
  29. }
  30. configureAndRefreshWebApplicationContext(cwac, servletContext);
  31. }
  32. }
  33. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

createWebApplicationContext 方法主要是通过反射创建一个ApplicationContext,实现逻辑为:
首先查看Web.xml中是否有配置:contextClass,如果存在则直接反射创建。
如果不存在,则加载Jar包中的ContextLoader.properties文件里的配置,默认配置如下:

  1. # Default WebApplicationContext implementation class for ContextLoader.
  2. # Used as fallback when no explicit context implementation has been specified as context-param.
  3. # Not meant to be customized by application developers.
  4. org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

表示使用:XmlWebApplicationContext
然后通过反射创建一个XmlWebApplicationContext实例:

  1. ReflectionUtils.makeAccessible(ctor);
  2. return ctor.newInstance(args);

configureAndRefreshWebApplicationContext 方法为XmlWebApplicationContext配置属性,从ServletConfig中读取在Web.xml里配置的contextConfigLocation,表示设置一个上下文的配置文件,用来初始化容器Bean工厂,最好执行容器的refresh方法,表示:在Web容器启动过程中,直接实例化ROOT Application,并提前初始化Bean。
代码如下:

  1. protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
  2. if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
  3. // The application context id is still set to its original default value
  4. // -> assign a more useful id based on available information
  5. String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
  6. if (idParam != null) {
  7. wac.setId(idParam);
  8. }
  9. else {
  10. // Generate default id...
  11. wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
  12. ObjectUtils.getDisplayString(sc.getContextPath()));
  13. }
  14. }
  15. wac.setServletContext(sc);
  16. String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
  17. if (configLocationParam != null) {
  18. wac.setConfigLocation(configLocationParam);
  19. }
  20. // The wac environment's #initPropertySources will be called in any case when the context
  21. // is refreshed; do it eagerly here to ensure servlet property sources are in place for
  22. // use in any post-processing or initialization that occurs below prior to #refresh
  23. ConfigurableEnvironment env = wac.getEnvironment();
  24. if (env instanceof ConfigurableWebEnvironment) {
  25. ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
  26. }
  27. customizeContext(sc, wac);
  28. wac.refresh();
  29. }

实例化Servlet Application

一般建议配置Servlet时,指定

0

不指定默认情况下为负数,表示当前首次请求时才初始化Servlet, 而配置大于等于0,表示Web容器启动时则执行初始化,这样可以提前初始化。

初始化DispatchServlet,执行顺序为:
org.springframework.web.servlet.HttpServletBean#init
读取Web.xml中的所有配置:如并把当前DispatcherServlet包装为一个Bean,通过setPropertyValue可调用属性对应的setter方法,典型的配置如下:

  1. <servlet>
  2. <servlet-name>backend</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <init-param>
  5. <param-name>contextConfigLocation</param-name>
  6. <param-value>classpath:web/webmvc-backend.xml</param-value>
  7. </init-param>
  8. <load-on-startup>0</load-on-startup>
  9. </servlet>

DispatchServlet对应的参数setter方法如下:

  1. /**
  2. * Set the context config location explicitly, instead of relying on the default
  3. * location built from the namespace. This location string can consist of
  4. * multiple locations separated by any number of commas and spaces.
  5. */
  6. public void setContextConfigLocation(String contextConfigLocation) {
  7. this.contextConfigLocation = contextConfigLocation;
  8. }

执行org.springframework.web.servlet.HttpServletBean#init完成后,contextConfigLocation的值为:
classpath:web/webmvc-backend.xml

代码如下:

  1. public final void init() throws ServletException {
  2. // Set bean properties from init parameters.
  3. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
  4. if (!pvs.isEmpty()) {
  5. try {
  6. BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
  7. ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
  8. bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
  9. initBeanWrapper(bw);
  10. bw.setPropertyValues(pvs, true);
  11. }
  12. catch (BeansException ex) {
  13. if (logger.isErrorEnabled()) {
  14. logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
  15. }
  16. throw ex;
  17. }
  18. }
  19. // Let subclasses do whatever initialization they like.
  20. initServletBean();
  21. }

然后执行DispatcherServlet的initServletBean,此方法的关键逻辑为:查找ServletConfig中的ROOT WebApplicationRoot并重新创建一个与当前Servlet相关的WebApplicationContext,设置WAC的parent为ROOT AC,然后放到ServletContext中。
当前DispacherServlet在ServletContext中的属性Key为:
org.springframework.web.servlet.FrameworkServlet.CONTEXT.#{ServletName}

这样可以做到隔离。

  1. protected WebApplicationContext initWebApplicationContext() {
  2. WebApplicationContext rootContext =
  3. WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  4. WebApplicationContext wac = null;
  5. if (this.webApplicationContext != null) {
  6. // A context instance was injected at construction time -> use it
  7. wac = this.webApplicationContext;
  8. if (wac instanceof ConfigurableWebApplicationContext) {
  9. ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
  10. if (!cwac.isActive()) {
  11. // The context has not yet been refreshed -> provide services such as
  12. // setting the parent context, setting the application context id, etc
  13. if (cwac.getParent() == null) {
  14. // The context instance was injected without an explicit parent -> set
  15. // the root application context (if any; may be null) as the parent
  16. cwac.setParent(rootContext);
  17. }
  18. configureAndRefreshWebApplicationContext(cwac);
  19. }
  20. }
  21. }
  22. if (wac == null) {
  23. // No context instance was injected at construction time -> see if one
  24. // has been registered in the servlet context. If one exists, it is assumed
  25. // that the parent context (if any) has already been set and that the
  26. // user has performed any initialization such as setting the context id
  27. wac = findWebApplicationContext();
  28. }
  29. if (wac == null) {
  30. // No context instance is defined for this servlet -> create a local one
  31. wac = createWebApplicationContext(rootContext);
  32. }
  33. if (!this.refreshEventReceived) {
  34. // Either the context is not a ConfigurableApplicationContext with refresh
  35. // support or the context injected at construction time had already been
  36. // refreshed -> trigger initial onRefresh manually here.
  37. onRefresh(wac);
  38. }
  39. if (this.publishContext) {
  40. // Publish the context as a servlet context attribute.
  41. String attrName = getServletContextAttributeName();
  42. getServletContext().setAttribute(attrName, wac);
  43. if (this.logger.isDebugEnabled()) {
  44. this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
  45. "' as ServletContext attribute with name [" + attrName + "]");
  46. }
  47. }
  48. return wac;
  49. }

创建Servlet WebApplicationContext的相关代码在
FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)

  1. protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
  2. Class<?> contextClass = getContextClass();
  3. ...
  4. ConfigurableWebApplicationContext wac =
  5. (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  6. wac.setEnvironment(getEnvironment());
  7. wac.setParent(parent);
  8. wac.setConfigLocation(getContextConfigLocation());
  9. configureAndRefreshWebApplicationContext(wac);
  10. return wac;
  11. }

其中:getContextClass默认为:XmlWebApplicationContext

所以在执行请求时,SpringMVC管辖的Controller可以任意调用Service或DAO层的Bean,原因在于通过SpringApplicationContext的parent来进行二次查找,首先查找Parent上下文里的Bean,找不到则查找当前上下文里的Bean。有点类似Java的类加载机制,采用的双亲委派模型。
实现简要代码:
image.png