【引言】
最近一段時間,公司招人,面試了不少人,主要是需要做過Spring Boot項目,所以,利用這個機會,結合一些面試題,做了一些整理。
【問題整理】
一. 什麼是Spring Boot?
SpringBoot 是 Spring 開源組織下的子項目,是 Spring 組件一站式解決方案,主要是簡化了使用 Spring 的難度,簡省了繁重的配置,提供了各種啓動器,開發者能快速上手。
二. Spring Boot 有什麼優勢?
1.Spring Boot 讓開發變得更簡單
Spring Boot 對開發效率的提升是全方位的,下面做一個簡單的對比:
在沒有Spring Boot之前,我們開發一個web項目需要做的一些工作:
- 配置web.xml,加載spring 和 spring mvc
- 配置數據庫連接,配置spring 事務
- 配置加載配置文件的讀取,開啓註解
- 配置日誌文件
… - 配置完成之後部署tomcat調試
可能還需要考慮各個版本的兼容性,jar 包衝突的可行性。
那麼如果使用Spring Boot之後,我們開發一個web項目只需要以下操作(以IDEA開發工具爲例):
- 直接新建項目,如下:
下一步,選擇需要的依賴:
最後finish,就可以直接進行開發工作。
2.Spring Boot 讓測試變得更簡單
Spring Boot 內置了7種強大的測試框架:
- Junit: 一個java語言的單元測試框架
- Spring Boot Test: 爲Spring Boot應用提供集成測試和工具支持
- AssertJ: 支持流式斷言的java測試框架
- Hamcrest: 一個匹配器庫
- Mockito: 一個java mock框架
- JSONassert: 一個針對JSON的斷言庫
- JsonPath: Json XPath庫
我們只需要在項目中引入spring-boot-starter-test,就可以對數據庫、Mock、Web等各種情況進行測試。
3.Spring Boot 讓配置變得更簡單
Spring Boot的核心思想:約定優於配置,即按約定編程,是一種軟件設計範式,旨在減少軟件開發人員需要做決定的數量,獲得簡單的好處,而又不失靈活性。
本質是說,開發人員僅需要規定應用中不符約定的部分。
4.Spring Boot 讓部署變得更簡單
Spring Boot項目不需要像傳統web項目那樣,需要配置tomcat,內嵌tomcat、jetty容器使得開發人員不需要關心容器的環境問題。
Jenkins是目前持續構建領域使用最廣泛的工具之一,使用Jenkins部署Spring Boot項目也非常簡單,只需要前期做一些簡單的配置,當我們需要發佈項目時只需要點擊項目對應的發佈按鈕,就可以將項目從版本庫中拉取、打包、發佈到目標服務器中,大大簡化了運維後期的維護工作。
我們也可以利用容器化技術,將Spring Boot項目做成鏡像,根據容器集羣的策略實現彈性擴容、動態部署等。所以Spring Boot + Docker + Jenkins會將Spring Boot項目的部署做的更簡單化、智能化。
5.Spring Boot 讓監控變得更簡單
Spring Boot自帶監控組件,即Spring Boot Actuator,通過它,我們可以查看應用配置的詳細信息,例如自動化配置信息、創建的Spring beans以及一些環境屬性等。
但它只能監控一個Spring Boot應用的缺點不能滿足監控多服務的實際情況,所以,基於它,又產生了一個更強大的監控開源軟件,Spring Boot Admin。每個Spring Boot應用都認爲是一個客戶端,通過HTTP或者使用Eureka註冊到admin server中進行展示。
Spring Boot Admin 向我們提供了所有被監控Spring Boot項目的基本信息,詳細的Health信息、內存信息、JVM信息、垃圾回收信息,各種配置信息(比如數據源、緩存列表)等,還可以直接修改logger的level.
三. Spring Boot 核心配置有哪幾個,有什麼區別?
Spring Boot 中有以下兩種配置文件:
1. bootstrap (.yml 或者 .properties)
2. application (.yml 或者 .properties)
(yml和properties兩種格式,主要是書寫上格式不同,另外,yml不知@PropertySource 註解導入配置。)
區別:
在 Spring Boot 中有兩種上下文,一種是 bootstrap,另外一種是 application。
bootstrap 是應用程序的父上下文,也就是說 bootstrap 加載優先於 applicaton。
bootstrap 主要用於從額外的資源來加載配置信息,還可以在本地外部配置文件中解密屬性。
這兩個上下文共用一個環境,它是任何Spring應用程序的外部屬性的來源。bootstrap 裏面的屬性會優先加載,它們默認也不能被本地相同配置覆蓋。
BootStarp的應用場景:
- 使用 Spring Cloud Config 配置中心時,這時需要在 bootstrap 配置文件中添加連接到配置中心的配置屬性來加載外部配置中心的配置信息;
- 一些固定的不能被覆蓋的屬性
- 一些加密/解密的場景
四. Spring Boot 的核心註解是哪個?它主要由哪幾個註解組成的?
啓動類上面的註解是**@SpringBootApplication**,它也是 Spring Boot 的核心註解,主要組合包含了以下 3 個註解:
-
@SpringBootConfiguration:組合了 @Configuration 註解,實現配置文件的功能。
-
@EnableAutoConfiguration:打開自動配置的功能,也可以關閉某個自動配置的選項,如關閉數據源自動配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
-
@ComponentScan:Spring組件掃描。
五. 運行 Spring Boot 有哪幾種方式?
- 打包用命令或者放到容器中運行
- 用 Maven/Gradle 插件運行
- 直接執行 main 方法運行
六. Spring Boot 自動配置原理?
@EnableAutoConfiguration實現了Spring Boot應用的自動裝配。在@EnableAutoConfiguration註解上,@Import({AutoConfigurationImportSelector.class}) 註解會引入AutoConfigurationImportSelector類,在AutoConfigurationImportSelector類中,完成了自動裝配的功能。
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
getCandidateConfigurations(annotationMetadata, attributes)方法爲獲取自動裝配的候選類集合,而此方法實現如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
其中主要方法是SpringFactoriesLoader.loadFactoryNames,其原理是查找所有的META-INF/spring.factories資源內容,文件內容如下:
也就是說,Spring Boot會默認給我們加載此文件中的配置項,這也就是爲什麼我們在使用Spring Boot應用的過程中,只需要在application.properties配置相關的配置項即可,而不需要做過多的其他配置。
七. Spring Boot 讀取配置文件的方式有哪幾種?
1. @Value註解讀取
/**
* 配置文件application.properties
*/
spring.datasource.db.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db.username=***
spring.datasource.db.password=***
/**
* 數據源配置
*/
@Configuration
@Data
public class DataSourceConfig {
@Value("${spring.datasource.db.url}")
String url;
@Value("${spring.datasource.db.username}")
String username;
@Value("${spring.datasource.db.password}")
String password;
}
2. @ConfigurationProperties註解讀取
/**
* 配置文件application.properties
*/
#數據源1
spring.datasource.db1.url=jdbc:mysql://localhost:3306/demo1?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db1.username=***
spring.datasource.db1.password=***
#數據源2
spring.datasource.db2.url=jdbc:mysql://localhost:3306/demo2?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db2.username=***
spring.datasource.db2.password=***
/**
* 多數據源配置
*/
@Configuration
public class DataSourceConfig {
/**
* 數據源1
* spring.datasource.db1 :application.properteis中對應屬性的前綴
* @return
*/
@Bean(name = "datasource1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
/**
* 數據源1
* spring.datasource.db2 :application.properteis中對應屬性的前綴
* @return
*/
@Bean(name = "datasource2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
/**
* 動態數據源: 通過AOP在不同數據源之間動態切換
* @return
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默認數據源
dynamicDataSource.setDefaultTargetDataSource(dataSource1());
// 配置多數據源
Map<Object, Object> dsMap = new HashMap();
dsMap.put("datasource1", dataSource1());
dsMap.put("datasource2", dataSource2());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
}
3. @PropertySource+@Value註解讀取指定文件
例如,在資源目錄下新建config/db-config.properties:
/**
* 配置文件db-config.properties
*/
spring.datasource.db.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db.username=***
spring.datasource.db.password=***
/**
* 數據源配置
*/
@Configuration
@Data
@PropertySource(value = {"config/db-config.properties"})
public class DataSourceConfig {
@Value("${spring.datasource.db.url}")
String url;
@Value("${spring.datasource.db.username}")
String username;
@Value("${spring.datasource.db.password}")
String password;
}
4. @PropertySource+@ConfigurationProperties註解讀取指定文件
/**
* 配置文件db-config.properties
*/
spring.datasource.db.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&allowMultiQueries=true
spring.datasource.db.username=***
spring.datasource.db.password=***
/**
* 數據源配置
*/
@Configuration
@Data
@ConfigurationProperties(prefix = "spring.datasource.db")
@PropertySource(value = {"config/db-config.properties"})
public class DataSourceConfig {
String url;
String username;
String password;
}
5. Environment讀取
以上所有加載出來的配置都可以通過Environment注入獲取到:
@Autowired
private Environment environment;
String url = environment.getProperty("spring.datasource.db.url");
...
八. 如何在Spring Boot啓動的時候運行一些特定的代碼?
- 實現接口 ApplicationRunner或者 CommandLineRunner
- 使用@PostConstruct註解
九. 如何理解Spring Boot中的Starters?
1. 是什麼
Starters可以理解爲啓動器,它包含了一系列可以集成到應用裏面的依賴包,你可以一站式集成Spring及其他技術,而不需要到處找示例及依賴的jar包。如你想使用Spring JPA訪問數據庫,只要加入spring-boot-starter-data-jpa啓動器依賴就能使用了。
2. 命名
Spring Boot官方的啓動器都是以spring-boot-starter-命名的,代表了一個特定的應用類型。
第三方的啓動器不能以spring-boot開頭命名,它們都被Spring Boot官方保留。一般一個第三方的應用這樣命名,像mybatis的mybatis-spring-boot-starter.
3. 分類
- 應用
- 生產
- 技術
- 其他第三方
十. Spring Boot 2.X新特性有哪些?
1. 配置變更
廢棄了1.X中的一些配置,增加了很多新的配置,詳細說明:
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Configuration-Changelog
2. JDK升級
2.X至少需要JDK8的支持,2.X裏面許多方法應用了JDK8的高級新特性,並且開始了對JDK9的支持
3. 第三方類庫升級
2.X對第三方類庫升級了所有能升級的穩定版本:
如Spring Framework 5+ ;Tomcat 8.5 + ; Hibernate 5.2 +
4. Data支持
- 2.X 默認使用了HiKariCP連接池
- 更加合理化的優化了數據庫初始化邏輯
- 提供了新配置spring.jdbc.template 方便分頁和排序
- 可以高級定製MongoDB客戶端
…
5. Web加強
- 使用內嵌式容器時,context path會和端口一起記錄並打印出來
- 所有支持的容器都支持過濾器的初始化
- Thymeleaf開始支持javax.time類型
- 提供了一個spring-boot-starter-json啓動器對JSON的讀寫的支持
6. Quartz支持
2.X提供了一個spring-boot-starter-quartz啓動器對定時任務框架Quartz的支持
十一. 保護Spring Boot 應用的方法有哪些?
1. 生產中使用HTTPS
@Configuration
public class WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel().requiresSecure();
}
}
2. 啓用CSRF保護
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
3. 使用內容安全策略防止XSS攻擊
內容安全策略(CSP)是一個增加的安全層,可幫助緩解XSS(跨站點腳本)和數據注入攻擊。要啓用它,你需要配置應用程序以返回Content-Security-Policy標題。
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/");
}
}
4. 使用OpenID Connect進行身份驗證
OpenID Connect(OIDC)是一個OAuth 2.0擴展,提供用戶信息,除了訪問令牌之外,它還添加了ID令牌,以及/userinfo可以從中獲取其他信息的端點,它還添加了發現功能和動態客戶端註冊的端點。
如果使用OIDC進行身份驗證,則無需擔心如何存儲用戶、密碼或對用戶進行身份驗證。相反,你可以使用身份提供商(IdP)爲你執行此操作,你的IdP甚至可能提供多因素身份驗證(MFA)等安全附加組件。
5. 管理密碼 使用密碼哈希
以純文本格式存儲密碼是最糟糕的事情之一。幸運的是,Spring Security默認情況下不允許使用純文本密碼。它還附帶了一個加密模塊,可用於對稱加密,生成密鑰和密碼散列(也就是密碼編碼)。
Spring Security提供了幾種實現,最受歡迎的是BCryptPasswordEncoder和Pbkdf2PasswordEncoder。
【總結】
Spring Boot應用已成爲很多公司的首選,項目應用是一方面,面試中理論也是很重要的一方面。很多人都是可能用過但並不瞭解其內部一些原理性的知識,對於問題的回答並不盡人意,便很難表現出自己的優勢,還是需要多一些深入纔行,這也是自己整理此篇博客的原因。