response线程安全详解

本文主要分析 HttpServletResponse 注入的线程安全问题

问 HttpServletResponse 是不是线程安全?

这个答案要针对具体的场景才能说明,接下来用3个场景演示 HttpServletResponse 是不是线程安全这个疑问

方法注入

先来看一段例子:

    @RequestMapping("methodInject")
    public void methodInject(HttpServletResponse myResponse) throws IOException {
        myResponse.getWriter().println("methodInject");
    }

看到这里忍不住就想问了: 放在参数里面的还能不是线程安全的吗?

线程安全是针对堆区,也就是类的实例字段。

放在栈的变量是不存在线程安不安全的问题的

本地保存

     private HttpServletResponse localResponse;

     @ModelAttribute
     public void local(HttpServletResponse response)  {
         this.localResponse = response;
     }

     @RequestMapping("localA")
     public void localA() throws IOException {
         this.localResponse .getWriter().println("localA");
     }

     @RequestMapping("localB")
     public void localB() throws IOException, InterruptedException {
         Thread.sleep(5000);
         this.localResponse .getWriter().println("localB");
     }

springmvc中提供了一个注解@ModelAttribute,在请求执行之前会执行这部分代码。

那么根据这个思路,可以我们是不是可以在BaseController里面封装HttpServletResponse,然后保存到成员变量

以后就可以直接使用了呢?

结果:

先访问localB,后访问localA。 请求localA返回: localB localA ,请求localB返回空

也就是说上面的猜想是不可行的,通过方法注入的 HttpServletResponse 不是线程安全的

实例字段注入

    @Autowired
    private HttpServletResponse response;

    @RequestMapping("memberA")
    public void memberA() throws IOException {
        response.getWriter().println("memberA");
    }

    @RequestMapping("memberB")
    public void memberB() throws IOException, InterruptedException {
        Thread.sleep(5000);
        response.getWriter().println("memberB");
    }

先访问memberB,后访问memberA. memberB等待5秒输出memberB,memberA输出memberA

对于这个结果感到特别意外。

为什么实例字段注入的就是线程安全的,而通过方法注入就不是线程安全的呢?

response实现类

分别断点在 memberA , methodInject 断点, 查看他们的实现类是什么

在方法注入时HttpServletResponse的实现类是ResponseFacade,不是一个ThreadLocal类型的变量,所以不是线程安全

在实例字段注入时,实现类是AutowireUtils.ObjectFactoryDelegatingInvocationHandler
他也不是一个线程安全的类,但是他是一个JDK动态代理的执行类,实现了InvocationHandler接口
最终的从requestAttributesHolder.get().getResponse()获得response, requestAttributesHolder是一个ThreadLocal.

接下来按照 response 通过 objectFactory.getObject() -> currentRequestAttributes().getResponse() 获得

currentRequestAttributes 通过 RequestContextHolder.currentRequestAttributes() -> requestAttributesHolder.get() 获得

requestAttributesHolder 定义是一个ThreadLocal类型的变量。 记录了HttpServletRequest,HttpServletResponse,等对象。

获取response 的详细代码见最下方的获取response

结论

  1. 通过 Autowired 注入的实例字段 HttpServletRequest,HttpServletResponse 都是线程安全的
  2. 通过方法注入的 HttpServletResponse 的实现类是 ResponseFacade
  3. 通过Autowired注入的 HttpServletResponse 的实现类是 AutowireUtils.ObjectFactoryDelegatingInvocationHandler

感谢好友 Docker 提出的疑问,才能总结出这篇文章

获取response

private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {

        private final ObjectFactory<?> objectFactory;

        public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
            this.objectFactory = objectFactory;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if (methodName.equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0]);
            }
            else if (methodName.equals("hashCode")) {
                // Use hashCode of proxy.
                return System.identityHashCode(proxy);
            }
            else if (methodName.equals("toString")) {
                return this.objectFactory.toString();
            }
            try {
            //objectFactory 实现类是 ResponseObjectFactory
                return method.invoke(this.objectFactory.getObject(), args);
            }
            catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }
    }
    private static class ResponseObjectFactory implements ObjectFactory<ServletResponse>, Serializable {

        @Override
        public ServletResponse getObject() {
            ServletResponse response = currentRequestAttributes().getResponse();
            if (response == null) {
                throw new IllegalStateException("Current servlet response not available - " +
                        "consider using RequestContextFilter instead of RequestContextListener");
            }
            return response;
        }

        @Override
        public String toString() {
            return "Current HttpServletResponse";
        }
    }
    public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
        RequestAttributes attributes = getRequestAttributes();
        if (attributes == null) {
            if (jsfPresent) {
                attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
            }
            if (attributes == null) {
                throw new IllegalStateException("No thread-bound request found: " +
                        "Are you referring to request attributes outside of an actual web request, " +
                        "or processing a request outside of the originally receiving thread? " +
                        "If you are actually operating within a web request and still receive this message, " +
                        "your code is probably running outside of DispatcherServlet/DispatcherPortlet: " +
                        "In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
            }
        }
        return attributes;
    }
    private static ServletRequestAttributes currentRequestAttributes() {
        RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
        if (!(requestAttr instanceof ServletRequestAttributes)) {
            throw new IllegalStateException("Current request is not a servlet request");
        }
        return (ServletRequestAttributes) requestAttr;
    }
    @Nullable
    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = requestAttributesHolder.get();
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值