springmvc在使用@ModelAttribute注解获取Request和Response会产生线程并发不安全问题
最近在使用公司之前的springMVC框架,在测试过程中发现了一个很奇怪的现象;在同时访问下并且这时后台返回较慢时,系统报session为空值或者是数据显示别人的数据。这时查找了业务代码的逻辑,并没有任何问题。此时问题肯定出现在框架里,经过根据调试。发现程序中运用了ThreadLocal类来存储session数据,赶紧学习下这个ThreadLocal类 发现
ThreadLocal利用线程作为key,自己为value
在线程池中线程是重复利用的,如果你不在请求执行完毕后清除它,危险可想而知!
1、更改自己的资料时发现更改到了别人的资料
2、未登录也可以操纵一下需要权限的操作
3、无缘无故登录到了别人的账号(串号?)
SpringMVC也是使用线程池来进行业务处理,所以我们需要在使用完时清理掉ThreadLocal
清理办法:
可以使用拦截器覆盖postHandle(响应时处理)调用ThreadLocal.remove
我满心 欢喜提交上线时,发现还是会出现这种情况。又各种调试,更大的坑被发现了;产生的问题如下代码
public class BaseController { protected HttpServletRequest request; protected HttpServletResponse response; protected HttpSession session; @ModelAttribute public void setReqAndRes(HttpServletRequest request, HttpServletResponse response) { this.request = request; this.response = response; this.session = request.getSession(); } }
上面的BaseController中使用ModelAttribute注解来获取request和response对象,这样基类继承该类,想要获取对象直接可以拿到,虽然这样很简便,但是出现了更为严重的问题。我们都知道SpringMVC控制层是单例的,这里会产生线程不安全问题,在并发量大的情况下,获取的对象可能是同一个对象,或者为null,这些都是并发造成的问题
分析:
1 . ModelAttribute注解有以下三个作用:
–1)有此注解的方法会在每一个请求前执行,也就是controller执行你请求的方法之前
–2)有此注解的参数,会从前端提交的表单中获取数据(现在一般不使用,因为不使用也可以获取到)
–3)有此注解的方法的返回值,可以直接在spring的view中使用(也可以在BaseContoller中添加方法,传入参数 Model model,spring会自动注入model参数,给这个model添加值,也可在view中使用)2 . SpringMVC会优先执行被@ModelAttribute注解的方法。也就是说我们每一次请求,都会去调用init()方法,进行初始化操作,那么就会对request,response,httpSession进行赋值操作,一旦赋值,当高并发时,线程问题是必然的
解决方法
一, 可以使用其他两种方式来获取request和response对象
1: 使用@Autowired或者@Resource注解注入,
public abstract class BaseController { @Autowired protected HttpServletRequest request; @Autowired protected HttpServletResponse response; }
使用@Autowired或者@Resource注解注入,是线程安全的,当用户发出请求后,会经过FrameworkServlet中的processRequest()方法做了一些骚操作,然后再交给子类DispatcherServlet中的doService()去处理这个请求。这些骚操作就包括把request,response对象包装成ServletRequestAttributes对象,然后放入到ThreadLocal中,看到ThreadLocal就明白了
二, 简单粗暴不会产生任何问题:直接在需要request和response对象得方法参数注入即可,有其他参数直接接着在后面写,即可,这个方法参数,作用域在方法,不会产生线程问题
@RequestMapping(value = "/cms/preview/{id}",method = RequestMethod.GET) public void preview(HttpServletResponse response, @PathVariable("id") String id) { }