SpringMVC原理--Controller线程安全

原文网址:SpringMVC原理--Controller线程安全_IT利刃出鞘的博客-CSDN博客

简介

        本文介绍SpringBoot中controller的线程安全性。

        主要分析注入request时的线程安全。结论是:注入request是线程安全的。

问题复现

代码

package com.example.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping
public class HelloController {
    @Autowired
    private HttpServletRequest request;

    @GetMapping("/test1")
    public String test1() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(request);
        return request.toString();
    }
}

线程安全性

线程安全(不是测出来的,是看代码得出的)

分析:在Spring中,Controller的scope是singleton(单例),也就是说在整个web系统中,只有一个TestController;但是其注入的request却是线程安全的。

测试:接口中睡眠,模拟多个请求同时处理

使用上边“代码示例”中的代码

Postman开两个窗口,都访问:http://localhost:8080/test1

结果:(这个结果很奇怪,只能去追寻源码。)

Current HttpServletRequest
Current HttpServletRequest

源码分析

简介

这么写是线程安全的,原因如下:

  1. 在Controller中注入的request是动态代理对象,ObjectFactoryDelegatingInvocationHandler的实例。
    1. 当我们调用成员域request的方法的时候其实是调用了objectFactory的getObject()对象的相关方法。这里的objectFactory是RequestObjectFactory.
  2. 请求刚进入springmvc的dispatcherServlet时会把request相关对象设置到RequestContextHolder的threadlocal中去。
  3. RequestObjectFactory的getObject其实是从RequestContextHolder的threadlocal中去取值的.

打断点

request的代理类:AutowireUtils$ObjectFactoryDelegatingInvocationHandler

在图中可以看出,request实际上是一个代理:代理的实现参见AutowireUtils的内部类ObjectFactoryDelegatingInvocationHandler:

/**
 * Reflective InvocationHandler for lazy access to the current target object.
 */
@SuppressWarnings("serial")
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 {
        // ……省略无关代码
        try {
            return method.invoke(this.objectFactory.getObject(), args); // 代理实现核心代码
        }
        catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

当我们调用request的方法method时,实际上是调用了由objectFactory.getObject()生成的对象的method方法;objectFactory.getObject()生成的对象才是真正的request对象。

工厂类:WebApplicationContextUtils$RequestObjectFactory

继续观察上图,发现objectFactory的类型为WebApplicationContextUtils的内部类RequestObjectFactory:

/**
 * Factory that exposes the current request object on demand.
 */
@SuppressWarnings("serial")
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
    @Override
    public ServletRequest getObject() {
        return currentRequestAttributes().getRequest();
    }
    @Override
    public String toString() {
        return "Current HttpServletRequest";
    }
}

其中,要获得request对象需要先调用currentRequestAttributes()方法获得RequestAttributes对象,该方法的实现如下:

/**
 * Return the current RequestAttributes instance as ServletRequestAttributes.
 */
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;
}

生成RequestAttributes对象的核心代码在类RequestContextHolder中,其中相关代码如下(省略了该类中的无关代码):

public abstract class RequestContextHolder {
    public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
        RequestAttributes attributes = getRequestAttributes();
        // 此处省略不相关逻辑…………
        return attributes;
    }
    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = requestAttributesHolder.get();
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<RequestAttributes>("Request attributes");
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
            new NamedInheritableThreadLocal<RequestAttributes>("Request context");
}

通过这段代码可以看出,生成的RequestAttributes对象是线程局部变量(ThreadLocal),因此request对象也是线程局部变量;这就保证了request对象的线程安全性。

另外需要注意的是,注入的必须是接口,才能保证是线程安全的:

Autowired#resolveAutowiringValue:

public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
    // 如果注入到的值为ObjectFactory类型(并且不是requiredType实例),就猪呢比下面的代理吧~~~
    if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
        ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
        // 若 autowiringValue 实现了Serializable 接口
        //   且requiredType接口(HttpServletRequest是接口,继承自ServletRequest)
        // 所以要注意,只有注入接口才是线程安全的,若注入实现类,线程就是不安全的(因为无法创建代理了) 
        if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
            // 创建出来的代理对象,才是最终要被注入进去的值====
            autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
                    new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
        }
        else {
            return factory.getObject();
        }
    }
    return autowiringValue;
}

其他网址

Spring中获取request的几种方法,及其线程安全性分析 - 编程迷思 - 博客园

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT利刃出鞘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值