解決子線程中獲取不到HttpServletRequest對象的問題
子線程中獲取不到HttpServletRequest對象
本文主要分享一下項目里遇到的獲取request對象為null的問題,具體是在登錄的時候觸發(fā)的郵箱提醒,獲取客戶端ip地址,然后通過ip地址定位獲取定位信息,從而提示賬號在哪里登錄。
但是登錄卻發(fā)現(xiàn)獲取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實現(xiàn)的,可能與子線程無法訪問父線程中設(shè)置的數(shù)據(jù)的問題有關(guān)。
- 不會ThreadLocal的童鞋,通過下面文章簡單了解一下ThreadLocal
- 子線程無法訪問父線程中通過ThreadLocal設(shè)置的變量
為了驗證自己的猜測,點開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"); /** * 根據(jù)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+鼠標(biāo)點擊,發(fā)現(xiàn)實際調(diào)用的是這個方法 * 所以默認(rèn)是通過ThreadLocal保存的變量 */ public static void setRequestAttributes(@Nullable RequestAttributes attributes) { setRequestAttributes(attributes, false); } /** * 獲取ThreadLocal中設(shè)置的RequestAttributes對象 */ @Nullable public static RequestAttributes getRequestAttributes() { // 因為默認(rèn)是通過ThreadLocal而不是InheritableThreadLocal保存, // 在子線程訪問不到在父線程中通過set()方法設(shè)置的變量 RequestAttributes attributes = requestAttributesHolder.get(); // 所以在子線程中,會走這個分支 if (attributes == null) { // 然后從InheritableThreadLocal中獲取RequestAttributes對象 // 因為調(diào)用上面第一個setRequestAttributes(RequestAttributes, boolean)方法時傳的參數(shù)是false,所以InheritableThreadLocal中沒有設(shè)置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; } }
總結(jié)
遇到這類問題,就把獲取request對象的代碼放在主線程中,避免因為ThreadLocal的問題導(dǎo)致程序異常。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot項目獲取請求頭當(dāng)中的token的方法
本文主要介紹了springboot項目獲取請求頭當(dāng)中的token的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11idea 訪問html頁面端口號顯示的是63342而不是8080
這篇文章主要介紹了idea 訪問html頁面端口號顯示的是63342而不是8080,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08Java之String、StringBuffer、StringBuilder的區(qū)別分析
今天搞安卓在看書的時候遇到了StringBuilder這個類型的東東,有點小迷,不知道它跟string、stringbuffer的關(guān)系式怎么樣的,趕快查閱相關(guān)資料,了解了個大概,拿出來分享一下2012-11-11Java實現(xiàn)自定義LinkedList類的示例代碼
LinkedList類跟ArrayList類不同,它通過指針以及結(jié)點的操作對鏈表進行增刪改查。本文就來和大家分享下Java如何為實現(xiàn)自定義LinkedList類,需要的可以參考一下2022-08-08spring cloud oauth2 實現(xiàn)用戶認(rèn)證登錄的示例代碼
這篇文章主要介紹了spring cloud oauth2 實現(xiàn)用戶認(rèn)證登錄的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10