springboot获取Request的方式和线程是否安全讨论

打印 上一主题 下一主题

主题 1041|帖子 1041|积分 3123

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1.获取Request的三种方式

一、直接注入的方式(保举)

作为一名稍微欺压症的开发人员,只保举这种方式,因为这种直接注入的可以在controller、service等任何你想要注入的地方注入,轻易明白,这种方式后面将会继续讨论是否是线程安全。
  1. @RestController
  2. @RequestMapping("/request")
  3. @RequiredArgsConstructor  //通过构造器注入
  4. public class RequestController {
  5.         
  6.     private final HttpServletRequest request1;
  7.     @GetMapping("/test1")
  8.     public void test1() {
  9.         //注入后就可以直接使用
  10.         request1.setAttribute("userName","leakey");
  11.     }
  12. }
复制代码
二、在controller里面利用参数注入(不保举)

这种方式request是作为controller的参数注入进来的,优点就是直观,让人感觉到每个请求都是独立的request(硬夸),缺点是后面的service或者其他地方必要用request对象的时候,必要带着他到处跑,那么优秀的你,想必也不能忍受吧!
  1. @RestController
  2. @RequestMapping("/request")
  3. public class RequestController {
  4.     @GetMapping("/test2")
  5.     public void test2(HttpServletRequest request2){
  6.         request2.setAttribute("userName","leakey");
  7.     }
  8. }
复制代码
三、通过RequestContextHolder获取

这种方式也可以在任何地方获取Request,优点固然是方便,缺点稍微不那么直观
  1. @RestController
  2. @RequestMapping("/request")
  3. public class RequestController {
  4.     @GetMapping("/test3")
  5.     public void test3(){
  6.         ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  7.         HttpServletRequest request3 = servletRequestAttributes.getRequest();
  8.         request3.setAttribute("userName","leakey");
  9.     }
  10. }
复制代码
2.谈谈线程安全的问题

一、疑问?是否存在线程安全

一个小同伴看了我写的代码,问我第一种用注入的方式获取的request是否存在线程安全问题,毕竟这个controller是单例,注入进来的request对象是不是也有大概是单例呢?越说越冲动,仿佛要发现一个大bug了,我立马做了一个小实验来简朴验证下。
  1. @RestController
  2. @RequestMapping("/request")
  3. @RequiredArgsConstructor
  4. @Slf4j
  5. public class RequestController {
  6.     private final HttpServletRequest request1;
  7.     @GetMapping("/test1")
  8.     public void test1() throws InterruptedException {
  9.         request1.setAttribute("userName","leakey");
  10.         log.info("success to set attribute userName,I'll go to sheep for 10 seconds");
  11.         Thread.sleep(10000);
  12.     }
  13.     @GetMapping("/test2")
  14.     public void test2() {
  15.         log.info("I am another quest,I'll try to get attribute userName");
  16.         Object userName = request1.getAttribute("userName");
  17.         log.info("userName is " + userName);
  18.     }
  19. }
复制代码
先访问接口test1,在10秒内访问test2接口,日记如下:
  1. 2024-09-08T11:08:34.155+08:00  INFO 34680 --- [nio-8080-exec-2] c.l.learn.controller.RequestController   : success to set attribute userName,I'll go to sheep for 10 seconds
  2. 2024-09-08T11:08:43.036+08:00  INFO 34680 --- [nio-8080-exec-3] c.l.learn.controller.RequestController   : I am another quest,I'll try to get attribute userName
  3. 2024-09-08T11:08:43.036+08:00  INFO 34680 --- [nio-8080-exec-3] c.l.learn.controller.RequestController   : userName is null
复制代码
很清晰的看到 test2的请求是拿不到test1设置进去的内容的,以是这个时刻你就可以大胆一点,跟同事先打包票出问题了算我的!。
二、大胆假设,小心求证

我们先修改下代码结构,打个断点,如下:

我们发现注入的是一个WebApplicationContextUtils里面的内部类RequestObjectFactory,注意到这里有一个getObject方法,返回是一个ServletRequest对象,方法是怎样被调用的呢?我们继续分析。
  1.         @SuppressWarnings("serial")
  2.         private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
  3.                 @Override
  4.                 public ServletRequest getObject() {
  5.                         return currentRequestAttributes().getRequest();
  6.                 }
  7.                 @Override
  8.                 public String toString() {
  9.                         return "Current HttpServletRequest";
  10.                 }
  11.         }
复制代码
我们可以在断点的地方看出来注入的内容一个署理对象,为AutowireUtils下面的ObjectFactoryDelegatingInvocationHandler,当我们必要获取对象的时候,就会执行反射method.invoke()方法,也就是说实际上就是调用了getObject方法里面的currentRequestAttributes().getRequest()方法了
  1. private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
  2.                 private final ObjectFactory<?> objectFactory;
  3.                 ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
  4.                         this.objectFactory = objectFactory;
  5.                 }
  6.                 @Override
  7.                 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8.                         return switch (method.getName()) {
  9.                                 case "equals" -> (proxy == args[0]); // Only consider equal when proxies are identical.
  10.                                 case "hashCode" -> System.identityHashCode(proxy); // Use hashCode of proxy.
  11.                                 case "toString" -> this.objectFactory.toString();
  12.                                 default -> {
  13.                                         try {
  14.                                                 yield method.invoke(this.objectFactory.getObject(), args);
  15.                                         }
  16.                                         catch (InvocationTargetException ex) {
  17.                                                 throw ex.getTargetException();
  18.                                         }
  19.                                 }
  20.                         };
  21.                 }
  22.         }
复制代码
通过上面的分析,当我们必要执行request里面的方法的时候(好比执行getAttribute或者setAttribute),就会先执行currentRequestAttributes().getRequest()来获取对象,那我们继续看看currentRequestAttributes方法到底拿到什么?
  1. private static ServletRequestAttributes currentRequestAttributes() {
  2.                 RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
  3.                 if (!(requestAttr instanceof ServletRequestAttributes servletRequestAttributes)) {
  4.                         throw new IllegalStateException("Current request is not a servlet request");
  5.                 }
  6.                 return servletRequestAttributes;
  7.         }
复制代码
我们注意到currentRequestAttributes方法里面返回的是RequestContextHolder对象里面的requestAttributesHolder ,而且requestAttributesHolder 这个对象是用ThreadLocal包装起来的,也就是他是线程安全的
  1. /**
  2. * Holder class to expose the web request in the form of a thread-bound
  3. * {@link RequestAttributes} object. The request will be inherited
  4. * by any child threads spawned by the current thread if the
  5. * {@code inheritable} flag is set to {@code true}.
  6. */
  7. public abstract class RequestContextHolder {
  8.         private static final boolean jsfPresent =
  9.                         ClassUtils.isPresent("jakarta.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
  10.     //线程安全
  11.         private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
  12.                         new NamedThreadLocal<>("Request attributes");
  13.         private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
  14.                         new NamedInheritableThreadLocal<>("Request context");
复制代码
RequestContextHolder类里面有两个告急的对象,一个是requestAttributesHolder,别的一个inheritableRequestAttributesHolder,开发老手一看就知道,一个是线程安全对象,只对当前线程可见,别的一个是针对子线程也可见。
我们一起来阅读下RequestContextHolder上面的注释,“一个持有者类,用于以线程绑定RequestAttributes对象的情势暴露Web请求。如果可继承标志被设置为true,那么由当前线程产生的任何子线程都将继承这个请求”
看到源码,我们也就知道解释清晰第三种方法,通过RequestContextHolder获取request
总结下:这是一个线程安全获取request的方式,各人轻松利用吧!
欢迎各人遇到不懂的地方,也可以咨询,一起办理问题!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

反转基因福娃

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表