• 1.SpringBoot学习(一)——第一个Spring Boot Web应用


    1.简介

    1.1 概述

    Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run". We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.

    Spring Boot 可以轻松创建单独的,基于生产级的 Spring 应用程序,您需要做的可能“仅仅是去运行”。 我们提供了 Spring Platform 对 Spring 框架和第三方库进行处理,尽可能的降低使用的复杂度。大多数情况下 Spring Boot 应用只需要非常少的配置。

    1.2 特点

    • Create stand-alone Spring applications(创建独立的 spring 应用)
    • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files) (集成 tomcat,jetty,undertow 等内置容器,无需打包成 war 包)
    • Provide opinionated 'starter' dependencies to simplify your build configuration (提供众多 starter 扩展来简化依赖配置)
    • Automatically configure Spring and 3rd party libraries whenever possible(无论何时都可以自动装配 spring 和第三方依赖)
    • Provide production-ready features such as metrics, health checks, and externalized configuration(提供生产环境特性,如 指标信息、健康检查和外部化配置等)
    • Absolutely no code generation and no requirement for XML configuration (无需代码生成和 xml 配置)

    1.3 对比 Spring

    1. Spring 是一种生态,它包含各种组件,针对开发中存在的问题提供了多种解决方案;

      image-20200407193005410

    2. Spring Boot 为快速启动且最小化配置的 Spring 应用而设计,并且具备用于构建生产级应用的各种特性,提供了一些内置 starter;

      image-20200709204302088

    3. Spring 应用需要复杂配置,一般在需要在 xml 中配置各种依赖;Spring Boot 简化了这些配置,默认使用注解进行扫描,最多只需要在 application.properties 中提供额外配置;

    4. 使用 maven 构建 Spring 应用需要提供各种 pom 依赖;而 Spring Boot 只需要提供了 starter 即可,starter 中已经对所需依赖进行了封装;

    5. Spring 应用最终需要打成 war 包放到 Severlet 容器中进行运行;而 Spring Boot 可以打成 jar 包,使用 java -jar 命令直接运行;

    6. ...

    2.环境

    1. JDK 1.8.0_201
    2. Spring Boot 2.2.0.RELEASE
    3. 构建工具(apache maven 3.6.3)
    4. 开发工具(IntelliJ IDEA )

    3.代码

    3.1 代码结构

    image-20200709233145417

    3.2 maven 依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    

    3.3 java代码

    DemoController.java

    @RestController
    public class DemoController {
    
        @GetMapping("/demo")
        public String demo() {
            return "demo";
        }
    }
    

    3.4 git 地址

    spring-boot/spring-boot-01-demo

    4.结果

    启动 SpringBootDemoApplication.main 方法,访问如下地址,页面显示 “demo” 表示服务运行正常

    ### GET /demo
    GET http://localhost:8080/demo
    

    image-20200709205650117

    5.源码分析

    5.1 Spring Boot 启动流程?

    image-20200709221114156

    从 main 方法开始

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

    SpringApplication.run -> ConfigurableApplicationContext

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 设置 “java.awt.headless” 属性
        configureHeadlessProperty();
        // 使用 SpringFactoryLoader 获取 SpringApplicationRunListener 实例的 listeners
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 逐个启动 SpringApplicationRunListener,应用开始启动事件
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 获取 Environment,根据 webType 获取不同类型;并配置 propertySource 和 profiles
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            // 打印 banner
            Banner printedBanner = printBanner(environment);
            // 创建 spring 应用上下文,类型和 webType 有关
            context = createApplicationContext();
            // 使用 SpringFactoryLoader 获取 SpringBootExceptionReporter 实例的 exceptionReporters
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                                                             new Class[] { ConfigurableApplicationContext.class }, context);
            // 上下文预处理,spring boot
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新上下文,spring context
            refreshContext(context);
            // 上下文后置处理,暂为空
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // listeners 启动完成事件
            listeners.started(context);
            // 触发 ApplicationRunner 和 CommandLineRunner
            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;
    }
    

    5.2 Spring Boot 如何加载 Tomcat?

    image-20200709223727506

    SpringApplication.run -> ConfigurableApplicationContext ,跟踪 tomcat 的创建过程,主要看 createApplicationContext() 和 refreshContext() 方法

    public ConfigurableApplicationContext run(String... args) {
        // ...
        try {
            // ...
            context = createApplicationContext();
            // ...
            refreshContext(context);
            // ...
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
    
        // ...
        return context;
    }
    

    createApplicationContext() 创建 spring 应用

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                    case SERVLET:
                        contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                        break;
                    case REACTIVE:
                        contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                        break;
                    default:
                        contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                    "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    

    最终创建的应用类型和 webApplicationType 有关,webApplicationType 在 SpringApplication 的构造函数中进行实例化

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    默认类型为 WebApplicationType.SERVLET

    static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }
    

    在 refreshContext 中调用了 refresh(context) 方法,这里的 applicationContext 为 AnnotationConfigServletWebServerApplicationContext

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }
    

    然后调用 AbstractApplicationContext.refresh() 方法

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            // ...
    
            try {
                // ...
                this.onRefresh();
                // ...
            } catch (BeansException var9) {
                // ...
            } finally {
                this.resetCommonCaches();
            }
        }
    }
    

    onRefresh() 是一个钩子方法,根据上面分析,这里使用的是 servlet,所以会调用到 ServletWebServerApplicationContext.onRefresh()

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

    在 ServletWebServerApplicationContext 中调用 createWebServer() 创建 web 服务

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = getWebServerFactory();
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }
    

    这里的 factory 通过 ServletWebServerFactory 来实例化,所以创建 ServletWebServer

    image-20200709225432357

    最终在 TomcatServletWebServerFactory 中创建了 Tomcat、Connector、Engine、Host 等,这里可以结合 apache-tomcat 的配置文件 server.xml 来分析几者之间的层级关系

    image-20200709225721299

    5.3 Spring Boot 默认容器为何是 tomcat?

    先看下 spring-boot-starter-web 的依赖结构

    image-20200710214932703

    spring-boot-starter-web 依赖了 spring-boot-starter-tomcat,又依赖了 tomcat-embed-core。但是只凭这个,并不能说明默认容器为 tomca 的原因。

    要弄清这个问题,就要涉及到 Spring Boot 的自动装配,以及 WebServer 的装配。

    image-20200710220743014

    @SpringBootApplication 是一个复合注解,它包含如下内容

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    @ConfigurationPropertiesScan
    public @interface SpringBootApplication {
        // ...
    }
    

    其中 @EnableAutoConfiguration 和自动装配相关,@EnableAutoConfiguration 声明如下

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
        // ...
    }
    

    @Import 导入一个 AutoConfigurationImportSelector,这个 Selector 中 selectImports() 实现如下

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                                                  annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    

    其中 getAutoConfigurationEntry 获取自动装配类型,它的实现如下

    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                                                               AnnotationMetadata annotationMetadata) {
        // 是否开启自动装配,默认开启,可通过 spring.boot.enableautoconfiguration 进行配置
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        // 获取元注解中的属性,它是一个 LinkedHashMap
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 获取要自动装配类的类名
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 去重
        configurations = removeDuplicates(configurations);
        // 要排除的装配类
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        // 过滤
        configurations = filter(configurations, autoConfigurationMetadata);
        // 触发自动装配导入事件
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
    

    在 getCandidateConfigurations 获取所有自动装配类,这个方法通过 SpringFactoriesLoader 加载 META-INF/spring.factories 中的内容,在 spring-boot-autoconfigure 的 spring.factories 中有如下内容

    image-20200710222137689

    其中有一条配置为 EmbeddedWebServerFactoryCustomizerAutoConfiguration,这个即为内容 webServer 的自动装配类

    image-20200710222221906

    它的实现如下

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication
    @EnableConfigurationProperties(ServerProperties.class)
    public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
    
        /**
    	 * Nested configuration if Tomcat is being used.
    	 */
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
        public static class TomcatWebServerFactoryCustomizerConfiguration {
    
            @Bean
            public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
                                                                                     ServerProperties serverProperties) {
                return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
            }
    
        }
    
        /**
    	 * Nested configuration if Jetty is being used.
    	 */
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
        public static class JettyWebServerFactoryCustomizerConfiguration {
    
            @Bean
            public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
                                                                                   ServerProperties serverProperties) {
                return new JettyWebServerFactoryCustomizer(environment, serverProperties);
            }
    
        }
    
        /**
    	 * Nested configuration if Undertow is being used.
    	 */
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
        public static class UndertowWebServerFactoryCustomizerConfiguration {
    
            @Bean
            public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
                                                                                         ServerProperties serverProperties) {
                return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
            }
    
        }
    
        /**
    	 * Nested configuration if Netty is being used.
    	 */
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnClass(HttpServer.class)
        public static class NettyWebServerFactoryCustomizerConfiguration {
    
            @Bean
            public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment,
                                                                                   ServerProperties serverProperties) {
                return new NettyWebServerFactoryCustomizer(environment, serverProperties);
            }
    
        }
    
    }
    

    结合上面 spring-boot-starter-web 中引入了 tomcat-embed-core 依赖,可以发现,默认装配的类型即为

    TomcatWebServerFactoryCustomizerConfiguration

  • 相关阅读:
    kafka与Rocketmq的区别【转】
    k8s故障解决干货文档链接
    利用local nginx搭建k8s-1.17.4高可用kubernetes集群
    生产gitlab还原步骤
    jenkins备份和还原
    ASP.NET Core3.1使用IdentityServer4中间件系列随笔(二):创建API项目,配置IdentityServer保护API资源
    使用Feign出现404错误问题
    高并发系统限流-漏桶算法和令牌桶算法
    框架-springmvc源码分析(二)
    框架-springmvc源码分析(一)
  • 原文地址:https://www.cnblogs.com/col-smile/p/13281796.html
Copyright © 2020-2023  润新知