这篇文章主要分享一下项目里遇到的获取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实现的,可能与子线程无法访问父线程中设置的数据的问题有关。
不会ThreadLocal的童鞋,通过下面文章简单了解一下ThreadLocal
子线程无法访问父线程中通过ThreadLocal设置的变量https://blog.csdn.net/heyl163_/article/details/139212930
为了验证自己的猜测,点开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的问题导致程序异常。
好了,文章就分享到这里了,看完如果对你有所帮助,不要忘了点赞+收藏哦~