SpringBoot ApplicationListener事件監(jiān)聽(tīng)接口使用問(wèn)題探究
終日惶惶,不知?dú)w路;一日寫(xiě)起代碼,突發(fā)奇想,若是在運(yùn)行時(shí)發(fā)現(xiàn)自定義上下文的數(shù)據(jù)丟失,我們?cè)撊绾谓鉀Q處理數(shù)據(jù)丟失的問(wèn)題?
問(wèn)題復(fù)現(xiàn)一下,大家看下面的代碼,觀察是否有問(wèn)題,又該如何解決這個(gè)問(wèn)題:
@RequestMapping("verify")
@RestController
@DependsOn({"DingAppInfoService","CloudChatAppInfoService"})
public class LoginAction {
@Qualifier("ElderSonService")
@Autowired
private ElderSonService elderSonService;
@Qualifier("EmployeeService")
@Autowired
private EmployeeService employeeService;
@Qualifier("UserThreadPoolTaskExecutor")
@Autowired
private ThreadPoolTaskExecutor userThreadPoolTaskExecutor;
private static AuthRequest ding_request = null;
private static RongCloud cloud_chat = null;
private static TokenResult register = null;
private static final ThreadLocal<String> USER_TYPE = new ThreadLocal<>();
/**
* 注意不能在bean的生命周期方法上添注@CheckAppContext注解
*/
@PostConstruct
public void beforeVerifySetContext() {
AppContext.fillLoginContext();
Assert.hasText(AppContext.getAppLoginDingId(), "初始化app_login_ding_id錯(cuò)誤");
Assert.hasText(AppContext.getAppLoginDingSecret(), "初始化app_login_ding_secret錯(cuò)誤");
Assert.hasText(AppContext.getAppLoginReturnUrl(), "初始化app_login_return_url錯(cuò)誤");
Assert.hasText(AppContext.getCloudChatKey(), "初始化cloud_chat_key錯(cuò)誤");
Assert.hasText(AppContext.getCloudChatSecret(), "初始化cloud_chat_secret錯(cuò)誤");
if (!(StringUtils.hasText(AppContext.getCloudNetUri()) || StringUtils.hasText(AppContext.getCloudNetUriReserve()))) {
throw new IllegalArgumentException("初始化cloud_net_uri與cloud_net_uri_reserve錯(cuò)誤");
}
ding_request = new AuthDingTalkRequest(
AuthConfig.builder().
clientId(AppContext.getAppLoginDingId()).
clientSecret(AppContext.getAppLoginDingSecret()).
redirectUri(AppContext.getAppLoginReturnUrl()).build());
cloud_chat = RongCloud.getInstance(AppContext.getCloudChatKey(), AppContext.getCloudChatSecret());
}
.....以下API方法無(wú)所影響......
}其中可能令人不解的是controller組件里初始化方法的代碼:
public static void fillLoginContext() {
DingAppInfo appInfo = SpringContextHolder.getBean(DingAppInfoService.class).findAppInfo(APP_CODE);
setDingVerifyInfo(appInfo);
CloudChatAppInfo cloudChatAppInfo = SpringContextHolder.getBean(CloudChatAppInfoService.class).findAppInfo(APP_CODE);
setCloudChatInfo(cloudChatAppInfo);
}
public static void setDingVerifyInfo(DingAppInfo dingAppInfo){
if (dingAppInfo.checkKeyWordIsNotNull(dingAppInfo)) {
put(APP_LOGIN_DING_ID, dingAppInfo.getApp_id());
put(APP_LOGIN_DING_SECRET, dingAppInfo.getApp_secret());
put(APP_LOGIN_RETURN_URL, dingAppInfo.getApp_return_url());
}
}
public static void setCloudChatInfo(CloudChatAppInfo cloudChatAppInfo){
if (cloudChatAppInfo.checkKeyWordIsNotNull(cloudChatAppInfo)){
put(CLOUD_CHAT_KEY,cloudChatAppInfo.getCloud_key());
put(CLOUD_CHAT_SECRET,cloudChatAppInfo.getCloud_secret());
put(CLOUD_NET_URI,cloudChatAppInfo.getCloud_net_uri());
put(CLOUD_NET_URI_RESERVE,cloudChatAppInfo.getCloud_net_uri_reserve());
}
}這里可以發(fā)現(xiàn)其實(shí)就是將一些項(xiàng)目定制的數(shù)據(jù)灌入我們的靜態(tài)自定義上下文AppContext的本地線(xiàn)程ThreadLocal<Map<String,String>>對(duì)象中去,但是我們知道這個(gè)類(lèi)型可是線(xiàn)程隔離的,不同的線(xiàn)程數(shù)據(jù)都不同,而我們的每一個(gè)請(qǐng)求都是一個(gè)線(xiàn)程,勢(shì)必會(huì)導(dǎo)致數(shù)據(jù)的丟失,所以我們就算是在組件初始化時(shí)將數(shù)據(jù)給進(jìn)去,下一個(gè)請(qǐng)求給進(jìn)來(lái)也是會(huì)報(bào)出異常的。
解決思路(實(shí)際上不是這么解決的,但是也可以這么做,代價(jià)是性能耗費(fèi)高):
設(shè)計(jì)一個(gè)監(jiān)聽(tīng)者,一個(gè)發(fā)布者,在請(qǐng)求進(jìn)入的方法上進(jìn)行切面處理,切面檢查AppContext對(duì)象數(shù)據(jù),若為空則發(fā)布事件,不為空則進(jìn)入方法:
事件原型:
public class AppContextStatusEvent extends ApplicationEvent {
public AppContextStatusEvent(Object source) {
super(source);
}
public AppContextStatusEvent(Object source, Clock clock) {
super(source, clock);
}
}監(jiān)聽(tīng)者:
@Component
public class AppContextListener implements ApplicationListener<AppContextStatusEvent> {
@Override
public void onApplicationEvent(AppContextStatusEvent event) {
if ("FillAppContext".equals(event.getSource())) {
AppContext.fillLoginContext();
} else if ("CheckAppContextLogin".equals(event.getSource())) {
boolean checkContext = AppContext.checkLoginContext();
if (!checkContext) {
AppContext.fillLoginContext();
}
}
}
}發(fā)布者(切面類(lèi)):
@Aspect
@Component("AppContextAopAutoSetting")
public class AppContextAopAutoSetting {
@Before("@annotation(com.lww.live.ApplicationListener.CheckAppContextLogin)")
public void CheckContextIsNull(JoinPoint joinPoint){
System.out.println("-----------aop---------CheckAppContextLogin---------start-----");
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
boolean value = signature.getMethod().getAnnotation(CheckAppContextLogin.class).value();
if (value){
boolean checkContext = AppContext.checkLoginContext();
if (!checkContext){
SpringContextHolder.pushEvent(new AppContextStatusEvent("FillAppContext"));
}
}
}
@After("@annotation(com.lww.live.ApplicationListener.CheckAppContextLogin)")
public void CheckContextIsNull(){
System.out.println("-----------aop---------CheckAppContextLogin---------end-----");
SpringContextHolder.pushEvent(new AppContextStatusEvent("CheckAppContextLogin"));
}
}那么AOP切面類(lèi)捕獲的是注解:
@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckAppContextLogin {
boolean value() default false;
String info() default "";
}這里不難發(fā)現(xiàn)我們?cè)谇忻娴那爸门c后置增強(qiáng)方法里都是先檢查AppContext數(shù)據(jù)的完整性,再進(jìn)行填充數(shù)據(jù)。這樣如果我們每一個(gè)請(qǐng)求方法都打上注解@CheckAppContextLogin也可以實(shí)現(xiàn),但是問(wèn)題是除填充的方法外其他的數(shù)據(jù)太難維護(hù)且切面劫持代理的代價(jià)太高,檢查數(shù)據(jù)的頻率太高。
正確的解決方案:
根據(jù)數(shù)據(jù)的業(yè)務(wù)功能劃分,因?yàn)橹饕菍?shí)現(xiàn)兩個(gè)對(duì)象的填充,哪怕這幾個(gè)數(shù)據(jù)丟失了,但是同一個(gè)controller組件的成員變量都是同一個(gè)對(duì)象,且都在初始化的時(shí)候進(jìn)行了初始化,故后續(xù)切換請(qǐng)求了也不影響它們實(shí)現(xiàn)業(yè)務(wù)的能力:
private static AuthRequest ding_request = null; private static RongCloud cloud_chat = null;
我們可以在攔截器中要求前端給我們傳遞當(dāng)前用戶(hù)的用戶(hù)類(lèi)型與唯一標(biāo)識(shí),來(lái)進(jìn)行每一次請(qǐng)求的用戶(hù)定制數(shù)據(jù)的封裝(減少請(qǐng)求內(nèi)調(diào)用方法鏈查庫(kù)操作):
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = (String) request.getSession().getAttribute("token");
String user_type = (String) request.getSession().getAttribute("user_type");
if (StringUtils.hasText(token) && StringUtils.hasText(user_type)) {
Context context = new Context();
if (Objects.equals(user_type, "elder_son")) {
ElderSon elderSon = elderSonService.getElderSonByElderSonId(token);
context.setContextByElderSon(elderSon);
return true;
} else if (Objects.equals(user_type, "employee")) {
Employee employee = employeeService.getEmployeeById(token);
context.setContextByEmployee(employee);
return true;
}
} else if (StringUtils.hasText(user_type)) {
response.sendRedirect("/verify/login?user_type=" + user_type);
return false;
}
return false;
}最后千萬(wàn)不要忘記remove一下ThreadLocal的引用:
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
AppContext.clear();
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}所以實(shí)際場(chǎng)景實(shí)際解決,核心是業(yè)務(wù),代碼簡(jiǎn)潔只是附帶的要求。
到此這篇關(guān)于SpringBoot ApplicationListener事件監(jiān)聽(tīng)接口使用問(wèn)題探究的文章就介紹到這了,更多相關(guān)SpringBoot ApplicationListener內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot 事件監(jiān)聽(tīng)的實(shí)現(xiàn)方法
- SpringBoot Application事件監(jiān)聽(tīng)的實(shí)現(xiàn)方案
- springboot+redis過(guò)期事件監(jiān)聽(tīng)實(shí)現(xiàn)過(guò)程解析
- SpringBoot加載應(yīng)用事件監(jiān)聽(tīng)器代碼實(shí)例
- SpringBoot監(jiān)聽(tīng)事件和處理事件程序示例詳解
- SpringBoot利用切面注解及反射實(shí)現(xiàn)事件監(jiān)聽(tīng)功能
- SpringBoot中的ApplicationListener事件監(jiān)聽(tīng)器使用詳解
- Springboot事件監(jiān)聽(tīng)與@Async注解詳解
- Java?Springboot異步執(zhí)行事件監(jiān)聽(tīng)和處理實(shí)例
- SpringBoot實(shí)現(xiàn)事件監(jiān)聽(tīng)(異步執(zhí)行)的示例代碼
相關(guān)文章
java中對(duì)象為null時(shí)的打印輸出方式
這篇文章主要介紹了java中對(duì)象為null時(shí)的打印輸出方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
JAVA多線(xiàn)程搶紅包的實(shí)現(xiàn)示例
這篇文章主要介紹了JAVA多線(xiàn)程搶紅包的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Java多線(xiàn)程生產(chǎn)者消費(fèi)者模式實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Java多線(xiàn)程生產(chǎn)者消費(fèi)者模式實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
Java CharacterEncodingFilter案例詳解
這篇文章主要介紹了Java CharacterEncodingFilter案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
SpringBoot+thymeleaf+Echarts+Mysql 實(shí)現(xiàn)數(shù)據(jù)可視化讀取的示例
本文主要介紹了SpringBoot+thymeleaf+Echarts+Mysql 實(shí)現(xiàn)數(shù)據(jù)可視化讀取的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
SpringCloudAlibaba Nacos開(kāi)啟鑒權(quán)解決跳過(guò)登錄頁(yè)面問(wèn)題
對(duì)于Nacos,如果需要開(kāi)啟權(quán)限控制,可以在 Nacos 控制臺(tái)上進(jìn)行配置,本文主要介紹了SpringCloudAlibaba Nacos開(kāi)啟鑒權(quán)解決跳過(guò)登錄頁(yè)面問(wèn)題,感興趣的可以了解一下2023-10-10
記一次在idea離線(xiàn)使用maven問(wèn)題(推薦)
這篇文章主要介紹了記一次在idea離線(xiàn)使用maven問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11

