springmvc中request的线程安全问题

时间:2022-06-25 23:17:32
2016-03-19 11:25 611人阅读 评论(1) 收藏 举报
springmvc中request的线程安全问题 分类:
Spring(4) springmvc中request的线程安全问题

版权声明:本文为博主原创文章,未经博主允许不得转载。

servlet是单例的,而tomcat则是在多个线程中调用servlet的处理方法。因此如果servlet存在实例对象,那么就会引出线程安全的问题。而springmvc允许在controller类中通过@Autowired配置request、response以及requestcontext等实例对象。这种配置方法是否线程安全?答案是——这种配置方法是线程安全的,request、response以及requestcontext在使用时不需要进行同步。而根据spring的默认规则,controller对于beanfactory而言是单例的。即controller只有一个,controller中的request等实例对象也只有一个。然而tomcat依旧会以多线程的方式访问controller。这种做法似乎并不能保证线程安全。我们如何理解这一矛盾?

在解释controller线程安全这一问题之前需要首先了解如下的一些问题和概念:

1.servlet的request域的问题:request域是javaweb的基础概念,他指的是从发起http请求到返回响应的这一段时间内,存在一个httprequest对象对应于http请求。以上的表述是没有问题的,然而有些人“自作主张”的将之前的表述换成了其他的描述方式:(1):request对象的生命周期以发起http请求开始,当http请求返回时结束;(2):用户发送一个请求的时候,request被创建,当用户关闭请求的时候,request会消亡。以上两种表述的主要错误在于混淆了http请求和request对象这两个概念。tomcat在接收到http请求的时候并不会创建一个request对象,即request对象并不是一个http请求的实例。只是request对象“恰巧”拥有了http请求中的所有参数而已。request对象在tomcat发起处理线程的时候就被创建,只有当处理线程终止的时候request才会被销毁。我们可以创建一个servlet类,并在doget和dopost方法上面打上断点。你会发现如果是同一个进程,即便发起多次访问,request对象的id始终不变。读者可以亲自尝试,用以验证本人说法的真伪。

2.Threadlocal类:该对象包含两个关键函数:set(Object obj)和get()。这两个函数与调用该函数的线程相关,set方法将某一对象“注入”到当前线程中,而get方法则是从当前线程中获取对象。

3.InvocationHandler接口:这是springmvc保证request对象线程安全的核心。通过实现该接口,开发者能够在Java对象方法执行时进行干预,搭配Threadlocal就能够实现线程安全。

下面将通过例子介绍springmvc如何保证request对象线程安全:

Httprequest接口:

  1. public interface HttpRequest {
  2. public void service();
  3. }

HttpRequestImpl类:对httprequest接口的具体实现,为了区别不同的HttpRequestImpl对象,本人为HttpRequestImpl设置了一个Double对象,如果不设置该对象,其默认为null

  1. public class HttpRequestImpl implements HttpRequest{
  2. public Double d;
  3. @Override
  4. public void service() {
  5. System.out.println("do some serivce, random value is "+d);
  6. }
  7. }

ThreadLocalTest类:负责向ThreadLocal设置对象和获取对象,本人设置ThreadLocal对象为static,因此ThreadLocalTest类中只能有一个ThreadLocal对象。

  1. public class ThreadLocalTest {
  2. public static ThreadLocal<HttpRequest> local=new ThreadLocal<HttpRequest>();
  3. public static void set(HttpRequest f){
  4. if(get()==null){
  5. System.out.println("ThreadLocal is null");
  6. local.set(f);
  7. }
  8. }
  9. public static HttpRequest get(){
  10. return local.get();
  11. }
  12. }

Factory类:该类是一个工厂类并且是单例模式,主要负责向ThreadLocalTest对象中设置和获取对象

  1. public class Factory{
  2. private static Factory factory=new Factory();
  3. private Factory(){
  4. }
  5. public static Factory getInstance(){
  6. return factory;
  7. }
  8. public HttpRequest getObject(){
  9. return (HttpRequest)ThreadLocalTest.get();
  10. }
  11. public void setObject(HttpRequest request){
  12. ThreadLocalTest.set(request);
  13. }
  14. }

Delegate类:该类实现了InvocationHandler接口,并实现了invoke方法

  1. import java.lang.reflect.InvocationHandler;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Proxy;
  4. public class Delegate implements InvocationHandler{
  5. private Factory factory;
  6. public Factory getFactory() {
  7. return factory;
  8. }
  9. public void setFactory(Factory factory) {
  10. this.factory = factory;
  11. }
  12. @Override
  13. public Object invoke(Object proxy, Method method, Object[] args)
  14. throws Throwable {
  15. return method.invoke(this.factory.getObject(), args);
  16. }
  17. }

ProxyUtils类:该类是一个工具类,负责生成一个httprequest对象的代理

  1. import java.lang.reflect.Proxy;
  2. public class ProxyUtils {
  3. public static HttpRequest getRequest(){
  4. HttpRequest request=new HttpRequestImpl();
  5. Delegate delegate=new Delegate();
  6. delegate.setFactory(Factory.getInstance());
  7. HttpRequest proxy=(HttpRequest) Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), delegate);
  8. return proxy;
  9. }
  10. }

TestThread类:该类用来模拟多线程调用controller的情况,类中拥有一个静态对象request。

  1. public class TestThread implements Runnable{
  2. private static HttpRequest request;
  3. public void init(){
  4. HttpRequestImpl requestimpl=new HttpRequestImpl();
  5. requestimpl.d=Math.random();
  6. Factory.getInstance().setObject(requestimpl);
  7. }
  8. @Override
  9. public void run() {
  10. System.out.println("*********************");
  11. init();
  12. request.service();
  13. System.out.println("*********************");
  14. }
  15. public static HttpRequest getRequest() {
  16. return request;
  17. }
  18. public static void setRequest(HttpRequest request) {
  19. TestThread.request = request;
  20. }
  21. }

main:测试类

  1. public class main {
  2. /**
  3. * @param args
  4. */
  5. public static void main(String[] args) {
  6. HttpRequest request=ProxyUtils.getRequest();
  7. TestThread thread1=new TestThread();
  8. thread1.setRequest(request);
  9. TestThread thread2=new TestThread();
  10. thread2.setRequest(request);
  11. Thread t1=new Thread(thread1);
  12. Thread t2=new Thread(thread2);
  13. t1.start();
  14. t2.start();
  15. }
  16. }

thread1和thread2设置了同一个request对象,正常来说这两个对象调用run方法时输出的随机值应该为null(因为设置给这两个对象的request并没有设置d的值)。然而事实上这两个线程在调用时不但输出了随机值而且随机值还各不相同。这是因为request对象设置了代理,当调用request对象的service方法时,代理对象会从Threadlocal中获取实际的request对象以替代调用当前的request对象。由于httprequest对象在处理线程中保持不变,因此controller通过调用httprequest对象的方法能够获取当前请求的参数。

以上都是一家之言,下面将通过展现springmvc源码的形式证明以上的说法:

ObjectFactoryDelegatingInvocationHandler类:该类是AutowireUtils的一个私有类,该类拦截了除了equals、hashcode以及toString以外的其他方法,其中的objectFactory是RequestObjectFactory实例。

  1. private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
  2. private final ObjectFactory objectFactory;
  3. public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) {
  4. this.objectFactory = objectFactory;
  5. }
  6. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  7. String methodName = method.getName();
  8. if (methodName.equals("equals")) {
  9. // Only consider equal when proxies are identical.
  10. return (proxy == args[0]);
  11. }
  12. else if (methodName.equals("hashCode")) {
  13. // Use hashCode of proxy.
  14. return System.identityHashCode(proxy);
  15. }
  16. else if (methodName.equals("toString")) {
  17. return this.objectFactory.toString();
  18. }
  19. try {
  20. return method.invoke(this.objectFactory.getObject(), args);
  21. }
  22. catch (InvocationTargetException ex) {
  23. throw ex.getTargetException();
  24. }
  25. }
  26. }

RequestObjectFactory类:其中currentReuqestAttributes负责从Threadlocal中获取对象

  1. private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
  2. public ServletRequest getObject() {
  3. return currentRequestAttributes().getRequest();
  4. }
  5. @Override
  6. public String toString() {
  7. return "Current HttpServletRequest";
  8. }
  9. }

既然需要从Threadlocal中获取对象,那springmvc在何时向Threadlocal设置了该对象呢?分别在如下两个类中完成:RequestContextListener和FrameworkServlet。RequestContextListener负责监听servletcontext,当servletcontext启动时,RequestContextListener向Threadlocal设置了httprequest对象。FrameworkServlet是DispatchServlet的基类,tomcat会在运行过程中启动新的线程,而该线程中并没有httprequest对象。因此servlet会在每次处理http请求的时候检验当前的Threadlocal中是否有httprequest对象,如果没有则设置该对象。

FrameworkServlet通过布尔值previousRequestAttributes检验httprequest是否存在的代码:

  1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
  2. throws ServletException, IOException {
  3. long startTime = System.currentTimeMillis();
  4. Throwable failureCause = null;
  5. // Expose current LocaleResolver and request as LocaleContext.
  6. LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
  7. LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
  8. // Expose current RequestAttributes to current thread.
  9. RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
  10. ServletRequestAttributes requestAttributes = null;
  11. if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
  12. requestAttributes = new ServletRequestAttributes(request);
  13. RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
  14. }
  15. if (logger.isTraceEnabled()) {
  16. logger.trace("Bound request context to thread: " + request);
  17. }
  18. try {
  19. doService(request, response);
  20. }
  21. catch (ServletException ex) {
  22. failureCause = ex;
  23. throw ex;
  24. }
  25. catch (IOException ex) {
  26. failureCause = ex;
  27. throw ex;
  28. }
  29. catch (Throwable ex) {
  30. failureCause = ex;
  31. throw new NestedServletException("Request processing failed", ex);
  32. }
  33. finally {
  34. // Clear request attributes and reset thread-bound context.
  35. LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
  36. if (requestAttributes != null) {
  37. RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
  38. requestAttributes.requestCompleted();
  39. }
  40. if (logger.isTraceEnabled()) {
  41. logger.trace("Cleared thread-bound request context: " + request);
  42. }
  43. if (logger.isDebugEnabled()) {
  44. if (failureCause != null) {
  45. this.logger.debug("Could not complete request", failureCause);
  46. }
  47. else {
  48. this.logger.debug("Successfully completed request");
  49. }
  50. }
  51. if (this.publishEvents) {
  52. // Whether or not we succeeded, publish an event.
  53. long processingTime = System.currentTimeMillis() - startTime;
  54. this.webApplicationContext.publishEvent(
  55. new ServletRequestHandledEvent(this,
  56. request.getRequestURI(), request.getRemoteAddr(),
  57. request.getMethod(), getServletConfig().getServletName(),
  58. WebUtils.getSessionId(request), getUsernameForRequest(request),
  59. processingTime, failureCause));
  60. }
  61. }
  62. }

RequestContextListener在context初始化时通过requestInitialized函数向Threadlocal设置httprequest对象的代码:

  1. public void requestInitialized(ServletRequestEvent requestEvent) {
  2. if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
  3. throw new IllegalArgumentException(
  4. "Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
  5. }
  6. HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
  7. ServletRequestAttributes attributes = new ServletRequestAttributes(request);
  8. request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
  9. LocaleContextHolder.setLocale(request.getLocale());
  10. RequestContextHolder.setRequestAttributes(attributes);
  11. }
 
 

springmvc中request的线程安全问题的更多相关文章

  1. springMVC中 request请求数据绑定到Controller入参 过程剖析

    前言:Controller方法的参数类型可以是基本类型,也可以是封装后的普通Java类型.若这个普通Java类型没有声明任何注解,则意味着它的每一个属性都需要到Request中去查找对应的请求参数.众 ...

  2. hibernate中session的线程安全问题

    Hibernate的基本特征是完成面向对象的程序设计语言到关系数据库的映射,在Hibernate中使用持久化对象PO(Persistent Object)完成持久化操作,对PO的操作必须在Sessio ...

  3. Java中如何解决线程安全问题

    给出一个问题,如下: 解决方案如下: public class Demo_5 { public static void main(String[] args) { //创建一个窗口 TicketWin ...

  4. Java 中线程安全问题

    不好意思,一个国庆假期给我放的都不知道东西南北了,放松,很放松,差一点就弃更了,感谢那些催更的小伙伴们! 虽然没有更新,但是日常的学习还是有的,以后我尽量给大家分享一些通用知识,非技术. 但是本期还是 ...

  5. Java日期时间API系列4-----Jdk7及以前的日期时间类的线程安全问题

    1.Date类为可变的,在多线程并发环境中会有线程安全问题. (1)可以使用锁来处理并发问题. (2)使用JDK8  Instant 或 LocalDateTime替代. 2.Calendar的子类为 ...

  6. 聊聊Servlet、Struts1、Struts2以及SpringMvc中的线程安全

    前言 很多初学者,甚至是工作1-3年的小伙伴们都可能弄不明白?servlet Struts1 Struts2 springmvc 哪些是单例,哪些是多例,哪些是线程安全? 在谈这个话题之前,我们先了解 ...

  7. Servlet&comma; Struts2和SpringMVC 并发访问线程安全问题

    第一部分: Servlet不是线程安全的. 要解释Servlet为什么不是线程安全的,需要了解Servlet容器(即Tomcat)使如何响应HTTP请求的. 当Tomcat接收到Client的HTTP ...

  8. Spring中构造器、init-method、&commat;PostConstruct、afterPropertiesSet孰先孰后,自动注入发生时间以及单例多例的区别、SSH线程安全问题

    首先明白,spring的IOC功能需要是利用反射原理,反射获取类的无参构造方法创建对象,如果一个类没有无参的构造方法spring是不会创建对象的.在这里需要提醒一下,如果我们在class中没有显示的声 ...

  9. (转)聊聊Servlet、Struts1、Struts2以及SpringMvc中的线程安全

    前言 很多初学者,甚至是工作1-3年的小伙伴们都可能弄不明白?servlet Struts1 Struts2 springmvc 哪些是单例,哪些是多例,哪些是线程安全? 在谈这个话题之前,我们先了解 ...

随机推荐

  1. 单例模式(Singleton Pattern)

    意图 保证一个类仅有一个实例,并提供一个该实例的全局访问点 可将一个实例扩展到n个实例.限定某类最多只能创建n个实例. 双重锁定实现单例模式 C# public sealed class Single ...

  2. VirtualBox提示:错误,创建一个新任务失败&comma;被召者解决办法

    被召者 RC: REGDB_E_CLASSNOTREG (0x80040154) 目前有两种解决办法: 解决方法一: 打开命令窗口(快捷键:窗口键+R,输入cmd点击确定) 输入cd D:\Progr ...

  3. OpenSSH后门获取root密码及防范

    OpenSSH后门获取root密码及防范 相对于Windows操作系统,Linux操作系统的密码较难获取.而很多Linux服务器都配置了Openssh服务,在获取root权限的情况下,可以通过修改或者 ...

  4. mysql的小知识点(关于数据库的导入导出 对于windows)

    对于,一个存在的数据,我们该如何去打包成.sql属性的文件呢? 直接进行这两条语句: D:\Program Files\MySQL\mysql\bin>mysqldump -u root -p ...

  5. SQL查询(笔记2——实体查询)

    SQL查询(笔记2——实体查询) 二.实体查询 如果查询返回了某个数据表的全部数据列,且该数据表有对应的持久化类映射,我们就把查询结果转换成实体查询.将查询结果转换成实体,可以使用SQLQuery提供 ...

  6. PHP之CI框架架设错误--Only variable references should be returned by reference

    解决参考 http://www.rafalinux.com/ La búsqueda fue bastante infructuosa, y hasta hace un par de días, na ...

  7. Haxe UI框架StablexUI的使用备忘与心得(序)

    最近在手上的项目开发中,从原来的使用Sprite全手写UI,开始逐步使用StablexUI,感觉还是相当不错的,强大.高效.轻量.灵活,非常适应我当前的实际需求. 不过作为小种语言的一个小众第三方开源 ...

  8. bingo 跨action异步获取参数

    html(定时器模拟异步) <script> setTimeout(function(){ window.teacInfo = {a:1,b:2}; },2000);</script ...

  9. ArcEngine保存栅格数据至rastercatalog

    将栅格数据(IRasterDataset)直接保存到数据库中很常见,但是保存到栅格目录下就比较少见,好不容易才找到,在这里记录一下. public void saveRasterDs2Catalog( ...

  10. 移动App测试实战—专项测试

       我们在进行了手工的功能测试之后,也开发了一些自动化测试用例,并且做了性能测试之后,测试工作看似比较完整了.但是当我们的App在大量的用户那里被安装和使用的时候,还是会有很多我们之前没有预料的问题 ...