6. Spring Boot的starters(重要)
一般认为,SpringBoot 微框架从两个主要层面影响 Spring 社区的开发者们:
- 基于 Spring 框架的"约定优先于配置(COC)"理念以及最佳实践之路。
- 提供了针对日常企业应用研发各种场景的 spring-boot-starter 自动配置依赖模块,如此多"开箱即用"的依赖模块,使得开发各种场景的 Spring 应用更加快速和高效。
SpringBoot 提供的这些"开箱即用"的依赖模块都约定以 spring-boot-starter- 作为命名的前缀,并且皆位于 org.springframework.boot 包或者命名空间下(虽然 SpringBoot 的官方参考文档中提到不建议大家使用 spring-boot-starter- 来命名自己写的类似的自动配置依赖模块,但实际上,配合不同的 groupId,这不应该是什么问题)。
如果我们访问 http://start.spring.io,并单击图 1 中的"Switch to the full version"链接,就会发现 SpringBoot1.3.2 默认支持和提供了大约 80 多个自动配置依赖模块。
应用日志和spring-boot-starter-logging
Java 的日志系统多种多样,从 java.util 默认提供的日志支持,到 log4j,log4j2,commons logging 等,复杂繁多,所以,应用日志系统的配置就会比较特殊,从而 spring-boot-starter-logging 也比较特殊一些,下面将其作为我们第一个了解的自动配置依赖模块。
假如 maven 依赖中添加了 spring-boot-starter-logging,如以下代码所示:
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-logging </artifactId>
</dependency>
那么,我们的 SpringBoot 应用将自动使用 logback 作为应用日志框架,SpringBoot 启动的时候,由 org.springframework.boot.logging.Logging-Application-Listener 根据情况初始化并使用。
SpringBoot 为我们提供了很多默认的日志配置,所以,只要将 spring-boot-starter-logging 作为依赖加入到当前应用的 classpath,则"开箱即用",不需要做任何多余的配置,但假设我们要对默认 SpringBoot 提供的应用日志设定做调整,则可以通过几种方式进行配置调整:
- 遵循 logback 的约定,在 classpath 中使用自己定制的 logback.xml 配置文件。
- 在文件系统中任何一个位置提供自己的 logback.xml 配置文件,然后通过 logging.config 配置项指向这个配置文件来启用它,比如在 application.properties 中指定如下的配置。
logging.config=/{some.path.you.defined}/any-logfile-name-I-like.log
SpringBoot 默认允许我们通过在配置文件或者命令行等方式使用 logging.file 和 logging.path 来自定义日志文件的名称和存放路径,不过,这只是允许我们在 SpringBoot 框架预先定义的默认日志系统设定的基础上做有限的设置,如果我们希望更灵活的配置,最好通过框架特定的配置方式提供相应的配置文件,然后通过 logging.config 来启用。
如果大家更习惯使用 log4j 或者 log4j2,那么也可以采用类似的方式将它们对应的 spring-boot-starter 依赖模块加到 Maven 依赖中即可:
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-log4j </artifactId>
</dependency>
或者
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-log4j2 </artifactId>
</dependency>
但一定不要将这些完成同一目的的 spring-boot-starter 都加到依赖中。
快速 Web 应用开发与 spring-boot-starter-web
在这个互联网时代,使用 Spring 框架除了开发少数的独立应用,大部分情况下实际上在使用 SpringMVC 开发 web 应用,为了帮我们简化快速搭建并开发一个 Web 项目,SpringBoot 为我们提供了 spring-boot-starter-web 自动配置模块。
只要将 spring-boot-starter-web 加入项目的 maven 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
我们就得到了一个直接可执行的 Web 应用,当运行项目就可以直接启动一个使用了嵌入式 tomcat 服务请求的 Web 应用,只不过,我们还没有提供任何服务 Web 请求的 Controller,所以,访问任何路径都会返回一个 SpringBoot 默认提供的错误页面(一般称其为 whitelabel error page),我们可以在当前项目下新建一个服务根路径 Web 请求的 Controller 实现:
@RestController
public class IndexController {
@RequestMapping("/")
public String index() {
return "hello, there";
}
}
重新运行 mvn spring-boot:run 并访问 http://localhost:8080,错误页面将被我们的 Controller 返回的消息所替代,一个简单的 Web 应用就这样完成了。
但是,简单的背后,其实却有很多"潜规则"(约定),我们只有充分了解了这些"潜规则",才能更好地应用 spring-boot-starter-web。
项目结构层面的约定
项目结构层面与传统打包为 war 的 Java Web 应用的差异在于,静态文件和页面模板的存放位置变了,原来是放在 src/main/webapp 目录下的一系列资源,现在都统一放在 src/main/resources 相应子目录下,比如:
- src/main/resources/static 用于存放各类静态资源,比如 css,js 等。
- src/main/resources/templates 用于存放模板文件,比如 *.vm。
当然,如果还是希望以 war 包的形式,而不是 SpringBoot 推荐使用的独立 jar 包形式发布 Web 应用,也可以继续原来 Java Web 应用的项目结构约定。
SpringMVC 框架层面的约定和定制
spring-boot-starter-web 默认将为我们自动配置如下一些 SpringMVC 必要组件:
- 必要的 ViewResolver,比如 ContentNegotiatingViewResolver 和 Bean-NameViewResolver。
- 将必要的 Converter、GenericConverter 和 Formatter 等 bean 注册到 IoC 容器。
- 添加一系列的 HttpMessageConverter 以便支持对 Web 请求和相应的类型转换。
- 自动配置和注册 MessageCodesResolver。
- 其他。
任何时候,如果我们对默认提供的 SpringMVC 组件设定不满意,都可以在 IoC 容器中注册新的同类型的 bean 定义来替换,或者直接提供一个基于 WebMvcConfigurerAdapter 类型的 bean 定义来定制,甚至直接提供一个标注了 @EnableWebMvc 的 @Configuration 配置类完全接管所有 SpringMVC 的相关配置,自己完全重新配置。
一些特性:
-
静态内容支持。可以添加多种静态内容, 例如HTML、JavaScript、CSS、media等等. 静态资源可以放在以下目录下
- /static(默认情况下)
- /public、
- /resources或/META-INF/。
可以通过修改spring.resources.static-locations 这个属性来改变路径
-
httpMessageConverter。如果您使用的是常规的Spring MVC, 想要获得JSON响应,则需要JSON的HttpMessageConverters bean. Spring Boot自动添加了Jackson的依赖,并配置好了HttpMessageConverters, 我们直接就可以在程序中使用JSON了。
-
JSON 处理. 可以通过更改 spring.jackson.date-format 来更改json日期的格式,譬如:
- spring.jackson.date-format =yyyy-MM-dd HH:mm:ss #日期格式配置
- spring.jackson.time-zone=GMT+8 # 时区配置
-
错误处理. 创建404和500页面,那么Spring Boot会自动使用它.
-
多种模板引擎支持. 只要加入了依赖 springboot-starter-<模板引擎>,那么就可以直接使用这个模板引擎.
更改一些默认配置
- server.port=8081 更改tomcat启动端口号
- server.address=10.0.0.1 服务器启动地址. 特别是希望在某个IP上运行时设定
- SSL相关配置(HTTPS) 后面有章节介绍
- server.port=8443
- server.ssl.key-store=classpath:keystore.jks
- server.ssl.key-store-password=secret
server.ssl.key-password=secret - session 相关配置
- server.servlet.session.store-dir=/tmp //Session 存储路径
- server.servlet.session.persistent=true //Session是否持久化
- server.servlet.session.timeout=15 //Session超时时间
- server.servlet.session.cookie.name=todo-cookie.dat //session的Cookie名字
server.servlet.session.cookie.path=/tmp/cookies //Session的Cookie存储路径 - Jackson配置
- spring.jackson.date-format =yyyy-MM-dd HH:mm:ss #日期格式配置
spring.jackson.time-zone=GMT+8 # 时区配置
spring-boot-starter-jdbc与数据访问(了解)
大部分 Java 应用都需要访问数据库,尤其是服务层,所以,SpringBoot 会为我们自动配置相应的数据访问设施。
若想 SpringBoot 为我们自动配置数据访问的基础设施,那么,我们需要直接或者间接地依赖 spring-jdbc,一旦 spring-jdbc 位于我们 SpringBoot 应用的 classpath,即会触发数据访问相关的自动配置行为,最简单的做法就是把 spring-boot-starter-jdbc 加为应用的依赖。
默认情况下,如果我们没有配置任何 DataSource,那么,SpringBoot 会为我们自动配置一个基于嵌入式数据库的 DataSource,这种自动配置行为其实很适合于测试场景,但对实际的开发帮助不大,基本上我们会自己配置一个 DataSource 实例,或者通过自动配置模块提供的配置参数对 DataSource 实例进行自定义的配置。
假设我们的 SpringBoot 应用只依赖一个数据库,那么,使用 DataSource 自动配置模块提供的配置参数是最方便的:
spring.datasource.url=jdbc:mysql://{database host}:3306/{databaseName}
spring.datasource.username={database username}
spring.datasource.password={database password}
当然,自己配置一个 DataSource 也是可以的,SpringBoot 也会智能地选择我们自己配置的这个 DataSource 实例(只不过必要性真不大)。
除了 DataSource 会自动配置,SpringBoot 还会自动配置相应的 JdbcTemplate、DataSourceTransactionManager 等关联"设施",可谓服务周到,我们只要在使用的地方注入就可以了:
class SomeDao {
@Autowired
JdbcTemplate jdbcTemplate;
public <T> List<T> queryForList(String sql){
// ...
}
// ...
}
不过,spring-boot-starter-jdbc 以及与其相关的自动配置也不总是带来便利,在某些场景下,我们可能会在一个应用中需要依赖和访问多个数据库,这个时候就会出现问题了。
假设我们在 ApplicationContext 中配置了多个 DataSource 实例指向多个数据库:
@Bean
public DataSource dataSource1() throws Throwable {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(...);
dataSource.setUsername(...);
dataSource.setPassword(...);
// TODO other settings if necessary in the future.
return dataSource;
}
@Bean
public DataSource dataSource2() throws Throwable {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(...);
dataSource.setUsername(...);
dataSource.setPassword(...);
// TODO other settings if necessary in the future.
return dataSource;
}
那么,不好意思,启动 SpringBoot 应用的时候会抛出类似如下的异常(Exception):
Exception):No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2
为了避免这种情况的发生,我们需要在 SpringBoot 的启动类上做点儿"手脚":
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
public class UnveilSpringChapter3Application {
public static void main(String[] args) {
SpringApplication.run(UnveilSpringChapter3Application.class, args);
}
}
也就是说,我们需要在这种场景下排除掉对 SpringBoot 默认提供的 DataSource 相关的自动配置。但如果我们还是想要享受 SpringBoot 提供的自动配置 DataSource 的机能,也可以通过为其中一个 DataSource 配置添加 org.springframework.context.annotation.Primary 这个 Annotation 的方式以实现两全其美:
@Bean
@Primary
public DataSource dataSource1() throws Throwable {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(...);
dataSource.setUsername(...);
dataSource.setPassword(...);
// TODO other settings if necessary in the future.
return dataSource;
}
@Bean
public DataSource dataSource2() throws Throwable {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(...);
dataSource.setUsername(...);
dataSource.setPassword(...);
// TODO other settings if necessary in the future.
return dataSource;
}
}
另外,SpringBoot 还提供了很多其他数据访问相关的自动配置模块,比如 spring-boot-starter-data-jpa、spring-boot-starter-data-mongodb 等,大家可以根据自己数据访问的具体场景选择使用这些自动配置模块。
如果选择了 spring-boot-starter-data-jpa 等关系数据库相关的数据访问自动配置模块,并且还需要同时依赖访问多个数据库,那么,也需要相应的在 SpringBoot 启动类中排除掉这些自动配置模块中的 AutoConfiguration 实现类(对应 spring-boot-starter-data-jpa 是 JpaRepositoriesAutoConfiguration),或者标注某个 DataSource 为 @Primary。