Spring注入的成员属性HttpServletRequest是线程安全的吗?【享学Spring MVC】(上)

简介: Spring注入的成员属性HttpServletRequest是线程安全的吗?【享学Spring MVC】(上)

前言


我们知道一个Http请求就是一个Request对象,Servlet规范中使用HttpServletRequest来表示一个Http请求。然而在Spring MVC中,官方并不建议你直接使用Servlet源生的API,如常见的HttpServletRequest/HttpServletResponse等,因为官方认为Servlet技术只是web的落地实现之一,它并不希望你使用具体API而和某项技术耦合,比如从Spring 5.0开始就出现了web的另一种实现方式:Reactive,它让Servlet技术从之前的必选项变成了可选项。


可即便如此,在日常开发中我们还是希望能得到表示一个请求的HttpServletRequest实例,Spring MVC也考虑到了这种诉求的“合理性”,所以获取起来其实也非常的方便。


正文


在讨论如题的疑问前,先简单的了解下Spring MVC有哪些方式可以得到一个HttpServletRequest,也就是每个请求都能对应一个HttpServletRequest。


得到HttpServletRequest的三种方式


粗略的统计一下,在Spring MVC中直接得到HttpServletRequest的方式有三种。


方式一:方法参数


在Controller的方法参数上写上HttpServletRequest,这样每次请求过来得到就是对应的HttpServletRequest喽。


@GetMapping("/test/request")
public Object testRequest(HttpServletRequest request) {
    System.out.println(request.getClass());
    return "success";
}


访问接口,控制台输出:该类属于Servlet自己的实现类,一切正常。

class org.apache.catalina.connector.RequestFacade



据我统计,使用这种方式获取每次请求对象实例是最多的,同时我认为它也是相对来说最为“低级”的一种方式。


想想你的Controller里有10个方法需要得到HttpServletRequest,20个?30个呢?会不会疯掉?


方式二:从RequestContextHolder上下文获取


注意:必须强转为ServletRequestAttributes才能获取到HttpServletRequest,毕竟它属于Servlet专用的API,需要专用的Attr来获取。


@GetMapping("/test/request")
public Object testRequest(HttpServletRequest request) {
  // 从请求上下文里获取Request对象
    ServletRequestAttributes requestAttributes = ServletRequestAttributes.class.cast(RequestContextHolder.getRequestAttributes());
    HttpServletRequest contextRequest = requestAttributes.getRequest();
    System.out.println(contextRequest.getClass());
    // 比较两个是否是同一个实例
    System.out.println(contextRequest == request);
    return "success";
}


请求接口,控制台输出:

class org.apache.catalina.connector.RequestFacade
true


需要注意的是,第二个输出的是true哦,证明从请求上下文里获取出来的是和方式一是同一个对象。


使用这种方式的唯一优点:在Service层,甚至Dao层需要HttpServletRequest对象的话比较方便,而不是通过方法参数传过来,更不优雅。


说明:虽然并不建议,甚至是禁止HttpServletRequest进入到Service甚至Dao层,但是万一有这种需求,请使用这种方式把而不要放在方法参数上传参了,很low的有木有。


它的缺点还是比较明显的:代码太长了,就为了获取个请求实例而已写这么多代码,有点小题大做了。况且若是10处要这个实例呢?岂不也要疯掉。当然你可以采用BaseController的方案试图缓解一下这个现象,形如这样:


public abstract class BaseController {
  public HttpServletRequest getRequest() {
    return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  }
  public HttpServletResponse getResponse() {
    return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
  }
    public HttpSession getSession() {
    return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession();
  }
}


方式三:依赖注入@Autowired


这种方式是最为优雅的获取方式,也是本文将要讲述的重点。

@Autowired
HttpServletRequest requestAuto;
@GetMapping("/test/request")
public Object testRequest(HttpServletRequest request) {
    System.out.println(requestAuto.getClass());
    System.out.println(requestAuto == request);
    return "success";
}


访问接口,打印:

class com.sun.proxy.$Proxy70
false



有没有觉得很奇怪:@Autowired注入进来的竟然是个JDK动态代理对象,当然这确是它保证线程安全的关键点之一。


使用这种方式获取HttpServletRequest为最优雅方式,推荐使用,这样你有再多方法需要都不用怕了,书写一次即可。

当然喽,用这种方式的选手少之又少,原因很简单:Controller是单例的,多疑成员属性线程不安全,会有线程安全问题。对自己掌握的知识不自信,从而导致不敢使用这是最直接的原因。


方式四:使用@ModelAttribute(错误方式)


这里特别演示一种错误方式:使用@ModelAttribute来获取HttpServletRequest实例,形如这样:

private HttpServletRequest request; 
@ModelAttribute
public void bindRequest(HttpServletRequest request) {
    this.request = request; 
}


请注意:这么做是100%不行的,因为线程不安全。虽然每次请求进来都会执行一次bindRequest()方法得到一个新的request实例,但是**成员属性request**它是所有线程共享的,所以这么做是绝对线程不安全的,请各位小伙伴注意喽。


依赖注入@Autowired方式是线程安全的吗?


作为一个有技术敏感性的程序员,你理应提出这样的质疑:


  • Spring MVC中的@Controller默认是单例的,其成员变量是在初始化时候就赋值完成了,就不会再变了
  • 而对于每一次请求,HttpServletRequest理应都是不一样的,否则不就串了吗


既然不可能在每次请求的时候给成员变量重新赋值(即便是这样也无法保证线程安全呀),那么到底什么什么原因使得这种方式靠谱呢?这一切的谜底都在它是个JDK动态代理对象上。


@Autowired与代理对象


这里其实设计到Spring依赖注入的原理解读,但很显然此处不会展开(有兴趣的朋友可出门左拐,我博客有不少相关文章),直接通过现象反推到结论:所有的@Autowired进来的JDK动态代理对象的InvocationHandler处理器均为AutowireUtils.ObjectFactoryDelegatingInvocationHandler。


AutowireUtils:
  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")) {
        return (proxy == args[0]);
      } else if (methodName.equals("hashCode")) {
        return System.identityHashCode(proxy);
      } else if (methodName.equals("toString")) {
        return this.objectFactory.toString();
      }
      // 执行目标方法。注意:目标实例对象是objectFactory.getObject()
      try {
        return method.invoke(this.objectFactory.getObject(), args);
      } catch (InvocationTargetException ex) {
        throw ex.getTargetException();
      }
    }
  }


该InvocationHandler处理器实现其实很“简陋”,最关键的点在于:最终invoke调用的实例是来自于objectFactory.getObject(),而这里使用的ObjectFactory是:WebApplicationContextUtils.RequestObjectFactory。

相关文章
|
30天前
|
缓存 前端开发 Java
Spring MVC 面试题及答案整理,最新面试题
Spring MVC 面试题及答案整理,最新面试题
85 0
|
29天前
|
SQL JavaScript Java
springboot+springm vc+mybatis实现增删改查案例!
springboot+springm vc+mybatis实现增删改查案例!
23 0
|
29天前
|
SQL Java 数据库连接
挺详细的spring+springmvc+mybatis配置整合|含源代码
挺详细的spring+springmvc+mybatis配置整合|含源代码
35 1
|
7天前
|
数据采集 前端开发 Java
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
21 3
|
7天前
|
存储 前端开发 Java
会话锦囊:揭示Spring MVC如何巧妙使用@SessionAttributes
会话锦囊:揭示Spring MVC如何巧妙使用@SessionAttributes
13 1
|
7天前
|
前端开发 Java Spring
数据之桥:深入Spring MVC中传递数据给视图的实用指南
数据之桥:深入Spring MVC中传递数据给视图的实用指南
24 3
|
7天前
|
JSON Java 数据库连接
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
14 1
|
16天前
|
前端开发 安全 Java
使用Java Web框架:Spring MVC的全面指南
【4月更文挑战第3天】Spring MVC是Spring框架的一部分,用于构建高效、模块化的Web应用。它基于MVC模式,支持多种视图技术。核心概念包括DispatcherServlet(前端控制器)、HandlerMapping(请求映射)、Controller(处理请求)、ViewResolver(视图解析)和ModelAndView(模型和视图容器)。开发流程涉及配置DispatcherServlet、定义Controller、创建View、处理数据、绑定模型和异常处理。
使用Java Web框架:Spring MVC的全面指南
|
23天前
|
敏捷开发 监控 前端开发
Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构
Spring+SpringMVC+Mybatis的分布式敏捷开发系统架构
55 0