Spring Boot基本知识介绍

一、Spring Boot简化了Spring应用的开发

  Spring 是Java企业应用(Java EE) 开发的事实标准,整合了Java EE应用涉及到的各类中间件调用的组件,提供了MVC框架,能够整合其他ORM持久层框架。在没有Spring Boot之前,为了实现一个基本的Spring MVC应用,需要编写一系列基本的配置代码,即Java Config的java代码(基于xml配置现在已经不再推荐使用)+配置文件properties。比如仅仅让Spring MVC生效,我们就需要人工编写一个类似如下的类,启动一个ApplicationContext,加载由@Configuration@ComponentScan实现的Java配置类;让DispatcherServlet生效,成为Controller层的分发入口。

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        //AppConfig是开发者编写的能够扫描到所有Bean的配置类
        //通常带注解@Configuration和@ComponentScan
        context.register(AppConfig.class);

        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

  在Spring Boot未出现之前,通常还需要将应用打包成war包,部署到tomcat等类似的java应用服务容器。基于Mysql等关系型数据库开发的场景则需要编写代码创建一个DataSource Bean,对于使用到事务管理的,我们还需要配置一个TransactionManager Bean,并且要保证Spring的aop机制生效。
  Spring Boot的出现极大程度简化了Spring应用的开发过程,采用“约定由于配置”的原则,Spring Boot能够基于环境变量配置和组件引入依赖的jar,合理推断需要配置的组件,自动创建所需的Spring Bean并将它们组装在一起。使得应用需要很少的配置,达到快速搭建启动项目的效果,提升了开发效率和质量。比如当我们引入了spring-boot-starter-web这一依赖,Spring boot探测到满足启动Spring MVC的条件,就自动创建Spring MVC运行环境。以Spring Boot2.7为例,我们可以看到
在类org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,创建了所需的DispatcherServlet。

    ... ...
    @Conditional({DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition.class})
    @ConditionalOnClass({ServletRegistration.class})
    @EnableConfigurationProperties({WebMvcProperties.class})
    protected static class DispatcherServletConfiguration {
        ... ...
        @Bean(name = {"dispatcherServlet"} )
        public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
            return dispatcherServlet;
        }
       ... ...

二、Spring Boot应用的一些细节分析

  为了方便后续的分析我们先在https://start.spring.io/ 创建一个demo来进行相关分析,为了能让应用快速跑起来,我们只选择了web和actuator两个模块,也就是引入了spring-boot-starter-webspring-boot-starter-actuator两个starter依赖。如图:
image
  我们可以看到生成项目的pom.xml指定了spring-boot-starter-parent作为父pom。

 ... ...
     <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.7</version>
            <relativePath/> <!-- lookup parent from repository -->
    </parent>
            ... ...
             ... ...
    <build>
        <plugins>
            <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

  我们简单分析下spring-boot-starter-parent的定义,发现该pom为我们指定了经过Spring官方验证过的兼容性比较好或者推荐使用的各类工具和框架的jar版本,在IDEA查看上述定义可以看到spring-boot-starter-parent所定义的依赖,其实是在spring-boot-starter-parent的parentspring-boot-dependencies中定义了所管理的jar,具体我们可以看到的<denpendencyManagement>。如:

    <dependencyManagement>
    <dependencies>
      ... ....
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>${commons-lang3.version}</version>
      </dependency>
   ... ....
     <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>${kafka.version}</version>
      </dependency>
      ... ....
     </dependencies>
    </dependencyManagement>

  这个机制能够保证引入三方库的兼容性,同时开发人员也不用为定义寻找合适版本的库苦恼。spring-boot-starter-parent同时也指定了编译文件的文本编码格式和Java编译级别等。

  <properties>
    <java.version>1.8</java.version>
    <resource.delimiter>@</resource.delimiter>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  </properties>

  应用开发者不再需要去设置自己pom中的编码格式和编译级别,减少重复工作和出错的可能。这些都是很好工程的实践,基于Spring Boot开发我们可以方便的享受到这些便捷。 在公司开发项目过程中,一些基础架构团队维护公司内部的sdk和开发规范时,可以定制内部统一的parent,并且也可以基于自己选择的Spring Boot版本,在spring-boot-starter-parent基础上,定制项目内部的父pom。
  值得注意的是项目生成的pom.xml<build>节点下面的spring-boot-maven-plugin这一打包插件,这个插件会将应用所需的依赖进行打包,将应用打包成一个fat jar,该jar可以独立运行和部署。使用解压软件对最终target生成的jar打开查看,可以看到依赖的jar被打包至Jar的BOOT-INF\lib路径下面,如图:
image
  查看jar的MANIFEST.MF文件(demo-0.0.1-SNAPSHOT.jar\META-INF\MANIFEST.MF)

Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: demo
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.example.demo.DemoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.7.7
Created-By: Maven JAR Plugin 3.2.2
Main-Class: org.springframework.boot.loader.JarLauncher

  可以看到Jar的入口函数,发现并不是我们代码写的main函数,而是org.springframework.boot.loader.JarLauncher中的main函数,这个类实现了将打包的fat jar中的BOOT-INF\classesBOOT-INF\lib下的jar设置成classpath,并且找到我们应用本身的main函数从而驱动整个应用。
  为了实现快速启动Spring的功能,Spring Boot背后帮我们创建很多Bean。在Spring的世界里,是基于Spring Bean实现组件业务层面或者扩展非业务上的其他功能(如日志,trace,监控等)。因此除了Spring内部如IOC,Aop等底层功能,我们看到应用相关功能的实现,我们几乎可以确定有对应的Spring Bean创建来实现相关的功能。比如:我们对于使用数据库事务管理时,需要有一个类似org.springframework.jdbc.datasource.DataSourceTransactionManager的Bean,从而由Spring事务管理框架进行事务开始提交回滚;在比如我们使用@Value进行属性注入时,处理@Value这一功能的则是类型为org.springframework.context.support.PropertySourcesPlaceholderConfigurer的Bean。
  在Spring Boot中,我们发现可使用极少的配置,然而却提供了很多的功能,如提供了默认的404页面。这些功能的启用是Spring Boot默认给我们创建了很多的Spring Bean。我们基于引入的Spring Acutator的Beans端点进行验证。由于acutator默认将此端点进行关闭,需要application.properties加入以下配置:

management.endpoints.web.exposure.include=beans。

  之后我们在浏览器中打开: http://localhost:8080/actuator/beans
这个接口可以返回我们应用中所有Spring bean的相关信息,可以看到,Spring Boot为我们实现默认404页面的功能是由org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController的bean提供,同时自动帮我们生成了解析@Value注解的org.springframework.context.support.PropertySourcesPlaceholderConfigurer的bean。

{
  "contexts": {
    "application": {
      "beans": {
          ... ...
          "basicErrorController": {
          "aliases": [],
          "scope": "singleton",
          "type": "org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController",
          "resource": "class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.class]",
          "dependencies": [
            "org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration",
            "errorAttributes"
          ]
        },
          ... ...
        "propertySourcesPlaceholderConfigurer": {
          "aliases": [],
          "scope": "singleton",
          "type": "org.springframework.context.support.PropertySourcesPlaceholderConfigurer",
          "resource": "class path resource [org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class]",
          "dependencies": []
        },
          ... ...
        }
      },
      "parentId": null
    }
  }
}

  当Spring Boot提供默认的配置实现不满足我们的预期时,我们可以通过环境变量或者配置文件进行定制,
比如我们想修改Web服务的默认端口,可以在application.properties加入配置:server.port=8081,改变默认端口8080。我们想改变日志的输出级别从info改成debug,可以配置logging.level.root=DEBUG。Spring Boot提供了大量的配置,让开发人员便捷的进行功能定制,这些配置用到时,需要细心的查看官方文档,比如Spring Boot 2.7的官方文档:https://docs.spring.io/spring-boot/docs/2.7.7/reference/htmlsingle/#appendix.application-properties
  另外我们可以通过编写我们自己的Spring Bean,控制Spring Boot不进行创建默认的bean进行,比如Spring Boot自动为我们创建的jackson的ObjectMapper bean.
在类:org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration中,
从ConditionalOnMissingBean注解上看出 如果我们定义了ObjectMapper bean,spring Boot则不会帮我们自动生成的ObjectMapper bean

... ...
      @Configuration(proxyBeanMethods = false)
      @ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
      static class JacksonObjectMapperConfiguration {

            @Bean
            @Primary
            @ConditionalOnMissingBean
            ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
            return builder.createXmlMapper(false).build();
            }

      }
... ...

  此时我们定义下我们自己的ObjectMapper,bean name为方法名myObjectMapper


@SpringBootApplication
public class DemoApplication {

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


	@Bean
	public ObjectMapper myObjectMapper(){
		ObjectMapper objectMapper=	new ObjectMapper();
		//反序列化时,遇到未知属性会不会报错
		objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		//修改序列化后日期格式
		objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
		return objectMapper;
	}
}

  此时我们可以再查看 http://localhost:8080/actuator/beans,可以发现原有的jacksonObjectMapper没有了,取而代之的使我们创建的myObjectMapper的bean.

{
  "contexts": {
    "application": {
      "beans": {
          ... ...
              "myObjectMapper": {
          "aliases": [],
          "scope": "singleton",
          "type": "com.fasterxml.jackson.databind.ObjectMapper",
          "resource": "com.example.demo.DemoApplication",
          "dependencies": [
            "demoApplication"
          ]
        }
          ... ...
        }
      },
      "parentId": null
    }
  }
}

  Spring Boot从工程实践、应用部署和自动化配置都背后实现了很多的细节,从而简化了Spring应用的开发。基于一个很小的demo应用,我们可以简单分析Spring Boot内部的一些机制,从而更好的掌握Spring Boot开发。

posted @ 2023-01-11 01:11  NotOnlyAnAnswer  阅读(69)  评论(0编辑  收藏  举报