文章目录
1. SpringBoot 简介
1.1 SpringBoot 的概念
SpringBoot 被称为搭建程序的 “脚手架”。其最主要作用就是帮我们快速的构建庞大的 Spring 项目,并且尽可能的减少一切 xml 配置,让我们关注于业务而非配置文件。
1.2 SpringBoot 的作用
SpringBoot 主要解决了以下两点问题:
-
复杂的配置
项目各种配置其实是开发时的损耗,因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以写配置挤占了写应用程序逻辑的时间。
-
混乱的依赖管理
项目的依赖管理也是件吃力不讨好的事情。决定项目里要用哪些库就已经够让人头痛的了,你还要知道这些库的哪个版本和其他库不会有冲突,这也是件棘手的问题。并且依赖管理也是一种损耗,添加依赖不是写应用程序代码。一旦选错了依赖的版本,随之而来的不兼容问题毫无疑问会是生产力杀手。
1.3 SpringBoot 的特点
Spring Boot 主要特点有:
- 创建独立的 Spring 应用程序
- 直接内嵌 tomcat、jetty 和 undertow(不需要打包成 war 包部署)
- 提供了固定化的 “starter” 配置,以简化构建配置
- 尽可能的自动配置 Spring 和第三方库
- 提供产品级的功能,如:安全指标、运行状况监测和外部化配置等
- 绝对不会生成代码,并且不需要 XML 配置
2. SpringBoot 快速入门
2.1 创建工程
- 打开 IDEA --> Create New Project --> Empty Project --> 填写项目名 --> Finish
- New Module --> Maven --> Next --> 填写项目信息 --> Finish
2.2 引入依赖
-
SpringBoot 提供了一个名为 spring-boot-starter-parent 的工程,里面已经对各种常用依赖(并非全部)的版本进行了管理,我们的项目需要以这个项目为父工程,这样我们就不用操心依赖的版本问题了
-
为了让 SpringBoot 帮我们完成各种自动配置,我们必须引入 SpringBoot 提供的自动配置依赖,我们称为 ”启动器“。spring-boot-starter-parent 工程将依赖关系声明为一个或者多个启动器,我们可以根据项目需求引入相应的启动器。因为我们是 web 项目,这里我们引入 web 启动器
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zt</groupId>
<artifactId>springboot-demo1</artifactId>
<version>1.0-SNAPSHOT</version>
<!--所有的 SpringBoot 应用都要以该工程为父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<!-- 启动器:每个启动器都有背后都有很多依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
2.3 编写 Controller
@RestController
@RequestMapping("/hello")
@EnableAutoConfiguration
public class HelloController {
@RequestMapping("/show")
public String show() {
return "Hello SpringBoot";
}
public static void main(String[] args) {
SpringApplication.run(HelloController.class,args);
}
}
2.4 启动测试
-
点击运行 main 方法
-
打开浏览器访问以下地址
http://localhost:8080/hello/show
2.5 注解详解
-
@EnableAutoConfiguration
开启 Spring 应用程序的自动配置,SpringBoot 基于你所添加的依赖和你自己定义的 Bean,试图去猜测并配置你想要的配置。
比如:我们引入了
spring-boot-starter-web
,而这个启动器中帮我们添加了tomcat
、SpringMVC
的依赖。此时自动配置就知道你是要开发一个 web 应用,所以就帮你完成了 web 及 SpringMVC 的默认配置了 -
@RestController
相当于 @Controller + @ResponseBody 两个注解的作用。返回 JSON 数据时,不需要在方法前面加 @ResponseBody 注解了。但使用 @RestController 这个注解,视图解析器就不起作用了,返回的内容就是 return 里的内容。
比如:return “success”,本来应该到 success.jsp 页面的,则其显示 success
2.6 优化入门程序
-
提出问题
如果有多个 Controller 该怎么做呢?
-
分析问题
如果在每一个 Controller 中都添加一个 main 方法和 @EnableAutoConfiguration 注解,那就无法同时启动多个 Controller 了,因为每个 main 方法都监听 8080 端口。所以,一个 SpringBoot 程序应该只有一个 main 方法
-
解决问题
通常情况下,我们都会在一个 SpringBoot 工程中的基包下创建一个全局的引导类,一些 SpringBoot 的全局注解(@EnableAutoConfiguration)以及程序的入口 main 方法都放在该类中。
-
实现
-
在 SpringBoot 的程序的基包下(引导类和 Controller 包在同级目录下),创建 TestApplication
/** * 引导类:SpringBoot 程序的入口 */ @EnableAutoConfiguration // 开启 SpringBoot 自动配置 @ComponentScan // 指定要扫描的包。如果没有指定,那么将从声明这个注解的类所在的包开始,扫描该包及其子包 public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
-
创建两个 Controller
@RestController @RequestMapping("/hello") public class HelloController { @RequestMapping("/show") public String show() { return "Hello SpringBoot"; } }
@RestController @RequestMapping("/hello2") public class Hello2Controller { @RequestMapping("/show") public String show() { return "Hello SpringBoot 2"; } }
-
启动项目测试
-
点击运行 main 方法
-
打开浏览器访问以下地址
http://localhost:8080/hello/show
http://localhost:8080/hello2/show
-
-
2.7 进一步优化入门程序
我们现在的引导类中使用了 @EnableAutoConfiguration 和 @ComponentScan 注解,还是有点麻烦。SpringBoot 提供了一个简单的注解:@SpringBootApplication
/**
* 引导类:SpringBoot 程序的入口
*/
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
@SpringBootApplication 是一个组合注解,其中重点的注解有 3 个:
- @SpringBootConfiguration:声明当前类是 SpringBoot 应用的配置类,项目中只能有一个
- @EnableAutoConfiguration:开启自动配置
- @ComponentScan:开启注解扫描
3. SpringBoot 配置 Bean
3.1 回顾 Spring 历史
事实上,在 Spring 3.0 开始,Spring 官方就已经开始推荐使用 Java 配置来代替传统的 xml 配置了,我们不妨来回顾一下 Spring 的历史:
-
Spring 1.0 时代
在此时因为 jdk1.5 刚刚出来,注解开发并未盛行,因此一切 Spring 配置都是 xml 格式,想象一下所有的 bean 都用 xml 配置,细思极恐啊,心疼那个时候的程序员2秒
-
Spring 2.0 时代
Spring 引入了注解开发,但是因为并不完善,因此并未完全替代 xml,此时的程序员往往是把 xml 与注解进行结合,貌似我们之前都是这种方式。
-
Spring 3.0 及以后
3.0 以后 Spring 的注解已经非常完善了,因此 Spring 推荐大家使用完全的 Java 配置来代替以前的 xml,不过似乎在国内并未推广盛行。然后当 SpringBoot 来临,人们才慢慢认识到 Java 配置的优雅。
3.2 Java 配置 Bean
Java 配置主要靠 Java 类和一些注解来达到和 xml 配置一样的效果,比较常用的注解有:
- @Configuration:声明一个类作为配置类,代替 xml 文件
- @Bean:声明在方法上,将方法的返回值加入 bean 容器,代替 bean 标签
- @Value:属性注入
- @PropertySource:指定外部属性文件。
我们接下来用 Java 配置来尝试实现连接池配置:
-
引入依赖
<dependency> <groupId>com.github.drtrang</groupId> <artifactId>druid-spring-boot2-starter</artifactId> <version>1.1.10</version> </dependency>
-
添加 jdbc.properties 到 resources 中
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/test jdbc.username=root jdbc.password=123456
-
配置数据源
@Configuration @PropertySource("jdbc.properties") public class JdbcConfiguration { @Value("${jdbc.driverClassName}") private String driverClassName; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource() { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driverClassName); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); return druidDataSource; } }
-
自动注入数据
@RestController @RequestMapping("/hello2") public class Hello2Controller { @Autowired private DataSource dataSource; @RequestMapping("/show") public String show() { return "Hello SpringBoot 2"; } }
-
测试
-
在 Hello2Controller 的 show 方法上打一个断点,然后 debug 运行 main 方法
-
打开浏览器访问以下地址
http://localhost:8080/hello2/show
-
查看 dataSource 属性注入成功
-
3.3 SpringBoot 的属性注入
在上面的案例中,我们实现了 Java 配置 Bean 的方式。但我们是使用 @Value 注入属性的,如果这时我们又有一个类要注入同样的属性,那我们又要写一遍 @Value,这显然会造成冗余代码。我们应该在一个属性读取类中读取属性,这样在任何类中都可以使用这些属性了。
3.3.1 属性读取类
新建一个属性读取类:
@ConfigurationProperties(prefix = "jdbc")
public class JdbcProperties {
private String driverClassName;
private String url;
private String username;
private String password;
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
注意:
- @ConfigurationProperties:声明当前类为属性读取类
- prefix = “jdbc”:读取配置文件中指定前缀为 jdbc 的值
- 在类中定义的属性名称也必须与配置文件中 jdbc 后面部分一致,并且必须具有 getter 和 setter 方法
- 这里我们并没有指定属性文件的地址,SpringBoot 默认会读取文件名为 application.properties 的资源文件,所以我们把 jdbc.properties 名称改为 application.properties,通常 SpringBoot 项目也只有 application.properties 这一个配置文件
3.3.2 四种属性注入
接下来要在 JdbcConfiguration 类注入 JdbcProperties 属性,可以通过以下四种方式:
-
@Autowired 注入(最常用)
@Configuration @EnableConfigurationProperties(JdbcProperties.class) public class JdbcConfiguration { @Autowired private JdbcProperties jdbcProperties; @Bean public DataSource dataSource() { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(jdbcProperties.getDriverClassName()); druidDataSource.setUrl(jdbcProperties.getUrl()); druidDataSource.setUsername(jdbcProperties.getUsername()); druidDataSource.setPassword(jdbcProperties.getPassword()); return druidDataSource; } }
-
构造函数注入
@Configuration @EnableConfigurationProperties(JdbcProperties.class) public class JdbcConfiguration { private JdbcProperties jdbcProperties; public JdbcConfiguration(JdbcProperties jdbcProperties) { this.jdbcProperties = jdbcProperties; } @Bean public DataSource dataSource() { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(jdbcProperties.getDriverClassName()); druidDataSource.setUrl(jdbcProperties.getUrl()); druidDataSource.setUsername(jdbcProperties.getUsername()); druidDataSource.setPassword(jdbcProperties.getPassword()); return druidDataSource; } }
-
通过 Bean 方法的形参注入
@Configuration @EnableConfigurationProperties(JdbcProperties.class) public class JdbcConfiguration { @Bean public DataSource dataSource(JdbcProperties jdbcProperties) { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(jdbcProperties.getDriverClassName()); druidDataSource.setUrl(jdbcProperties.getUrl()); druidDataSource.setUsername(jdbcProperties.getUsername()); druidDataSource.setPassword(jdbcProperties.getPassword()); return druidDataSource; } }
-
直接在方法上使用 @ConfigurationProperties(prefix = “jdbc”),SpringBoot 就会自动调用这个 Bean 的 set 方法,然后完成注入,但前提是该类必须有对应属性的 set 方法,这种方式显然有一定的局限性
@Configuration public class JdbcConfiguration { @ConfigurationProperties(prefix = "jdbc") @Bean public DataSource dataSource() { DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } }
注意:
- @EnableConfigurationProperties 注解的作用是:让使用 @ConfigurationProperties 注解的类生效。
- 如果一个配置类只配置 @ConfigurationProperties 注解,而没有使用 @Component,那么在 IOC 容器中是获取不到这个 Bean 的,@EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入。
3.3.3 测试
-
在 Hello2Controller 的 show 方法上打一个断点,然后 debug 运行 main 方法
-
打开浏览器访问以下地址
http://localhost:8080/hello2/show
-
查看 dataSource 属性注入成功
4. SpringBoot 默认配置原理
通过刚才的学习,我们知道 @EnableAutoConfiguration 会开启 SpringBoot 的自动配置,并且根据你引入的依赖来生效对应的默认配置。那么问题来了:
-
这些默认配置是怎么配置的,在哪里配置的呢?
在我们的项目中,已经引入了一个依赖:spring-boot-autoconfigure,其中定义了大量自动配置类
-
为何引入依赖就会触发配置呢?
引入相关依赖后,就会满足默认配置上的一些条件注释,于是就会触发配置,如 WebMvcAutoConfiguration 上就有条件注释,如下:
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
-
这些默认配置的属性来自哪里呢?
在 WebMvcAutoConfiguration 类中通过 @EnableConfigurationProperties 引入了两个属性读取类,如下:
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
接下来我们看看 WebMvcProperties
@ConfigurationProperties( prefix = "spring.mvc" ) public class WebMvcProperties { private Format messageCodesResolverFormat; private Locale locale; private WebMvcProperties.LocaleResolver localeResolver; private String dateFormat; private boolean dispatchTraceRequest; private boolean dispatchOptionsRequest; private boolean ignoreDefaultModelOnRedirect; private boolean publishRequestHandledEvents; private boolean throwExceptionIfNoHandlerFound; private boolean logResolvedException; private String staticPathPattern; private final WebMvcProperties.Async async; private final WebMvcProperties.Servlet servlet; private final WebMvcProperties.View view; private final WebMvcProperties.Contentnegotiation contentnegotiation; private final WebMvcProperties.Pathmatch pathmatch; public WebMvcProperties() { this.localeResolver = WebMvcProperties.LocaleResolver.ACCEPT_HEADER; this.dispatchTraceRequest = false; this.dispatchOptionsRequest = true; this.ignoreDefaultModelOnRedirect = true; this.publishRequestHandledEvents = true; this.throwExceptionIfNoHandlerFound = false; this.logResolvedException = false; this.staticPathPattern = "/**"; this.async = new WebMvcProperties.Async(); this.servlet = new WebMvcProperties.Servlet(); this.view = new WebMvcProperties.View(); this.contentnegotiation = new WebMvcProperties.Contentnegotiation(); this.pathmatch = new WebMvcProperties.Pathmatch(); } ... }
WebMvcProperties 中已经配置好了默认的属性,如果我们要覆盖这些默认属性,只需要在 application.properties 中定义与其前缀 prefix 和字段名一致的属性即可
5. 总结
前面说到了 SpringBoot 主要解决了以下两点问题,现在再来看看 SpringBoot 是怎样解决两点问题的:
-
复杂的配置
SpringBoot 采用默认配置,我们也可以在 application.properties 文件来覆盖这些默认属性,这样虽然使用的还是默认配置,但是配置中的值改成了我们自定义的。
-
混乱的依赖管理
SpringBoot 提供了 stater(启动器),引入后就会自动管理依赖及版本了。