HttpServletRequest被注入到静态的引用中是否有线程安全问题呢?
先给出结论:我做的结果是不会。
问题引入:
在做系统添加日志的时候,每次新写Controller的时候都需要引入日志类的Service的对象,实在过于麻烦,于是写了一个日志工具类,将添加日志的方法写为一个静态的方法。
@Component
public class CommonUtil {
private CommonUtil(){}
private static LogService logService;
@Resource
public void setLogService(LogService logService) {
CommonUtil.logService = logService;
}
public static void saveLog(HttpServletRequest request,Log log){
//通过logService对象进行保存
}
}
对于静态方法中注入对象,这个已经非常常见了,不足为奇,方式也是很简单,写一个set方法,set方法上写@Resource,这样Spring在启动的时候会调用这个方法给声明的属性注入对象。(这个没深究,大概意思是这样,应该是在启动的时候注入)
然后就觉得,还需要传递request,这个东西也是也可以注入,就想到了下面这样的代码
@Component
public class CommonUtil {
private CommonUtil(){}
private static LogService logService;
@Resource
public void setLogService(LogService logService) {
CommonUtil.logService = logService;
}
private static HttpServletRequest httpServletRequest;
@Resource
public void setHttpServletRequest(HttpServletRequest httpServletRequest) {
System.out.println(httpServletRequest);
CommonUtil.httpServletRequest = httpServletRequest;
}
public static void saveLog(Log log){
//通过logService对象进行保存
}
}
想法很奇怪,做法也很奇怪,但是测试了一下,可以用,我就没管了,知道我同事过来跟我说这个可能有线程安全问题,(毕竟需要从request里设置用户访问时候的ip,如果有线程安全问题的话,日志记录的就可能是A在ip1访问的记录,记录成了A在ip2的记录,问题还是比较大的,仔细想了一下。也确实会有安全问题)
然后百度了一下,大多数人都不是注入到静态的声明上,如下代码
@Resource
private HttpServletRequest httpServletRequest;
然后看了一下,先说结论:这样不会有线程安全问题。
解释有很多文章在讲,这里推荐一个讲的比较详细的,点下方进行查看,这里不做过多赘述,我们主要说被注入到静态引用中是否会有线程安全问题,
因为是在工作中,最快解决问题才是最主要的,我第一反应就是把这个request用参数传递的方式传递过来,无非就是多传递一个参数而已,对我来说只是把所有调用这个方法的地方改一遍而已,而且现在Ctrl+H可以直接替换了,不是啥大问题。但是这个问题困扰着我,所以写了下面的代码进行测试
/**
* TestController
*/
@RestController
public class TestController {
@GetMapping("/test")
public String test(HttpServletRequest request) {
CommonUtil.getParam(request);
return "success";
}
}
/**
* ClassName:CommonUtil
*/
@Component
public class CommonUtil {
private CommonUtil() {}
private static HttpServletRequest httpServletRequest;
@Resource
public void setHttpServletRequest(HttpServletRequest httpServletRequest) {
System.out.println(httpServletRequest);
CommonUtil.httpServletRequest = httpServletRequest;
}
public static void getParam(HttpServletRequest request) {
String v = request.getParameter("v");
if(v.equals("1")) {
try {
Thread.sleep(10000);
}catch(Exception e) {
}
}
String z = httpServletRequest.getParameter("z");
System.out.println(z);
}
}
在启动的时候发现了一个输出:
红色框中的内容,代表了系统启动的时候会设置上静态的HttpServletRequest对象
开始测试:
根据代码逻辑,在访问参数v=1时,进入到CommonUtil的getParam的方法内,会睡10S,在此10S内,访问参数v=2时,不会进入睡眠代码
第一步:在浏览器1中访问http://127.0.0.1:99/test?v=1&z=10
在执行睡眠代码的10S内执行第二步
第二步:在浏览器2中访问http://127.0.0.1:99/test?z=200&v=2
等待两个浏览器页面都显示了success之后,控制台的输出内容为:
输出结果表示了,静态HttpServletRequest不会有线程安全的问题。
至于为啥,我没深究,上面的传送门有代码跟踪,有大神懂的话,可以评论一下。
虽然证明了不会有线程安全的问题,但是不建议这么写到生产环境。
第一可读性非常的差
第二,我已经被老大骂过一遍了,事实证明这么写会影响心情。