zoukankan      html  css  js  c++  java
  • 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;
    }

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

  • 相关阅读:
    PowerMock详解
    java -agent与Javassist
    gradle_____最后到齐的构建工具
    JVM--参数调优
    提高速度 history 的利用
    shell-异步执行
    redis常用命令与使用分析
    chrome mac 快捷键
    idea的快捷键和操作
    mysql事务以及隔离级别
  • 原文地址:https://www.cnblogs.com/sglx/p/15637584.html
Copyright © 2011-2022 走看看