springboot事件监听ApplicationListener接口应用场景

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/6100.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023.4.3

ctrlf:全局替换 alt鼠标左键&#xff1a;整列编辑 ctrlaltl&#xff1a;格式化 AJAX 简介 概念&#xff1a;AJAX&#xff1a;异步&#xff08;客户端不用等待服务端的反应&#xff09;的Javascript和XML AJAX的作用&#xff1a; 与服务器进行数据交互&#xff1a;通过AJA…

Adaptive AUTOSAR——Execution Management(VRTE 3.0 R21-11)

ChatGPT回答&#xff1a; Adaptive AUTOSAR Execution Management 模块是用于管理软件执行的模块&#xff0c;其主要功能包括&#xff1a; 任务调度&#xff1a;支持对任务进行调度和执行&#xff0c;以实现系统中各个功能的协调运行&#xff1b;软件组件管理&#xff1a;支持对…

python 读取xml从入门到精通

XML &#xff08;Extensible Markup Language&#xff09;&#xff0c;可扩展标记语言&#xff0c;是一种被广泛应用于网络上的文件格式。在互联网上&#xff0c;网页里的信息都以 XML格式存储&#xff0c;例如 HTML、 CSV、 JSON等。随着电子商务的发展&#xff0c;人们需要在…

【面试】Java异常面试题

文章目录Java异常架构与异常关键字Java异常简介Java异常架构1. Throwable2. Error&#xff08;错误&#xff09;3. Exception&#xff08;异常&#xff09;4. 受检异常与非受检异常Java异常关键字Java异常处理声明异常抛出异常捕获异常如何选择异常类型常见异常处理方式直接抛出…

35岁,失业6个月终于接到降薪offer:有面就面,薪酬不限,随机应变说瞎话,对奇葩面试官保持礼貌克制,为拿offer什么都能忍...

被裁后为了生存&#xff0c;人需要做出什么改变&#xff1f;一位35岁网友在失业6个月后终于拿到offer&#xff0c;虽然降薪到四年前的水平&#xff0c;但能继续养家糊口&#xff0c;楼主已经很满意了&#xff0c;并分享了自己的个人经验&#xff1a;1.挖掘历史项目经验&#xf…

为什么ClassPathResource可以读取到流?- 第465篇

历史文章&#xff08;文章累计460&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 S…

68-信号量

文章目录信号量一.什么是信号量二.信号量的接口三.信号量的使用1.思路2.代码3.编译及结果四.ipcs命令信号量 信号量就是控制某个进程能够对某个资源进行访问;保证同一时刻只能由一个进程对 某个资源进程访问; 打印机 一.什么是信号量 信号量是一个特殊的变量&#xff0c;一…

全网最详细的UI自动化测试元素定位方法总结

目录 前言 元素定位概述 常用的元素定位器 元素定位方法 元素定位技巧 总结 前言 UI自动化测试是软件测试中的一个重要环节&#xff0c;它可以通过模拟用户的实际操作&#xff0c;自动化执行UI界面上的测试用例&#xff0c;以提高测试效率和准确性。元素定位是UI自动化测…

卡尔曼滤波浅析

文章目录前言任务状态预测外部影响因素外部不确定性状态更新利用测量进一步修正状态合并两个高斯分布公式汇总图形化解释总结&#xff08;readme&#xff09;references前言 Kalman Filter算法&#xff0c;是一种递推预测滤波算法&#xff0c;算法中涉及到滤波&#xff0c;也涉…

数据更新 | CnOpenData法拍房数据

法拍房数据 一、数据简介 法拍房&#xff0c;即“法院拍卖房产”&#xff0c;是被法院强制执行拍卖的房屋 。当债务人&#xff08;业主&#xff09;无力履行借款合约或无法清偿债务时&#xff0c;而被债权人经司法程序向法院申请强制执行&#xff0c;将债务人名下房屋拍卖&…

sql注入靶场练习

文章目录Less-1Less-2Less-3Less-4Less-5Less-6Less-7Less-8Less-9Less-10Less-11Less-12Less-13Less-14Less-15Less-16Less-17less-18Less-19Less-20Less-1 union没有被过滤&#xff0c;先试出来长度。 ?id1orderby3%23 ?id1orderby4%23到4时&#xff0c;发现 然后再试出来…

JVM与Java体系

JVM体系跟着尚硅谷的康师傅学习 JVM内存与垃圾回收概述 除了大部分的Java开发 人员&#xff0c;除了会在项目中使用到与Java平台相关的框架&#xff0c;与API&#xff0c;对于Java的虚拟机了解甚少。但是也需要我们知道如何处理OOM&#xff0c;SOF异常&#xff0c;除了…

Java实现一个简单的东南西北中的面板

目录 一、前言 二、代码部分 1.代码 三、程序运行结果&#xff08;面板弹出&#xff09; 四、涉及到的知识点代码 一、前言 1.本代码是我在上学时写的&#xff0c;有一些地方没能完美实现&#xff0c;请包涵也请多赐教&#xff01; 2.本弹窗界面可以根据简单的要求进行…

字节跳动CVPR 2023论文精选来啦(内含一批图像生成新研究)

计算机视觉领域三大顶会之一的 CVPR 今年已经开奖啦。 今年的 CVPR 将于六月在加拿大温哥华举办&#xff0c;和往年一样&#xff0c;字节跳动技术团队的同学们收获了不少中选论文&#xff0c;覆盖文本生成图像、语义分割、目标检测、自监督学习等多个领域&#xff0c;其中不少…

Linux 中yum获取的的详细步骤

**1.yum的作用 可以帮我们管理RPM包 可以帮我们安装软件&#xff0c; 如果软件有其他依赖&#xff0c;会帮我们安装依赖后在安装软件 类似于Maven 2.yum命令 search 查询命令或者软件 info 查看包的信息 list / list jdk 查询安装的rpm包&#xff0c;或者只查询某一周 3.更换yu…

【Paper】2016_基于LQR的多智能体系统协同最优控制_姚蒙

姚蒙. 基于LQR的多智能体系统协同最优控制[D].华南理工大学,2016. 文章目录第四章 线性离散时间多智能体系统协同最优控制4.1 引言4.2 离散时间多智能体系统一致最优控制4.3 离散时间领导者-跟随者系统跟踪最优控制4.4 数值仿真Ref第四章 线性离散时间多智能体系统协同最优控制…

HttpRunner3.x(7) hook函数

目录 调用 hook 函数 测试用例层面&#xff08;testcase&#xff09;-3.1.11不支持 测试步骤层面&#xff08;teststep&#xff09; 编写hook函数 请求request预处理 返回 response 处理 HttpRunner 从 1.4.5 版本开始实现了全新的 hook 机制&#xff0c;可以在请求前和请…

JavaSE——方法的使用

目录 一、方法的概念及使用 1、什么是方法(method) 2、方法定义 3、方法调用的执行过程 4、实参和形参的关系 二、方法重载 1、为什么需要方法重载 2、方法重载概念 3、方法签名 三、递归 1、递归的概念 2、递归执行过程分析 3、递归练习 一、方法的概念及使用 1、…

Java 你还在用lsit.contain做去重? 你是故意的还是不小心的?

前言 最近又是一轮代码review , 发现了一些实现去重的代码&#xff0c;在使用 lsit.contain ...... 如&#xff1a; 我沉思&#xff0c;是不是其实很多初学者也存在这种去重使用问题&#xff1f; 所以我选择把这个事情整出来&#xff0c;分享一下。 正文 首先是造出一个 List&l…

Linux基础教程

Linux 目标 ​ Linux概述【了解】 ​ Linux网络连接三种模式【了解】 Linux目录结构【掌握】 Linux常用命令【掌握】 Linux操作目录的命令【掌握】 Linux操作文件的命令【掌握】 Linux的Vi编辑器【会用】 Linux打包与解压命令【掌握】 一、Linux概述 1、Linux的概述…
最新文章