从Spring源码分析@Autowired注入的request是否线程安全

最近看到这样一段代码,是关于在业务代码中操作HttpServletRequest的,如下:

@RestController
@RequestMapping("/user")
public class UserController {
	@Autowired
	HttpServletRequest request;

	@GetMapping("login")
	public void login() {
		Object xx = request.getSession().getAttribute("xx");
		request.getSession().setAttribute("xx", "xx");
	}
}

在UserController中,直接将HttpServletRequest设置为共享变量request,由@Autowired完成注入。
由于Tomcat是多线程处理请求的,意味着会有多个线程同时操作request,给我的第一感觉是:难道没有线程安全问题吗?

正是出于并发问题的考虑,所以我操作request时,一般都是手动从RequestContextHolder中获取的,如下:

HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();

因为RequestContextHolder内部是使用ThreadLocal来维护Request的,线程间隔离,所以不存在线程安全问题,这样使用是没有问题的。
那用@Autowired注入,有没有线程安全的问题呢?


@Autowired并发测试

实践是检验真理的唯一标准。
写了个测试程序,如下,用Jmeter开启多个线程并发请求,结果如下:
在这里插入图片描述

从控制台的输出结果可以看到,虽然request是共享变量,多线程在同时操作,但是Session是彼此隔离的,互不影响的,并没有串号。
是不是感觉很神奇???这有点不符合常理,Spring是如何做到这一点的呢?


源码跟踪

要想知道Spring底层是如何做到的,必须要看源码。当然了,没必要全看,可以Debug跟踪,只看重要的源码。
源码太多,这里就不贴了,笔者整理了一下调用链路,如下图:
在这里插入图片描述

我试着用语言描述一下,大概逻辑是:
通过@Autowired注入的Request对象,其实并非是原生的HttpServletRequest对象,而是由Spring通过JDK动态代理技术生成的一个代理对象。
代理对象只是一个空壳,本身不具备功能,所有的操作都让RequestObjectFactory.getObject()返回的对象去处理了。
RequestObjectFactory.getObject()底层就是从RequestContextHolder的ThreadLocal变量requestAttributesHolder获取的。

说白了,从代码上看似是多个线程并发操作一个共享变量request,其实Spring底层通过一个代理对象让客户端去操作了ThreadLocal中的request,
即每个线程都只操作自己的request,是线程隔离的,所以也就不存在并发安全问题了。

Spring的代码一层套一层,可能不是很好表述,为此笔者写了一个模拟程序,大致说明了【通过动态代理来让共享变量线程间隔离】的问题,大家可以参考下。

/**
 * @author: pch
 * @description: 模拟Spring注入HttpServletRequest的底层原理代码
 * @date: 2020/10/21
 **/
public class Demo {
	// 共享变量request
	static HttpServletRequest request;

	static {
		// 模拟Spring注入的过程,这里用静态代码块来完成赋值
		request = (HttpServletRequest) Proxy.newProxyInstance(HttpServletRequest.class.getClassLoader(),
				new Class[]{HttpServletRequest.class}, new RequestProxy());
	}

	public static void main(String[] args) {
		// 开启三个线程
		for (int i = 0; i < 3; i++) {
			new Thread(()->{
				initRequest();
				request.getSession().put("thread", Thread.currentThread().getName());
				try {
					// 为了效果明显,sleep一秒
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 结果正确,Session之间没有串号
				System.err.println(Thread.currentThread().getName() + ":" + request.getSession().get("thread"));
			}).start();
		}
	}

	// 模拟请求过来,初始化
	private static void initRequest() {
		//绑定request到ThreadLocal
		RequestHolder.setRequest(new MyHttpServletRequest());
	}
}

// Request真正的持有者
class RequestHolder{
	private static ThreadLocal<HttpServletRequest> holder = new ThreadLocal<>();

	static void setRequest(HttpServletRequest request){
		holder.set(request);
	}

	static HttpServletRequest getRequest(){
		return holder.get();
	}
}

// Request代理
class RequestProxy implements InvocationHandler {
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 代理对象中,从RequestHolder中获取request再去执行,代理对象本身只是一个简单包装
		return method.invoke(RequestHolder.getRequest(), args);
	}
}

// 模拟HttpServletRequest接口
interface HttpServletRequest {
	Map getSession();
}

// 实现
class MyHttpServletRequest implements HttpServletRequest{
	private Map map = new HashMap();

	@Override
	public Map getSession() {
		return map;
	}
}

尾巴

综上所述,由@Autowired注入的共享变量request并不存在线程安全问题,大家可以放心大胆的用,使用起来也很方便,不用写一长串代码了。

©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页