在spring的注解 @RequestMapping
之下能够直接获取 HttpServletRequest
来得到诸如request header等重要的请求信息:java
@Slf4j @RestController @RequestMapping("/test") public class TestController { private static final String HEADER = "app-version"; @RequestMapping(value = "/async", method = RequestMethod.GET) public void test(HttpServletRequest request) { request.getHeader(HEADER); } }
每每,这些重要的信息也会在异步线程中被使用到。因而,一个很天然的想法是,那不如直接把这里获取到的request当作参数传到其它spawn出的子线程里,好比:spring
@Slf4j @RestController @RequestMapping("/test") public class TestController { private static final String HEADER = "app-version"; @RequestMapping(value = "/async", method = RequestMethod.GET) public void test(HttpServletRequest request) { log.info("Main thread: " + request.getHeader(HEADER)); new Thread(() -> { log.info("Child thread: " + request.getHeader(HEADER)); }).start(); } }
在header中设置"app-version"为1.0.1后发送 <base_url>/test/async
请求,能够看到结果:安全
Main thread: 1.0.1Child thread: 1.0.1app
可是,坑,也就此出现了。框架
因为 HttpServletRequest
不是线程安全的(后知后觉),当主线程完成本身的工做返回response后,相应的诸如 HttpServletRequest
等对象就会被销毁。为了看到这个现象,咱们能够在子线程中多等待一段时间来保证主线程先于子线程结束。异步
@Slf4j @RestController @RequestMapping("/test") public class TestController { private static final String HEADER = "app-version"; private static final long CHILD_THREAD_WAIT_TIME = 5000; @RequestMapping(value = "/async", method = RequestMethod.GET) public void test(HttpServletRequest request) { log.info("Main thread: " + request.getHeader(HEADER)); new Thread(() -> { try { Thread.sleep(CHILD_THREAD_WAIT_TIME); } catch (Throwable e) { } log.info("Child thread: " + request.getHeader(HEADER)); }).start(); } }
在header中设置"app-version"为1.0.1后发送 <base_url>/test/async
请求,能够看到结果:async
Main thread: 1.0.1Child thread: nullurl
显然,谁也没办法保证本身spawn出来的子线程会先于主线程结束,因此直接传递 HttpServletRequest
参数给子线程是不可行的。spa
网上有一种方法是经过spring框架自带的 RequestContextHolder
来获取request,这对异步线程来说是不可行的。由于只有在负责request处理的线程才能调用到 RequestContextHolder
对象,其它线程中它会直接为空。线程
那么,一个能够想到的笨办法是将request的值取出来,注入到自定义的对象中,而后将这个对象做为参数传递给子线程:
@Slf4j @RestController @RequestMapping("/test") public class TestController { private static final String HEADER = "app-version"; private static final long MAIN_THREAD_WAIT_TIME = 0; private static final long CHILD_THREAD_WAIT_TIME = 5000; @RequestMapping(value = "/async", method = RequestMethod.GET) public void test(HttpServletRequest request) { log.info("Main thread: " + request.getHeader(HEADER)); TestVo testVo = new TestVo(request.getHeader(HEADER)); new Thread(() -> { try { Thread.sleep(CHILD_THREAD_WAIT_TIME); } catch (Throwable e) { } log.info("Child thread: " + request.getHeader(HEADER) + ", testVo = " + testVo.getAppVersion()); }).start(); try { Thread.sleep(MAIN_THREAD_WAIT_TIME); } catch (Throwable e) { } } @Data @AllArgsConstructor public static class TestVo { private String appVersion; } }
再按照"app-version"为1.0.1发送请求后获得:
Main thread: 1.0.1Child thread: null, testVo = 1.0.1
嗯,终于成功了。
故事彷佛到此就结束了,但若是仔细考察细节的话,有几个问题是值得思考的:
一个合理的推理是:主线程结束时,调用了一个 destroy()
方法,这个方法主动将 HttpServletRequest
中的资源释放,例如调用了存放header的map对应的 clear()
方法。如此,在子线程中便没法得到以前的"app-version"所对应的value了。而TestVo因为是用户本身建立,必然不可能实如今 destroy()
方法中写出释放资源的代码。它的值也就保存下来了。
另外,不管主线程是否调用了 destroy()
方法,真正回收的时候仍是GC的工做,这也就解释了在子线程中不是报null exception,而只是取不到特定的key所对应的值。
进一步,咱们还能够思考的问题是,为何在主线程的 destoy()
方法中,不直接将request对象赋值为null呢?
这个问题看似有些蹊跷,而实则根本不成立。由于就算你把主线程的request变量赋值为null时,子线程中的另外一个变量已经指向了这个request对应的内存,依旧能够拿到相应的值。例如:
@Slf4j @RestController @RequestMapping("/test") public class TestController { private static final String HEADER = "app-version"; private static final long MAIN_THREAD_WAIT_TIME = 5000; private static final long CHILD_THREAD_WAIT_TIME = 3000; @RequestMapping(value = "/async", method = RequestMethod.GET) public void test(HttpServletRequest request) { log.info("Main thread: " + request.getHeader(HEADER)); TestVo testVo = new TestVo(request); new Thread(() -> { try { Thread.sleep(CHILD_THREAD_WAIT_TIME); } catch (Throwable e) { } log.info("Child thread: " + testVo.getRequest().getHeader(HEADER)); }).start(); request = null; try { Thread.sleep(MAIN_THREAD_WAIT_TIME); } catch (Throwable e) { } } @Data @AllArgsConstructor public static class TestVo { private HttpServletRequest request; } }
按照"app-version"为1.0.1发送请求后获得:
Main thread: 1.0.1Child thread: 1.0.1
这里让子线程等待3秒,以便主线程有充分的时间将request赋值为null。但child线程依旧能够拿到对应的值。
因此,将request变量赋值为null根本没法作到释放资源。因此对request里保存header的map来说,将变量赋值为null没法保证其它地方的引用也会一并消失。最直接有效的方法是调用 clear()
让map中的每个元素失效。
因此总结起来是:
destroy()
方法被调用了内部map的 clear()
方法,致使没法获取到header的值。destroy()
方法中被释放,因此还能继续保持原有的值。