SpringMVC中出现的线程安全问题分析
(ps:前几个星期发生的事情)之前同事跟我说不要用@Autowired方式注入HttpServletRequest(ps:我们的代码之前使用的是第2种方式)。同事的意思大概是注入的HttpServletRequest对象是同一个而且存在线程安全问题。我保持质疑的态度,看了下源码,证实了@Autowired方式不存在线程安全问题,而@ModelAttribute方式存在线程安全问题。
观看本文章之前,最好看一下我上一篇写的文章:
1.通过循环引使用问题来分析Spring源码
2.你真的理解Spring MVC解决请求流程吗?
public abstract class BaseController { @Autowired protected HttpSession httpSession; @Autowired protected HttpServletRequest request;}public abstract class BaseController1 { protected HttpServletRequest request; protected HttpServletResponse response; protected HttpSession httpSession; @ModelAttribute public void init(HttpServletRequest request, HttpServletResponse response, HttpSession httpSession) { this.request = request; this.response = response; this.httpSession = httpSession; }}线程安全测试
@RequestMapping("/test")@RestControllerpublic class TestController extends BaseController { @GetMapping("/1") public void test1() throws InterruptedException {// System.out.println("thread.id=" + Thread.currentThread().getId());// System.out.println("thread.name=" + Thread.currentThread().getName());// ServletRequestAttributes servletRequestAttributes =// ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());//// HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest(); TimeUnit.SECONDS.sleep(10);// System.out.println("base.request=" + request); System.out.println("base.request.name=" + request.getParameter("name")); } @GetMapping("/2") public void test2() throws InterruptedException {// System.out.println("thread.id=" + Thread.currentThread().getId());// System.out.println("thread.name=" + Thread.currentThread().getName());// ServletRequestAttributes servletRequestAttributes =// ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());//// HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();// System.out.println("base.request=" + request); System.out.println("base.request.name=" + request.getParameter("name")); } @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); binder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, true)); }}通过JUC的CountDownLatch,模拟同一时刻100个并发请求。
public class Test { public static void main(String[] args) { CountDownLatch start = new CountDownLatch(1); CountDownLatch end = new CountDownLatch(100); CustomThreadPoolExecutor customThreadPoolExecutor = new CustomThreadPoolExecutor( 100, 100, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100) ); for (int i = 0; i < 100; i++) { final int finalName = i; CustomThreadPoolExecutor.CustomTask task = new CustomThreadPoolExecutor.CustomTask( new Runnable() { @Override public void run() { try { start.await(); HttpUtil.get("http://localhost:8081/test/2?name=" + finalName); } catch (Exception ex) { ex.printStackTrace(); } finally { end.countDown(); } } } , "success"); customThreadPoolExecutor.submit(task); } start.countDown(); try { end.await(); } catch (InterruptedException ex) { ex.printStackTrace(); } customThreadPoolExecutor.shutdown(); }}通过观看base.request.name的值并没有null值和存在值重复的现象,很一定的说@Autowired注入的HttpServletRequest不存在线程安全问题。
base.request.name=78base.request.name=20base.request.name=76base.request.name=49base.request.name=82base.request.name=12base.request.name=80base.request.name=91base.request.name=92base.request.name=30base.request.name=28base.request.name=36base.request.name=41base.request.name=73base.request.name=29base.request.name=2base.request.name=81base.request.name=43base.request.name=35base.request.name=22base.request.name=6base.request.name=27base.request.name=17base.request.name=70base.request.name=65base.request.name=84base.request.name=14base.request.name=54base.request.name=67base.request.name=19base.request.name=21base.request.name=66base.request.name=11base.request.name=53base.request.name=9base.request.name=72base.request.name=64base.request.name=0base.request.name=44base.request.name=89base.request.name=77base.request.name=48base.request.name=1base.request.name=8base.request.name=74base.request.name=46base.request.name=88base.request.name=26base.request.name=24base.request.name=62base.request.name=61base.request.name=51base.request.name=96base.request.name=33base.request.name=45base.request.name=5base.request.name=95base.request.name=68base.request.name=60base.request.name=56base.request.name=42base.request.name=57base.request.name=10base.request.name=55base.request.name=90base.request.name=47base.request.name=97base.request.name=40base.request.name=85base.request.name=86base.request.name=69base.request.name=98base.request.name=13base.request.name=32base.request.name=37base.request.name=4base.request.name=23base.request.name=50base.request.name=38base.request.name=59base.request.name=99base.request.name=71base.request.name=25base.request.name=58base.request.name=34base.request.name=7base.request.name=93base.request.name=31base.request.name=3base.request.name=39base.request.name=75base.request.name=94base.request.name=83base.request.name=63base.request.name=79base.request.name=16base.request.name=52base.request.name=15base.request.name=87base.request.name=18很显著发现base.request.name的值存在null或者者重复的现象,说明通过@ModelAttribute注入的HttpServletRequest存在线程安全问题。
base.request.name=97base.request.name=59base.request.name=63base.request.name=14base.request.name=82base.request.name=49base.request.name=86base.request.name=13base.request.name=99base.request.name=29base.request.name=45base.request.name=85base.request.name=8base.request.name=35base.request.name=69base.request.name=70base.request.name=16base.request.name=21base.request.name=74base.request.name=20base.request.name=34base.request.name=23base.request.name=96base.request.name=19base.request.name=67base.request.name=15base.request.name=27base.request.name=43base.request.name=39base.request.name=47base.request.name=87base.request.name=71base.request.name=41base.request.name=38base.request.name=nullbase.request.name=31base.request.name=32base.request.name=76base.request.name=55base.request.name=75base.request.name=93base.request.name=nullbase.request.name=56base.request.name=1base.request.name=18base.request.name=89base.request.name=65base.request.name=10base.request.name=78base.request.name=nullbase.request.name=80base.request.name=24base.request.name=88base.request.name=88base.request.name=44base.request.name=53base.request.name=58base.request.name=61base.request.name=60base.request.name=37base.request.name=92base.request.name=42base.request.name=11base.request.name=68base.request.name=72base.request.name=91base.request.name=79base.request.name=33base.request.name=66base.request.name=54base.request.name=40base.request.name=94base.request.name=46base.request.name=83base.request.name=17base.request.name=64base.request.name=26base.request.name=90base.request.name=7base.request.name=62base.request.name=57base.request.name=73base.request.name=98base.request.name=30base.request.name=6base.request.name=2base.request.name=28base.request.name=5base.request.name=95base.request.name=9base.request.name=3base.request.name=51base.request.name=4base.request.name=52base.request.name=12base.request.name=25base.request.name=36base.request.name=84base.request.name=81base.request.name=50源码分析
1.在Spring容器初始化中,refresh()方法会调使用postProcessBeanFactory(beanFactory);。它是个模板方法,在BeanDefinition被装载后(所有BeanDefinition被加载,但是没有bean被实例化),提供一个修改beanFactory容器的入口。这里还是贴下AbstractApplicationContext中的refresh()方法吧。
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1.Prepare this context for refreshing. prepareRefresh(); // 2.Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3.Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // 4.Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // 5.Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // 6.Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // 7.Initialize message source for this context. initMessageSource(); // 8.Initialize event multicaster for this context. initApplicationEventMulticaster(); // 9.Initialize other special beans in specific context subclasses. onRefresh(); //10. Check for listener beans and register them. registerListeners(); // 11.Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); //12. Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }2.因为postProcessBeanFactory是模板方法,它会被子类AbstractRefreshableWebApplicationContext重写。在AbstractRefreshableWebApplicationContext的postProcessBeanFactory()做以下几件事情。
1.注册ServletContextAwareProcessor。
2.注册需要忽略的依赖接口ServletContextAware、ServletConfigAware。
3.注册Web应使用的作使用域和环境配置信息。
@Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig)); beanFactory.ignoreDependencyInterface(ServletContextAware.class); beanFactory.ignoreDependencyInterface(ServletConfigAware.class); WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext); WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig); }- WebApplicationContextUtils中的registerWebApplicationScopes(),beanFactory注册了request,application,session,globalSession作使用域,也注册了需要处理的依赖:ServletRequest、ServletResponse、HttpSession、WebRequest。
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) { beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false)); beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true)); if (sc != null) { ServletContextScope appScope = new ServletContextScope(sc); beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); // Register as ServletContext attribute, for ContextCleanupListener to detect it. sc.setAttribute(ServletContextScope.class.getName(), appScope); } beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()); beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory()); beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory()); beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory()); if (jsfPresent) { FacesDependencyRegistrar.registerFacesDependencies(beanFactory); } }4.RequestObjectFactory, ResponseObjectFactory, SessionObjectFactory都实现了ObjectFactory的接口,注入的值其实是getObject()的值。
/** * Factory that exposes the current request object on demand. */ @SuppressWarnings("serial") private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable { @Override public ServletRequest getObject() { return currentRequestAttributes().getRequest(); } @Override public String toString() { return "Current HttpServletRequest"; } } /** * Factory that exposes the current response object on demand. */ @SuppressWarnings("serial") private static class ResponseObjectFactory implements ObjectFactory<ServletResponse>, Serializable { @Override public ServletResponse getObject() { ServletResponse response = currentRequestAttributes().getResponse(); if (response == null) { throw new IllegalStateException("Current servlet response not available - " + "consider using RequestContextFilter instead of RequestContextListener"); } return response; } @Override public String toString() { return "Current HttpServletResponse"; } } /** * Factory that exposes the current session object on demand. */ @SuppressWarnings("serial") private static class SessionObjectFactory implements ObjectFactory<HttpSession>, Serializable { @Override public HttpSession getObject() { return currentRequestAttributes().getRequest().getSession(); } @Override public String toString() { return "Current HttpSession"; } } /** * Factory that exposes the current WebRequest object on demand. */ @SuppressWarnings("serial") private static class WebRequestObjectFactory implements ObjectFactory<WebRequest>, Serializable { @Override public WebRequest getObject() { ServletRequestAttributes requestAttr = currentRequestAttributes(); return new ServletWebRequest(requestAttr.getRequest(), requestAttr.getResponse()); } @Override public String toString() { return "Current ServletWebRequest"; } }5.很显著,我们从getObject()中获取的值是从绑定当前线程的RequestAttribute中获取的,内部实现是通过ThreadLocal去完成的。看到这里,你应该明白了一点点。
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<RequestAttributes>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<RequestAttributes>("Request context"); private static ServletRequestAttributes currentRequestAttributes() { RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); if (!(requestAttr instanceof ServletRequestAttributes)) { throw new IllegalStateException("Current request is not a servlet request"); } return (ServletRequestAttributes) requestAttr; } public static RequestAttributes currentRequestAttributes() throws IllegalStateException { RequestAttributes attributes = getRequestAttributes(); if (attributes == null) { if (jsfPresent) { attributes = FacesRequestAttributesFactory.getFacesRequestAttributes(); } if (attributes == null) { throw new IllegalStateException("No thread-bound request found: " + "Are you referring to request attributes outside of an actual web request, " + "or processing a request outside of the originally receiving thread? " + "If you are actually operating within a web request and still receive this message, " + "your code is probably running outside of DispatcherServlet/DispatcherPortlet: " + "In this case, use RequestContextListener or RequestContextFilter to expose the current request."); } } return attributes; } public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; }6.我们再来捋一捋@Autowired注入HttpServletRequest对象的过程。这里以HttpServletRequest对象注入举例。首先调使用DefaultListableBeanFactory中的findAutowireCandidates()方法,判断autowiringType类型能否和requiredType类型一致或者者是autowiringType能否是requiredType的父接口(父类)。假如满足条件的话,我们会从resolvableDependencies中通过autowiringType(对应着上文的ServletRequest)拿到autowiringValue(对应着上文的RequestObjectFactory)。而后调使用AutowireUtils.resolveAutowiringValue()对我们的ObjectFactory进行解决。
protected Map<String, Object> findAutowireCandidates( String beanName, Class<?> requiredType, DependencyDescriptor descriptor) { String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this, requiredType, true, descriptor.isEager()); Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length); for (Class<?> autowiringType : this.resolvableDependencies.keySet()) { if (autowiringType.isAssignableFrom(requiredType)) { Object autowiringValue = this.resolvableDependencies.get(autowiringType); autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType); if (requiredType.isInstance(autowiringValue)) { result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); break; } } } for (String candidate : candidateNames) { if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) { addCandidateEntry(result, candidate, descriptor, requiredType); } } if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) { // Consider fallback matches if the first pass failed to find anything... DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); for (String candidate : candidateNames) { if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor)) { addCandidateEntry(result, candidate, descriptor, requiredType); } } if (result.isEmpty()) { // Consider self references as a final pass... // but in the case of a dependency collection, not the very same bean itself. for (String candidate : candidateNames) { if (isSelfReference(beanName, candidate) && (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) && isAutowireCandidate(candidate, fallbackDescriptor)) { addCandidateEntry(result, candidate, descriptor, requiredType); } } } } return result; }- 很显著,对我们的RequestObjectFactory进行了JDK动态代理商。原来我们通过@Autowired注入拿到的HttpServletRequest对象是代理商对象。
public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) { if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(), new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory)); } else { return factory.getObject(); } } return autowiringValue; } 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")) { // 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(); } } }8.我们再来看SpringMVC是怎样把HttpServletRequest对象放入到ThreadLocal中。当使用户发出请求后,会经过FrameworkServlet中的processRequest()方法做了少量骚操作,而后再交给子类DispatcherServlet中的doService()去解决这个请求。这些骚操作就包括把request,response对象包装成ServletRequestAttributes对象,而后放入到ThreadLocal中。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); 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 { resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } if (logger.isDebugEnabled()) { if (failureCause != null) { this.logger.debug("Could not complete request", failureCause); } else { if (asyncManager.isConcurrentHandlingStarted()) { logger.debug("Leaving response open for concurrent processing"); } else { this.logger.debug("Successfully completed request"); } } } publishRequestHandledEvent(request, response, startTime, failureCause); } }- buildRequestAttributes()方法将当前request和response对象包装成ServletRequestAttributes对象。initContextHolders()负责把RequestAttributes对象放入到requestAttributesHolder(ThreadLocal)中。一切真相大白。
protected ServletRequestAttributes buildRequestAttributes( HttpServletRequest request, HttpServletResponse response, RequestAttributes previousAttributes) { if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) { return new ServletRequestAttributes(request, response); } else { return null; // preserve the pre-bound RequestAttributes instance } } private void initContextHolders( HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) { if (localeContext != null) { LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable); } if (requestAttributes != null) { RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); } if (logger.isTraceEnabled()) { logger.trace("Bound request context to thread: " + request); } } public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) { if (attributes == null) { resetRequestAttributes(); } else { if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); } else { requestAttributesHolder.set(attributes); inheritableRequestAttributesHolder.remove(); } } } private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader()); private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<RequestAttributes>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<RequestAttributes>("Request context");- SpringMVC会优先执行被@ModelAttribute注解的方法。也就是说我们每一次请求,都会去调使用init()方法,对request,response,httpSession进行赋值操作,并发问题也由此产生。
private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container) throws Exception { while (!this.modelMethods.isEmpty()) { InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod(); ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class); if (container.containsAttribute(ann.name())) { if (!ann.binding()) { container.setBindingDisabled(ann.name()); } continue; } Object returnValue = modelMethod.invokeForRequest(request, container); if (!modelMethod.isVoid()){ String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType()); if (!ann.binding()) { container.setBindingDisabled(returnValueName); } if (!container.containsAttribute(returnValueName)) { container.addAttribute(returnValueName, returnValue); } } } }public abstract class BaseController1 { protected HttpServletRequest request; protected HttpServletResponse response; protected HttpSession httpSession; @ModelAttribute public void init(HttpServletRequest request, HttpServletResponse response, HttpSession httpSession) { this.request = request; this.response = response; this.httpSession = httpSession; }}尾言
大家好,我是cmazxiaoma(寓意是沉梦昂志的小马),希望和你们一起成长进步,感谢各位阅读本文章。
小弟不才。
假如您对这篇文章有什么意见或者者错误需要改进的地方,欢迎与我探讨。
假如您觉得还不错的话,希望你们可以点个赞。
希望我的文章对你能有所帮助。
有什么意见、见地或者疑惑,欢迎留言探讨。
最后送上:心之所向,素履以往。生如逆旅,一苇以航。
saoqi.png 上一篇 目录 已是最后
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » SpringMVC中出现的线程安全问题分析