JAVA?SSE接口開發(fā)中的Spring?Security與異步線程池配置方法
JAVA SSE接口開發(fā)中的Spring Security與異步線程池
本文記錄了SSE(Server-Sent Events)接口開發(fā)場景中遇到的兩個(gè)問題——Spring Security的AccessDeniedException異常與SimpleAsyncTaskExecutor生產(chǎn)環(huán)境警告,提供從根源分析到落地實(shí)現(xiàn)的完整解決方案,同時(shí)附上線程池參數(shù)的科學(xué)估算方法,適用于Spring Boot/Spring MVC的SSE接口生產(chǎn)環(huán)境配置。
一、SSE接口的Spring Security權(quán)限異常解析與解決
1. 異常根源:異步請求的二次權(quán)限校驗(yàn)沖突
當(dāng)使用注解中使用了produces = MediaType.TEXT_EVENT_STREAM_VALUE定義SSE接口時(shí)(列如:@PostMapping(value = “/chat”,produces = MediaType.TEXT_EVENT_STREAM_VALUE)),會觸發(fā)Servlet異步處理機(jī)制,異常產(chǎn)生的完整鏈路如下:
- 客戶端發(fā)起請求,Spring Security攔截初始請求,權(quán)限校驗(yàn)通過后進(jìn)入業(yè)務(wù)邏輯,通過SseEmitter向客戶端流式發(fā)送數(shù)據(jù);
- 當(dāng)SseEmitter調(diào)用complete()方法完成數(shù)據(jù)發(fā)送后,Servlet容器會觸發(fā)一次DispatcherType.ASYNC類型的內(nèi)部轉(zhuǎn)發(fā),這是Servlet規(guī)范的異步回調(diào)機(jī)制;
- Spring Security默認(rèn)攔截所有類型請求(包括ASYNC),但此時(shí)SSE響應(yīng)已提交,且異步轉(zhuǎn)發(fā)的請求上下文可能不完整(如請求路徑信息缺失),導(dǎo)致權(quán)限校驗(yàn)失敗并拋出AccessDeniedException,而響應(yīng)已無法修改,最終僅能在日志中記錄異常。
核心矛盾:SSE的異步特性與Spring Security默認(rèn)攔截規(guī)則的生命周期沖突,非用戶真實(shí)權(quán)限不足問題。
2. 解決方案:精準(zhǔn)排除異步請求的權(quán)限校驗(yàn)
核心思路:通過.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() 設(shè)置保留初始請求的權(quán)限校驗(yàn),讓Spring Security忽略ASYNC類型的異步轉(zhuǎn)發(fā)請求,兼顧安全性與功能正確性。
2.1 安全配置實(shí)現(xiàn)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
// CSRF禁用,因?yàn)椴皇褂胹ession
.csrf(AbstractHttpConfigurer::disable)
// 禁用HTTP響應(yīng)標(biāo)頭
.headers(header -> header.cacheControl(HeadersConfigurer.CacheControlConfig::disable).frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
// 認(rèn)證失敗處理類
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token,所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解標(biāo)記允許匿名訪問的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
// 對于登錄login 注冊register 驗(yàn)證碼captchaImage 允許匿名訪問
requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() //新增設(shè)置*
// 靜態(tài)資源,可匿名訪問
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
.requestMatchers("/webjars/**", "/druid/**").permitAll()
// 除上面外的所有請求全部需要鑒權(quán)認(rèn)證
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class).build();
}
}2.2 其他情況
也可以使用`.requestMatchers("/test").permitAll()`開放SSE接口!但是該方式會放棄網(wǎng)關(guān)層面的權(quán)限控制,需在業(yè)務(wù)代碼中手動(dòng)校驗(yàn)身份,極易引發(fā)安全問題。二、異步線程池警告的解決:生產(chǎn)級配置
1. 警告原因:默認(rèn)執(zhí)行器的資源隱患
Spring MVC處理SSE等異步請求時(shí),默認(rèn)使用SimpleAsyncTaskExecutor,其核心缺陷是無線程復(fù)用機(jī)制——每次請求都會創(chuàng)建新線程。在高并發(fā)場景下,大量線程的創(chuàng)建與銷毀會耗盡CPU和內(nèi)存資源,因此Spring會拋出明確的生產(chǎn)環(huán)境使用警告。
2. 解決方案:自定義線程池實(shí)現(xiàn)線程復(fù)用
通過配置ThreadPoolTaskExecutor(基于線程池的執(zhí)行器),并通過WebMvcConfigurer將其綁定到Spring MVC的異步支持,實(shí)現(xiàn)線程復(fù)用與資源管控。
2.1 線程池配置類
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class AsyncWebMvcConfig implements WebMvcConfigurer {
/**
* 配置Spring MVC異步請求的任務(wù)執(zhí)行器
*/
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(mvcAsyncTaskExecutor()); // 綁定自定義線程池
configurer.setDefaultTimeout(60_000L); // 可選:SSE會話超時(shí)時(shí)間(毫秒),按需調(diào)整
}
/**
* 定義生產(chǎn)級線程池Bean
*/
@Bean(name = "mvcAsyncTaskExecutor")
public Executor mvcAsyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int cpuCores = Runtime.getRuntime().availableProcessors(); // 動(dòng)態(tài)獲取CPU核心數(shù)
// 核心參數(shù)配置
executor.setCorePoolSize(cpuCores * 2); // 核心線程數(shù):IO密集型任務(wù)推薦CPU核心數(shù)*2
executor.setMaxPoolSize(cpuCores * 5); // 最大線程數(shù):高峰期彈性擴(kuò)容上限
executor.setQueueCapacity(100); // 任務(wù)隊(duì)列:緩沖瞬時(shí)突發(fā)流量
executor.setThreadNamePrefix("sse-async-"); // 線程名前綴:便于日志追蹤和監(jiān)控
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒絕策略
executor.setKeepAliveSeconds(60); // 空閑線程存活時(shí)間:超過核心線程數(shù)的空閑線程60秒后銷毀
executor.initialize(); // 初始化執(zhí)行器
return executor;
}
}2.2 服務(wù)層使用自定義線程池
若在服務(wù)層使用@Async注解實(shí)現(xiàn)異步邏輯,需指定自定義線程池的Bean名稱,避免使用默認(rèn)執(zhí)行器:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class SseAsyncService {
// 明確指定使用自定義線程池
@Async("mvcAsyncTaskExecutor")
public void handleSseMessage(String message) {
// SSE相關(guān)的異步業(yè)務(wù)邏輯
System.out.println("處理SSE消息:" + message);
}
}三、線程池核心參數(shù)的科學(xué)估算與調(diào)優(yōu)
線程池的核心參數(shù)(corePoolSize、maxPoolSize、queueCapacity)直接影響接口性能,需結(jié)合任務(wù)類型(CPU密集/IO密集)和系統(tǒng)資源科學(xué)配置,以下為可落地的估算方法與調(diào)優(yōu)思路。
1. 核心參數(shù)估算邏輯
| 參數(shù)名稱 | 估算依據(jù) | 參考公式/建議值 |
|---|---|---|
| corePoolSize(核心線程數(shù)) | 任務(wù)類型決定CPU利用率,避免線程切換損耗 | CPU密集型:CPU核心數(shù) + 1IO密集型(如SSE):CPU核心數(shù) * 2 |
| maxPoolSize(最大線程數(shù)) | 系統(tǒng)高峰期的彈性處理能力,避免資源過載 | 公式:(CPU核心數(shù) * 目標(biāo)CPU使用率) / (1 - 阻塞系數(shù))經(jīng)驗(yàn)值:核心線程數(shù)的2-5倍(SSE推薦3-5倍) |
| queueCapacity(隊(duì)列容量) | 緩沖瞬時(shí)流量,平衡響應(yīng)延遲與任務(wù)堆積風(fēng)險(xiǎn) | 經(jīng)驗(yàn)值:100-500(過大易導(dǎo)致延遲,過小易觸發(fā)拒絕策略) |
關(guān)鍵概念:阻塞系數(shù)指任務(wù)執(zhí)行中IO等待(如網(wǎng)絡(luò)請求、數(shù)據(jù)庫操作)的時(shí)間占比,SSE接口為典型IO密集型任務(wù),阻塞系數(shù)約0.8-0.9。
2. 生產(chǎn)環(huán)境調(diào)優(yōu)步驟
- 基準(zhǔn)測試:低負(fù)載下運(yùn)行接口,驗(yàn)證功能正常且線程池?zé)o異常;
- 壓力測試:使用JMeter、Gatling等工具模擬高并發(fā),逐步提升并發(fā)用戶數(shù)(如從100到1000);
- 核心監(jiān)控指標(biāo):聚焦以下指標(biāo)判斷配置合理性:
- 系統(tǒng)資源:CPU使用率(建議穩(wěn)定在60%-80%)、內(nèi)存占用(無持續(xù)增長);
- 線程池狀態(tài):活躍線程數(shù)(是否頻繁接近maxPoolSize)、隊(duì)列任務(wù)數(shù)(是否持續(xù)高位)、任務(wù)拒絕次數(shù)(需為0);
- 接口性能:平均響應(yīng)時(shí)間(無明顯波動(dòng))、錯(cuò)誤率(低于0.1%)。
- 參數(shù)調(diào)整策略:
- CPU瓶頸早現(xiàn)但吞吐量低:核心線程數(shù)過高,適當(dāng)下調(diào);
- 活躍線程達(dá)上限且隊(duì)列滿:適度提升maxPoolSize和queueCapacity;
- 任務(wù)頻繁被拒絕:需從服務(wù)擴(kuò)容(增加CPU/內(nèi)存)或業(yè)務(wù)優(yōu)化(拆分任務(wù))層面解決。
3. 拒絕策略選擇
當(dāng)線程池和隊(duì)列均滿時(shí),拒絕策略決定新任務(wù)的處理方式,SSE接口推薦優(yōu)先級如下:
- CallerRunsPolicy(推薦):由提交任務(wù)的線程(如Tomcat工作線程)直接執(zhí)行任務(wù),產(chǎn)生自然背壓,減緩請求提交速度,避免任務(wù)丟失;
- DiscardOldestPolicy:僅當(dāng)任務(wù)允許丟棄時(shí)使用,丟棄隊(duì)列中最舊的任務(wù)后接收新任務(wù);
- AbortPolicy(默認(rèn)):直接拋出RejectedExecutionException,需結(jié)合業(yè)務(wù)捕獲處理,否則會導(dǎo)致接口報(bào)錯(cuò)。
四、核心結(jié)論與實(shí)踐建議
- SSE接口的AccessDeniedException:核心解決手段是通過NegatedRequestMatcher排除ASYNC類型請求的權(quán)限校驗(yàn),而非開放接口權(quán)限;
- 異步線程池警告:必須自定義ThreadPoolTaskExecutor實(shí)現(xiàn)線程復(fù)用,SimpleAsyncTaskExecutor嚴(yán)禁用于生產(chǎn)環(huán)境;
- 參數(shù)配置核心:以CPU核心數(shù)為基準(zhǔn),區(qū)分任務(wù)類型(SSE為IO密集型),結(jié)合壓測與監(jiān)控動(dòng)態(tài)調(diào)優(yōu);
- 監(jiān)控優(yōu)先:為線程池設(shè)置清晰的線程名前綴,便于通過APM工具(如SkyWalking)監(jiān)控線程狀態(tài),快速定位問題。
(注:文檔部分內(nèi)容可能由 AI 生成)
到此這篇關(guān)于JAVA SSE接口開發(fā)中的Spring Security與異步線程池配置的文章就介紹到這了,更多相關(guān)Spring Security與異步線程池配置內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中 URL實(shí)現(xiàn)斷點(diǎn)下載
Java中 URL實(shí)現(xiàn)斷點(diǎn)下載,需要的朋友可以參考一下2013-03-03
Spring中事務(wù)管理方案和事務(wù)管理器及事務(wù)控制的API詳解
這篇文章主要介紹了Spring中事務(wù)管理方案和事務(wù)管理器及事務(wù)控制的API詳解,事務(wù)管理是指對事務(wù)進(jìn)行管理和控制,以確保事務(wù)的正確性和完整性,事務(wù)管理的作用是保證數(shù)據(jù)庫的數(shù)據(jù)操作的一致性和可靠性,需要的朋友可以參考下2023-08-08
spring boot多數(shù)據(jù)源動(dòng)態(tài)切換代碼實(shí)例
這篇文章主要介紹了spring boot多數(shù)據(jù)源動(dòng)態(tài)切換代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
Java中鎖的實(shí)現(xiàn)和內(nèi)存語義淺析
這篇文章主要給大家介紹了關(guān)于Java中鎖的實(shí)現(xiàn)和內(nèi)存語義的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
多線程Thread,Runnable,Callable實(shí)現(xiàn)方式
這篇文章主要為大家詳細(xì)介紹了Java多線程如何實(shí)現(xiàn)Thread,Runnable,Callable的方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
SpringBoot之HttpWebServiceMessageSenderBuilder用法詳解
這篇文章主要介紹了SpringBoot之HttpWebServiceMessageSenderBuilder用法,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
解決IDEA誤刪out目錄下的文件導(dǎo)致404無法訪問的問題
這篇文章主要介紹了解決IDEA誤刪out目錄下的文件導(dǎo)致404無法訪問的情況,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02

