Springboot零配置原理

一、引言

  传统的SSM框架开发需要多种配置文件,application.xml、springmvc.xml、web.xml等等,然后对写好的程序进行编译、打包,丢到tomcat的webapp下部署、启动。但是经过后续框架的发展,基本可以做到零配置文件,也不需要单独安装tomcat进行部署。实现的原理就是Spring提供的java config,以及内置的tomcat,当然目前已经存在了一种把他们整合在一起的框架了,就是Springboot,如果我们不用Springboot,该怎么做呢?

二、 示例

1、添加jar包

    compile("org.apache.tomcat.embed:tomcat-embed-core:9.0.12")
    compile("org.apache.tomcat.embed:tomcat-embed-jasper:9.0.12")
//spring相关的就省略了,自行添加

2、编写mvc配置类

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


}

3、添加自定义初始化类

public class MyWebApplicationInitialier implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //用java注解的方式,去初始化spring上下文环境
        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方法

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();
        //强制Tomcat server等待,避免main线程执行结束后关闭
        tomcat.getServer().await();
    }
}

5、控制层

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

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

 三、源码解析

1、tomcat启动SPI机制

  tomcat启动时,在初始化StandardContext类时,会加载resources\META-INF\services\javax.servlet.ServletContainerInitializer文件中配置的实现类,并调用其onStartup方法

org.springframework.web.SpringServletContainerInitializer

初始化StandardContext,调用startInternal方法

    protected synchronized void startInternal() throws LifecycleException {

        ···

        try {
            ···

                // 生命周期事件
                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

                ···

            // 调用 ServletContainerInitializers 实现类的onStartup方法,并将initializers的value值set集合和servlet上下文传进去
            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、触发生命周期事件

    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() {
        
        ···
        
        // 找到 ServletContainerInitializer 所有的实现类
        // 放到initializerClassMap中,key是实现类,value是一个set集合用于存储@HandlesTypes注解的实现类
        if (ok) {
            processServletContainerInitializers();
        }

        ···

        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            
            // 从所有jar包中找出所有ServletContainerInitializer实现类上使用了@HandlesTypes注解
            // 的value值对应的接口的实现类,放入到initializerClassMap的value值中
            if (ok) {
                processAnnotations(
                        orderedFragments, webXml.isMetadataComplete(), javaClassCache);
            }    
        }

        // 把找到的ServletContainerInitializer配置放入StandardContext的属性initializers中
        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());
                }
            }
        }
    }

找到所有ServletContainerInitializer的实现类

     protected void processServletContainerInitializers() {

        List<ServletContainerInitializer> detectedScis;
        try {
            //找到并加载ServletContainerInitializer的实现类
            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) {
            //存放ServletContainerInitializer的实现类,初始化value值
            initializerClassMap.put(sci, new HashSet<Class<?>>());

            ···
        }
    }

添加到StandardContext的initializers属性中去

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

2、调用ServletContainerInitializer所有实现类的onStartup方法

@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) {
            //调用HandlesTypes注解的所有实现类的onStartup方法
            initializer.onStartup(servletContext);
        }
    }

}    

这里就会调用到我们自定义的MyWebApplicationInitialier实现类的onStartup方法,并且传入了servlet的上下文,具体就不再解析了,都是spring容器的初始化内容,以前也都讲解过了。

四、spring-boot的内嵌tomcat原理

  springboot的启动使用的是一个main方法,调用的是SpringApplication.run(GtripApplication.class, args)方法,我们直接来看这个方法。

    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 {
            //调用父类的refresh方法
            super.refresh();
        }
        catch (RuntimeException ex) {
            WebServer webServer = this.webServer;
            if (webServer != null) {
                webServer.stop();
            }
            throw ex;
        }
    }
    //AbstractApplicationContext
    public void refresh() throws BeansException, IllegalStateException {
        ···
        //调用子类的方法
        onRefresh();

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

父类的refresh方法就不多讲了,之前已经讲过了spring的源码,在onRefresh方法里,子类上下文对这个方法进行了重写,创建了web容器。

    private void createWebServer() {
        WebServer webServer = this.webServer;
        //如果使用的是jar包启动,获取的一定是null
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            //获取到的是ServletWebServerFactory类型的bean,是一个工厂
            ServletWebServerFactory factory = getWebServerFactory();
            //创建并启动web容器
            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();
    }

这里提前放入了一段lambda表达式getSelfInitializer(),后续会执行

    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
        //返回的是一个lambda表达式
        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);
        }
    }

继续创建webServer

    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        //创建实例
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        //实例化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);
        }
     //初始化DispatcherServlet的关键代码 prepareContext(tomcat.getHost(), initializers);
//启动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(); } }); // 启动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 } // 启动了一个守护线程,防止线程停止 startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }

关键代码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);
        ```
    }
//实现了ServletContainerInitializer接口,那么创建StandardContext的时候,就会调用他的onStartup方法
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) {
                //执行之前放入的lambda表达式的onStartup方法
                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;
    }

}

可以看到之前加入的lambda表达式,最终被封装到了TomcatStarter,它实现了ServletContainerInitializer接口,那么最后在创建StandardContext的时候,会调用lambda表达式的onStartup方法。

    //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();
        //把DispatcherServlet放入到tomcat上下文
        return servletContext.addServlet(name, this.servlet);
    }

调用tomcat的start方法,然后就是后续的向内引爆,启动一个一个子容器。

Springboot2.3.5版本tomcat配置

最大连接数:在同一时间,tomcat能够接受的最大连接数。对于Java的阻塞式BIO,默认值是maxthreads的值;如果在BIO模式使用定制的Executor执行器,默认值将是执行器中maxthreads的值。对于Java新的NIO模式,maxConnections 默认值是8192。

最大线程数:每一次HTTP请求到达Web服务,tomcat都会创建一个线程来处理该请求,那么最大线程数决定了Web服务容器可以同时处理多少个请求。maxThreads默认200,可以适当增加,但是,增加线程是有成本的,更多的线程,不仅仅会带来更多的线程上下文切换成本,而且意味着带来更多的内存消耗。JVM中默认情况下在创建新线程时会分配大小为1M的线程栈,所以,更多的线程异味着需要更多的内存。这里给出的建议:1核2g内存,线程数建议200;4核8g内存,线程数建议800。

等待连接数:当所有的请求处理线程都在使用时,所能接收的连接请求的队列的最大长度。当队列已满时,任何的连接请求都将被拒绝,默认值100。

最小工作空闲线程数:默认值10

在ServerProperties类中

//最大连接数
private int maxConnections = 8192;
//等待连接数
private int acceptCount = 100;
//最大线程数
@Deprecated
@DeprecatedConfigurationProperty(replacement = "server.tomcat.threads.max")
public int getMaxThreads() {
    return getThreads().getMax();
}
//最小工作空闲线程数
@Deprecated
@DeprecatedConfigurationProperty(replacement = "server.tomcat.threads.min-spare")
public int getMinSpareThreads() {
    return getThreads().getMinSpare();
}
public static class Threads {
    private int max = 200;
    private int minSpare = 10;
}

最大线程数和最小工作空闲线程数都可以通过相应的配置修改。

原文地址:https://www.cnblogs.com/sglx/p/15637584.html