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-web
和spring-boot-starter-actuator
两个starter依赖。如图:
我们可以看到生成项目的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
路径下面,如图:
查看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\classes
和BOOT-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开发。