Spring source code analysis 12: spring boot loading mechanism

Time:2022-4-28

Spring source code analysis 12: spring boot loading mechanism

1. spring-bootIncluded modules

Before analyzing the loading mechanism of springboot, let’s take a look at the officialspring-bootWhat modules are included and what are their uses.

Spring boot official warehouse

  • spring-bootThe core package of springboot, including:spring-frameworkPackaging and expansion ofapplication.yamlLoading mechanism, annotation starting mechanism, etc
  • spring-boot-autoconfigureapplication.yamlConfiguration item definition for
  • spring-boot-actuator: health inspection, audit, statistics and monitoring of spring
  • spring-boot-actuator-autoconfigure: for spring health inspection, audit, statistics and monitoringapplication.yamlConfiguration item support
  • spring-boot-cli: command line execution support
  • spring-boot-devtools: automatically refresh the application by listening to changes in class files during development
  • spring-boot-starters/*: springboot starter, the following projects are all dependency definitions without code
  • spring-boot-tools/*: springboot Kit

2. Application entrance

A packaged application has the following basic directory structure

- /
  - BOOT-INF
    -Classes # store application class files and resources resource files
    -Lib # dependent jar
  - META-INF
    - MANIFEST. Startup configuration of MF # application
  -Org / springframework / boot / loader / # spring boot loader code

We can generally usejava -jar app.jarTo start the application,MANIFEST.MFIs to configure the startup of the application

# MANIFEST.MF

Manifest-Version: 1.0
Start-Class: com.example.demo.App
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.1.4.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher

For Java, the configuration here is onlyMain-ClassIs valid, that is, specify the entry class

3. JarLauncher

JarLauncher
The main function of is to start the application through jar

public class JarLauncher extends ExecutableArchiveLauncher {
    //Entrance starting method
    public static void main(String[] args) throws Exception {
        new JarLauncher().launch(args);
    }
}

Now let’s carefully decompose this class and take a look at the inheritance relationship first

- Launcher
  - ExecutableArchiveLauncher
    - JarLauncher

3.1. Launcher

Launcher
The main function of is to realize the basic startup mechanism

public abstract class Launcher {
    //Start application
    protected void launch(String[] args) throws Exception {
        //Get class loader
        ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
        //Get startup class
        String launchClass = getMainClass();
        //Start
        launch(args, launchClass, classLoader);
    }

    //Start
    protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
        Thread.currentThread().setContextClassLoader(classLoader);
        //Create runner, run
        createMainMethodRunner(launchClass, args, classLoader).run();
    }

    //Create runner
    protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
        return new MainMethodRunner(mainClass, args);
    }

    //Get startup class,由子类实现
    protected abstract String getMainClass() throws Exception;
}

Let’s seeMainMethodRunner
How does it work

public class MainMethodRunner {
    private final String mainClassName;
    private final String[] args;

    public MainMethodRunner(String mainClass, String[] args) {
        this.mainClassName = mainClass;
        this.args = (args != null) ? args.clone() : null;
    }

    public void run() throws Exception {
        Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
        //Get the main method of mainclass
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        mainMethod.setAccessible(true);
        //Call the main method
        mainMethod.invoke(null, new Object[] { this.args });
    }

}

3.2. ExecutableArchiveLauncher

ExecutableArchiveLauncher
The main function of is to execute archive files (jar, war)

public abstract class ExecutableArchiveLauncher extends Launcher {
    //Startup class
    private static final String START_CLASS_ATTRIBUTE = "Start-Class";

    //Get startup class
    @Override
    protected String getMainClass() throws Exception {
        //Get ` manifest. In archive file (jar, war) MF ` file
        Manifest manifest = this.archive.getManifest();
        String mainClass = null;
        if (manifest != null) {
            //Gets the class specified by start class as the start class
            mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
        }
        if (mainClass == null) {
            //Startup class不能为空
            throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
        }
        return mainClass;
    }
}

3.3. JarLauncher

JarLauncher
The main function of is to execute the jar file and specify the classpath

public class JarLauncher extends ExecutableArchiveLauncher {
    //Can I load the following files
    static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
        if (entry.isDirectory()) {
            return entry.getName().equals("BOOT-INF/classes/");
        }
        return entry.getName().startsWith("BOOT-INF/lib/");
    };

    //"Boot-inf / classes /" and "boot-inf / lib / *. Jar" are loaded as Classpaths
    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
    }
}

4. Start the application

Let’s look at a basic startup

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

SpringApplication
ofrunMethod to start a springboot application

public class SpringApplication {
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        //Specify the startup main source and operation parameters
        return new SpringApplication(primarySources).run(args);
    }
}

5. SpringApplication.run

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

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //Resource loader
        this.resourceLoader = resourceLoader;
        //Start primary source
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //Get spring Components defined in the factories file that implement the bootstrapper interface
        this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
        //Get spring The component defined in the factories file that implements the applicationcontextinitializer interface
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //Get spring The component defined in the factories file that implements the applicationlistener interface
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //Get the entry class file, such as com example. demo. App. class
        this.mainApplicationClass = deduceMainApplicationClass();
    }

    //Run project
    public ConfigurableApplicationContext run(String... args) {
        //Create startup context
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        //Application context
        ConfigurableApplicationContext context = null;

        // ...  Code omission

        //Get spring Components defined in the factories file that implement the springapplicationrunlistener interface
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //Start listener
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            //Application parameters
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //Prepare environment objects
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            //Initialize spring beaninfo. Ignore configuration
            configureIgnoreBeanInfo(environment);
            //When creating an application context, the annotation configservletwebserver ApplicationContext is used by default
            context = createApplicationContext();

            //Prepare application context
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            //Call the refresh method of context to refresh the context data
            refreshContext(context);

            // ...  Code omission

            //Start of application listening function call
            listeners.started(context);
            //Run application
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            // ...  Code omission
        }

        try {
            //Call the listening function of application running
            listeners.running(context);
        }
        catch (Throwable ex) {
            // ...  Code omission
        }
        return context;
    }

    //Create startup context
    private DefaultBootstrapContext createBootstrapContext() {
        //The bootstrap default method in each component is used to initialize bootstrap default
        DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
        this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext));
        return bootstrapContext;
    }
}
public class SpringApplication {
    //Prepare environment objects
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        //Create a standard environment for spring Web
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //Configuration environment
        configureEnvironment(environment, applicationArguments.getSourceArgs());

        // ...  Code omission

        //Configure additionalprofiles
        configureAdditionalProfiles(environment);
        //Binding environment and Application
        bindToSpringApplication(environment);

        // ...  Code omission

        return environment;
    }

    //Configuration environment
    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        //Add data conversion and formatting services
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
        }
        //Configure the property source of the command line propertysources
        configurePropertySources(environment, args);
    }
}
public class SpringApplication {
    //Prepare application context
    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        //Setting environment
        context.setEnvironment(environment);
        //Add bean name generator, resource loader and data conversion service
        postProcessApplicationContext(context);
        //Call the initialize method of the component in initializers
        applyInitializers(context);

        // ...  Code omission

        //Get bean factory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //Register springapplicationarguments singleton
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);

        // ...  Code omission

        //Get all sources
        Set<Object> sources = getAllSources();
        //Load resources under these sources
        load(context, sources.toArray(new Object[0]));

        // ...  Code omission
    }

    //Load resources under these sources
    protected void load(ApplicationContext context, Object[] sources) {
        //Create beandefinitionloader
        BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);

        // ...  Code omission

        //Load resources
        loader.load();
    }
}
public class SpringApplication {
    //Run application
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<>();
        //Application runner
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        //Command line runner
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());

        for (Object runner : new LinkedHashSet<>(runners)) {
            if (runner instanceof ApplicationRunner) {
                //Call applicationrunner Run method
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                //Call commandlinerunner Run method
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }
}

In this section, there are several points that need to be analyzed slowly

  • AnnotationConfigServletWebServerApplicationContextHow to manage applications and data
  • BeanDefinitionLoader.loadHow to load resources

6. AnnotationConfigServletWebServerApplicationContext

AnnotationConfigServletWebServerApplicationContext
Is the default application context used by springboot

Let’s look at inheritance first

- GenericWebApplicationContext
  - ServletWebServerApplicationContext
    - AnnotationConfigServletWebServerApplicationContext

GenericWebApplicationContext
staySpring source code analysis II: context component (webapplicationcontext)We have already talked about it. Let’s have a look firstServletWebServerApplicationContext

6.1. ServletWebServerApplicationContext

ServletWebServerApplicationContext
Its main function is to start itself as an application service

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
        implements ConfigurableWebServerApplicationContext {
    //Refresh application
    @Override
    protected void onRefresh() {
        super.onRefresh();

        //When refreshing, re create an application service
        createWebServer();
    }

    //Create an application service
    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();

        //Both application service and context are empty
        if (webServer == null && servletContext == null) {
            //Get the bean of servletwebserverfactory, with built-in implementation of tomcat, jetty, undertow and netty
            ServletWebServerFactory factory = getWebServerFactory();
            //When the application service is initialized, call self initialize
            this.webServer = factory.getWebServer(getSelfInitializer());

            // ...  Code omission
        }
        //If there is a context object, call self initialize to initialize it
        else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                // ...  Code omission
            }
        }

        // ...  Code omission
    }
}
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
        implements ConfigurableWebServerApplicationContext {
    //Initialize application
    private void selfInitialize(ServletContext servletContext) throws ServletException {
        // ...  Code omission

        //Get the beans of all servletcontextinitializers and call the onstartup method of these components
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
}

6.2. AnnotationConfigServletWebServerApplicationContext

AnnotationConfigServletWebServerApplicationContext
The main function of is to automatically scan the beans defined by annotations

public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
        implements AnnotationConfigRegistry {
    //Annotation bean definition reader
    private final AnnotatedBeanDefinitionReader reader;
    //Classpath bean definition scanner
    private final ClassPathBeanDefinitionScanner scanner;
    //Registered annotation class component
    private final Set<Class<?>> annotatedClasses = new LinkedHashSet<>();
    //Packages to be scanned
    private String[] basePackages;

    public AnnotationConfigServletWebServerApplicationContext() {
        //Initialize reader and scanner
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

    public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
        super(beanFactory);
        //Initialize reader and scanner
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

    public AnnotationConfigServletWebServerApplicationContext(Class<?>... annotatedClasses) {
        this();
        //Register the bean and refresh the application context data
        register(annotatedClasses);
        refresh();
    }

    public AnnotationConfigServletWebServerApplicationContext(String... basePackages) {
        this();
        //Scan the package and refresh the application context data
        scan(basePackages);
        refresh();
    }
}
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
        implements AnnotationConfigRegistry {
    //Register bean
    @Override
    public final void register(Class<?>... annotatedClasses) {
        this.annotatedClasses.addAll(Arrays.asList(annotatedClasses));
    }

    //Scan package
    @Override
    public final void scan(String... basePackages) {
        this.basePackages = basePackages;
    }

    //After the application context is refreshed, beans and scanning packages are loaded automatically
    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        super.postProcessBeanFactory(beanFactory);
        if (this.basePackages != null && this.basePackages.length > 0) {
            this.scanner.scan(this.basePackages);
        }
        if (!this.annotatedClasses.isEmpty()) {
            this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
        }
    }
}

7. BeanDefinitionLoader.load

In the above code, resource loading is a classBeanDefinitionLoader
Completed

class BeanDefinitionLoader {
    //Load resources
    void load() {
        //Traversal loading
        for (Object source : this.sources) {
            load(source);
        }
    }

    //Load a single resource
    private void load(Object source) {
        //Load resources under class
        if (source instanceof Class<?>) {
            loadClass((Class<?>) source);
            return;
        }
        //Load resources of resource
        if (source instanceof Resource) {
            loadResource((Resource) source);
            return;
        }
        //Load resources under package
        if (source instanceof Package) {
            loadPackage((Package) source);
            return;
        }
        //Load resources represented by characters
        if (source instanceof CharSequence) {
            loadCharSequence((CharSequence) source);
            return;
        }
        //Others cannot be loaded
        throw new IllegalArgumentException("Invalid source type " + source.getClass());
    }
}
class BeanDefinitionLoader {
    //Load resources under class
    private void loadClass(Class<?> source) {
        //Loading annotations under classes
        this.annotatedReader.register(source);
    }

    //Load resources of resource
    private void loadResource(Resource source) {
        //Load XML of resource
        this.xmlReader.loadBeanDefinitions(source);
    }

    //Load resources under package
    private void loadPackage(Package source) {
        //Comments under scan package
        this.scanner.scan(source.getName());
    }

    //Load resources represented by characters
    private void loadCharSequence(CharSequence source) {
        //Replace placeholder ${}
        String resolvedSource = this.scanner.getEnvironment().resolvePlaceholders(source.toString());
        //Load as a class. If successful, return
        try {
            load(ClassUtils.forName(resolvedSource, null));
            return;
        }
        catch (IllegalArgumentException | ClassNotFoundException ex) {}
        //Load as a resource. If successful, return
        if (loadAsResources(resolvedSource)) {
            return;
        }
        //Load as a package. If successful, return
        Package packageResource = findPackage(resolvedSource);
        if (packageResource != null) {
            load(packageResource);
            return;
        }
        //Others cannot be loaded
        throw new IllegalArgumentException("Invalid source '" + resolvedSource + "'");
    }
}

follow-up

More blogs, seehttps://github.com/senntyou/blogs

Author:Deep (@ senntyou)

Copyright notice: Free Reprint – non commercial – non derivative – keep signature(Creative sharing 3.0 License