解决子线程中获取不到HttpServletRequest对象的问题

 更新时间:2024年07月08日 11:56:14   作者:沐雨橙风ιε  
这篇文章主要介绍了解决子线程中获取不到HttpServletRequest对象的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

子线程中获取不到HttpServletRequest对象

本文主要分享一下项目里遇到的获取request对象为null的问题,具体是在登录的时候触发的邮箱提醒,获取客户端ip地址,然后通过ip地址定位获取定位信息,从而提示账号在哪里登录。

但是登录却发现获取request对象的时候报错了。

具体的代码

如下:这个异常是自己手动抛出的。

package cn.edu.sgu.www.mhxysy.util;
 
import cn.edu.sgu.www.mhxysy.consts.MimeType;
import cn.edu.sgu.www.mhxysy.exception.GlobalException;
import cn.edu.sgu.www.mhxysy.restful.JsonResult;
import cn.edu.sgu.www.mhxysy.restful.ResponseCode;
import com.alibaba.fastjson.JSON;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
/**
 * http工具类
 * @author heyunlin
 * @version 1.0
 */
public class HttpUtils {
 
    /**
     * 获取HttpServletRequest对象
     * @return HttpServletRequest
     */
    public static HttpServletRequest getRequest() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
 
        if (attributes != null ) {
            return ((ServletRequestAttributes) attributes).getRequest();
        }
 
        throw new GlobalException(ResponseCode.ERROR, "获取request对象失败");
    }
 
}

在项目其他地方也有用这个工具了获取HttpServletRequest对象,都能获取到,觉得很是奇怪。

点进去RequestContextHolder这个类的代码里看了一下,好像找到问题了~

这是基于ThreadLocal实现的,可能与子线程无法访问父线程中设置的数据的问题有关。

为了验证自己的猜测,点开RequestContextHolder的源代码~

public abstract class RequestContextHolder  {    
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
			new NamedThreadLocal<>("Request attributes");
 
	private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
			new NamedInheritableThreadLocal<>("Request context");
 
	/**
	 * 根据inheritable的值决定通过ThreadLocal或InhertitableThreadLocal保存RequestAttributes对象 
	 */
	public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
		if (attributes == null) {
			resetRequestAttributes();
		} else {
			if (inheritable) {
				inheritableRequestAttributesHolder.set(attributes);
 
				requestAttributesHolder.remove();
			} else {
				requestAttributesHolder.set(attributes);
 
				inheritableRequestAttributesHolder.remove();
			}
		}
	}
 
    /**
	 * 通过Ctrl+鼠标点击,发现实际调用的是这个方法
     * 所以默认是通过ThreadLocal保存的变量
	 */
    public static void setRequestAttributes(@Nullable RequestAttributes attributes) {
		setRequestAttributes(attributes, false);
	}
 
    /**
	 * 获取ThreadLocal中设置的RequestAttributes对象 
	 */
	@Nullable
	public static RequestAttributes getRequestAttributes() {
        // 因为默认是通过ThreadLocal而不是InheritableThreadLocal保存,
        // 在子线程访问不到在父线程中通过set()方法设置的变量
		RequestAttributes attributes = requestAttributesHolder.get();
 
        // 所以在子线程中,会走这个分支
		if (attributes == null) {
            // 然后从InheritableThreadLocal中获取RequestAttributes对象
            // 因为调用上面第一个setRequestAttributes(RequestAttributes, boolean)方法时传的参数是false,所以InheritableThreadLocal中没有设置RequestAttributes对象,因此,这里get()还是null,最后attributes的值为null
			attributes = inheritableRequestAttributesHolder.get();
		}
 
		return attributes;
	}
 
}

于是,把涉及获取request对象ip地址获取的代码放在线程外面,这样就避免了空指针问题了~

package cn.edu.sgu.www.mhxysy.chain.login.impl;
 
import cn.edu.sgu.www.mhxysy.chain.login.UserLoginHandler;
import cn.edu.sgu.www.mhxysy.config.property.EmailProperties;
import cn.edu.sgu.www.mhxysy.config.property.SystemSettingsProperties;
import cn.edu.sgu.www.mhxysy.entity.location.Location;
import cn.edu.sgu.www.mhxysy.util.EmailUtils;
import cn.edu.sgu.www.mhxysy.util.IpUtils;
import cn.edu.sgu.www.mhxysy.util.LocationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
 
/**
 * @author heyunlin
 * @version 1.0
 */
@Component
public class EmailSendHandler implements UserLoginHandler {
 
    private Object params;
    private UserLoginHandler next;
 
    private final EmailUtils emailUtils;
    private final EmailProperties emailProperties;
    private final SystemSettingsProperties systemSettingsProperties;
 
    @Autowired
    public EmailSendHandler(
            EmailUtils emailUtils,
            EmailProperties emailProperties,
            SystemSettingsProperties systemSettingsProperties) {
        this.emailUtils = emailUtils;
        this.emailProperties = emailProperties;
        this.systemSettingsProperties = systemSettingsProperties;
    }
 
    @Override
    public void handle() {
        if (emailProperties.isEnable()) {
            String ip = IpUtils.getIp();
            String username = (String) params;
            String zoneId = systemSettingsProperties.getZoneId();
            // 定义日期格式
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
 
            new Thread(() -> {
                try {
                    String address = "广东广州";
                    Location location = LocationUtils.getLocation(ip);
 
                    if (systemSettingsProperties.isUseRealLocation()) {
                        String locationAddress = location.getAddress();
 
                        if (locationAddress != null) {
                            address = locationAddress;
                        }
                    }
 
                    String text = "您的账号" + username + "在" + address + "登录了。" +
                            "[" + LocalDateTime.now(ZoneId.of(zoneId)).format(formatter) + "]";
 
                    emailUtils.sendMessage(text);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
 
        if (next != null) {
            next.handle();
        }
    }
 
    @Override
    public void setNext(UserLoginHandler next) {
        this.next = next;
    }
 
    @Override
    public void setParams(Object params) {
        this.params = params;
    }
 
}

总结

遇到这类问题,就把获取request对象的代码放在主线程中,避免因为ThreadLocal的问题导致程序异常。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java之SSM中bean相关知识汇总案例讲解

    Java之SSM中bean相关知识汇总案例讲解

    这篇文章主要介绍了Java之SSM中bean相关知识汇总案例讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • Java8使用Stream流实现List列表查询、统计、排序以及分组

    Java8使用Stream流实现List列表查询、统计、排序以及分组

    List的Stream流操作可以简化我们的代码,减少程序运行的压力,应对上面的问题,下面这篇文章主要给大家介绍了关于Java8使用Stream流实现List列表查询、统计、排序以及分组的相关资料,需要的朋友可以参考下
    2023-06-06
  • Java应用打包成Docker镜像

    Java应用打包成Docker镜像

    这篇文章主要为大家介绍了Java应用打包成Docker镜像的过程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • SpringBoot统一接口返回及全局异常处理高级用法

    SpringBoot统一接口返回及全局异常处理高级用法

    这篇文章主要为大家介绍了SpringBoot统一接口返回及全局异常处理高级用法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 一文学会使用sa-token解决网站权限验证

    一文学会使用sa-token解决网站权限验证

    这篇文章主要为大家介绍了使用sa-token解决网站权限验证方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • Java 3D入门之基本图形功能 附源码

    Java 3D入门之基本图形功能 附源码

    Java3D API是Sun定义的用于实现3D显示的接口。3D技术是底层的显示技术,Java3D提供了基于Java的上层接口。Java3D把OpenGL和DirectX这些底层技术包装在Java接口中。这种全新的设计使3D技术变得不再繁琐且可以加入到J2SE、J2EE的整套架构,故保证了Java3D技术强大的扩展性
    2021-10-10
  • mybatis报错元素内容必须由格式正确的字符数据或标记组成异常的解决办法

    mybatis报错元素内容必须由格式正确的字符数据或标记组成异常的解决办法

    今天小编就为大家分享一篇关于mybatis查询出错解决办法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • SpringCloud微服务的调用与远程调用测试示例

    SpringCloud微服务的调用与远程调用测试示例

    这篇文章主要介绍了SpringCloud微服务的调用与远程调用测试示例,服务调用者-可以暂时认为是与用户交互的角色(因为存在微服务之间的调用),可以根据该用户的类型将其赋予不同的服务调用权限,通过一次http请求访问调用对应的微服务获取想要的数据
    2023-04-04
  • 一文详解如何更改电脑使用的JDK版本

    一文详解如何更改电脑使用的JDK版本

    我们在日常学习或者工作中,难免会遇到需要使用不同的jdk版本进行开发,这篇文章主要给大家介绍了关于如何更改电脑使用的JDK版本的相关资料,需要的朋友可以参考下
    2024-01-01
  • Spring Boot集成教程之异步调用Async

    Spring Boot集成教程之异步调用Async

    在项目中,当访问其他人的接口较慢或者做耗时任务时,不想程序一直卡在耗时任务上,想程序能够并行执行,我们可以使用多线程来并行的处理任务,也可以使用spring提供的异步处理方式@Async。需要的朋友们下面来一起看看吧。
    2018-03-03

最新评论