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接口:
- public interface HttpRequest {
- public void service();
- }
HttpRequestImpl类:对httprequest接口的具体实现,为了区别不同的HttpRequestImpl对象,本人为HttpRequestImpl设置了一个Double对象,如果不设置该对象,其默认为null
- public class HttpRequestImpl implements HttpRequest{
- public Double d;
- @Override
- public void service() {
- System.out.println("do some serivce, random value is "+d);
- }
- }
ThreadLocalTest类:负责向ThreadLocal设置对象和获取对象,本人设置ThreadLocal对象为static,因此ThreadLocalTest类中只能有一个ThreadLocal对象。
- public class ThreadLocalTest {
- public static ThreadLocal<HttpRequest> local=new ThreadLocal<HttpRequest>();
- public static void set(HttpRequest f){
- if(get()==null){
- System.out.println("ThreadLocal is null");
- local.set(f);
- }
- }
- public static HttpRequest get(){
- return local.get();
- }
- }
Factory类:该类是一个工厂类并且是单例模式,主要负责向ThreadLocalTest对象中设置和获取对象
- public class Factory{
- private static Factory factory=new Factory();
- private Factory(){
- }
- public static Factory getInstance(){
- return factory;
- }
- public HttpRequest getObject(){
- return (HttpRequest)ThreadLocalTest.get();
- }
- public void setObject(HttpRequest request){
- ThreadLocalTest.set(request);
- }
- }
Delegate类:该类实现了InvocationHandler接口,并实现了invoke方法
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- public class Delegate implements InvocationHandler{
- private Factory factory;
- public Factory getFactory() {
- return factory;
- }
- public void setFactory(Factory factory) {
- this.factory = factory;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- return method.invoke(this.factory.getObject(), args);
- }
- }
ProxyUtils类:该类是一个工具类,负责生成一个httprequest对象的代理
- import java.lang.reflect.Proxy;
- public class ProxyUtils {
- public static HttpRequest getRequest(){
- HttpRequest request=new HttpRequestImpl();
- Delegate delegate=new Delegate();
- delegate.setFactory(Factory.getInstance());
- HttpRequest proxy=(HttpRequest) Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), delegate);
- return proxy;
- }
- }
TestThread类:该类用来模拟多线程调用controller的情况,类中拥有一个静态对象request。
- public class TestThread implements Runnable{
- private static HttpRequest request;
- public void init(){
- HttpRequestImpl requestimpl=new HttpRequestImpl();
- requestimpl.d=Math.random();
- Factory.getInstance().setObject(requestimpl);
- }
- @Override
- public void run() {
- System.out.println("*********************");
- init();
- request.service();
- System.out.println("*********************");
- }
- public static HttpRequest getRequest() {
- return request;
- }
- public static void setRequest(HttpRequest request) {
- TestThread.request = request;
- }
- }
main:测试类
- public class main {
- /**
- * @param args
- */
- public static void main(String[] args) {
- HttpRequest request=ProxyUtils.getRequest();
- TestThread thread1=new TestThread();
- thread1.setRequest(request);
- TestThread thread2=new TestThread();
- thread2.setRequest(request);
- Thread t1=new Thread(thread1);
- Thread t2=new Thread(thread2);
- t1.start();
- t2.start();
- }
- }
thread1和thread2设置了同一个request对象,正常来说这两个对象调用run方法时输出的随机值应该为null(因为设置给这两个对象的request并没有设置d的值)。然而事实上这两个线程在调用时不但输出了随机值而且随机值还各不相同。这是因为request对象设置了代理,当调用request对象的service方法时,代理对象会从Threadlocal中获取实际的request对象以替代调用当前的request对象。由于httprequest对象在处理线程中保持不变,因此controller通过调用httprequest对象的方法能够获取当前请求的参数。
以上都是一家之言,下面将通过展现springmvc源码的形式证明以上的说法:
ObjectFactoryDelegatingInvocationHandler类:该类是AutowireUtils的一个私有类,该类拦截了除了equals、hashcode以及toString以外的其他方法,其中的objectFactory是RequestObjectFactory实例。
- private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
- private final ObjectFactory objectFactory;
- public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) {
- this.objectFactory = objectFactory;
- }
- 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 {
- return method.invoke(this.objectFactory.getObject(), args);
- }
- catch (InvocationTargetException ex) {
- throw ex.getTargetException();
- }
- }
- }
RequestObjectFactory类:其中currentReuqestAttributes负责从Threadlocal中获取对象
- private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
- public ServletRequest getObject() {
- return currentRequestAttributes().getRequest();
- }
- @Override
- public String toString() {
- return "Current HttpServletRequest";
- }
- }
既然需要从Threadlocal中获取对象,那springmvc在何时向Threadlocal设置了该对象呢?分别在如下两个类中完成:RequestContextListener和FrameworkServlet。RequestContextListener负责监听servletcontext,当servletcontext启动时,RequestContextListener向Threadlocal设置了httprequest对象。FrameworkServlet是DispatchServlet的基类,tomcat会在运行过程中启动新的线程,而该线程中并没有httprequest对象。因此servlet会在每次处理http请求的时候检验当前的Threadlocal中是否有httprequest对象,如果没有则设置该对象。
FrameworkServlet通过布尔值previousRequestAttributes检验httprequest是否存在的代码:
- protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- long startTime = System.currentTimeMillis();
- Throwable failureCause = null;
- // Expose current LocaleResolver and request as LocaleContext.
- LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
- LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
- // Expose current RequestAttributes to current thread.
- RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
- ServletRequestAttributes requestAttributes = null;
- if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
- requestAttributes = new ServletRequestAttributes(request);
- RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
- }
- if (logger.isTraceEnabled()) {
- logger.trace("Bound request context to thread: " + request);
- }
- try {
- doService(request, response);
- }
- catch (ServletException ex) {
- failureCause = ex;
- throw ex;
- }
- catch (IOException ex) {
- failureCause = ex;
- throw ex;
- }
- catch (Throwable ex) {
- failureCause = ex;
- throw new NestedServletException("Request processing failed", ex);
- }
- finally {
- // Clear request attributes and reset thread-bound context.
- LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
- if (requestAttributes != null) {
- RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
- requestAttributes.requestCompleted();
- }
- if (logger.isTraceEnabled()) {
- logger.trace("Cleared thread-bound request context: " + request);
- }
- if (logger.isDebugEnabled()) {
- if (failureCause != null) {
- this.logger.debug("Could not complete request", failureCause);
- }
- else {
- this.logger.debug("Successfully completed request");
- }
- }
- if (this.publishEvents) {
- // Whether or not we succeeded, publish an event.
- long processingTime = System.currentTimeMillis() - startTime;
- this.webApplicationContext.publishEvent(
- new ServletRequestHandledEvent(this,
- request.getRequestURI(), request.getRemoteAddr(),
- request.getMethod(), getServletConfig().getServletName(),
- WebUtils.getSessionId(request), getUsernameForRequest(request),
- processingTime, failureCause));
- }
- }
- }
RequestContextListener在context初始化时通过requestInitialized函数向Threadlocal设置httprequest对象的代码:
- public void requestInitialized(ServletRequestEvent requestEvent) {
- if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
- throw new IllegalArgumentException(
- "Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
- }
- HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
- ServletRequestAttributes attributes = new ServletRequestAttributes(request);
- request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
- LocaleContextHolder.setLocale(request.getLocale());
- RequestContextHolder.setRequestAttributes(attributes);
- }
springmvc中request的线程安全问题的更多相关文章
- springMVC中 request请求数据绑定到Controller入参 过程剖析
前言:Controller方法的参数类型可以是基本类型,也可以是封装后的普通Java类型.若这个普通Java类型没有声明任何注解,则意味着它的每一个属性都需要到Request中去查找对应的请求参数.众 ...
- hibernate中session的线程安全问题
Hibernate的基本特征是完成面向对象的程序设计语言到关系数据库的映射,在Hibernate中使用持久化对象PO(Persistent Object)完成持久化操作,对PO的操作必须在Sessio ...
- Java中如何解决线程安全问题
给出一个问题,如下: 解决方案如下: public class Demo_5 { public static void main(String[] args) { //创建一个窗口 TicketWin ...
- Java 中线程安全问题
不好意思,一个国庆假期给我放的都不知道东西南北了,放松,很放松,差一点就弃更了,感谢那些催更的小伙伴们! 虽然没有更新,但是日常的学习还是有的,以后我尽量给大家分享一些通用知识,非技术. 但是本期还是 ...
- Java日期时间API系列4-----Jdk7及以前的日期时间类的线程安全问题
1.Date类为可变的,在多线程并发环境中会有线程安全问题. (1)可以使用锁来处理并发问题. (2)使用JDK8 Instant 或 LocalDateTime替代. 2.Calendar的子类为 ...
- 聊聊Servlet、Struts1、Struts2以及SpringMvc中的线程安全
前言 很多初学者,甚至是工作1-3年的小伙伴们都可能弄不明白?servlet Struts1 Struts2 springmvc 哪些是单例,哪些是多例,哪些是线程安全? 在谈这个话题之前,我们先了解 ...
- Servlet, Struts2和SpringMVC 并发访问线程安全问题
第一部分: Servlet不是线程安全的. 要解释Servlet为什么不是线程安全的,需要了解Servlet容器(即Tomcat)使如何响应HTTP请求的. 当Tomcat接收到Client的HTTP ...
- Spring中构造器、init-method、@PostConstruct、afterPropertiesSet孰先孰后,自动注入发生时间以及单例多例的区别、SSH线程安全问题
首先明白,spring的IOC功能需要是利用反射原理,反射获取类的无参构造方法创建对象,如果一个类没有无参的构造方法spring是不会创建对象的.在这里需要提醒一下,如果我们在class中没有显示的声 ...
- (转)聊聊Servlet、Struts1、Struts2以及SpringMvc中的线程安全
前言 很多初学者,甚至是工作1-3年的小伙伴们都可能弄不明白?servlet Struts1 Struts2 springmvc 哪些是单例,哪些是多例,哪些是线程安全? 在谈这个话题之前,我们先了解 ...
随机推荐
- 每日Scrum--No.7
Yesterday:学习和设计路线的编程 Today:编写代码 Problem:.在设计查询参观路线的时候,整个逻辑特别的混乱,设想了各种树,图以及网的遍历问题,但经过多次与同学的交流以及网上的查询资 ...
- 【MySQL】MySQL忘记root密码解决方案
转眼间从实习到现在已经快两年了.两年的工作做遇到过很多很多的拦路虎,大部分也通过搜索引擎找到了解决的方案.奈何大脑不是硬盘,偶尔有的问题第二次遇到还是有点抓蒙...所以决定把这些东西记录在博客上.这样 ...
- MySQL的replace函数的用法
REPLACE(field,find_str,replace_str): 字段field的内容中的find_str 将被 替换为 replace_str . 例如: update short_url ...
- 烂泥:KVM使用裸设备配置虚拟机
本文由秀依林枫提供友情赞助,首发于烂泥行天下. 何谓裸设备?百度百科显示: 裸设备(raw device),也叫裸分区(原始分区),是一种没有经过格式化,不被Unix通过文件系统来读取的特殊块设备文件 ...
- Linux 之创建工作目录-mkdir
在Linux下创建工作目录,一般使用 "mkdir" 指令,一下将介绍"mkdir"指令的使用方法,供大家参考. 一.使用帮助 在Linux终端(命令行)输入: ...
- linux进程间通信-有名管道(FIFO)
有名管道(FIFO) 命名管道也被称为FIFO文件,是一种特殊的文件.由于linux所有的事物都可以被视为文件,所以对命名管道的使用也就变得与文件操作非常统一. (1)创建命名管道 用如下两个函数中的 ...
- Linux下的C之2048
#include <stdio.h> #include <stdlib.h> #include <curses.h> #include <time.h> ...
- 首个攻击该Mac OS系统的恶意软件——KeRanger
首个攻击该Mac OS系统的恶意软件——KeRanger 曾几何时,苹果操作系统一度被人认为是最安全的操作系统.然而近几年,针对苹果系统的攻击日益增多,影响范围也越来越大.无独有偶,近日,苹果Mac ...
- HDU 1556 Color the ball
这题用线段树的话简直就是一个水题..不过刚学树状数组,要用一下. 题意:每次给你a,b,表明a~b之间涂色,然后最后一次输出每个气球被涂色的次数. 要用树状数组就要考虑怎么转化为前缀和问题,这题可以这 ...
- Unity-WIKI 之 DebugConsole
功能预览 使用说明 1.把 DebugConsole.cs 放在 Standard Assets 目录下(重要) 2.如果你想修改 DebugConsole的一些参数,把DebugConsole.cs ...