SpringMVC 初始化

1、HttpServletBean

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware

HttpServletBean 继承了HttpServlet,所以它只是在功能上对 HttpServlet 进行了一些扩展。

init() 方法,覆盖了她的祖先类GenericServlet 中定义的空实现,并且这个方法会在容器初始化每个servlet的时候调用一次。

HttpServletBean 是在HttpServlet 的基础上提供了把 servlet 配置中相关的一些属性、参数设置到成员变量上的功能,这么做的好处是可以很方便的通过 getter/setter 方法获取参数值,而不是通过一个通用的 Map 去获取。

/**
 * Map config parameters onto bean properties of this servlet, and
 * invoke subclass initialization.
 * @throws ServletException if bean properties are invalid (or required
 * properties are missing), or if subclass initialization fails.
 */
@Override
public final void init() throws ServletException {
   if (logger.isDebugEnabled()) {
      logger.debug("Initializing servlet '" + getServletName() + "'");
   }

   // Set bean properties from init parameters.
   try {
      PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
      BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
      ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
      bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
      initBeanWrapper(bw);
      bw.setPropertyValues(pvs, true);
   }
   catch (BeansException ex) {
      logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
      throw ex;
   }

   // Let subclasses do whatever initialization they like.
   initServletBean();

   if (logger.isDebugEnabled()) {
      logger.debug("Servlet '" + getServletName() + "' configured successfully");
   }
}

2、FrameworkServlet

为什么是到这个类的 initServletBean() 方法呢?
因为根据继承关系,DispatcherServlet 继承 FrameworkServlet,DispatcherServlet是在配置文件中配置的。貌似不对,initServletBean() 方法有注释,Let subclasses do whatever initialization they like。

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware

/**
 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
 * have been set. Creates this servlet's WebApplicationContext.
 */
@Override
protected final void initServletBean() throws ServletException {
   getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
   if (this.logger.isInfoEnabled()) {
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
   }
   long startTime = System.currentTimeMillis();

   try {
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
   }
   catch (ServletException ex) {
      this.logger.error("Context initialization failed", ex);
      throw ex;
   }
   catch (RuntimeException ex) {
      this.logger.error("Context initialization failed", ex);
      throw ex;
   }

   if (this.logger.isInfoEnabled()) {
      long elapsedTime = System.currentTimeMillis() - startTime;
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
            elapsedTime + " ms");
   }
}

protected WebApplicationContext initWebApplicationContext()

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent)

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)

public String getNamespace() {
return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}

然后回到 FrameworkServlet 的 initWebApplicationContext() 方法,继续向下执行
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}

3、DispatcherServlet

public class DispatcherServlet extends FrameworkServlet
这个实现调用初始化策略

/**
 * This implementation calls {@link #initStrategies}.
 */
@Override
protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

initHandlerMappings 默认会先探测 ApplicationContext 对象中已经设置好的任何HandlerMapping对象,如果有就使用定义好的,如果没有则调用方法getDefaultStrategies,使用默认配置。DispathcerServlet.properties 文件中默认的 BeanNameUrlHandlerMapping 和 DefaultAnnotationHandlerMapping 两个HandlerMapping 对象。

/**
 * Initialize the HandlerMappings used by this class.
 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
 * we default to BeanNameUrlHandlerMapping.
 */
private void initHandlerMappings(ApplicationContext context) {
   this.handlerMappings = null;

   if (this.detectAllHandlerMappings) {
      // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
      Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
      if (!matchingBeans.isEmpty()) {
         this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
         // We keep HandlerMappings in sorted order.
         OrderComparator.sort(this.handlerMappings);
      }
   }
   else {
      try {
         HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
         this.handlerMappings = Collections.singletonList(hm);
      }
      catch (NoSuchBeanDefinitionException ex) {
         // Ignore, we'll add a default HandlerMapping later.
      }
   }

   // Ensure we have at least one HandlerMapping, by registering
   // a default HandlerMapping if no other mappings are found.
   if (this.handlerMappings == null) {
      this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
      if (logger.isDebugEnabled()) {
         logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
      }
   }
}

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)

protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
return context.getAutowireCapableBeanFactory().createBean(clazz);
}

4、AbstractAutowireCapableBeanFactory

public <T> T createBean(Class<T> beanClass) throws BeansException {
   // Use prototype bean definition, to avoid registering bean as dependent bean.
   RootBeanDefinition bd = new RootBeanDefinition(beanClass);
   bd.setScope(SCOPE_PROTOTYPE);
   bd.allowCaching = ClassUtils.isCacheSafe(beanClass, getBeanClassLoader());
   return (T) createBean(beanClass.getName(), bd, null);
}

看下 DefaultAnnotationHandlerMapping 的层次关系,BeanNameUrlHandlerMapping 类层次关系是一样的。

public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping
public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
        implements HandlerMapping, Ordered
public abstract class WebApplicationObjectSupport extends ApplicationObjectSupport implements ServletContextAware
public abstract class ApplicationObjectSupport implements ApplicationContextAware

5、ApplicationObjectSupport

public abstract class ApplicationObjectSupport implements ApplicationContextAware
抽象类 ApplicationObjectSupport 实现了 ApplicationContextAware 接口,spring容器的后置处理器会调用 setApplicationContext 方法。

DefaultAnnotationHandlerMapping 实例化,父类同样实例化。

public final void setApplicationContext(ApplicationContext context) throws BeansException {
   if (context == null && !isContextRequired()) {
      // Reset internal context state.
      this.applicationContext = null;
      this.messageSourceAccessor = null;
   }
   else if (this.applicationContext == null) {
      // Initialize with passed-in context.
      if (!requiredContextClass().isInstance(context)) {
         throw new ApplicationContextException(
               "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
      }
      this.applicationContext = context;
      this.messageSourceAccessor = new MessageSourceAccessor(context);
      initApplicationContext(context);
   }
   else {
      // Ignore reinitialization if same context passed in.
      if (this.applicationContext != context) {
         throw new ApplicationContextException(
               "Cannot reinitialize with different application context: current one is [" +
               this.applicationContext + "], passed-in one is [" + context + "]");
      }
   }
}

initApplicationContext(context); 会先执行子类 WebApplicationObjectSupport 的 initApplicationContext 方法,然后执行自己的 initApplicationContext 方法,ApplicationObjectSupport 本身的该方法没有任何处理,只是调用了一个空的 initApplicationContext 方法,这个无参的重载方法被当作一个钩子供子类方法来实现。

6、AbstractDetectingUrlHandlerMapping

spring 默认的两个处理器映射类都继承自 AbstractDetectingUrlHandlerMapping,并且类初始化将被执行的 initApplicationContext 方法也在这个类中得到实现。

public void initApplicationContext() throws ApplicationContextException {
   super.initApplicationContext();
   detectHandlers();
}

先执行超类的 initApplicationContext 方法,这个超类的方法完成的任务就是对定义好的累计恶气改装并放入到 adaptedInterceptors 数组中供以后使用。
detectHandlers(); 请求与处理器映射的关键方法。

7、WebApplicationObjectSupport

protected void initApplicationContext(ApplicationContext context) {
    super.initApplicationContext(context);
    if (this.servletContext == null && context instanceof WebApplicationContext) {
        this.servletContext = ((WebApplicationContext)context).getServletContext();
        if (this.servletContext != null) {
            this.initServletContext(this.servletContext);
        }
    }
}

8、回到 AbstractDetectingUrlHandlerMapping

/**
 * Register all handlers found in the current ApplicationContext.
 * <p>The actual URL determination for a handler is up to the concrete
 * {@link #determineUrlsForHandler(String)} implementation. A bean for
 * which no such URLs could be determined is simply not considered a handler.
 * @throws org.springframework.beans.BeansException if the handler couldn't be registered
 * @see #determineUrlsForHandler(String)
 */
protected void detectHandlers() throws BeansException {
   if (logger.isDebugEnabled()) {
      logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
   }
   String[] beanNames = (this.detectHandlersInAncestorContexts ?
         BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
         getApplicationContext().getBeanNamesForType(Object.class));

   // Take any bean name that we can determine URLs for.
   for (String beanName : beanNames) {
      String[] urls = determineUrlsForHandler(beanName);
      if (!ObjectUtils.isEmpty(urls)) {
         // URL paths found: Let's consider it a handler.
         // 注册处理器,即添加一些映射关系到 handlerMap中,LinkedHashMap
         registerHandler(urls, beanName);
      }
      else {
         if (logger.isDebugEnabled()) {
            logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
         }
      }
   }
}

9、AbstractUrlHandlerMapping

protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
   Assert.notNull(urlPaths, "URL path array must not be null");
   for (String urlPath : urlPaths) {
      registerHandler(urlPath, beanName);
   }
}

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
   Assert.notNull(urlPath, "URL path must not be null");
   Assert.notNull(handler, "Handler object must not be null");
   Object resolvedHandler = handler;

   // Eagerly resolve handler if referencing singleton via name.
   if (!this.lazyInitHandlers && handler instanceof String) {
      String handlerName = (String) handler;
      if (getApplicationContext().isSingleton(handlerName)) {
         resolvedHandler = getApplicationContext().getBean(handlerName);
      }
   }

   Object mappedHandler = this.handlerMap.get(urlPath);
   if (mappedHandler != null) {
      if (mappedHandler != resolvedHandler) {
         throw new IllegalStateException(
               "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
               "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
      }
   }
   else {
      if (urlPath.equals("/")) {
         if (logger.isInfoEnabled()) {
            logger.info("Root mapping to " + getHandlerDescription(handler));
         }
         setRootHandler(resolvedHandler);
      }
      else if (urlPath.equals("/*")) {
         if (logger.isInfoEnabled()) {
            logger.info("Default mapping to " + getHandlerDescription(handler));
         }
         setDefaultHandler(resolvedHandler);
      }
      else {
         this.handlerMap.put(urlPath, resolvedHandler);
         if (logger.isInfoEnabled()) {
            logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
         }
      }
   }
}

在容器初始化时会建立所有 url 和 controller 的对应关系,保存到 Map<url, controller> 中。Tomcat启动时会通知spring初始化容器(加载bean定义信息和初始化所有单例bean),然后springmvc会遍历容器中的bean,获取每一个controller‘中的所有方法访问的url,然后将url和controller保存到一个Map中。
1、ApplicationContext 初始化时建立所有url和controller类的对应管理(用Map保存)
2、根据请求url找到对应的controller,并从controller中找到处理请求的方法
3、request参数绑定到方法的形参,执行方法处理结果,返回结果视图

但在某些特殊情况下,bean需要实现某个功能,但该功能必须借助于spring容器才能实现,此时就必须让该bean获取它所在的spring容器,可以让该bean实现ApplicationContextAwire接口。

必须在spring配置文件中指定该类,@Component

spring容器会检测容器中的所有bean,如果发现某个bean实现了ApplicationContextAwire接口,spring容器会在创建该bean之后,自动调用该bean的setApplicationContextAware() 方法,调用该方法时,会将容器本身作为参数传递给该方法,该方法的实现部分将spring传入的参数(容器本身)赋给对象的实例变量applicationContext,因此接下来可以通过该applicationContext实例变量访问容器本身。

注解 @Component
为什么ApplicationObjectSupport 没有@Component注解,而自定义的SpringContextUtis 却必须有注解?
因为DefaultAnnotationHandlerMapping 实例化,父类同样实例化。

10、DefaultAnnotationHandlerMapping

determineUrlsForHandler 方法,查看定义的 RequestMapping 注解,然后把符合要求的urls返回。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容