Spring5MVC——初始化入口分析

什么是SpringMVC

SpringMVC是一個基于MVC的web框架,屬于Spring中的一個模塊,它和Spring不需要通過中間層進行整合就可以一起使用。

SpringMVC框架是以請求為驅(qū)動,圍繞Servlet設(shè)計,將請求發(fā)給控制器,然后通過模型對象,分派器來展示請求結(jié)果視圖。其中核心類是DispatcherServlet,它是一個Servlet,頂層是實現(xiàn)的Servlet接口。

SpringMVC請求處理流程


流程說明:

  • 1、客戶端(瀏覽器)發(fā)送請求,直接請求到DispatcherServlet。

  • 2、DispatcherServlet根據(jù)請求信息調(diào)用HandlerMapping,解析請求對應(yīng)的Handler。

  • 3、解析到對應(yīng)的Handler后,開始由HandlerAdapter適配器處理。

  • 4、HandlerAdapter會根據(jù)Handler來調(diào)用真正的處理器開處理請求,并處理相應(yīng)的業(yè)務(wù)邏輯。

  • 5、處理器處理完業(yè)務(wù)后,會返回一個ModelAndView對象,Model是返回的數(shù)據(jù)對象,View是個邏輯上的View。

  • 6、ViewResolver會根據(jù)邏輯View查找實際的View。

  • 7、DispaterServlet把返回的Model傳給View。

  • 8、通過View返回給請求者(瀏覽器)

SpringMVC使用

傳統(tǒng)的XML配置方式

傳統(tǒng)的XML配置方式,需要在webapp/WEB-INF目錄下web.xml文件,在文件里面需要配置Context和Servlet,主要是讓那個Tomcat Context初始化的時候去初始化Spring容器相關(guān)的服務(wù),隨后保存在Context實例里面,以供其servlet來使用。

<web-app>
    <!--上下文參數(shù),在監(jiān)聽器中被使用,實際就是key-value,key=contextConfigLocation寫死-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!--監(jiān)聽器配置,初始化WebApplicationContext-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!--前端控制器-->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
        <!--是啟動順序,讓這個Servlet隨Servletp容器一起啟動。--> 
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

ContextLoaderListener

ContextLoaderListener可以指定在Web應(yīng)用程序啟動時載入Ioc容器,正是通過ContextLoader來實現(xiàn)的,可以說是Ioc容器的初始化工作。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent event) {
        //初始化webApplicationCotext
        initWebApplicationContext(event.getServletContext());
    }
}

ContextLoaderListener實現(xiàn)了ServletContextListener,其本質(zhì)是一個Servlet的監(jiān)聽器,Tomcat會優(yōu)先加載Servlet的監(jiān)聽器組件,以保證在Servlet被創(chuàng)建之前,即Context組件進行初始化的時候,去調(diào)用其里面的contextInitialized方法來根據(jù)contextConfigLocation去讀取并解析Spring容器的配置,去創(chuàng)建并刷新出容器的實例來。

initWebApplicationContext

public class ContextLoader {

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        //從ServletContext中查找,是否存在以
        // WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為Key的值
        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!");
        }

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

        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            // 創(chuàng)建web上下文,默認是XmlWebApplicationContext
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            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);
                }
            }
            //將ApplicationContext放入ServletContext中,
            // 其key為<WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
            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) {
                //將ApplicationContext放入ContextLoader的全局靜態(tài)常量Map中,
                // 其中key為:Thread.currentThread().getContextClassLoader()即當(dāng)前線程類加載器
                currentContextPerThread.put(ccl, this.context);
            }

            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
            }

            return this.context;
        }
        catch (RuntimeException | Error ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
    }
}
  • 該方法會調(diào)用configureAndRefreshWebApplicationContext(cwac, servletContext)去創(chuàng)建并刷新Spring容器,刷新完成之后,就會將容器的實例保存到Tomcat的Context組件實例里,以供后續(xù)要創(chuàng)建出來的DispatcherServlet實例來調(diào)用。

  • 隨后在Servlet里面配置DispatcherServlet,并且給Servlet的contextConfigLocation初始化變量賦值上SpringMVC的配置文件路徑,以方便在DispatcherServlet初始化的時候去調(diào)用init方法去讀取contextConfigLocation里面的配置文件并加載相關(guān)的配置。通常情況下會加載RequestMapping標(biāo)記的類,以及對應(yīng)的方法和請求路徑、請求方法的映射。

注解配置方式

// 完整命名: javax.servlet.ServletContainerInitializer
public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}

ServletContainerInitializer 是 Servlet 3.0 新增的一個接口,主要用于在容器啟動階段通過編程風(fēng)格注冊Filter、Servlet以及Listener,以取代通過web.xml配置注冊。這樣就利于開發(fā)內(nèi)聚的web應(yīng)用框架。

Servlet3通過SPI的機制允許自定義一個ServletContainerInitializer的實現(xiàn)類,Servlet容器會在啟動的時候自動調(diào)用實現(xiàn)類的onStartup方法,我們可以在該方法中進行一些Servlet對象的注冊。

另外在實現(xiàn)ServletContainerInitializer時還可以通過@HandlesTypes注解定義本實現(xiàn)類希望處理的類型,容器會將當(dāng)前應(yīng)用中所有這一類型(繼承或者實現(xiàn))的類放在ServletContainerInitializer接口的集合參數(shù)中傳遞進來。如果不定義處理類型,或者應(yīng)用中不存在相應(yīng)的實現(xiàn)類,則集合參數(shù)為空。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<>();

        // webAppInitializerClasses 就是servlet3.0規(guī)范中為我們收集的 WebApplicationInitializer 接口的實現(xiàn)類的class
        // 從webAppInitializerClasses中篩選并實例化出合格的相應(yīng)的類
        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)
                                ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        // 這行代碼說明我們在實現(xiàn)WebApplicationInitializer可以通過繼承Ordered, PriorityOrdered來自定義執(zhí)行順序
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            // 迭代每個initializer實現(xiàn)的方法
            initializer.onStartup(servletContext);
        }
    }
}
  • 1、通過@HandlesType注解在onStartup方法的參數(shù)列表中注入感興趣的類,即WebApplicationInitializer;

  • 2、將WebApplicationInitializer的每個實現(xiàn)類,都新建一個實例,并放入initializers列表中;

  • 3、遍歷initializers列表,對每個WebApplicationInitializer實例執(zhí)行其onStartup方法。

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        registerDispatcherServlet(servletContext);
    }

    protected void registerDispatcherServlet(ServletContext servletContext) {
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() must not return null or empty");
        //創(chuàng)建Spring web ioc 容器
        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
        //創(chuàng)建DispatcherServlet實例
        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        if (registration == null) {
            throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
                    "Check if there is another servlet registered under the same name.");
        }

        registration.setLoadOnStartup(1);
        //路徑映射
        registration.addMapping(getServletMappings());
        registration.setAsyncSupported(isAsyncSupported());
        //請求攔截器,可以在此處控制編碼
        Filter[] filters = getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
                registerServletFilter(servletContext, filter);
            }
        }

        customizeRegistration(registration);
    }
}

WebApplicationInitializer的實現(xiàn)類及其功能

WebApplicationInitializer的實現(xiàn)類有很多,重點看一下AbstractAnnotationConfigDispatcherServletInitializer

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
        extends AbstractDispatcherServletInitializer {


    @Override
    @Nullable
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            context.register(configClasses);
            return context;
        }
        else {
            return null;
        }
    }


    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        Class<?>[] configClasses = getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            context.register(configClasses);
        }
        return context;
    }


    @Nullable
    protected abstract Class<?>[] getRootConfigClasses();


    @Nullable
    protected abstract Class<?>[] getServletConfigClasses();

}

這個類提供了兩個方法的實現(xiàn),以及兩個抽象方法供子類繼承

  • 1、createRootApplicationContext:創(chuàng)建根容器。

  • 2、createServletApplicationContext:創(chuàng)建servlet容器。

  • 3、getRootConfigClasses:抽象類,用于注冊根容器的配置類,相當(dāng)于spring.xml。

  • 4、getServletConfigClasses:抽象的類,用于注冊servlet容器的配置類,相當(dāng)于springmvc.xml。

  • 5、它還從AbstractDispatcherServletInitializer類繼承了getServletMappings方法,用于注冊servlet的映射。

  • 6、還從AbstractDispatcherServletInitializer類中繼承了getServletFilters方法,用于攔截并處理請求的編碼。

因此,我們可以自定義一個WebApplicationInitializer的實現(xiàn)類,繼承AbstractAnnotationConfigDispatcherServletInitializer;在servlet容器啟動時,會創(chuàng)建spring根容器和servlet容器,代替web.xml配置文件。同時,我們可以看到,在基于注解的springmvc開發(fā)中,真正用于代替web.xml的是WebApplicationInitializer,而并不是ServletContainerInitializer,這與本文開頭提到的基于注解的servlet開發(fā)有些區(qū)別。

根容器和servlet容器

  • 根容器用于管理@Service、@Repository等業(yè)務(wù)邏輯層和數(shù)據(jù)庫交互層組件。
  • servlet容器用于管理@Controller、視圖解析器、攔截器等跟頁面處理有關(guān)的組件。

使用步驟

  • 1、導(dǎo)包或添加依賴:spring-web、spring-webmvc。

  • 2、編寫數(shù)據(jù)庫訪問層、業(yè)務(wù)邏輯層、控制層等組件,這個跟基于配置文件的springmvc沒有區(qū)別。

  • 3、編寫根容器和servlet容器的配置類,這里不需要添加@Configuration注解。

  • 4、自定義WebApplicationInitializer,繼承AbstractAnnotationConfigDispatcherServletInitializer。

  • 5、在3的實現(xiàn)類中注冊根容器和servlet容器的配置類,以及servlet映射。

  • 6、在servlet容器中中注冊視圖解析器、攔截器等組件,用法是使servlet容器配置類實現(xiàn)WebMvcConfigurer接口,然后選擇相應(yīng)的方法進行注冊。

添加依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>


compile group: 'org.springframework', name: 'spring-webmvc', version: '5.2.6.RELEASE'

自定義springmvc攔截器

/**
 * 自定義的springmvc攔截器
 */
public class CustomedInterceptor implements HandlerInterceptor {

    /**
     * 重寫preHandle方法
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle正在執(zhí)行...");
        return true;
    }

    /**
     * 重寫postHandle方法
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle正在執(zhí)行...");
    }

    /**
     * 重寫afterCompletion方法
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion正在執(zhí)行...");
    }
}

SpringRootConfig

根容器的配置類,用于管理數(shù)據(jù)庫訪問層、業(yè)務(wù)邏輯層等組件,相當(dāng)于spring.xml

@Configuration
@ComponentScan("com.yibo.service")
public class SpringRootConfig {

}

MVCConfig

  • servlet容器的配置類,用于管理控制層、視圖解析器等組件,相當(dāng)于springmvc.xml
  • 可以在其中配置視圖解析器、靜態(tài)資源解析器、攔截器等組件
@Configuration
@ComponentScan("com.yibo.controller")
@EnableWebMvc
public class MVCConfig implements WebMvcConfigurer {

    /**
     * 注冊視圖解析器
     * @return
     */
    /*@Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
        internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
        internalResourceViewResolver.setSuffix(".jsp");
        return internalResourceViewResolver;
    }*/

    /**
     * 注冊視圖解析器
     * @param registry
     */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/jsp/", ".jsp");
    }

    /**
     * 注冊靜態(tài)資源解析器
     * 將springmvc處理不了的請求交給tomcat
     * @param configurer
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    /**
     * 注冊攔截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustomedInterceptor());
    }
}

StartWebApplicationInitializer

自定義的WebApplicationInitializer,用于注冊根容器、servlet容器、servlet映射、攔截器、監(jiān)聽器等,相當(dāng)于web.xml

/**
 * 自定義web容器啟動器
 */
public class StartWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * SpringContext中相關(guān)的bean
     * 注冊根容器配置類
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{SpringRootConfig.class};
    }

    /**
     * DispatcherServlet中上下文相關(guān)的bean
     * 注冊servlet容器(mvc子容器)配置類
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{MVCConfig.class};
    }

    /**
     * Servlet請求映射路徑
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 攔截并處理請求的編碼
     * @return
     */
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        encodingFilter.setForceEncoding(true);
        return new Filter[]{encodingFilter};
    }
}

參考:
https://blog.csdn.net/lqzkcx3/article/details/78507169

https://www.cnblogs.com/dubhlinn/p/10808879.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容