1. Introduction

In developing web applications using the SpringBoot framework, just knowing DispatcherServlet as an entry point for Web requests, If you don’t know how a request is forwarded to a DispatcherServlet or what the request does before it is forwarded to a DispatcherServlet, read this article to clear up your confusion.

2. Create a Web service factory

Open the ServletWebServerFactoryAutoConfiguration automatic configuration class, as you can see the automatic configuration class introduces four configuration class

2.1 Inject the Web service factory back processor

2.2 Declare the Tomcat Web Service Factory

By default, Tomcat will be used as the Web container, but you can also choose Jetty and Undertow and declare the corresponding factory

2.3 Declaring service Factory Custom Configurations

ServletWebServerFactoryCustomizer through ServerProperties to custom configuration of the Web service factory, such as setting up IP, port and the way of downtime

TomcatWebServerFactoryCustomizer setup Tomcat by ServerProperties minimum and maximum number of threads, maximum number of connections, etc

2.4 summary

Some important information in this section:

  • The statementWebServerFactoryCustomizerBeanPostProcessorIs used as a Web service factory custom configuration
  • The statementServletWebServerFactoryCustomizerCustom Configuration
  • The statementTomcatServletWebServerFactoryCustomizerCustom Configuration
  • The statementTomcatWebServerFactoryCustomizerCustom Configuration

3. Create a Web service

3.1 Obtaining a Web service Factory

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

Has been stated in section 2.2 part Web service factory, so get here as TomcatServletWebServerFactory; In section 2.1 part defines the rear WebServerFactoryCustomizerBeanPostProcessor processor, rear processors will callback WebServerFactoryCustomizer implementation, To complete the TomcatServletWebServerFactory custom configurations

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

3.2 Creating a Web Service

According to the Web service factory to create a Web service, and the incoming ServletContextInitializer implementation

private void createWebServer(a) {
    this.webServer = factory.getWebServer(getSelfInitializer());
}
Copy the code

3.2.1 create a Tomcat

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

3.2.2 Creating a Server and Service

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
	  tomcat.getService().addConnector(connector);
}
Copy the code
public Server getServer(a) {
    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;
}
Copy the code

3.2.3 create Connector

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

3.2.4 Creating an Engine and Host

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
  tomcat.getHost().setAutoDeploy(false);
}
Copy the code
public Host getHost(a) {
    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;
}
Copy the code
public Engine getEngine(a) {
    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;
}
Copy the code

3.2.5 create Context

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

3.2.6 configuration Context

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    / / create ServletContainerInitializer object, and hold ServletContextInitializer array
    TomcatStarter starter = new TomcatStarter(initializers);
    if (context instanceof TomcatEmbeddedContext) {
        TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
        embeddedContext.setStarter(starter);
        embeddedContext.setFailCtxIfServletStartFails(true);
    }
    // Set the container initializer to TomcatStarter
    context.addServletContainerInitializer(starter, NO_CLASSES);
}
Copy the code

3.2.7 summary

The logic of this section is exactly the same as the configuration in server.xml


      
<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>
Copy the code

4. Start the Web service

Use Tomcat’s start() method to initialize and start the corresponding components

4.1 Initialization Process

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

4.2 Startup Process

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

4.3 the callbackServletContainerInitializer

In the process of StandardContext start will callback ServletContainerInitializer in section 3.2.6 Settings

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

4.4 the callbackServletContextInitializer

TomcatStarter ServletContainerInitializer, traverse all ServletContextInitializer, and call the onStartup () method

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

ServletContextInitializer are defined as follows

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

4.4.1 setServletContextInitializer

public ServletContextInitializerBeans(ListableBeanFactory beanFactory, Class
       ... initializerTypes) {
    this.initializers = new LinkedMultiValueMap<>();
    / / specified type for ServletContextInitializer
    this.initializerTypes = (initializerTypes.length ! =0)? Arrays.asList(initializerTypes) : Collections.singletonList(ServletContextInitializer.class);/ / access type for ServletContextInitializer beans from the container and add
    addServletContextInitializerBeans(beanFactory);
    // Assemble the corresponding ServletRegistrationBean and FilterRegistrationBean from the container and add them
    addAdaptableBeans(beanFactory);
    / / get all ServletContextInitializer implementation
    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);
}
Copy the code

4.5 in order toDispatcherServletRegistrationBeanAs an example

DispatcherServletRegistrationBean ServletContextInitializer interface is achieved, Learn from section 4.4.1 can realize ServletContextInitializer interface beans will be added to the ServletContextInitializer collection

@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
       
         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);
        returnregistration; }}Copy the code

4.5.1 Executing 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);
}
Copy the code

4.5.2 Adding a Servlet to context

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

    // Check whether there is a Wrapper with the corresponding name
    Wrapper wrapper = (Wrapper) context.findChild(servletName);
    if (wrapper == null) {
        / / create a Wrapper
        wrapper = context.createWrapper();
        wrapper.setName(servletName);
        // Add Wrapper to the context
        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 {
        // Set the Class for the Servlet
        wrapper.setServletClass(servlet.getClass().getName());
        // Set the Servlet instance
        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;
}
Copy the code

4.6 in order toCharacterEncodingFilterAs an example

CharacterEncodingFilter implements the Filter interface, Learn from section 4.4.1 can implement the Filter interface beans will be assembled into the corresponding FilterRegistrationBean and added to the ServletContextInitializer collection

@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter(a) {
    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;
}
Copy the code

4.6.1 Executing 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);
}
Copy the code

4.6.2 Adding a Filter to a Context

private FilterRegistration.Dynamic addFilter(String filterName, String filterClass, Filter filter) throws IllegalStateException {
	// Check whether the corresponding Filter exists
    FilterDef filterDef = context.findFilterDef(filterName);
    if (filterDef == null) {
        // Create a FilterDef object
        filterDef = new FilterDef();
        // Set the Filter name
        filterDef.setFilterName(filterName);
        // Add FilterDef to the context
        context.addFilterDef(filterDef);
    } else {
        if(filterDef.getFilterName() ! =null&& filterDef.getFilterClass() ! =null) {
            return null; }}if (filter == null) {
        filterDef.setFilterClass(filterClass);
    } else {
        // Set the Class of Filter
        filterDef.setFilterClass(filter.getClass().getName());
        / / set the Filter
        filterDef.setFilter(filter);
    }

    return new ApplicationFilterRegistration(filterDef, context);
}
Copy the code

4.7 summary

Program realization of the Filter, the Servlet, ServletContextInitializer interface beans will be in StandardContext ServletContextInitializer set and be added to the context

5. Receive the processing request

5.1 Receiving Requests

When we enter a request to access in the browser, the Connector component receives the request and ultimately passes it to the CoyoteAdapter for processing

@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());
            // The request is passed to the container for processingconnector.getService().getContainer().getPipeline().getFirst().invoke( request, response); }}}Copy the code

5.2 Processing Requests

Based on the previous analysis, StandardEngine contains StandardHost, StandardHost contains StandardContext, StandardContext contains StandardWrapper, So the request is handed over to the 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. Get the Servlet, i.e. DispatcherServletservlet = wrapper.allocate(); }}// 2. Construct the ApplicationFilterChain object and set Filter and 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. Execute the doFilter() method of Filter. After the Filter chain is complete, execute the service() method of Servlet
                    filterChain.doFilter
                        (request.getRequest(), response.getResponse());
                }
            }

        }
    }
}
Copy the code