一、前言
上篇文章我們已經聊了SpringBoot的啟動過程中的各類擴展點,那么從http://start.spring.io上我們生成的demo項目中,到目前就剩下了maven工程的pom.xml
還沒有進行探索了,那么本文我們就來看看這里面到底都有啥,把大力出奇跡的常見spring-boot-starter來聊一聊,以便更好地使用SpringBoot.
二、SpringBoot項目的pom.xml文件解析
首先,我們還是按照一貫的作風,先上源碼,再解析:
<?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.hafiz</groupId>
<artifactId>springboot-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<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>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
我們可以看到所有的結構都是我們熟悉的。首先映入眼簾的是:<parent></parent>
標簽,熟悉Maven的朋友都知道,這個標簽用來定義要繼承的父pom的信息,它用來定義SpringBoot項目可能用到的依賴和插件聲明以及一些資源文件聲明,這樣我們就可以在自己的SpringBoot項目中用到這些依賴或者插件的時候直接飲用,而不用指定版本號,正如我們上面看到的spring-boot-starter-web
、spring-boot-starter-test
依賴以及spring-boot-maven-plugin
插件一樣,父pom.xml的源碼如下:
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.13.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
<artifactId>spring-boot-starter-parent</artifactId>
<packaging>pom</packaging>
<name>Spring Boot Starter Parent</name>
<description>Parent pom providing dependency and plugin management for applications
built with Maven</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<java.version>1.6</java.version>
<resource.delimiter>@</resource.delimiter> <!-- delimiter that doesn't clash with Spring ${} placeholders -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<!-- Turn on filtering by default for application properties -->
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<excludes>
<exclude>**/application*.yml</exclude>
<exclude>**/application*.yaml</exclude>
<exclude>**/application*.properties</exclude>
</excludes>
</resource>
</resources>
<pluginManagement>
<plugins>
<!-- Apply more sensible defaults for user projects -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>${start-class}</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests.java</include>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<archive>
<manifest>
<mainClass>${start-class}</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>${start-class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<configuration>
<delimiters>
<delimiter>${resource.delimiter}</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
</configuration>
</plugin>
<!-- Support our own plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>${start-class}</mainClass>
</configuration>
</plugin>
<!-- Support shade packaging (if the user does not want to use our plugin) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.13.RELEASE</version>
</dependency>
</dependencies>
<configuration>
<keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${start-class}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
但是我們發現,該父pom文件還有自己的父pom,作用類似,不再詳解,感興趣的可以自己去翻閱源碼。那么這樣看來我們自己的SpringBoot項目中的pom.xml
文件就剩下顯眼的兩個依賴以及一個插件了(沒有指定版本號的原因前面已經解釋了),那我們接下來就來聊一聊這些非常重要的spring-boot-starter依賴。
三、不可或缺的spring-boot-starter
我們從前面就知道了,SpringBoot能夠如此方便便捷,其實都是得益於這些“開箱即用”的依賴模塊,那SpringBoot設計者約定這些“開箱即用”的依賴模塊的命名都以spring-boot-starter-
開始,並且這些模塊都位於org.springframework.boot
包或者命名空間下面。我們也可以模仿者來實現自己的自動配置依賴模塊,也已spring-boot-starter-
開頭,是不是就很"正宗"呢?(雖然SpringBoot官方不建議我們這樣做,以免跟官方提供的混淆,但是其實我們使用自己的groupId,這樣命名應該不是啥問題)。
這些starter其實都有約定好的默認配置,但是它也允許我們調整這些默認配置,以便完成定制化的需求,我們可以改變默認配置的常見方式有以下幾種:
- 命令行參數(Command Line Args)
- 系統環境變量(Environment Variables)
- 位於文件系統中的配置文件
- 位於classpath中的配置文件
- 固化到代碼中的配置項
這幾種方式從上到下優先級從高到低排列,高優先級的配置會覆蓋優先級低的配置。還有就是不管位於文件系統還是classpath中的配置文件,SpringBoot應用默認的文件名稱都是application.properties
,可以放在當前項目的根目錄下或者名稱為config的子目錄下。
SpringBoot其實提供了很多這樣的模塊,我們就挑幾個我們常用的這樣的模塊來解析,其他的大家就舉一反三。以達到在工作和開發中靈活運用這些spring-boot-starter模塊的效果。
1. spring-boot-starter-logging以及應用日志
如果我們在maven依賴中添加了spring-boot-starter-logging
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
那也就意味着我們的SpringBoot應用自動使用logback作為日志框架,在啟動的時候,由org.springframework.boot.logging.LoggingApplicationListener
根據情況初始化並使用。默認情況下,SpringBoot已經給我們提供好了很多默認的日志配置,我們只需要將spring-boot-starter-logging
作為依賴加入到你的SpringBoot應用就可以了,但是如果我們要對這些默認配置進行定制,可以有兩種方式進行:
-
遵守logback的約定,在classpath中使用定制化的logback.xml配置文件。
-
在文件系統中任意一個地方提供自己的logback.xml配置文件,然后通過如下配置來
application.properties
中指定我們日志系統配置文件位置:logging.config=/{your config file location}}/logback.xml
如果我們已經習慣了log4j或log4j2,那我們只需要把spring-boot-starter-logging
換成如下的starter就好。
<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>
2. 用於快速構建web應用的spring-boot-starter-web
現如今,我們在工作中大部分實際用的還是SpringMVC開發的web應用,SpringBoot當然貼心的為我們開發了一個web項目模塊,讓我們更加方便的開發web應用。maven依賴如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
這樣我們就可以得到一個可以直接執行的Web應用,然后我們運行mvn spring-boot:run
,就能直接啟動一個基於嵌入式tomcat容器的Web應用了,然后就可以像這篇文章中定義controller來供用戶訪問了。但是呢,這簡單的表象之下,其實卻隱藏着很多約定,我們要把這些潛規則了解清楚才能更好地應用spring-boot-starter-web
。
2.1 項目結構的“潛規則”
傳統的Java Web項目中,我們的靜態文件以及頁面模板都是放在src/main/webapp
目錄下,但是在SpringBoot應用中,這些文件被統一放在src/main/resources
相應的子目錄下:
src/main/resources/static
目錄用於存放各種靜態資源,如:js、css、image等。src/main/resources/template
目錄用於存放模板文件。
細心地我們會發現SpringBoot的web應用已經變成了jar包而再是war包,如果我們還是希望以war包的形式發布也是可以的。
2.2 SpringMVC框架層面的約定及定制
spring-boot-starter-web
默認將為我們自動配置如下一些SpringMVC必要的組件:
- ViewResolver,如:
ContentNegotiatingViewResolver
和BeanNameViewResolver
。 - Converter,如:
GenericConverter
和Formatter
等bean被注冊到IoC容器。 - 默認添加一系列
HttpMessageConverter
用於支持對Web請求和相應的類型轉換。 - 自動配置和注冊
MessageCodesResolver
。 - 其他必要組件…
2.3 嵌入式Web容器的約定和定制
我們知道spring-boot-starter-web
默認把嵌入式tomcat作為web容器來對外提供HTTP服務,默認使用8080端口對外監聽和提供服務。這里我們可能會有兩個疑問:
-
我們不想使用默認的嵌入式tomcat容器怎么辦?
很簡單,我們只需要引入
spring-boot-starter-jetty
或spring-boot-starter-undertow
依賴就能替代默認嵌入式tomcat容器了。 -
我們想要把啟動后提供服務的端口改掉怎么辦?
我們可以通過在配置文件中修改啟動端口就可以了,如:
server.port=9000
其實,spring-boot-starter-web
提供了很多以server.
作為前綴的配置以用來修改嵌入式容器的配置,如:
server.port
server.address
server.ssl.*
server.tomcat.*
那若這些還滿足不了你,SpringBoot甚至都允許我們直接對嵌入式Web容器實例進行定制化,我們通過向IoC容器中注冊一個EmbeddedServletContainerCustomizer
類型的組件來實現:
package com.hafiz.springbootdemo;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
/**
* @author hafiz.zhang
* @description: 自定義內嵌容器配置
* @date Created in 2018/6/10 12:09.
*/
public class DemoEmbeddedTomcatCustomizer implements EmbeddedServletContainerCustomizer {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(9111);
container.setContextPath("/demo");
// ...
}
}
如果還要再深入的定制,那就需要實現對應內嵌容器的Factory並注冊到IoC容器:
- TomcatEmbeddedServletContainerFactory
- JettyEmbeddedServletContainerFactory
- UndertowEmbeddedServletContainerFactory
但是,我們幾乎沒有可能需要這樣的定制化,也不建議這樣的定制化,使用SpringBoot默認的spring-boot-starter-web
提供的配置項列表已經很簡單、很完整了。
3. 用於數據訪問的spring-boot-starter-jdbc
我們知道,現實中大多數的Java應用都需要訪問數據庫,那SpringBoot肯定不會放過這個組件,它會很貼心的為我們自動配置好相應的數據訪問工具。我們只需要在pom.xml
中添加以下依賴就好了:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
這樣,在我們沒有配置任何DataSource的情況下,SpringBoot會默認為我們自動配置一個基於嵌入式數據的DataSource,這種自動配置適合於測試場景,生產環境不適合。大多數情況下,我們都會自己配置DataSource實例,或通過自動配置模塊提供的配置參數對DataSource實例配置自定義的參數。
若我們的SpringBoot應用只依賴一個數據庫,那我們直接使用自動配置模塊提供的配置參數最方便快捷:
spring.datasource.url=jdbc:mysql://{db host}:{db port}/{db name}
spring.datasource.username={db user name}
spring.datasource.password={db password}
有的小伙伴說了:那我自己配置一個DataSource行不行?答案是當然可以,SpringBoot會很智能的優先選擇使用我們自己配置的這個DataSource,但是感覺多此一舉!你要知道,SpringBoot除了自動幫我們配置DataSource以外,還自動幫我們配置了相應的JdbcTemplate
以及DataSourceTransactionManager
等相關的組件,我們只需要在需要使用的地方直接使用@Autowired
注解引用就好了。
那SpringBoot是不是一直貼心呢?很明顯不是的,如果我們的單個項目需要依賴和訪問多個數據庫,這個時候就不行了,就算是我們在ApplicationContext中配置了多個DataSource實例來訪問多個數據庫:
@Bean
public DataSource dataSource1() throws Throwable {
DruidDataSource ds = new DruidDataSource();
ds.setUrl(...);
ds.setUsername(...);
ds.setPassword(...);
// set other db setting
return ds;
}
@Bean
public DataSource dataSource2() throws Throwable {
DruidDataSource ds = new DruidDataSource();
ds.setUrl(...);
ds.setUsername(...);
ds.setPassword(...);
// set other db setting
return ds;
}
啟動項目時,你就會發現如下的異常:
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 DemoSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(DemoSpringBootApplication.class, args);
}
}這也就是說我們需要排除掉SpringBoot默認的DataSource的相關的自動配置。
-
使用
@primary
注解那我們既要配置兩個數據源,又要使用SpringBoot默認的DataSource,這時我們就可以為我們配置的兩個DataSource中的任意一個使用
@primary
注解就可以了。@Bean
@Primary
public DataSource dataSource1() throws Throwable {
DruidDataSource ds = new DruidDataSource();
ds.setUrl(...);
ds.setUsername(...);
ds.setPassword(...);
// set other db setting
return ds;
}
@Bean
public DataSource dataSource2() throws Throwable {
DruidDataSource ds = new DruidDataSource();
ds.setUrl(...);
ds.setUsername(...);
ds.setPassword(...);
// set other db setting
return ds;
}除此之外,SpringBoot還提供了很多其他數據源訪問相關的自動配置模塊,如:
spring-boot-starter-jpa
、spring-boot-starter-mongodb
等。
四、總結
除了本文我們介紹的常用的三個spring-boot-starter以外,SpringBoot還提供了很多別的starter,包括spring-boot-starter-aop
、spring-boot-starter-security
、spring-boot-starter-actuator
等等。我們通過本文舉一反三,可以做到用的時候得心應手。棒!給自己一個贊~