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

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

文章目录


RequestContextHolder

背景

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

1.RequestContextHolder的使用

  • RequestContextHolder顾名思义,持有上下文的Request容器.使用是很简单的,具体使用如下:
java 复制代码
//两个方法在没有使用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,

java 复制代码
//得到存储进去的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.

java 复制代码
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. 发布事
java 复制代码
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.

java 复制代码
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

java 复制代码
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

java 复制代码
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 的上下文工具类]
java 复制代码
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();
    }
}
相关推荐
曾令胜2 小时前
excel导出使用arthas动态追踪方法调用耗时后性能优化的过程
spring·性能优化·excel
多云几多2 小时前
Yudao单体项目 springboot Admin安全验证开启
java·spring boot·spring·springbootadmin
摇滚侠3 小时前
Spring Boot 3零基础教程,Spring Intializer,笔记05
spring boot·笔记·spring
兮动人4 小时前
Spring Bean耗时分析工具
java·后端·spring·bean耗时分析工具
MESSIR224 小时前
Spring IOC(控制反转)中常用注解
java·spring
hello 早上好9 小时前
深入 Spring 条件化配置底层:从硬编码到通用注解的实现原理
java·后端·spring
亚林瓜子9 小时前
Spring中Date日期序列化与反序列化中格式设置
java·后端·spring·jackson·date
哞哞不熬夜13 小时前
JavaEE--Spring MVC
spring·java-ee·mvc
Java水解14 小时前
深入剖析Spring Boot依赖注入顺序:从原理到实战
后端·spring
椎49515 小时前
web后端开发——原理
spring boot·spring·mybatis