SpringBoot ApplicationListener事件监听接口使用问题探究

作者:ForestSpringH 时间:2023-03-04 22:44:13 

终日惶惶,不知归路;一日写起代码,突发奇想,若是在运行时发现自定义上下文的数据丢失,我们该如何解决处理数据丢失的问题?

问题复现一下,大家看下面的代码,观察是否有问题,又该如何解决这个问题:

@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());
       }
   }

这里可以发现其实就是将一些项目定制的数据灌入我们的静态自定义上下文AppContext的本地线程ThreadLocal<Map<String,String>>对象中去,但是我们知道这个类型可是线程隔离的,不同的线程数据都不同,而我们的每一个请求都是一个线程,势必会导致数据的丢失,所以我们就算是在组件初始化时将数据给进去,下一个请求给进来也是会报出异常的。

解决思路(实际上不是这么解决的,但是也可以这么做,代价是性能耗费高):

设计一个监听者,一个发布者,在请求进入的方法上进行切面处理,切面检查AppContext对象数据,若为空则发布事件,不为空则进入方法:

事件原型:

public class AppContextStatusEvent extends ApplicationEvent {
   public AppContextStatusEvent(Object source) {
       super(source);
   }
   public AppContextStatusEvent(Object source, Clock clock) {
       super(source, clock);
   }
}

监听者:

@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();
           }
       }
   }
}

发布者(切面类):

@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 "";
}

这里不难发现我们在切面的前置与后置增强方法里都是先检查AppContext数据的完整性,再进行填充数据。这样如果我们每一个请求方法都打上注解@CheckAppContextLogin也可以实现,但是问题是除填充的方法外其他的数据太难维护且切面劫持代理的代价太高,检查数据的频率太高。

正确的解决方案:

根据数据的业务功能划分,因为主要是实现两个对象的填充,哪怕这几个数据丢失了,但是同一个controller组件的成员变量都是同一个对象,且都在初始化的时候进行了初始化,故后续切换请求了也不影响它们实现业务的能力:

private static AuthRequest ding_request = null;
private static RongCloud cloud_chat = null;

我们可以在 * 中要求前端给我们传递当前用户的用户类型与唯一标识,来进行每一次请求的用户定制数据的封装(减少请求内调用方法链查库操作):

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);
   }

所以实际场景实际解决,核心是业务,代码简洁只是附带的要求。

来源:https://blog.csdn.net/m0_59588838/article/details/129891333

标签:SpringBoot,ApplicationListener,事件监听接口
0
投稿

猜你喜欢

  • SpringBoot整合freemarker实现代码生成器

    2023-07-17 20:31:08
  • Android实现的秒表计时器示例

    2023-04-26 11:32:56
  • Jenkins Host key verification failed问题解决

    2021-07-14 12:53:41
  • 基于C#设计一个双色球选号工具

    2021-10-07 20:39:57
  • Java Spring开发环境搭建及简单入门示例教程

    2021-08-17 07:36:28
  • 简单谈谈Struts动态表单(DynamicForm)

    2022-10-07 07:24:29
  • 在web.config和app.config文件中增加自定义配置节点的方法

    2021-11-03 03:21:41
  • Java集合教程之Collection实例详解

    2022-12-01 23:45:13
  • Jexcel实现按一定规则分割excel文件的方法

    2023-06-29 07:52:01
  • Java比较两个对象大小的三种方法详解

    2021-07-11 18:55:16
  • Java byte数组操纵方式代码实例解析

    2022-02-18 16:54:12
  • Unity Shader实现描边OutLine效果

    2022-01-13 03:11:13
  • Java常见面试题之多线程和高并发详解

    2023-07-24 09:33:51
  • 高斯混合模型与EM算法图文详解

    2022-10-02 12:05:02
  • Android Activity向右滑动返回

    2021-12-06 16:56:06
  • C语言实现的猴子分桃问题算法解决方案

    2022-10-19 19:03:32
  • Android语音识别技术详解及实例代码

    2022-09-23 03:46:33
  • Android编程实现google消息通知功能示例

    2023-02-02 20:00:27
  • Android仿360悬浮小球自定义view实现示例

    2021-10-22 10:16:14
  • Mac Android Studio 3.0 Terminal 中文乱码问题处理

    2023-08-12 13:42:09
  • asp之家 软件编程 m.aspxhome.com