之前做项目的时候,在controller中多个方法需要用到request和session获取用户相关值,为了方便写了个BaseController所有controller基础它,在BaseController中Autowired注解request和httpsession,这样子,不需要在各个接口单独加上request入参。这样子的设计在开发阶段和测试阶段都用了有一段时间,一直没有问题。最近偶然在国内技术博客上看到说这样子会有线程安全问题,心想是不是之前的测试没做好,遂马上着手重新进行了单元测试,再次证明没有发现线程安全问题。
针对spring下接口共用公共变量request会不会产生线程安全问题进行测试显示,同一个Controller中hashcode是相同的(说明了request并不会在每次请求中重新生成新的对象注入),但却并不会产生线程安全问题,每次请求获取到的参数都是新的是正确的。实际上,在框架初始IOC的时候是创建了一个Request对象的代理类,从而完成了初始注入,代理类负责从ThreadLocal中获取真正的Request对象并调用相应的方法,每次调用代理request的方法都相当于调用了该次请求真正的request对象的方法,因此不产生线程安全的问题。
所以,我要告诉大家的是,在controller中@Autowire注入request,并不会产生线程安全问题,可以放心的使用。
这是我在controller中注入使用的代码:
基类BaseController(这里的TokenConstitutor原来我是用的spring的HttpSession,后来几个项目组讨论决定统一使用token,才自己做了token获取userId的封装,不需要太在意):
import java.beans.PropertyEditorSupport;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import com.wanships.framework.TokenConstitutor;
import com.wanships.utils.StringEscapeUtil;
public class BaseController {
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
TokenConstitutor tokenConstitutor;
@Autowired(required=false)
HttpServletRequest request;
public String getUserId(){
return (String) tokenConstitutor.getToken(request).getAttribute("userId");
}
/**
* 初始化数据绑定
* 1. 将所有传递进来的String进行HTML编码,防止XSS攻击
* 2. 将字段中Date类型转换为String类型
*/
@InitBinder
protected void initBinder(WebDataBinder binder) {
// String类型转换,将所有传递进来的String进行HTML编码,防止XSS攻击
binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
// setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
setValue(text == null ? null : StringEscapeUtil.escapeHtml4(text.trim()));
}
@Override
public String getAsText() {
Object value = getValue();
return value != null ? value.toString() : "";
}
});
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
long timestamp = Long.parseLong(text);
setValue(new Date(timestamp));
}
@Override
public String getAsText() {
Date value = (Date) getValue();
return value.getTime()+"";
}
});
}
}
继承:
@Controller
@RequestMapping(value = { "${path.prefix.webctrl}/area" })
public class WebAreaMarkController extends BaseController{
...
调用getUserID():
@RequestMapping("deleteMark")
@ResponseBody
public BaseOut deleteMark(@RequestParam Integer id) {
String userId = getUserId();
return ConsMsgUtil.getBaseRtn(amService.deleteMark(userId, id));
}