【五】SpringBoot - web项目的部署方式及启动原理

本章我们来讲讲Spring Boot web项目两种部署方式,第一种是jar包部署;第二种是war包部署,本文将分别介绍两种方式的原理。

1 jar包部署

1.1 创建demo

首先创建Spring Boot项目,在pom.xml中引入WEB依赖

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

启动类上加入springmvc注解

@SpringBootApplication
@RestController
public class SpringBootLearnDemo1Application {

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

    @RequestMapping("/hello")
    public String hello() {

        return "hello springboot";
    }
}
好了以上就是一个简单的demo,然后我们开始启动项目。

打成jar包,在jar包目录下通过java -jar 启动项目


启动成功如下,可以看到端口为8080


访问地址:http://127.0.0.1:8080/hello结果如下

1.2 启动原理

为什么通过java -jar 方式可以启动我们Spring Boot web项目呢?
首先我们知道java -jar 方式启动执行jar包执行的是项目根目录下面的/META-INF/MANIFEST.MF文件中配置的主类(Main-Class)的main方法,现在打开jar找到MANIFEST.MF文件如下

在这里插入图片描述
可以看到该文件见中有两个配置Main-Class,Start-Class,这些配置都是在我们打包时通过我们在pom.xml文件中配置的插件spring-boot-maven-plugin配置的
现在我们打开配置的Main-Class,打开该类需要引入如下依赖 

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-loader</artifactId>
</dependency>

打开JarLauncher找到main方法

在这里插入图片描述
main方法中创建了JarLauncher对象,调用了launch方法。

 进入launch方法

在这里插入图片描述
launch方法里获取了ClassLoader,且通过getMainClass方法获取了Spring Boot启动类。

 进入getMainClass方法

在这里插入图片描述
可以看出该方法在获取MANIFEST.MF文件中配置的Start-Class,而前文我们已经知道Start-Class为com.jt.springbootlearndemo1.SpringBootLearnDemo1Applicati即是Spring Boot的启动类 

返回launch方法中,该方法的最后一行调用了另一个launch重载方法

在这里插入图片描述
进入launch重载方法

在这里插入图片描述

 该方法创建了一个MainMethodRunner对象,调用了其run方法

直接进入MainMethodRunner的run方法

在这里插入图片描述
该方法通过反射执行了Spring Boot启动类的main方法 

我们知道web项目都是运行在web容器当中,而Spring Boot内置了web容器,如tomcat,那么Spring Boot启动类的main方法是如何启动tomcat?

进入启动类的main方法

在这里插入图片描述
可以看到调用了SpringApplication的静态run方法,一路跟进去发现调用了其另一个非静态run方法。

 进入run方法

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        /**
         *定义了spring容器
         */
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            /**
             *创建spring容器AnnotationConfigServletWebServerApplicationContext
             */
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            /**
             * 初始化容器
             */
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            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;
    }

目前我们只看我注释的地方,即创建了一个SpringBoot实现的spring容器(AnnotationConfigServletWebServerApplicationContext),并初始化容器。

进入初始化容器的方法refreshContext

在这里插入图片描述
该方法又调用了refresh方法,一路refresh下去,最终调用了AbstractApplicationContext的refresh,如下 

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                /**
                 * Spring Boot实现了该方法创建了web容器
                 */
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

该方法为容器的初始化方法,加载了BeanDefinition,并实例化。Spring Boot实现了该方法调用的onRefresh创建了web容器。

进入onRefresh方法

在这里插入图片描述
调用了createWebServer方法 

private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
        /**
         * 实例化了创建web容器的工厂类
         */
            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();
    }

该类通过ServletWebServerFactory 工厂类的getWebServer方法创建了web容器。

进入getWebServer方法

@Override
    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 = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        /**
         * 启动tomcat容器
         */
        return getTomcatWebServer(tomcat);
    }

在该方法中创建并启动了Tomcat容器;启动tomcat容器在最后一行调用了getTomcatWebServer方法,进入该方法

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
    }

该方法中将tomcat封装成了TomcatWebServer,进入其构造方法

在这里插入图片描述
在该方法中最后一行调用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 the server to trigger initialization listeners
                /**
                 * 启动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
                }

                // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                // blocking non-daemon to stop immediate shutdown
                startDaemonAwaitThread();
            }
            catch (Exception ex) {
                stopSilently();
                destroySilently();
                throw new WebServerException("Unable to start embedded Tomcat", ex);
            }
        }
    }

该方法中通过 this.tomcat.start()启动了tomcat,以上就是jar包方式启动的原理。

2 war包部署

前面我们说了jar包部署,Spring Boot也给我们提供了war包部署方式。

2.1 创建demo

修改pom.xml文件,配置packaging为war包方式,显示指定spring-boot-starter-tomcat,并将scope属性配置为provided,表示打包的时候将不会将该依赖打进去。

在这里插入图片描述
创建一个WebApplicationInitializer接口的子类,不直接实现该接口Spring Boot已经帮我们实现了一个SpringBootServletInitializer,继承该类并重写configure方法,在SpringApplicationBuilder 中传入启动类。 

public class MyServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(SpringBootLearnDemo1Application.class);
    }
}

配置完毕,打成war部署到tomcat即可

2.2 启动原理

我们知道使用springmvc的时候我们需要在web.xml文件中配置DispatcherServlet,在初始化方法里spring容器启动,那使用springbooot打成war部署到tomcat是如何启动spring容器的呢?下面让我们一起来看看Spring Boot打成war包是如何启动spring容器的。

首先看我们新创建的类

在这里插入图片描述
该类继承了SpringBootServletInitializer,查看其类图

在这里插入图片描述 SpringBootServletInitializer实现了WebApplicationInitializer接口,我们知道该接口是spring利用servlet3.0特性做的一个扩展,在tomcat容器启动的时候会调其onStartup方法,下面进入onStartup方法

public void onStartup(ServletContext servletContext) throws ServletException {
        // Logger initialization is deferred in case an ordered
        // LogServletContextInitializer is being used
        this.logger = LogFactory.getLog(getClass());
        /**
         * 这里创建了web容器
         */
        WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
        if (rootApplicationContext != null) {
            servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
        }
        else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
                    + "return an application context");
        }
    }

可以看到该方法中调用createRootApplicationContext方法创建了spring容器。

进入createRootApplicationContext方法

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
        SpringApplicationBuilder builder = createSpringApplicationBuilder();
        builder.main(getClass());
        ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
            builder.initializers(new ParentContextApplicationContextInitializer(parent));
        }
        builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
        builder = configure(builder);
        builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
        /**
         * 创建SpringApplication
         */
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty()
                && MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
            application.addPrimarySources(Collections.singleton(getClass()));
        }
        Assert.state(!application.getAllSources().isEmpty(),
                "No SpringApplication sources have been defined. Either override the "
                        + "configure method or add an @Configuration annotation");
        // Ensure error pages are registered
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }
        application.setRegisterShutdownHook(false);
        return run(application);
    }

该方法创建了一个SpringApplicationBuilder,见名知意,肯定是使用该类创建了一个SpringApplication,这个类大家是否有似曾相识的感觉,上文中通过jar包部署就是调用了该类的run方法启动,下面我们进入该方法的最后一行,调用了run方法。

在这里插入图片描述 果然在run方法里面调用SpringApplication的run方法,进入了和上文中jar包方式相同的逻辑。
但是这个逻辑不是会在spring容器初始化的时候,在onRefresh方法中启动内置的tomcat容器吗?
我们再次来看onRefresh方法

在这里插入图片描述
进入createWebServer方法 

private void createWebServer() {
        WebServer webServer = this.webServer;
        /**
         * 由于我们同tomcat以及启动所以servletContext不为空
         * 所以不走内容容器的逻辑
         */
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = getWebServerFactory();
            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();
    }

该方法在创建Tomcat容器之前判断了ServletContext是否为空,由于tomcat已经先启动所以此处的ServletContext是不等于空的所以,不会走内置tomcat容器。以上就是war部署方式的原理。

3 总结

以上就是jar包和war包部署的启动原理,我们可以看出这是两个相反的逻辑,jar包部署是spring容器带动tomcat容器启动,而war包部署是tomcat容器带动spring容器启动。

4 其他启动方式

除了以上介绍的启动方式,还有:
1.不需要打包直接进入项目的pom.xml文件所在目录,maven插件启动

mvn spring-boot:run

2.进入jar或war目录, loader 启动

java org.springframework.boot.loader.JarLauncher  
java org.springframework.boot.loader.WarLauncher

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot启动原理主要是基于Spring框架自动化配置和约定大于配置的原则,通过SpringApplication类自动化配置和加载应用程序的相关组件,然后启动应用程序。 具体来说,Spring Boot的启动过程包括以下几个步骤: 1. 加载应用程序的主配置类 Spring Boot启动时会加载应用程序的主配置类,也就是带有@SpringBootApplication注解的类。该注解包含了多个注解,包括@ComponentScan、@EnableAutoConfiguration和@Configuration等。 @ComponentScan用于扫描应用程序中的组件,包括@Service、@Component、@Controller、@Repository等。 @EnableAutoConfiguration用于自动化配置Spring应用程序所需的各个组件,包括数据源、JPA、Web、日志等。 @Configuration用于声明应用程序的配置类。 2. 执行自动化配置 Spring Boot通过@EnableAutoConfiguration注解来自动化配置应用程序所需的各个组件。它会根据应用程序的依赖关系和配置信息,自动配置各个组件的实现类,并将它们注册到Spring容器中。 3. 启动应用程序 完成自动化配置后,Spring Boot会创建一个Spring应用程序上下文,并加载所有的组件。然后,它会使用EmbeddedServletContainerFactory创建一个嵌入式Servlet容器,并启动应用程序。 4. 运行应用程序 应用程序启动后,Spring Boot会根据应用程序的配置信息,自动读取应用程序的配置文件,包括application.properties和application.yml等。它还会根据配置信息,自动装配各个组件,以便应用程序可以正常运行。 总之,Spring Boot的启动原理是基于自动化配置和约定大于配置的原则,通过@SpringBootApplication注解、@EnableAutoConfiguration注解、自动化配置和嵌入式Servlet容器等机制,实现了快速、简便、高效的应用程序开发和部署

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值