Open Source Project Recommendation

Pepper Metrics is an open source tool I developed with my colleagues (github.com/zrbcool/pep…) , by collecting jedis mybatis/httpservlet/dubbo/motan performance statistics, and exposure to Prometheus and other mainstream temporal database compatible data, through grafana show the trend. Its plug-in architecture also makes it easy for users to extend and integrate other open source components. Please give us a STAR, and we welcome you to become developers to submit PR and improve the project together.

Start with a simple Spring Boot Web project

We know that it’s very easy to write a web project in Spring-boot. Pom inherits spring-boot-parent and then introduces a dependency on spring-boot-starter-Web, and then writes a main boot class like this, and then writes Controller. It’s very simple. Something like this:

@SpringBootApplication
public class SampleApplication {
    public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); }}// Then write a Controller to declare a Rest service
@RestController
@RequestMapping("/perf")
public class PerfController {
    @RequestMapping("/trace")
    public Object trace(a) {
        Object result = yourLogic();
        returnresult; }}Copy the code

Talk about SpringApplication. Run

But we thought, what does Spring-Boot do to make our job so easy? How does it integrate Spring, Spring-MVC, tomcat? Let’s look at the entire initialization process from a project startup perspective.

PS: the following code analysis process, focuses on the process of concatenated, call to a variable, the author will directly give the variables of the specific implementation, readers may be confused, but don’t stop, take it for granted according to the author’s idea of the first process after stroke, later for each major variable is initialized and the process of selecting the implementation to explain them one by one.

Start with SpringApplication.run: The method definition is as follows

public static ConfigurableApplicationContext run(Class
       [] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();/ / 1)
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        refreshContext(context);/ / 2)
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}
Copy the code

1) Context = createApplicationContext() is responsible for creating the Spring main container. This method is implemented dynamically based on the classes that the project depends on at runtime. If it is a web project will choose AnnotationConfigServletWebServerApplicationContext, as for the choice of rules and the reasons, here to ignore, later on (space-time door: ServletWebServerApplicationContext). Next, we focus on 2) refreshContext (context) method Its internal ultimately calls the method (AbstractApplicationContext applicationContext). The refresh () method, we expand this method

@Override
public void refresh(a) throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);
        try {
            postProcessBeanFactory(beanFactory);
            invokeBeanFactoryPostProcessors(beanFactory);
            registerBeanPostProcessors(beanFactory);
            initMessageSource();
            initApplicationEventMulticaster();
            onRefresh();/ / 3)
            registerListeners();
            finishBeanFactoryInitialization(beanFactory);
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
        finally{ resetCommonCaches(); }}}Copy the code

In fact, our call to the spring-Context package has nothing to do with spring-boot. This is just the refresh() part of a standard SpringApplicationContext during standard startup. We’re not breaking down the Spring startup process, so we’ll just focus on what works in conjunction with Tomcat and Spring-MVC. Direct look at 3) onRefresh () method, is because AnnotationConfigServletWebServerApplicationContext ServletWebServerApplicationContext subclasses, So the process into ServletWebServerApplicationContext onRefresh () method

@Override
protected void onRefresh(a) {
    super.onRefresh();
    try {
        createWebServer();/ / 4)
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex); }}Copy the code

You can see that this 4)createWebServer() is our key

private void createWebServer(a) {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();/ / 5)
        this.webServer = factory.getWebServer(getSelfInitializer());/ / 6)
    }
    else if(servletContext ! =null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}
Copy the code

ServletWebServerFactory Factory = getWebServerFactory(); On the access to the specific implementation is TomcatServletWebServerFactory (space-time door: TomcatServletWebServerFactory) 6) this. The webServer = factory. GetWebServer (getSelfInitializer ()); GetSelfInitializer () :

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer(a) {
    return this::selfInitialize;
}
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

This interesting, returns an this: : selfInitialize, method definition is to return org. Springframework. Boot. Web. Servlet. ServletContextInitializer, we look at the definition what is it

@FunctionalInterface
public interface ServletContextInitializer {
	void onStartup(ServletContext servletContext) throws ServletException;
}
Copy the code

SelfInitialize this logic will be called later in the process. This.webserver = factory.getWebServer(…) , let’s look at the implementation:

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory ! =null)?this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);/ / 7)
    return getTomcatWebServer(tomcat);
}
Copy the code

You can see that Tomcat instance is created as an internal implementation of webServer, then the Connector is injected into Tomcat’s Service container, then the AutoDeploy property of the default Host container is set and other Tomcat initialization tasks are performed. The most important line is 7) Let’s see:

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if(documentRoot ! =null) {
        context.setResources(newLoaderHidingResourceRoot(context)); }...// omit some of the code we don't care about
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);/ / 8)
    host.addChild(context);// Add context to host as a child of host
    configureContext(context, initializersToUse);/ / 9)
    postProcessContext(context);
}
Copy the code

We can see its call the host. The addChild (context) to add context to the host as a host of children, and then the 8) find all ServletContextInitializer implementation and combined into an array, Then call the 9)configureContext method. Let’s see:

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    TomcatStarter starter = new TomcatStarter(initializers);/ / 10)
    if (context instanceof TomcatEmbeddedContext) {
        TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
        embeddedContext.setStarter(starter);
        embeddedContext.setFailCtxIfServletStartFails(true);
    }
    context.addServletContainerInitializer(starter, NO_CLASSES);/ / 11)./ / ignore
}
Copy the code

10) The TomcatStarter object is created and added to the Context’s conainerInitializer list (see 11), so that the TomcatStarter instance will be invoked during tomcat’s container startup. So what does Tom Starter do

class TomcatStarter implements ServletContainerInitializer {...private finalServletContextInitializer[] initializers; . TomcatStarter(ServletContextInitializer[] initializers) {this.initializers = initializers; }...@Override
	public void onStartup(Set
       
        > classes, ServletContext servletContext)
       > throws ServletException {
		try {
			for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); }}catch (Exception ex) {
			this.startUpException = ex;
			if (logger.isErrorEnabled()) {
				logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "+ ex.getMessage()); }}}... }Copy the code

TomcatStarter hooks context startup events and calls all injected initializers onStartup methods. Familiar? This is the @functionalInterface interface. Let’s take a closer look at the initializer onStartup

/ / ServletWebServerApplicationContext class
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer(a) {
    return this::selfInitialize;
}
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

Can see its getServletContextInitializerBeans () of each ServletContextInitializer calls the onStartup method

protected Collection<ServletContextInitializer> getServletContextInitializerBeans(a) {
    return new ServletContextInitializerBeans(getBeanFactory());
}
Copy the code

Look at the new ServletContextInitializerBeans (getBeanFactory ()) do

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory, Class
       ... initializerTypes) {
    this.initializers = new LinkedMultiValueMap<>();
    this.initializerTypes = (initializerTypes.length ! =0)? Arrays.asList(initializerTypes) : Collections.singletonList(ServletContextInitializer.class); addServletContextInitializerBeans(beanFactory); addAdaptableBeans(beanFactory); 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

Can see it from the beanFactory gets all of the spring container ServletContextInitializer implementation, this part in ServletRegistrationBean about integration, ServletRegistrationBean injection process reference: space-time door: Dispatcherservletregistrationbean

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
    for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
        for(Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory, initializerType)) { addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory); }}}private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
    if (initializer instanceofServletRegistrationBean) { Servlet source = ((ServletRegistrationBean<? >) initializer).getServlet(); addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source); }else if (initializer instanceofFilterRegistrationBean) { Filter source = ((FilterRegistrationBean<? >) initializer).getFilter(); addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source); }else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
        String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
        addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
    }
    else if (initializer instanceofServletListenerRegistrationBean) { EventListener source = ((ServletListenerRegistrationBean<? >) initializer).getListener(); addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source); }else{ addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer); }}Copy the code

And then the process is done, and we call the onStartup method of the ServletRegistrationBean, Will call to the servletContext. AddServlet Servlet3.0 standards will DispatchServlet intercept all requests into a servlet container. See the following code:

//RegistrationBean
@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);
}
//DynamicRegistrationBean
@Override
protected final void register(String description, ServletContext servletContext) {
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(
                StringUtils.capitalize(description) + " was not registered " + "(possibly already registered?) ");
        return;
    }
    configure(registration);
}
//ServletRegistrationBean
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    String name = getServletName();
    return servletContext.addServlet(name, this.servlet);
}
Copy the code

At this point, all integration is completed, and the startup process is handed over to Tomcat.

Unfinished story: How are dependent components initialized

TomcatServletWebServerFactory

Spring-boot-autoconfigure/meta-INF /spring.factories

. # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\ ...Copy the code

Then we’ll look at ServletWebServerFactoryAutoConfiguration class

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {... }Copy the code

The @ Import part introduces ServletWebServerFactoryConfiguration. EmbeddedTomcat. Class, in-depth look at the

@Configuration
class ServletWebServerFactoryConfiguration {
	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {
		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory(a) {
			return newTomcatServletWebServerFactory(); }}... }Copy the code

The Spring Boot determines whether the current runtime environment is qualified according to @conditionalonClass, which contains the Tomcat JAR package. If the content is created TomcatServletWebServerFactory Bean instance to join the spring container management, useful later.

ServletWebServerApplicationContext

Actual start, start its subclasses AnnotationConfigServletWebServerApplicationContext, we’ll look at SpringApplication class, SpringApplication actually decides which ApplicationContext to use at run time depending on the situation

public SpringApplication(ResourceLoader resourceLoader, Class
       ... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}
Copy the code

WebApplicationType. DeduceFromClasspath () is used to automatically identify the value, look at the implementation:

static WebApplicationType deduceFromClasspath(a) {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if(! ClassUtils.isPresent(className,null)) {
            returnWebApplicationType.NONE; }}return WebApplicationType.SERVLET;
}
Copy the code

As you can see, this is determined by determining whether there are servlet-related classes in the Classloader, so it is determined by the runtime.

DispatcherServletRegistrationBean

DispatcherServletRegistrationBean is to ensure that our DispatcherServlet is injected into the Servlet container key and came into effect, Spring-boot-autoconfigure/meta-INF /spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
Copy the code

Look at implementation

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
	@Configuration
	@Conditional(DispatcherServletRegistrationCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		private final WebMvcProperties webMvcProperties;

		private final MultipartConfigElement multipartConfig;

		public DispatcherServletRegistrationConfiguration(WebMvcProperties webMvcProperties, ObjectProvider
       
         multipartConfigProvider)
        {
			this.webMvcProperties = webMvcProperties;
			this.multipartConfig = multipartConfigProvider.getIfAvailable();
		}

		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
			DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
					this.webMvcProperties.getServlet().getPath());
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
			if (this.multipartConfig ! =null) {
				registration.setMultipartConfig(this.multipartConfig);
			}
			returnregistration; }}}Copy the code

As you can see, it is like the spring container is registered DispatcherServletRegistrationBean Bean instance, take a look at its inheritance relationships:


@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    String name = getServletName();
    return servletContext.addServlet(name, this.servlet);
}
Copy the code

It calls the ServletContext. AddServlet method will DispatchServlet added to the Servlet container, this is the registration Servlet Servlet3.0 method. When is addRegistration invoked, you might ask? Based on the inheritance relationship, look at the parent class’s parent class, RegistrationBean, which has one

@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

The Register method is a template method that calls the implementation of a subclass DynamicRegistrationBean

@Override
protected final void register(String description, ServletContext servletContext) {
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered " + "(possibly already registered?) ");
        return;
    }
    configure(registration);
}
Copy the code

The addRegistration method is a template method that implements the addRegistration implementation of the ServletRegistrationBean. The onStartup method is called in the process of springApplication.run (), which we talked about in the main process and won’t go into here. This integrates DispatchServlet with Tomcat, DispatchServlet uses the template method design pattern to assign specific requests to different handlers. This will be discussed later. This article mainly focuses on the integration principle part of Spring Boot with Spring MVC and Tomcat.