Spring Boot source code analysis process and start the Spring MVC integration

Open source projects recommended

Pepper Metrics is My colleagues and I developed an open source tool ( github.com/zrbcool/pep... ), which was collected by jedis / mybatis / httpservlet / dubbo / motan operating performance statistics, and to expose timing prometheus other mainstream database compatibility data, by grafana show trends. Of its plug-in architecture is very user-friendly extensions and other integrated open source components.
Please give a star, while welcome to become a developer to submit PR together to improve the project.

Chatted from a simple Spring Boot Web project

We know that with spring-boot write a web project is very easy, pom inherited spring-boot-parent and dependent on the introduction of spring-boot-starter-web, write a master boot this class, and then you can go write Controller, and very simple, like this:

@SpringBootApplication
public class SampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }
}
// 然后再写一个Controller声明一个Rest服务
@RestController
@RequestMapping("/perf")
public class PerfController {
    @RequestMapping("/trace")
    public Object trace() {
        Object result = yourLogic();
        return result;
    }
}
复制代码

Talk SpringApplication.run

But we thought about it behind the spring-boot in the end do what makes our job so simple, how it will be spring, spring-mvc, tomcat integrate with it? Next we start the project point of view to analyze the whole initialization process.

PS: When the following code analysis process, the series focuses on the process, calls to a variable, the authors will be given directly to the specific implementation of this variable, the reader may be confused, but do not stop, the first granted by the author of the thinking process stroked finish, behind the selection process will be initialized and implemented one by one interpretation for each of the major variables.

Speaking from SpringApplication.run: The method is defined 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;
}
复制代码

Let's break down this run method
to look at 1) context = createApplicationContext ()
is responsible for creating spring primary container, inside this method is based on the dependence of specific projects run-time class to dynamically choose to implement, if it is web project will be selected AnnotationConfigServletWebServerApplicationContext, as for rules and reasons for choice, here's ignore, will be devoted to the back (door time and space: ServletWebServerApplicationContext ).
Next we look at the focus 2) refreshContext (context) method
within their method eventually calls the ((AbstractApplicationContext) applicationContext) .refresh ( ) method, we put this method to expand

@Override
public void refresh() 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();
        }
    }
}
复制代码

In fact, here we have to call the package a spring-context, in fact, has nothing to with the spring-boot relationship, and this fact during the refresh () is a standard part of SpringApplicationContext standard start, we do not start the process of decomposition of the spring, so we are only concerned moiety that binds tomcat, spring-mvc.
See Direct 3) onRefresh () method, as is a subclass ServletWebServerApplicationContext AnnotationConfigServletWebServerApplicationContext, so the flow advances ServletWebServerApplicationContext OnRefresh () method

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();//4)
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}
复制代码

You can see that 4) createWebServer (), is the key to our

private void createWebServer() {
    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();
}
复制代码

Wherein:
. 5) ServletWebServerFactory getWebServerFactory Factory = ();
phrase obtained above is a specific implementation TomcatServletWebServerFactory (warp gate: TomcatServletWebServerFactory ) 6) this.webServer = factory.getWebServer (getSelfInitializer ());
look 6) getSelfInitializer ( )method:

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    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);
    }
}
复制代码

This bit mean, is a return this :: selfInitialize, the method is defined to return org.springframework.boot.web.servlet.ServletContextInitializer, we look at the definition of what it is

@FunctionalInterface
public interface ServletContextInitializer {
	void onStartup(ServletContext servletContext) throws ServletException;
}
复制代码

@FunctionalInterface java8 is supported in a functional lambda selfInitialize this interface logic which will be called later in the process.
Continue to see 6) in this.webServer = factory.getWebServer (...), we look to achieve:

@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);
}
复制代码

You can see inside the Tomcat instance is created as an internal webServer implementation, and then injected into the Tomcat Connector Service container, and then set the default Host container AutoDeploy property and other Tomcat initialization, the most important line 7)
we look at :

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if (documentRoot != null) {
        context.setResources(new LoaderHidingResourceRoot(context));
    }
    ...//省略我们不关注的部分代码
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);//8)
    host.addChild(context);//将context加入host作为host的子容器
    configureContext(context, initializersToUse);//9)
    postProcessContext(context);
}
复制代码

We can see that it calls host.addChild (context) context will be added as a sub-host host container, and then eight) Find all ServletContextInitializer realized and combined into an array, then calls 9) configureContext method, we look at:

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)
    ...//忽略
}
复制代码

10) created TomcatStarter object and starter added conainerInitializer list context, see 11), so that the container starts tomcat process will be called to this TomcatStarter instance.
We look at what has been done TomcatStarter

class TomcatStarter implements ServletContainerInitializer {
    ...
	private final ServletContextInitializer[] initializers;
	...
	TomcatStarter(ServletContextInitializer[] initializers) {
		this.initializers = initializers;
	}
    ...
	@Override
	public void onStartup(Set<Class<?>> 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());
			}
		}
	}
    ...
}
复制代码

We can see TomcatStarter equivalent to hook the start of the event context, and then call the method onStartup injected initializers of all, deja vu is not it? That said earlier @FunctionalInterface function interface, then we in-depth look onStartup the initializer of the aforementioned specific content

//ServletWebServerApplicationContext类当中
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    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);
    }
}
复制代码

We can see that all calls onStartup method for each ServletContextInitializer getServletContextInitializerBeans () of

protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());
}
复制代码

Look at new ServletContextInitializerBeans (getBeanFactory ()) did what

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
        Class<? extends ServletContextInitializer>... 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);
}
复制代码

Can be seen that acquires all ServletContextInitializer vessel from beanFactory implemented in spring, in the herein with respect to the integrated portion ServletRegistrationBean, the implantation process ServletRegistrationBean Reference: warp gate: 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 instanceof ServletRegistrationBean) {
        Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
        addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
    }
    else if (initializer instanceof FilterRegistrationBean) {
        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 instanceof ServletListenerRegistrationBean) {
        EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
        addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
    }
    else {
        addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
                initializer);
    }
}
复制代码

Then the process would shun, we will call to ServletRegistrationBean of onStartup methods will eventually call into a standard Servlet3.0 of servletContext.addServlet will DispatchServlet injected into the servlet container intercepts all requests.
See the code below:

//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);
}
复制代码

So far all the integration is complete, start tomcat process to complete.

I did not finish the story: how the individual components are dependent initialization

TomcatServletWebServerFactory

spring-boot-autoconfigure / META-INF / spring.factories in some configurations:

...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
...
复制代码

Then we look at the 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 {
    ...
}
复制代码

@Import part which introduces ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, in-depth look at

@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() {
			return new TomcatServletWebServerFactory();
		}
	}
	...
}
复制代码

Piece Spring Boot the current runtime environment meets the conditions according to @ConditionalOnClass judge, that contains a tomcat jar package, if it is created to meet the Bean instance TomcatServletWebServerFactory added spring container management useful later.

ServletWebServerApplicationContext

The actual start, started its subclasses AnnotationConfigServletWebServerApplicationContext, we look at the SpringApplication class, in fact SpringApplication decide which ApplicationContext to use according to the situation at runtime

View createApplicationContext () method
This.webApplicationType then this is where's the value of it? We look at this construction method

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();
}
复制代码

WebApplicationType.deduceFromClasspath () is used to automatically identify the value, look achieved:

static WebApplicationType deduceFromClasspath() {
    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)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}
复制代码

You can see that it is by determining whether there Servlet class classloader related to the judge, so the judge is running.

DispatcherServletRegistrationBean

DispatcherServletRegistrationBean is the key to ensuring our DispatcherServlet is injected into the Servlet container and in effect, we look at it is how to initialize a
spring-boot-autoconfigure / META- INF / spring.factories in some configurations:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
复制代码

Look at the 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<MultipartConfigElement> 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);
			}
			return registration;
		}

	}
}
复制代码

It can be seen that the vessel is registered as the spring of DispatcherServletRegistrationBean Bean instance, look at its inheritance:


ServletRegistrationBean class parent class has the following methods:

@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    String name = getServletName();
    return servletContext.addServlet(name, this.servlet);
}
复制代码

It calls ServletContext.addServlet method DispatchServlet added to the Servlet container, which is registered in the servlet method Servlet3.0.
Then you might ask, addRegistration what is the time to call it? According to inheritance, the parent class RegistrationBean view its parent, which has a

@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);
}
复制代码

register template method is a method call to achieve sub-class of 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);
}
复制代码

addRegistration template method is a method to achieve that is in front of addRegistration ServletRegistrationBean achieved, onStartup method will flow SpringApplication.run () method is called to, say, when the main flow has been talked about, not repeat them here
so will DispatchServlet is integrated with Tomcat, DispatchServlet template method design pattern, the specific request handler assigned to different treatment, this will be mentioned later herein, this will mainly focus on the principle of an integrated part of the Spring Boot Spring MVC and Tomcat.

Guess you like

Origin juejin.im/post/5d649ca4518825168d37c43d