Spring Basic Series - Container Startup Process

Keywords: Spring Web Server SpringBoot xml

Original articles, reprinted please indicate the source: Spring Basic Series - Container Startup Process (2)

I. overview

The last article described the container in a SSM-based web project Startup process In this article, let's look at the container startup process in the SpringBook project.

II. Start-up process

The Springboot project needs to be started directly from the main method.

Source 1 - From DemoApplicaiton (Application Customized)

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

The first call is the run method in Spring Application.

Source 2 - From: Spring Application

    public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

The first run method calls the second run method.

There are two steps in the second run method:

  • Step 1: Create an instance of Spring Application CAITON
  • Step 2: Call the run method

First, we create an instance of Spring Application:

Source 3 - From: Spring Application

    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //Judging the type of application
        this.webApplicationType = deduceWebApplicationType();
        //Adding initializers
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        //Adding listeners
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //Locate the main method class and record it
        this.mainApplicationClass = deduceMainApplicationClass();
    }

There are three types of SpringBook applications:

Source 4 - From: Web Application Type

public enum WebApplicationType {
    NONE,SERVLET,REACTIVE
}

Analysis:

  • NONE: An application is not a web application, so you don't have to start a built-in server program at startup time
  • SERVLET: This is a Servlet-based web application that needs to open a built-in Servlet container (web server)
  • REACTIVE: This is a reactive web application that needs to start a built-in reactive web server

The three criteria are as follows.

Source 5 - From: Spring Application

    private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

Four constants are involved in the above method:

Source 6 - From: Spring Application

    private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
            + "web.reactive.DispatcherHandler";

    private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
            + "web.servlet.DispatcherServlet";

    private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";

    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

The code is simple.

Then set up the initializer, combined with the previous description in the SSM framework of web applications also have initializers, these two represent the same concept, different from the previous way of processing, here is just the initializer found and set to initializers properties.

Let's see how it gets these initializers:

Source 7 - From: Spring Application, Spring Factories Loader

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        //Gets a collection of class names of the specified type using the specified class loader
        Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //Create examples
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        //sort
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    
   // Here's from: Spring Factories Loader
    
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        //First try to get it from the cache
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try 
            //Get the configuration from the META-INF/spring.factories file
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            //Add to Cache Standby
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

Thus, the initializer is obtained from the META-INF/spring.factories file, so we can build the directory file to add, provided that we need a custom initializer.

In addition, only when the first call needs to be read from the file, the second can be retrieved from the cache, even if not the content of the initializer, because the first time all the configuration content will be retrieved and parsed to save to the cache standby. This saves a lot of time for the next step of setting up listeners.

The last step is to record the main method class.

Source 8 - From: Spring Application

    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

The method call stack is acquired by creating a new runtime exception, and the class where the method named main is located is searched in the stack.

If we want to get the call stack of a method, we can also use this method, using exceptions to get the call stack.

After the Spring Application instance is created, the run method is directly invoked to execute the application startup.

Source 9 - From: Spring Application

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //Configure the system property headless, which defaults to true
        configureHeadlessProperty();
        //Get all runtime listeners
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();//lsnrctl start
        try {
            //Encapsulating custom parameters
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //Configure environment according to parameters
            //If it's a web project, it creates a Standard Servlet Environment, otherwise it's a Standard Environment.
            //Then configure the properties and profile
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            //Set whether BeanInfo is ignored by default to true
            configureIgnoreBeanInfo(environment);
            //Print banner
            Banner printedBanner = printBanner(environment);
            //Create Application Context
            //If it is a Servlet web project, create Annotation ConfigEmbedded Web Application Context, if it is Reactive web
            //The project creates Annotation Config Reactive Web Server Application Context, otherwise Annotation Config Application Context
            context = createApplicationContext();
            //Loading exception reports
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, 
                new Class[] { ConfigurableApplicationContext.class }, context);
            //Preparatory context environment, i.e. initialization
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //Refresh Context
            refreshContext(context);
            //After refresh operation, these are two hole methods, which can be customized to achieve logic.
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);//After the container and application are enabled, call Runner is executed before
            //Callback to runner after the container is started, including: Application Runner and CommandLineRunner
            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;
    }

This is all the logical entry for the entire container to start, which can be understood by reference to the annotations. We focus on the prepareContext method, which is a pre-initialization operation before the container refreshes:

Source 10 - From: Spring Application

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        //Setting environment to context
        context.setEnvironment(environment);
        //Setting up BeanName Generator
        //Setting up a resource loader or class loader
        postProcessApplicationContext(context);
        //Execution initializer
        applyInitializers(context);
        listeners.contextPrepared(context);//Execute the contextPrepared operation of the listener
        //Configuration log information
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans
        //Spring Application Arguments (Spring Boot Banner)
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //Load beans into containers
        load(context, sources.toArray(new Object[0]));
        //Execute the contextLoaded operation of the listener
        listeners.contextLoaded(context);
    }

Focusing on the load method, his goal is to load the Bean definition:

Source 11 - From: Spring Application

    //To prepare for the load operation, create a resource reader scanner
    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        //Create Bean Definition Reader, Scanner (Annotated Bean Definition Reader, XmlBean Definition Reader,
        //GroovyBeanDefinitionReader,ClassPathBeanDefinitionScanner)
        BeanDefinitionLoader loader = createBeanDefinitionLoader(
                getBeanDefinitionRegistry(context), sources);
        //Configure BeanName generator, resource loader, environment parameters to reader and scanner
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        //Execute Bean Loading
        loader.load();
    }

    //Statistics on the number of loaded beans
    public int load() {
        int count = 0;
        for (Object source : this.sources) {
            count += load(source);
        }
        return count;
    }

    //Loading bean resources for different situations
    private int load(Object source) {
        Assert.notNull(source, "Source must not be null");
        //
        if (source instanceof Class<?>) {
            return load((Class<?>) source);
        }
        //Resource resources may be beans defined by XML or beans defined by groovy
        if (source instanceof Resource) {
            return load((Resource) source);
        }
        //Packet resources are typically used for annotation-defined Bean resources, requiring scanners to scan packages
        if (source instanceof Package) {
            return load((Package) source);
        }
        //
        if (source instanceof CharSequence) {
            return load((CharSequence) source);
        }
        throw new IllegalArgumentException("Invalid source type " + source.getClass());
    }

Load different resources.

Let's go back to the key operation refreshContext(context) in the run method:

Source 12 - From: Spring Application

    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }

Now it's time to execute the refresh() method, so let's take a look at this refresh.

Posted by nishith82 on Sun, 05 May 2019 21:20:39 -0700