Springboot zero configuration principle

1, Introduction

Traditional SSM framework development requires a variety of configuration files, application xml,springmvc.xml,web.xml and so on, and then compile and package the written program, and throw it into tomcat's webapp for deployment and startup. However, after the development of the follow-up framework, it can basically achieve zero configuration file, and there is no need to install tomcat separately for deployment. The implementation principle is the java config provided by Spring and the built-in tomcat. Of course, there is already a framework to integrate them, namely Springboot. What should we do if we don't use Springboot?

2, Example

1. Add jar package

    compile("org.apache.tomcat.embed:tomcat-embed-core:9.0.12")
    compile("org.apache.tomcat.embed:tomcat-embed-jasper:9.0.12")
//spring Relevant are omitted and added by yourself

2. Write mvc configuration class

@Configuration
@ComponentScan("com.luban.mybatis")
public class AppConfig extends WebMvcConfigurationSupport {


}

3. Add custom initialization class

public class MyWebApplicationInitialier implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //use java Annotation, to initialize spring Context
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        System.out.println("init context");
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }

}

4. main method

public class AppTest {
    public static void main(String[] args) throws LifecycleException, ClassNotFoundException, IllegalAccessException, InstantiationException {

        Tomcat tomcat = new Tomcat();
        tomcat.setPort(8081);
                tomcat.addWebapp("/", "C:\\workspace\\software\\apache-tomcat-9.0.55");
        tomcat.start();
        //force Tomcat server Wait, avoid main Close after thread execution
        tomcat.getServer().await();
    }
}

5. Control layer

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/get")
    public String testGet() {
        return "ok";
    }
}

III. source code analysis

1. tomcat starts SPI mechanism

When tomcat starts, resources \ meta-inf \ services \ javax. XML will be loaded when the StandardContext class is initialized servlet. The implementation class configured in the servletcontainerinitializer file and calls its onStartup method

org.springframework.web.SpringServletContainerInitializer

Initialize StandardContext and call startInternal method

    protected synchronized void startInternal() throws LifecycleException {

        ···

        try {
            ···

                // Lifecycle events
                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

                ···

            // call ServletContainerInitializers Implementation class onStartup Method, and initializers of value value set Set sum servlet Context passed in
            for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
                initializers.entrySet()) {
                try {
                    entry.getKey().onStartup(entry.getValue(),
                            getServletContext());
                } catch (ServletException e) {
                    log.error(sm.getString("standardContext.sciFail"), e);
                    ok = false;
                    break;
                }
            }

            ···
        } finally {
            ···
        }

        ···
    }

1. Trigger lifecycle events

    protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        for (LifecycleListener listener : lifecycleListeners) {
            listener.lifecycleEvent(event);
        }
    }
    //ContextConfig
    public void lifecycleEvent(LifecycleEvent event) {

        ```

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            configureStart();
        } 
        ```

    }
    protected synchronized void configureStart() {
       
       ```
       
       webConfig();

       ```

    }
    protected void webConfig() {
        
        ···
        
        // find ServletContainerInitializer All implementation classes
        // put to initializerClassMap In the middle, key Is an implementation class, value It's a set Collection for storage@HandlesTypes Implementation class of annotation
        if (ok) {
            processServletContainerInitializers();
        }

        ···

        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            
            // From all jar Find all in the package ServletContainerInitializer Used on the implementation class@HandlesTypes annotation
            // of value The implementation class of the interface corresponding to the value is put into initializerClassMap of value In value
            if (ok) {
                processAnnotations(
                        orderedFragments, webXml.isMetadataComplete(), javaClassCache);
            }    
        }

        // Take what you found ServletContainerInitializer Configuration put StandardContext Properties of initializers in
        if (ok) {
            for (Map.Entry<ServletContainerInitializer,
                    Set<Class<?>>> entry :
                        initializerClassMap.entrySet()) {
                if (entry.getValue().isEmpty()) {
                    context.addServletContainerInitializer(
                            entry.getKey(), null);
                } else {
                    context.addServletContainerInitializer(
                            entry.getKey(), entry.getValue());
                }
            }
        }
    }

Find the implementation classes for all servletcontainerinitializers

     protected void processServletContainerInitializers() {

        List<ServletContainerInitializer> detectedScis;
        try {
            //Find and load ServletContainerInitializer Implementation class of
            WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
            detectedScis = loader.load(ServletContainerInitializer.class);
        } catch (IOException e) {
            log.error(sm.getString(
                    "contextConfig.servletContainerInitializerFail",
                    context.getName()),
                e);
            ok = false;
            return;
        }

        for (ServletContainerInitializer sci : detectedScis) {
            //deposit ServletContainerInitializer Implementation class, initialization value value
            initializerClassMap.put(sci, new HashSet<Class<?>>());

            ···
        }
    }

Add to the initializers property of StandardContext

    //StandardContext
    public void addServletContainerInitializer(
            ServletContainerInitializer sci, Set<Class<?>> classes) {
        initializers.put(sci, classes);
    }

2. Call the onStartup method of all implementation classes of ServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)
                                ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            //call HandlesTypes Annotation of all implementation classes onStartup method
            initializer.onStartup(servletContext);
        }
    }

}    

Here, the onStartup method of our custom MyWebApplicationInitialier implementation class will be called, and the servlet context will be passed in. It will not be parsed. It is all the initialization content of the spring container, which has been explained before.

4, Embedded tomcat principle of spring boot

springboot starts with a main method that calls springapplication Run (gtripapplication. Class, args) method. Let's look at this method directly.

    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);
    }

    public ConfigurableApplicationContext run(String... args) {
            ```
            refreshContext(context);
            ···
        return context;
    }
    private void refreshContext(ConfigurableApplicationContext context) {
        if (this.registerShutdownHook) {
            shutdownHook.registerApplicationContext(context);
        }
        refresh(context);
    }

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
        refresh((ConfigurableApplicationContext) applicationContext);
    }
    protected void refresh(ConfigurableApplicationContext applicationContext) {
        applicationContext.refresh();
    }
    //ServletWebServerApplicationContext
    public final void refresh() throws BeansException, IllegalStateException {
        try {
            //Calling the parent class refresh method
            super.refresh();
        }
        catch (RuntimeException ex) {
            WebServer webServer = this.webServer;
            if (webServer != null) {
                webServer.stop();
            }
            throw ex;
        }
    }
    //AbstractApplicationContext
    public void refresh() throws BeansException, IllegalStateException {
        ···
        //Call methods of subclasses
        onRefresh();

        ···
    }
    //ServletWebServerApplicationContext
    protected void onRefresh() {
        super.onRefresh();
        try {
            //establish web container
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }

I won't talk much about the refresh method of the parent class. I've talked about the source code of spring before. In the onRefresh method, the subclass context rewrites this method and creates a web container.

    private void createWebServer() {
        WebServer webServer = this.webServer;
        //If using jar When the package is started, what you get must be null
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            //What you get is ServletWebServerFactory Type bean,It's a factory
            ServletWebServerFactory factory = getWebServerFactory();
            //Create and launch web container
            this.webServer = factory.getWebServer(getSelfInitializer());
            getBeanFactory().registerSingleton("webServerGracefulShutdown",
                    new WebServerGracefulShutdownLifecycle(this.webServer));
            getBeanFactory().registerSingleton("webServerStartStop",
                    new WebServerStartStopLifecycle(this, this.webServer));
        } else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }

Here, a lambda expression getSelfInitializer() is put in advance, which will be executed later

    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
        //Returns a lambda expression
        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);
        }
    }

Continue creating webServer

    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        //Create instance
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        //instantiation  connector
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        //Engine
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
     //Key code for initializing DispatcherServlet prepareContext(tomcat.getHost(), initializers);
//start-up tomcat return getTomcatWebServer(tomcat); } protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown()); } public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null; initialize(); } private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // start-up tomcat this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // A daemon thread was started to prevent the thread from stopping startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }

Key code prepareContext(tomcat.getHost(), initializers)

    protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
        ```
        ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
        host.addChild(context);
        configureContext(context, initializersToUse);
        ```
    }
    
    protected void configureContext(Context context, ServletContextInitializer[] initializers) {
        TomcatStarter starter = new TomcatStarter(initializers);
        ```
    }
//Realized ServletContainerInitializer Interface, then create StandardContext When you're ready, you'll call him onStartup method
class TomcatStarter implements ServletContainerInitializer {

    private static final Log logger = LogFactory.getLog(TomcatStarter.class);

    private final ServletContextInitializer[] initializers;

    private volatile Exception startUpException;

    TomcatStarter(ServletContextInitializer[] initializers) {
        this.initializers = initializers;
    }

    @Override
    public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
        try {
            for (ServletContextInitializer initializer : this.initializers) {
                //Placed before execution lambda Expressional onStartup method
                initializer.onStartup(servletContext);
            }
        }
        catch (Exception ex) {
            this.startUpException = ex;
            // Prevent Tomcat from logging and re-throwing when we know we can
            // deal with it in the main thread, but log for information here.
            if (logger.isErrorEnabled()) {
                logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
                        + ex.getMessage());
            }
        }
    }

    Exception getStartUpException() {
        return this.startUpException;
    }

}

You can see that the lambda expression added before is finally encapsulated in TomcatStarter, which implements the ServletContainerInitializer interface. Finally, when creating the StandardContext, the onStartup method of the lambda expression will be called.

    //RegistrationBean
    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
    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
    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
        String name = getServletName();
        //hold DispatcherServlet Put into tomcat context
        return servletContext.addServlet(name, this.servlet);
    }

Call the start method of tomcat, and then detonate inward to start a sub container.

 

Keywords: Spring Cloud

Added by duke on Wed, 05 Jan 2022 14:38:57 +0200