2023-09-12  阅读(1)
原文作者:一直不懂 原文地址:https://blog.csdn.net/shenchaohao12321/article/details/80223497

传统的Servlet注册是配置在/WEB-INF/web.xml下,在容器启动的时候注册Servlet。Servlet3规范为我们又提供了一种更加方便的注册方法。容器启动的时候扫描jar包,找到jar包下一个文件路径为下面这个的文件。

    META-INF/services/javax.servlet.ServletContainerInitializer

该文件的内容必须是一个实现了javax.servlet.ServletContainerInitializer接口的实现类,该接口下有一个方法会自动被调用。

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

其中第一个参数是一个Set,当该实现类上有注解@HandlesTypes,注解的value属性是一个Class[],这个数组及其子类(或实现类)会被注入到webAppInitializerClasses参数中。

下面我们看看Spring是如何利用这种方式完成DispatcherServlet的注册。

在spring-web模块下,META-INF/services/javax.servlet.ServletContainerInitializer文件中的内容为:

    org.springframework.web.SpringServletContainerInitializer

该类实现了ServletContainerInitializer接口,并标有@HandlesTypes注解。

    @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<>();
          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");
          AnnotationAwareOrderComparator.sort(initializers);
          for (WebApplicationInitializer initializer : initializers) {
             initializer.onStartup(servletContext);
          }
       }
    }

onStartup方法会将WebApplicationInitializer的实现类实例放到List中排序,依次调用onStartup()方法。Spring为我们提供了一个WebApplicationInitializer的抽象实现类AbstractAnnotationConfigDispatcherServletInitializer,通常我们只需要继承此类来完成DispatcherServlet与spring context的注册。

202309122022464581.png

AbstractAnnotationConfigDispatcherServletInitializer的父类AbstractDispatcherServletInitializer覆盖了onStartup()方法,

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

首先调用父类的方法完成root WebApplicationContext的初始化,然后调用registerDispatcherServlet()方法完成DispatcherServlet的注册。

    protected void registerDispatcherServlet(ServletContext servletContext) {
       String servletName = getServletName();
       Assert.hasLength(servletName, "getServletName() must not return empty or null");
       WebApplicationContext servletAppContext = createServletApplicationContext();
       Assert.notNull(servletAppContext,
             "createServletApplicationContext() did not return an application " +
             "context for servlet [" + servletName + "]");
       FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
       dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
       ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
       Assert.notNull(registration,
             "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);
    }

暂时只关注Servlet的注册,Spring容器相关的以后介绍,createDispatcherServlet()方法创建了一个DispatcherServlet实例,然后通过servletContext.addServlet()方法完成DispatcherServlet的注册。

    protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
       return new DispatcherServlet(servletAppContext);
    }

以上就是DispatcherServlet的注册过程。


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文