SpringBoot ApplicationListener事件監(jiān)聽接口使用問題探究
終日惶惶,不知歸路;一日寫起代碼,突發(fā)奇想,若是在運行時發(fā)現(xiàn)自定義上下文的數(shù)據(jù)丟失,我們該如何解決處理數(shù)據(jù)丟失的問題?
問題復現(xià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錯誤"); Assert.hasText(AppContext.getAppLoginDingSecret(), "初始化app_login_ding_secret錯誤"); Assert.hasText(AppContext.getAppLoginReturnUrl(), "初始化app_login_return_url錯誤"); Assert.hasText(AppContext.getCloudChatKey(), "初始化cloud_chat_key錯誤"); Assert.hasText(AppContext.getCloudChatSecret(), "初始化cloud_chat_secret錯誤"); if (!(StringUtils.hasText(AppContext.getCloudNetUri()) || StringUtils.hasText(AppContext.getCloudNetUriReserve()))) { throw new IllegalArgumentException("初始化cloud_net_uri與cloud_net_uri_reserve錯誤"); } 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方法無所影響...... }
其中可能令人不解的是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ù)據(jù)灌入我們的靜態(tài)自定義上下文AppContext的本地線程ThreadLocal<Map<String,String>>對象中去,但是我們知道這個類型可是線程隔離的,不同的線程數(shù)據(jù)都不同,而我們的每一個請求都是一個線程,勢必會導致數(shù)據(jù)的丟失,所以我們就算是在組件初始化時將數(shù)據(jù)給進去,下一個請求給進來也是會報出異常的。
解決思路(實際上不是這么解決的,但是也可以這么做,代價是性能耗費高):
設(shè)計一個監(jiān)聽者,一個發(fā)布者,在請求進入的方法上進行切面處理,切面檢查AppContext對象數(shù)據(jù),若為空則發(fā)布事件,不為空則進入方法:
事件原型:
public class AppContextStatusEvent extends ApplicationEvent { public AppContextStatusEvent(Object source) { super(source); } public AppContextStatusEvent(Object source, Clock clock) { super(source, clock); } }
監(jiān)聽者:
@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ā)布者(切面類):
@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切面類捕獲的是注解:
@Inherited @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CheckAppContextLogin { boolean value() default false; String info() default ""; }
這里不難發(fā)現(xiàn)我們在切面的前置與后置增強方法里都是先檢查AppContext數(shù)據(jù)的完整性,再進行填充數(shù)據(jù)。這樣如果我們每一個請求方法都打上注解@CheckAppContextLogin也可以實現(xiàn),但是問題是除填充的方法外其他的數(shù)據(jù)太難維護且切面劫持代理的代價太高,檢查數(shù)據(jù)的頻率太高。
正確的解決方案:
根據(jù)數(shù)據(jù)的業(yè)務(wù)功能劃分,因為主要是實現(xiàn)兩個對象的填充,哪怕這幾個數(shù)據(jù)丟失了,但是同一個controller組件的成員變量都是同一個對象,且都在初始化的時候進行了初始化,故后續(xù)切換請求了也不影響它們實現(xiàn)業(yè)務(wù)的能力:
private static AuthRequest ding_request = null; private static RongCloud cloud_chat = null;
我們可以在攔截器中要求前端給我們傳遞當前用戶的用戶類型與唯一標識,來進行每一次請求的用戶定制數(shù)據(jù)的封裝(減少請求內(nèi)調(diào)用方法鏈查庫操作):
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; }
最后千萬不要忘記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); }
所以實際場景實際解決,核心是業(yè)務(wù),代碼簡潔只是附帶的要求。
到此這篇關(guān)于SpringBoot ApplicationListener事件監(jiān)聽接口使用問題探究的文章就介紹到這了,更多相關(guān)SpringBoot ApplicationListener內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot 事件監(jiān)聽的實現(xiàn)方法
- SpringBoot Application事件監(jiān)聽的實現(xiàn)方案
- springboot+redis過期事件監(jiān)聽實現(xiàn)過程解析
- SpringBoot加載應(yīng)用事件監(jiān)聽器代碼實例
- springboot?事件監(jiān)聽器的案例詳解
- SpringBoot利用切面注解及反射實現(xiàn)事件監(jiān)聽功能
- SpringBoot中的ApplicationListener事件監(jiān)聽器使用詳解
- Springboot事件監(jiān)聽與@Async注解詳解
- Java?Springboot異步執(zhí)行事件監(jiān)聽和處理實例
- SpringBoot實現(xiàn)事件監(jiān)聽(異步執(zhí)行)的示例代碼
相關(guān)文章
教你通過B+Tree平衡多叉樹理解InnoDB引擎的聚集和非聚集索引
大家都知道B+Tree是從二叉樹演化而來,在這之前我們來先了解二叉樹、平衡二叉樹、平衡多叉樹,這篇文章主要介紹了通過B+Tree平衡多叉樹理解InnoDB引擎的聚集和非聚集索引,需要的朋友可以參考下2022-01-01springboot多環(huán)境進行動態(tài)配置的方法
這篇文章主要介紹了springboot多環(huán)境下如何進行動態(tài)配置,本文主要分享了如何在springboot的項目中使用多環(huán)境配置,重點是”spring.profiles.active“屬性,需要的朋友可以參考下2022-06-06SpringBoot調(diào)用Poi-tl實現(xiàn)渲染數(shù)據(jù)并生成Word文檔
這篇文章主要為大家詳細介紹了SpringBoot如何調(diào)用Poi-tl實現(xiàn)渲染數(shù)據(jù)并生成Word文檔,文中的示例代碼講解詳細,有需要的小伙伴可以了解下2023-09-09兩種Spring服務(wù)關(guān)閉時對象銷毀的實現(xiàn)方法
spring提供了兩種方式用于實現(xiàn)對象銷毀時去執(zhí)行的操作,本文主要為大家詳細介紹了這兩種方式的具體實現(xiàn),文中的示例代碼講解詳細,希望對大家有所幫助2023-04-04SpringBoot中@ConditionalOnBean實現(xiàn)原理解讀
這篇文章主要介紹了SpringBoot中@ConditionalOnBean實現(xiàn)原理,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02java 域?qū)ο蠊蚕頂?shù)據(jù)的實現(xiàn)
本文主要介紹了java 域?qū)ο蠊蚕頂?shù)據(jù)的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-03-03