SpringMVC 初始化

1清钥、HttpServletBean

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware

HttpServletBean 繼承了HttpServlet,所以它只是在功能上對 HttpServlet 進(jìn)行了一些擴(kuò)展同眯。

init() 方法,覆蓋了她的祖先類GenericServlet 中定義的空實現(xiàn)白对,并且這個方法會在容器初始化每個servlet的時候調(diào)用一次怔匣。

HttpServletBean 是在HttpServlet 的基礎(chǔ)上提供了把 servlet 配置中相關(guān)的一些屬性、參數(shù)設(shè)置到成員變量上的功能亏狰,這么做的好處是可以很方便的通過 getter/setter 方法獲取參數(shù)值役纹,而不是通過一個通用的 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() 方法呢促脉?
因為根據(jù)繼承關(guān)系,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() 方法,繼續(xù)向下執(zhí)行
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
這個實現(xiàn)調(diào)用初始化策略

/**
 * 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 默認(rèn)會先探測 ApplicationContext 對象中已經(jīng)設(shè)置好的任何HandlerMapping對象丁逝,如果有就使用定義好的汁胆,如果沒有則調(diào)用方法getDefaultStrategies,使用默認(rèn)配置霜幼。DispathcerServlet.properties 文件中默認(rèn)的 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 的層次關(guān)系罪既,BeanNameUrlHandlerMapping 類層次關(guān)系是一樣的铸题。

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 實現(xiàn)了 ApplicationContextAware 接口琢感,spring容器的后置處理器會調(diào)用 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); 會先執(zhí)行子類 WebApplicationObjectSupport 的 initApplicationContext 方法烘挫,然后執(zhí)行自己的 initApplicationContext 方法,ApplicationObjectSupport 本身的該方法沒有任何處理柬甥,只是調(diào)用了一個空的 initApplicationContext 方法饮六,這個無參的重載方法被當(dāng)作一個鉤子供子類方法來實現(xiàn)。

6苛蒲、AbstractDetectingUrlHandlerMapping

spring 默認(rèn)的兩個處理器映射類都繼承自 AbstractDetectingUrlHandlerMapping卤橄,并且類初始化將被執(zhí)行的 initApplicationContext 方法也在這個類中得到實現(xiàn)。

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

先執(zhí)行超類的 initApplicationContext 方法臂外,這個超類的方法完成的任務(wù)就是對定義好的累計惡氣改裝并放入到 adaptedInterceptors 數(shù)組中供以后使用窟扑。
detectHandlers(); 請求與處理器映射的關(guān)鍵方法。

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.
         // 注冊處理器,即添加一些映射關(guān)系到 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 的對應(yīng)關(guān)系,保存到 Map<url, controller> 中克懊。Tomcat啟動時會通知spring初始化容器(加載bean定義信息和初始化所有單例bean)忱辅,然后springmvc會遍歷容器中的bean,獲取每一個controller‘中的所有方法訪問的url谭溉,然后將url和controller保存到一個Map中墙懂。
1、ApplicationContext 初始化時建立所有url和controller類的對應(yīng)管理(用Map保存)
2扮念、根據(jù)請求url找到對應(yīng)的controller损搬,并從controller中找到處理請求的方法
3、request參數(shù)綁定到方法的形參,執(zhí)行方法處理結(jié)果巧勤,返回結(jié)果視圖

但在某些特殊情況下嵌灰,bean需要實現(xiàn)某個功能,但該功能必須借助于spring容器才能實現(xiàn)颅悉,此時就必須讓該bean獲取它所在的spring容器沽瞭,可以讓該bean實現(xiàn)ApplicationContextAwire接口。

必須在spring配置文件中指定該類剩瓶,@Component

spring容器會檢測容器中的所有bean驹溃,如果發(fā)現(xiàn)某個bean實現(xiàn)了ApplicationContextAwire接口,spring容器會在創(chuàng)建該bean之后,自動調(diào)用該bean的setApplicationContextAware() 方法延曙,調(diào)用該方法時豌鹤,會將容器本身作為參數(shù)傳遞給該方法,該方法的實現(xiàn)部分將spring傳入的參數(shù)(容器本身)賦給對象的實例變量applicationContext枝缔,因此接下來可以通過該applicationContext實例變量訪問容器本身傍药。

注解 @Component
為什么ApplicationObjectSupport 沒有@Component注解,而自定義的SpringContextUtis 卻必須有注解魂仍?
因為DefaultAnnotationHandlerMapping 實例化,父類同樣實例化拣挪。

10擦酌、DefaultAnnotationHandlerMapping

determineUrlsForHandler 方法,查看定義的 RequestMapping 注解菠劝,然后把符合要求的urls返回赊舶。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赶诊,隨后出現(xiàn)的幾起案子笼平,更是在濱河造成了極大的恐慌,老刑警劉巖舔痪,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寓调,死亡現(xiàn)場離奇詭異,居然都是意外死亡锄码,警方通過查閱死者的電腦和手機(jī)夺英,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滋捶,“玉大人痛悯,你說我怎么就攤上這事≈乜撸” “怎么了载萌?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我扭仁,道長垮衷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任斋枢,我火速辦了婚禮帘靡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瓤帚。我一直安慰自己描姚,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布戈次。 她就那樣靜靜地躺著轩勘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怯邪。 梳的紋絲不亂的頭發(fā)上绊寻,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天,我揣著相機(jī)與錄音悬秉,去河邊找鬼澄步。 笑死,一個胖子當(dāng)著我的面吹牛和泌,可吹牛的內(nèi)容都是我干的村缸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼武氓,長吁一口氣:“原來是場噩夢啊……” “哼梯皿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起县恕,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤东羹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后忠烛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體属提,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年况木,在試婚紗的時候發(fā)現(xiàn)自己被綠了垒拢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡火惊,死狀恐怖求类,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屹耐,我是刑警寧澤尸疆,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布椿猎,位于F島的核電站,受9級特大地震影響寿弱,放射性物質(zhì)發(fā)生泄漏犯眠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一症革、第九天 我趴在偏房一處隱蔽的房頂上張望筐咧。 院中可真熱鬧,春花似錦噪矛、人聲如沸量蕊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽残炮。三九已至,卻和暖如春缩滨,著一層夾襖步出監(jiān)牢的瞬間势就,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工脉漏, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留苞冯,地道東北人。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓侧巨,卻偏偏與公主長得像抱完,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刃泡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容