SpringMVC--02--上下文工具类(RequestContextHolder)

news2024/10/6 8:25:10

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • RequestContextHolder
    • 背景
    • 1.RequestContextHolder的使用
    • 2.request和response怎么和当前请求挂钩?
    • 3.request和response等是什么时候设置进去的?
  • 案例应用---用户信息文工具类
    • AuthSessionEntity
    • AuthSessionConstants
    • AuthContextHolder ---用户信息文工具类


RequestContextHolder

背景

  • 最近遇到的问题是在service获取request和response,正常来说在service层是没有request的,然而直接从controlller传过来的话解决方法太粗暴,后来发现了SpringMVC提供的RequestContextHolder遂去分析一番,并借此对SpringMVC的结构深入了解一下,后面会再发文章详细分析源码

1.RequestContextHolder的使用

  • RequestContextHolder顾名思义,持有上下文的Request容器.使用是很简单的,具体使用如下:

在这里插入图片描述

//两个方法在没有使用JSF的项目中是没有区别的
//RequestContextHolder.getRequestAttributes();
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();


//从session里面获取对应的值
String str = (String) requestAttributes.getAttribute("name",RequestAttributes.SCOPE_SESSION);
 
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
HttpServletResponse response = ((ServletRequestAttributes)requestAttributes).getResponse();

2.request和response怎么和当前请求挂钩?

首先分析RequestContextHolder这个类,里面有两个ThreadLocal保存当前线程下的request,

//得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");

//可被子线程继承的request
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");

再看getRequestAttributes()方法,相当于直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request.

public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = requestAttributesHolder.get();
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }

3.request和response等是什么时候设置进去的?

找这个的话需要对springMVC结构的DispatcherServlet的结构有一定了解才能准确的定位该去哪里找相关代码.

在IDEA中会显示如下的继承关系.

左边1这里是Servlet的接口和实现类.

右边2这里是使得SpringMVC具有Spring的一些环境变量和Spring容器.类似的XXXAware接口就是对该类提供Spring感知,简单来说就是如果想使用Spring的XXXX就要实现XXXAware,spring会把需要的东西传送过来.

那么剩下要分析的的就是三个类,简单看下源码

  1. HttpServletBean 进行初始化工作

  2. FrameworkServlet 初始化 WebApplicationContext,并提供service方法预处理请

  3. DispatcherServlet 具体分发处理.

那么就可以在FrameworkServlet查看到该类重写了service(),doGet(),doPost()…等方法,这些实现里面都有一个预处理方法processRequest(request, response);,所以定位到了我们要找的位置

查看processRequest(request, response);的实现,具体可以分为三步:

  1. 获取上一个请求的参数
  2. 重新建立新的参数
  3. 设置到XXContextHolder
  4. 父类的service()处理请求
  5. 恢复request
  6. 发布事
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取上一个请求保存的LocaleContext
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//建立新的LocaleContext
    LocaleContext localeContext = buildLocaleContext(request);
//获取上一个请求保存的RequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//建立新的RequestAttributes
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, 
response, previousAttributes);
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), 
new RequestBindingInterceptor());
//具体设置的方法
    initContextHolders(request, localeContext, requestAttributes);
try {
        doService(request, response);
    }
catch (ServletException ex) {
failureCause = ex;
throw ex;
    }
catch (IOException ex) {
   failureCause = ex;
   throw ex;
    }
catch (Throwable ex) {
   failureCause = ex;
   throw new NestedServletException("Request processing failed", ex);
    }
finally {
//恢复
        resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
        }
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
            }
else {
if (asyncManager.isConcurrentHandlingStarted()) {
                    logger.debug("Leaving response open for concurrent processing");
                }
else {
this.logger.debug("Successfully completed request");
                }
            }
        }
//发布事件
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

再看initContextHolders(request, localeContext, requestAttributes)方法,把新的RequestAttributes设置进LocalThread,实际上保存的类型为ServletRequestAttributes,这也是为什么在使用的时候可以把RequestAttributes强转为ServletRequestAttributes.

private void initContextHolders(HttpServletRequest request, 
                                LocaleContext localeContext, 
                                RequestAttributes requestAttributes) {
if (localeContext != null) {
        LocaleContextHolder.setLocaleContext(localeContext, 
this.threadContextInheritable);
    }
if (requestAttributes != null) {
        RequestContextHolder.setRequestAttributes(requestAttributes, 
this.threadContextInheritable);
    }
if (logger.isTraceEnabled()) {
        logger.trace("Bound request context to thread: " + request);
    }
}

因此RequestContextHolder里面最终保存的为ServletRequestAttributes,这个类相比RequestAttributes方法是多了很多.

案例应用—用户信息文工具类

AuthSessionEntity



import lombok.Data;

import java.util.List;
import java.util.Set;

/**
 **/
@Data
public class AuthSessionEntity {
    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 登录用户名
     */
    private String userName;
    /**
     * 登录用户真实姓名
     */
    private String realName;
    /**
     * 所属部门ID
     **/
    private Long organizeId;
    /**
     * 负责部门ID
     **/
    private Long chargedOrganizeId;
    /**
     * 部门名称
     **/
    private String organizeName;
    /**
     * 所属部门code
     */
    private String organizeCode;
    /**
     * 负责部门code
     */
    private String chargeOrganizeCode;
    /**
     * 用户角色集合
     **/
    private Set<Long> roles;
    /**
     * 角色名称
     **/
    private String roleName;
    /**
     * 是否首次登录
     **/
    private Integer accountStatus;
    /**
     * 是否为超管
     */
    private boolean isSuperAdmin;
    /**
     * 用户权限列表
     **/
    private List<AuthPermissionEntity> menuVoList;

    private boolean firstLogin;

    private boolean headquartersTag;

}

AuthSessionConstants

public class AuthSessionConstants {
    /**
     * 前端cookie中的key值
     **/
    public static final String AUTH_COOKIE_NAME = "SESSION";
    /**
     * 当前登录用户session信息
     */
    public static final String CURRENT_USER = "CURRENT_USER";
    /**
     * 登录验证码
     */
    public static final String LOGIN_CAPTCHA = "LOGIN_CAPTCHA";
    /**
     * 登录验证码有效期
     */
    public static final long CAPTCHA_EXPIRED_TIME = 3L * 60L * 1000L;
}

AuthContextHolder —用户信息文工具类

  • RequestContextHolder [springMvc 的上下文工具类]


import com.alibaba.fastjson.JSON;
import com..framework.auth.entity.AuthSessionEntity;
import com..framework.exception.AppException;
import com..framework.exception.UnauthorizedException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Objects;

/**

 * @description 认证配置上下文持有者,替代调原先的BaseController,作用参照springMvc 的上下文工具类
 * @see RequestContextHolder [springMvc 的上下文工具类]
 **/
public class AuthContextHolder {

    /**
     * 保存用户信息到当前会话
     */
    public static void saveCurrentUserSession(AuthSessionEntity authSessionEntity) {
        if (authSessionEntity == null) {
            return;
        }
        HttpSession session = getCurrentSession();
        session.setAttribute(AuthSessionConstants.CURRENT_USER, JSON.toJSONString(authSessionEntity));
    }

    /**
     * 移除当前会话的用户信息
     */
    public static void removeCurrentUserSession() {
        HttpSession session = getCurrentSession();
        session.removeAttribute(AuthSessionConstants.CURRENT_USER);
        session.invalidate();
    }

    /**
     * 获取当前用户登录信息
     */
    public static AuthSessionEntity getCurrentUser() {
        HttpSession session = getCurrentSession();
        String currentUser = (String) session.getAttribute(AuthSessionConstants.CURRENT_USER);
        if (StringUtils.isBlank(currentUser)) {
            throw new UnauthorizedException(AuthError.USER_NOT_LOGIN);
        }
        return JSON.parseObject(currentUser, AuthSessionEntity.class);
    }

    /**
     * 当前用户是否登陆
     * @return
     */
    public static boolean isLogin() {
        HttpSession session = getCurrentSession();
        String currentUser = (String) session.getAttribute(AuthSessionConstants.CURRENT_USER);
        return !StringUtils.isBlank(currentUser);
    }

    /**
     * 获取当前登录用户会话
     */
    public static HttpSession getCurrentSession() {
        return getRequest().getSession();
    }

    public static Cookie getAuthCookie() {
        Cookie[] cookies = getRequest().getCookies();
        Cookie authCookie = null;
        for (Cookie cookie : cookies) {
            if (AuthSessionConstants.AUTH_COOKIE_NAME.equals(cookie.getName()))
                authCookie = cookie;
        }
        if (authCookie == null)
            throw new AuthException(AuthError.USER_NOT_LOGIN);

        return authCookie;
    }

    public static String getAuthSource() {
        return getRequest().getHeader(AuthFilter.X_SOURCE);
    }

    private static HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.
                getRequestAttributes())).getRequest();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++题解】1027 - 求任意三位数各个数位上数字的和

问题&#xff1a;1027 - 求任意三位数各个数位上数字的和 类型&#xff1a;基础问题 题目描述&#xff1a; 对于一个任意的三位自然数 x &#xff0c;编程计算其各个数位上的数字之和 S 。 输入&#xff1a; 输入一行&#xff0c;只有一个整数 x(100≤x≤999) 。 输出&…

微信小程序 uniapp+vue城市公交线路查询系统dtjl3

小程序Android端运行软件 微信开发者工具/hbuiderx uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 前端&#xff1a;HTML5,CSS3 VUE 后端&#xff1a;java(springbootssm)/python(flaskdja…

STM32完成软件I2C通讯

今天的重点是利用STM32的软件方案和MPU60506轴姿态传感器建立通讯&#xff0c;今天只完成了简单的发送地址和接收应答的部分&#xff0c;特此记录一下过程&#xff0c;以后忘记可以随时翻出来看看。 先介绍最基本的I2C通讯的最基本的6个时序&#xff1a; 一&#xff1a;起始条…

goproxy 简单介绍 及一键安装脚本

goproxy 官网 https://goproxy.cn/ GoProxy 是一项用于 Go 模块的高性能代理服务&#xff0c;旨在为 Go 开发人员提供更快速、更可靠的模块下载体验。它提供以下主要功能&#xff1a; 全球分布式代理服务器: GoProxy 在全球多个地区部署了代理服务器&#xff0c;例如拉斯维加…

驱动开发:探索DRIVER_OBJECT驱动对象

本章将探索驱动程序开发的基础部分&#xff0c;了解驱动对象DRIVER_OBJECT结构体的定义&#xff0c;一般来说驱动程序DriverEntry入口处都会存在这样一个驱动对象&#xff0c;该对象内所包含的就是当前所加载驱动自身的一些详细参数&#xff0c;例如驱动大小&#xff0c;驱动标…

B站基于Apache Ranger的大数据权限服务的技术演进

01 背景 随着云计算、大数据技术的日趋成熟&#xff0c;复杂多元、规模庞大的数据所蕴含的经济价值和社会价值逐步凸显&#xff0c;数据安全也是企业面临的巨大挑战&#xff0c;B站一直致力于对用户隐私数据的保护。 02 Ranger概述 2.1 用户认证 提到安全&#xff0c;就不得不…

uni-app web端使用getUserMedia,摄像头拍照

<template><view><video id"video"></video></view> </template> 摄像头显示在video标签上 var opts {audio: false,video: true }navigator.mediaDevices.getUserMedia(opts).then((stream)> {video document.querySelec…

emmet语法--快速生成html标签

emmet语法介绍 可以直接把它理解为快捷键。 通过一定简略的缩写配合快捷键&#xff0c;直接生成我们想要的html代码。 vscode中已经内置了emmet语法&#xff0c;可以直接使用。 emmet的核心就是tab键&#xff0c;我们输入关键词然后按下tap就可以直接生成我们要的代码。 标…

高云FPGA的管脚约束文件的复制

问&#xff1a;Gowin里面能不能直接拷贝一个管脚约束文件进去用&#xff1f; 答&#xff1a; 可以直接拷贝&#xff0c;但是拷贝前后两个工程对应的芯片必须要是同一个芯片 拷贝方法: 第一步&#xff1a;按照被拷贝约束文件对应的芯片新建一个工程&#xff0c;然后将原工程文…

产品推荐 | 基于Lattice的Crosslink系列LIF-MD6000 FPGA开发板

1、产品概述 LATTICE是一家老牌的FPGA厂家。在CPLD和FPGA低成本&#xff0c;小封装独树一帜。特别在消费电子&#xff0c;小型化设备&#xff0c;控制领域用的比较多。 Crosslink开发板采用Lattice的Crosslink家族系列芯片&#xff0c;LIF-MD6000-6JM80。Crosslink芯片具有低…

取出/var/log/secure中一小时内登录失败超过三次的IP

取出/var/log/secure中一小时内登录失败超过三次的IP 前两个字段是日期&#xff0c;第三个字段是小时&#xff0c;第四个字段是IP cat /var/log/secure | sort -i | awk -F [ :] /Failed/{a[$1" "$2" "$3" "$4" "$(NF-3)]}END{for(i …

二百三十一、ClickHouse——DBeaver连接ClickHouse中时间戳字段的时区差了8小时

一、目的 在用kettle把MySQL中的数据同步到ClickHouse中&#xff0c;发现kettle里的数据显示正常、DataGrip查询ClickHouse中的数据显示正常&#xff0c;但是DBeaver中显示的ClickHosue中的时间字段晚8个小时 二、错误原因 DBeaver的数据库时区设置有问题 三、解决办法 右…

DP10RF001一款工作于200MHz~960MHz低功耗、高性能、单片集成的(G)FSK/OOK无线收发芯片

产品概述. DP10RF001是一款工作于200MHz~960MHz范围内的低功耗、高性能、单片集成的(G)FSK/OOK无线收发机芯片。内部集成完整的射频接收机、射频发射机、频率综合器、调制解调器&#xff0c;只需配备简单、低成本的外围器件就可以获得良好的收发性能。芯片支持灵活可设的数据包…

[大模型]Langchain-Chatchat安装和使用

项目地址&#xff1a; https://github.com/chatchat-space/Langchain-Chatchat 快速上手 1. 环境配置 首先&#xff0c;确保你的机器安装了 Python 3.8 - 3.11 (我们强烈推荐使用 Python3.11)。 $ python --version Python 3.11.7接着&#xff0c;创建一个虚拟环境&#xff…

Day98:云上攻防-云原生篇K8s安全Config泄漏Etcd存储Dashboard鉴权Proxy暴露

目录 云原生-K8s安全-etcd(Master-数据库)未授权访问 etcdV2版本利用 etcdV3版本利用 云原生-K8s安全-Dashboard(Master-web面板)未授权访问 云原生-K8s安全-Configfile鉴权文件泄漏 云原生-K8s安全-Kubectl Proxy不安全配置 知识点&#xff1a; 1、云原生-K8s安全-etcd未…

Failure Unable to retrieve image details-解决Portainer与Docker v26不兼容问题

文章目录 前言解决过程解决方法一解决方法二 前言 今天登上服务器习惯性的升级了yum软件包&#xff0c;结果这一更新后导致Portainer不能正常使用&#xff0c;后续进行成功解决&#xff0c;简单记录一下 解决过程 事故过程模拟 当时顺手升级下docker版本(V26.0.1) 结果Porta…

OpenHarmony南向之编译构建框架

title: OpenHarmony南向之编译构建框架 categories:- OpenHarmony tags:- Ninja- GN author: shell cover: /img/oh/build_framework_ZN.png date: 2024-01-03 21:05:38概述 OpenHarmony编译子系统是以GN和Ninja构建为基座&#xff0c;对构建和配置粒度进行部件化抽象、对内建…

智能售货机:引领便捷生活

智能售货机&#xff1a;引领便捷生活 在这个科技迅速进步的时代&#xff0c;便捷已成为生活的必需。智能售货机作为技术与便利完美结合的产物&#xff0c;正逐渐改变我们的购物方式&#xff0c;为都市生活增添新的活力。 智能售货机的主要优势是它的极致便利性。不论是在地铁…

局域网管理软件哪个好?局域网电脑管理系统实践案例

之前有一个公司案例&#xff0c;是这样的&#xff1a; 公司名称&#xff1a;智慧科技有限公司 背景&#xff1a; 智慧科技有限公司是一家拥有数百名员工的中型企业&#xff0c;随着业务的快速发展&#xff0c;公司面临着网络管理上的挑战。 员工在日常工作中需要频繁地访问…

ChatGPT加持,需求分析再无难题

简介 在实际工作过程中&#xff0c;常常需要拿到产品的PRD文档或者原型图进行需求分析&#xff0c;为产品的功能设计和优化提供建议。 而使用ChatGPT可以很好的帮助分析和整理用户需求。 实践演练 接下来&#xff0c;需要使用ChatGPT 辅助我们完成需求分析的任务 注意&…