首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring 註解驅動啟動源碼深度解析:從 WebInitializer 到 Tomcat 容器

     編輯:Java 綜合教學

零 XML 配置下,Spring 是如何被 Tomcat 啟動的?

繼承 AbstractAnnotationConfigDispatcherServletInitializer 後,一個普通 Java 類如何接管整個 Spring 容器啟動?本文通過源碼追蹤,完整還原 Servlet 3.0+ 規范 + Spring SPI 機制 + Tomcat 容器 三方協作的啟動鏈路。

一、起點:自定義初始化類

@Order(1)
public class WebMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override protected Class<?>[] getRootConfigClasses() {
        return new Class[] { RootConfig.class, WebSecurityConfig.class };
    }
    @Override protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebConfig.class };
    }
    @Override protected String[] getServletMappings() { return new String[] { "/" }; }
    @Override protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter() };
    }
}

二、源碼追蹤鏈路圖

WebMvcInit (用戶代碼)
    ↓ extends
AbstractAnnotationConfigDispatcherServletInitializer
    ↓ implements WebApplicationInitializer
AbstractContextLoaderInitializer
    ↓ onStartup(ServletContext)
        → registerContextLoaderListener()
            → createRootApplicationContext()  【關鍵:創建根容器】
                → new AnnotationConfigWebApplicationContext()
                → register(configClasses)     --> 掃描 @Configuration/@Component
        → ContextLoaderListener 綁定到 ServletContext
    ↓ extends
AbstractDispatcherServletInitializer
    ↓ onStartup(ServletContext)
        → registerDispatcherServlet()
            → createServletApplicationContext()  【關鍵:創建 Web 容器】
                → new AnnotationConfigWebApplicationContext()
                → register(servletConfigClasses)
            → new DispatcherServlet(rootContext)
            → servletContext.addServlet("dispatcher", dispatcher)
            → mapping.addUrlPatterns("/")

三、關鍵源碼逐層剖析

1. AbstractContextLoaderInitializer — 根容器啟動

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        registerContextLoaderListener(servletContext);  // 模板方法
    }

    protected void registerContextLoaderListener(ServletContext servletContext) {
        WebApplicationContext rootAppContext = createRootApplicationContext(); // 子類實現
        if (rootAppContext != null) {
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            listener.setContextInitializers(getRootApplicationContextInitializers());
            servletContext.addListener(listener);  // 註冊到 Servlet 容器
        }
    }

    protected abstract WebApplicationContext createRootApplicationContext();
}

2. AbstractAnnotationConfigDispatcherServletInitializer — 實現模板方法

@Override
protected WebApplicationContext createRootApplicationContext() {
    Class<?>[] configClasses = getRootConfigClasses();  // 用戶返回 RootConfig.class 等
    if (!ObjectUtils.isEmpty(configClasses)) {
        AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
        rootAppContext.register(configClasses);  // 註冊配置類,觸發掃描
        return rootAppContext;
    }
    return null;
}

3. SpringServletContainerInitializer — Spring 的 SPI 入口

@HandlesTypes(WebApplicationInitializer.class)  // 掃描所有實現類
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) {
        List<WebApplicationInitializer> initializers = new LinkedList<>();
        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // 過濾接口、抽象類、非實現類
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers())
                        && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    initializers.add((WebApplicationInitializer) waiClass.newInstance());
                }
            }
        }
        AnnotationAwareOrderComparator.sort(initializers);  // @Order 排序
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);  // 統一調用
        }
    }
}

4. Tomcat 容器視角:StandardContext.startInternal()

四、完整啟動時序圖

Tomcat 啟動
    │
    ├─> 掃描 META-INF/services/javax.servlet.ServletContainerInitializer
    │       └─> 發現 SpringServletContainerInitializer
    │
    ├─> SpringServletContainerInitializer.onStartup()
    │       ├─> @HandlesTypes 掃描類路徑下所有 WebApplicationInitializer 實現類
    │       ├─> 實例化並按 @Order 排序
    │       └─> 依次調用 onStartup(ServletContext)
    │
    ├─> AbstractContextLoaderInitializer.onStartup()
    │       └─> registerContextLoaderListener()
    │             └─> createRootApplicationContext()  --> AnnotationConfigWebApplicationContext
    │                   └─> register(RootConfig.class, WebSecurityConfig.class)
    │                         └─> 掃描 @Component/@Configuration/@Bean  --> 根容器就緒
    │                   └─> ContextLoaderListener 綁定到 ServletContext
    │
    └─> AbstractDispatcherServletInitializer.onStartup()
            └─> registerDispatcherServlet()
                  ├─> createServletApplicationContext() --> AnnotationConfigWebApplicationContext
                  │       └─> register(WebConfig.class) --> 掃描 Controller、ViewResolver
                  ├─> new DispatcherServlet(rootWebAppContext)
                  ├─> servletContext.addServlet("dispatcher", dispatcher)
                  └─> mapping.addUrlPatterns("/")  --> Web 容器就緒

五、擴展性設計亮點

  1. SPI 解耦:Tomcat 不依賴 Spring,通過標準 ServletContainerInitializer 介面調用
  2. 多初始化器共存:Spring Security、Spring Session 等框架同樣實現 WebApplicationInitializer,@HandlesTypes 一網打盡
  3. 順序控制:@Order + AnnotationAwareOrderComparator 保證根容器先於 Web 容器啟動
  4. 雙容器架構:根容器(Service/DAO)作為父上下文,Web 容器(Controller/View)繼承並可覆蓋

六、調試技巧

  • 在 SpringServletContainerInitializer.onStartup() 打斷點,觀察 webAppInitializerClasses 集合
  • 在 AbstractContextLoaderInitializer.registerContextLoaderListener() 打斷點,檢查 rootAppContext Bean 定義數量
  • 啟動參數加 -Ddebug 可看到 Tomcat 詳細的初始化日誌

「框架的魔法,其實只是『約定優於配置』+『標準化 SPI』+『模板方法模式』的教科書級組合。」

下一步可深入:AnnotationConfigWebApplicationContext 的 refresh() 流程(BeanDefinition 註冊 → BeanPostProcessor → 實例化 → 注入 → 初始化)、DispatcherServlet.doDispatch() 請求處理鏈路。歡迎關注系列源碼解析!🔍

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved