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。

相关文章
|
2月前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
149 3
|
2月前
|
Java 测试技术 程序员
为什么Spring不推荐@Autowired用于字段注入?
作为Java程序员,Spring框架在日常开发中使用频繁,其依赖注入机制带来了极大的便利。然而,尽管@Autowired注解简化了依赖注入,Spring官方却不推荐在字段上使用它。本文将探讨字段注入的现状及其存在的问题,如难以进行单元测试、违反单一职责原则及易引发NPE等,并介绍为何Spring推荐构造器注入,包括增强代码可读性和维护性、方便单元测试以及避免NPE等问题。通过示例代码展示如何将字段注入重构为构造器注入,提高代码质量。
109 1
|
17天前
|
Java Spring
一键注入 Spring 成员变量,顺序编程
介绍了一款针对Spring框架开发的插件,旨在解决开发中频繁滚动查找成员变量注入位置的问题。通过一键操作(如Ctrl+1),该插件可自动在类顶部添加`@Autowired`注解及其成员变量声明,同时保持光标位置不变,有效提升开发效率和代码编写流畅度。适用于IntelliJ IDEA 2023及以上版本。
一键注入 Spring 成员变量,顺序编程
|
10天前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
24 4
|
28天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
108 2
|
2月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
2月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
65 2
|
2月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
72 1
|
2月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
199 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
3月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向