面试官:请求是如何达到DispatcherServlet?

567 阅读5分钟

1. 前言

在使用SpringBoot框架开发web应用程序,只是了解DispatcherServlet作为web请求的入口,却不知道请求是如何转发给DispatcherServlet以及请求在转发给DispatcherServlet 之前做了哪些处理,阅读本文,带你解开心中的疑惑。

2.创建web服务工厂

打开ServletWebServerFactoryAutoConfiguration自动配置类,可以看到该自动配置类引入了4个配置类

2.1 注入Web服务工厂后置处理器

2.2 声明Tomcat Web服务工厂

默认情况下会使用Tomcat作为Web容器,当然你也可以选择JettyUndertow,同时也会声明对应的工厂

2.3 声明服务工厂自定义配置

ServletWebServerFactoryCustomizer通过ServerProperties对Web服务工厂进行自定义配置,比如设置ip、端口、停机方式等

TomcatWebServerFactoryCustomizer通过ServerProperties来设置Tomcat最小、最大线程数、最大连接数等

2.4 小结

该部分几个比较重要的信息:

  • 声明了WebServerFactoryCustomizerBeanPostProcessor,用作Web服务工厂自定义配置
  • 声明了ServletWebServerFactoryCustomizer自定义配置
  • 声明了TomcatServletWebServerFactoryCustomizer自定义配置
  • 声明TomcatWebServerFactoryCustomizer自定义配置

3.创建Web服务

3.1 获取Web服务工厂

private void createWebServer() {
	ServletWebServerFactory factory = getWebServerFactory();
}
protected ServletWebServerFactory getWebServerFactory() {
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

2.2章节部分已经声明过Web服务工厂,因此此处获取到的是TomcatServletWebServerFactory;在2.1章节部分定义了WebServerFactoryCustomizerBeanPostProcessor后置处理器,后置处理器中会回调WebServerFactoryCustomizer的实现,从而完成对TomcatServletWebServerFactory自定义配置

private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
    LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
        .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
        .invoke((customizer) -> customizer.customize(webServerFactory));
}

3.2 创建Web服务

根据Web服务工厂创建Web服务,并传入ServletContextInitializer的实现

private void createWebServer() {
    this.webServer = factory.getWebServer(getSelfInitializer());
}

3.2.1 创建Tomcat

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
}

3.2.2 创建Server和Service

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
	  tomcat.getService().addConnector(connector);
}
public Server getServer() {
    if (server != null) {
        return server;
    }
    System.setProperty("catalina.useNaming", "false");
    server = new StandardServer();
    initBaseDir();
    // Set configuration source
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
    server.setPort( -1 );

    Service service = new StandardService();
    service.setName("Tomcat");
    server.addService(service);
    return server;
}

3.2.3 创建Connector

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
  Connector connector = new Connector(this.protocol);
}

3.2.4 创建Engine和Host

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
  tomcat.getHost().setAutoDeploy(false);
}
public Host getHost() {
    Engine engine = getEngine();
    if (engine.findChildren().length > 0) {
        return (Host) engine.findChildren()[0];
    }

    Host host = new StandardHost();
    host.setName(hostname);
    getEngine().addChild(host);
    return host;
}
public Engine getEngine() {
    Service service = getServer().findServices()[0];
    if (service.getContainer() != null) {
        return service.getContainer();
    }
    Engine engine = new StandardEngine();
    engine.setName( "Tomcat" );
    engine.setDefaultHost(hostname);
    engine.setRealm(createDefaultRealm());
    service.setContainer(engine);
    return engine;
}

3.2.5 创建Context

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    host.addChild(context);
}

3.2.6 配置Context

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    // 创建ServletContainerInitializer对象,并持有ServletContextInitializer数组
    TomcatStarter starter = new TomcatStarter(initializers);
    if (context instanceof TomcatEmbeddedContext) {
        TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
        embeddedContext.setStarter(starter);
        embeddedContext.setFailCtxIfServletStartFails(true);
    }
    // 设置容器初始化器为TomcatStarter
    context.addServletContainerInitializer(starter, NO_CLASSES);
}

3.2.7 小结

该部分的逻辑正好与server.xml中的配置一致

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
      
    <Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <context>
        </context>
      </Host>
    </Engine>
  </Service>
</Server>

4.启动Web服务

通过Tomcatstart()方法来初始化、启动对应的组件

4.1 初始化过程

  • StandardServer.initInternal()
  • StandardService.initInternal()
  • Connector..initInternal()
  • StandardEngine.initInternal()
  • StandardHost.initInternal()
  • StandardContext.initInternal()

4.2 启动过程

  • StandardServer.startInternal()
  • StandardService.startInternal()
  • Connector.startInternal()
  • StandardEngine.startInternal()
  • StandardHost.startInternal()
  • StandardContext.startInternal()

4.3 回调ServletContainerInitializer

StandardContext启动过程中会回调在3.2.6章节设置的ServletContainerInitializer

@Override
protected synchronized void startInternal() throws LifecycleException {
	// Call ServletContainerInitializers
    for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
         initializers.entrySet()) {
        try {
            // 创建ApplicationContext并回调ServletContainerInitializer
            entry.getKey().onStartup(entry.getValue(),
                                     getServletContext());
        } catch (ServletException e) {
            log.error(sm.getString("standardContext.sciFail"), e);
            ok = false;
            break;
        }
    }
}

4.4 回调ServletContextInitializer

ServletContainerInitializer的实现 TomcatStarter,遍历所有ServletContextInitializer,并调用onStartup()方法

@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
        for (ServletContextInitializer initializer : this.initializers) {
            initializer.onStartup(servletContext);
        }
    }
}

ServletContextInitializer的定义如下

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());  
}

4.4.1 设置ServletContextInitializer

public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
			Class<? extends ServletContextInitializer>... initializerTypes) {
    this.initializers = new LinkedMultiValueMap<>();
    // 指定类型为ServletContextInitializer
    this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
        : Collections.singletonList(ServletContextInitializer.class);
    // 从容器中获取类型为ServletContextInitializer的Bean并添加
    addServletContextInitializerBeans(beanFactory);
    // 从容器中获取类型为Servlet、Filter的Bean组装成对应的ServletRegistrationBean、FilterRegistrationBean并添加
    addAdaptableBeans(beanFactory);
    // 获取所有ServletContextInitializer实现
    List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
        .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
        .collect(Collectors.toList());
    this.sortedList = Collections.unmodifiableList(sortedInitializers);
    logMappings(this.initializers);
}

4.5 以DispatcherServletRegistrationBean为例

DispatcherServletRegistrationBean实现了ServletContextInitializer接口,从4.4.1章节可以得知实现ServletContextInitializer接口的Bean会被添加到ServletContextInitializer集合中

@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                                                                           WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                                                                                               webMvcProperties.getServlet().getPath());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }

}

4.5.1 执行onStartup()方法

@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
    String description = getDescription();
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    register(description, servletContext);
}

4.5.2 添加Servlet到上下文

private ServletRegistration.Dynamic addServlet(String servletName, String servletClass,
            Servlet servlet, Map<String,String> initParams) throws IllegalStateException {

    // 是否存在名称为对应名称的Wrapper
    Wrapper wrapper = (Wrapper) context.findChild(servletName);
    if (wrapper == null) {
        // 创建Wrapper
        wrapper = context.createWrapper();
        wrapper.setName(servletName);
        // 将Wrapper添加到上下文中
        context.addChild(wrapper);
    } else {
        if (wrapper.getName() != null &&
            wrapper.getServletClass() != null) {
            if (wrapper.isOverridable()) {
                wrapper.setOverridable(false);
            } else {
                return null;
            }
        }
    }

    ServletSecurity annotation = null;
    if (servlet == null) {
        wrapper.setServletClass(servletClass);
        Class<?> clazz = Introspection.loadClass(context, servletClass);
        if (clazz != null) {
            annotation = clazz.getAnnotation(ServletSecurity.class);
        }
    } else {
        // 设置Servlet对应的Class
        wrapper.setServletClass(servlet.getClass().getName());
        // 设置Servlet实例
        wrapper.setServlet(servlet);
        if (context.wasCreatedDynamicServlet(servlet)) {
            annotation = servlet.getClass().getAnnotation(ServletSecurity.class);
        }
    }

    ServletRegistration.Dynamic registration =
        new ApplicationServletRegistration(wrapper, context);
    if (annotation != null) {
        registration.setServletSecurity(new ServletSecurityElement(annotation));
    }
    return registration;
}

4.6 以CharacterEncodingFilter为例

CharacterEncodingFilter实现了Filter接口,从4.4.1章节可以得知实现Filter接口的Bean会被组装成对应的FilterRegistrationBean并添加到ServletContextInitializer集合中

@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
    CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
    filter.setEncoding(this.properties.getCharset().name());
    filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
    filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
    return filter;
}

4.6.1 执行onStartup()方法

@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
    String description = getDescription();
    if (!isEnabled()) {
        logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
        return;
    }
    register(description, servletContext);
}

4.6.2 添加Filter到上下文

private FilterRegistration.Dynamic addFilter(String filterName,
            String filterClass, Filter filter) throws IllegalStateException {
	// 是否存在对应名称的Filter
    FilterDef filterDef = context.findFilterDef(filterName);
    if (filterDef == null) {
        // 创建FilterDef对象
        filterDef = new FilterDef();
        // 设置Filter名称
        filterDef.setFilterName(filterName);
        // 添加FilterDef到上下文
        context.addFilterDef(filterDef);
    } else {
        if (filterDef.getFilterName() != null &&
            filterDef.getFilterClass() != null) {
            return null;
        }
    }

    if (filter == null) {
        filterDef.setFilterClass(filterClass);
    } else {
        // 设置Filter对应的Class
        filterDef.setFilterClass(filter.getClass().getName());
        // 设置Filter
        filterDef.setFilter(filter);
    }

    return new ApplicationFilterRegistration(filterDef, context);
}

4.7 小结

程序中实现FilterServletServletContextInitializer接口的Bean会被放入ServletContextInitializer集合并被添加到上下文StandardContext

5.接收处理请求

5.1 接收请求

当我们在浏览器中输入要访问的请求时,由Connector组件接收请求,最终交由CoyoteAdapter处理

@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
    throws Exception {

    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);
    try {
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            //check valves if we support async
            request.setAsyncSupported(
                connector.getService().getContainer().getPipeline().isAsyncSupported());
            // 请求交由容器来处理
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                request, response);
        }
    }
}

5.2处理请求

基于前面的分析可以得知StandardEngine包含StandardHostStandardHost包含StandardContextStandardContext包含StandardWrapper,因此请求会交给StandardWrapperValve处理

@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    StandardWrapper wrapper = (StandardWrapper) getContainer();
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();

    try {
        if (!unavailable) {
            // 1.获取Servlet,也就是DispatcherServlet
            servlet = wrapper.allocate();
        }
    }
    
    // 2.构建ApplicationFilterChain对象,设置Filter和Servlet
    ApplicationFilterChain filterChain =
        ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    Container container = this.container;
    try {
        if ((servlet != null) && (filterChain != null)) {
            // Swallow output if needed
            if (context.getSwallowOutput()) {
               // 
            } else {
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else {
                    // 3.执行Filter的doFilter()方法,Filter链执行完成后,执行Servlet的service()方法
                    filterChain.doFilter
                        (request.getRequest(), response.getResponse());
                }
            }

        }
    }
}