springboot項目中根據profile來控制自動配置類的加載

      先說一下背景吧,最近在做個新的項目,主要負責系統用戶權限這部分,說到用戶權限,那麼必不可少的就是用戶會話信息以及上下文數據的保存,一般包括三種:

  1. 保存在用戶session中
  2. 保存在web容器的上下文中
  3. 保存在中間件(redis、mongodb等等)

分析一下每一種方式的使用場景。

第一種方式的優點就是實現簡單,而且不用再給項目引入特別的依賴就可以實現,主要過程就是在過濾器中調用session.setAttribute()方法將用戶的會話信息以及上下文數據保存到session的屬性中;

第二種方式與第一種的思路差不多,只不過是把用戶會話信息以及上下文數據保存在了servletContext對象的屬性中,主要過程就是servletContext.setAttrbute(sessionId,object),這種方式還有一個妙用就是可以做單一終端登錄限制,也就是不允許同一個賬號同時在多個終端或者瀏覽器登錄,有利於提高用戶數據的安全性;

前面兩種方式實現起來都比較簡單,但唯一不好的就是無法支持高可用,在分佈式多實例的場景下,是一定要保證用戶上下文數據的一致性,不然用戶一個請求一會到A實例上,一會到B實例上,那不得出大事了,當然這個問題可以通過代理服務器的會話保持特性來避免,但是依賴別人來完成功能總是不可靠的(萬一運維配錯配置了呢?最後鍋還得自己扛),這時候第三種方式就很優秀了。使用中間件將用戶上下文數據保存起來,程序的每個實例讀取上下文數據的時候都去中間件獲取,更新的時候也保存到中間件,這樣就不會有數據一致性的問題了,雖然給項目引入了中間件,但是支持了高可用,這個代價還是值得的,畢竟人生總少不了捨得,合適就好。

從項目設計來看,要是能同時支持單點和多實例的方式是最好的,單實例運行的時候,用戶上下文數據保存在session,需要多實例部署的時候,通過簡單的配置就可以支持多實例部署,這種無疑是最好的。目前我們項目都是使用的springboot框架,而且springboot的生態也是比較良好,各種組件都有,當然對於共享session的支持也肯定是有的啦,比如spring-session-data-redis組件,maven依賴引入就好了,spring-session組件的功能還是相當強大的,主要思想就是對用戶的request、response對象進行了包裝,在實現中對getSession、setAttrbute方法進行了重寫,將用戶的上下文數據直接保存到中間件或者從中間件讀取用戶數據。

依賴是引入了,就會對中間件服務有依賴,程序啓動時就必然需要去連接中間件服務,不然程序無法啓動成功,但是如果只會有單實例部署的需求呢?明明都不需要中間件服務了,難道僅僅爲了讓程序啓動成功就去部署一箇中間件服務?當然你可以說把依賴去掉不就好了,這樣程序啓動就不需要依賴中間件服務了,那是不是需要重新打包?萬一後面客戶業務多了,性能出現瓶頸了,又需要多實例部署了,難道又把依賴加回去,再重新打包?明顯不是一個好的解決方案。那麼問題來了,最好的解決方案是什麼呢?

通過配置來控制程序當然是最優的方案了。spring-session-data-redis是通過spring-boot-autoconfig依賴中的默認自動配置類SessionAutoConfiguration來實現自動配置的,這個默認就是啓用的,那麼我們是不是隻要能控制SessionAutoConfiguration自動配置類是否啓用來控制是否依賴中間件呢?當然是可以的。springboot最大的特點就是自動配置,在@Configuration註解的說明中提到了@Profile註解,可以使用@Profile註解來控制該@Configuration註解的類是否生效,同時再加上@EnableAutoConfiguration註解的exclude屬性,就可以控制哪些自動配置類不需要。

直接上代碼:

@Profile("!ha")
@Configuration
@EnableAutoConfiguration(exclude={SessionAutoConfiguration.class})
public class ExcludeAutoConfiguration{
}

創建一個ExcludeAutoConfiguration的配置類,該類會在springboot掃描包的時候被掃到,解釋一下上面的三個註解:

  1. @Profile("!ha")表示沒有激活ha的profile時有效
  2. @Configuration表示該類爲配置類
  3. @EnableAutoConfiguration(exclude={SessionAutoConfiguration.class}) 表示開啓自動配置並排除SessionAutoConfiguration自動配置類,這個@EnableAutoConfiguration註解在springboot程序的@SpringBootApplication中是默認加載的,也就是在springboot框架的程序中是默認啓用的,這裏用這個註解主要是爲了使用exclude的屬性

上面這個類的作用就是在非ha的profile激活時,排除SessionAutoConfiguration自動配置類,這樣就不會加載spring-session-data-redis依賴,也就不會去連接中間件了。當需要使用中間件時,可以通過激活ha的profile來自動啓用中間件,這樣豈不是很完美,只需要重啓一下應用就可以支持多實例場景,再也不用半夜起來改代碼發包了。

上面主要是提供了一種方案,可以用到很多中實際的場景中,比如對象存儲方案,可以通過配置來控制是要存到阿里雲還是騰訊雲還是其他雲廠商。

重要!!!使用這種方案時,springboot版本必須高於2.0.9.RELEASE,低於或等於2.0.9.RELEASE的版本是不支持的,原因看下面的源碼:

		@Override
		public Iterable<Entry> selectImports() {
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions)
					.flatMap(Collection::stream).collect(Collectors.toSet());
			Set<String> processedConfigurations = new LinkedHashSet<>();
			Set<String> processedExclusions = new LinkedHashSet<>();
			this.autoConfigurationEntries.forEach((entry) -> {
				List<String> configurations = new ArrayList<>(entry.getConfigurations());
				configurations.removeAll(allExclusions);
				configurations.removeIf(processedConfigurations::contains);
				Set<String> exclusions = new HashSet<>(entry.getExclusions());
				exclusions.removeIf(processedExclusions::contains);
				// This now represents the exact state of this entry based on the
				// state of all other entries
				processedConfigurations.addAll(configurations);
				processedExclusions.addAll(exclusions);
			});

			return sortAutoConfigurations(processedConfigurations,
					getAutoConfigurationMetadata())
							.stream()
							.map((importClassName) -> new Entry(
									this.entries.get(importClassName), importClassName))
							.collect(Collectors.toList());
		}

分析一下方法處理邏輯:

1、合併所有添加了@EnableAutoConfiguration(exclude={})註解中exclude屬性指定的自動配置類

2、遍歷所有的autoConfigurationEntries,在遍歷過程中移除要排除的自動配置類

3、最後對剩餘的自動配置類進行排序處理,原理也是通過@Order註解進行排序

springboot版本低於或等於2.0.9.RELEASE的代碼中是沒有1和2的邏輯,只有排序處理!!!!!

配置類的掃描最終會調用到這個方法對所有的配置類進行過濾、排序,從所有的自動配置類中過濾掉@EnableAutoConfiguration(exlude={})註解中exclude屬性指定的類,至此就達到了通過指定profile來動態開啓或者關閉某些組件的目的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章