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();
    }
}
相关推荐
计算机毕设指导62 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
ExiFengs3 小时前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring
瓜牛_gn3 小时前
依赖注入注解
java·后端·spring
一元咖啡4 小时前
SpringCloud Gateway转发请求到同一个服务的不同端口
spring·spring cloud·gateway
java亮小白19976 小时前
Spring循环依赖如何解决的?
java·后端·spring
苏-言6 小时前
Spring IOC实战指南:从零到一的构建过程
java·数据库·spring
草莓base7 小时前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
冰帝海岸13 小时前
01-spring security认证笔记
java·笔记·spring
没书读了14 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
代码小鑫17 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计