SpringMVC中出現的執行緒安全問題分析

cmazxiaoma發表於2018-08-27

(ps:前幾個星期發生的事情)之前同事跟我說不要使用@Autowired方式注入HttpServletRequest(ps:我們的程式碼之前用的是第2種方式)。同事的意思大概是注入的HttpServletRequest物件是同一個而且存線上程安全問題。我保持質疑的態度,看了下原始碼,證明了@Autowired方式不存線上程安全問題,而@ModelAttribute方式存線上程安全問題。

觀看本文章之前,最好看一下我上一篇寫的文章:

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")
@RestController
public 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=78
base.request.name=20
base.request.name=76
base.request.name=49
base.request.name=82
base.request.name=12
base.request.name=80
base.request.name=91
base.request.name=92
base.request.name=30
base.request.name=28
base.request.name=36
base.request.name=41
base.request.name=73
base.request.name=29
base.request.name=2
base.request.name=81
base.request.name=43
base.request.name=35
base.request.name=22
base.request.name=6
base.request.name=27
base.request.name=17
base.request.name=70
base.request.name=65
base.request.name=84
base.request.name=14
base.request.name=54
base.request.name=67
base.request.name=19
base.request.name=21
base.request.name=66
base.request.name=11
base.request.name=53
base.request.name=9
base.request.name=72
base.request.name=64
base.request.name=0
base.request.name=44
base.request.name=89
base.request.name=77
base.request.name=48
base.request.name=1
base.request.name=8
base.request.name=74
base.request.name=46
base.request.name=88
base.request.name=26
base.request.name=24
base.request.name=62
base.request.name=61
base.request.name=51
base.request.name=96
base.request.name=33
base.request.name=45
base.request.name=5
base.request.name=95
base.request.name=68
base.request.name=60
base.request.name=56
base.request.name=42
base.request.name=57
base.request.name=10
base.request.name=55
base.request.name=90
base.request.name=47
base.request.name=97
base.request.name=40
base.request.name=85
base.request.name=86
base.request.name=69
base.request.name=98
base.request.name=13
base.request.name=32
base.request.name=37
base.request.name=4
base.request.name=23
base.request.name=50
base.request.name=38
base.request.name=59
base.request.name=99
base.request.name=71
base.request.name=25
base.request.name=58
base.request.name=34
base.request.name=7
base.request.name=93
base.request.name=31
base.request.name=3
base.request.name=39
base.request.name=75
base.request.name=94
base.request.name=83
base.request.name=63
base.request.name=79
base.request.name=16
base.request.name=52
base.request.name=15
base.request.name=87
base.request.name=18
複製程式碼

很明顯發現base.request.name的值存在null或者重複的現象,說明通過@ModelAttribute注入的HttpServletRequest存線上程安全問題。

base.request.name=97
base.request.name=59
base.request.name=63
base.request.name=14
base.request.name=82
base.request.name=49
base.request.name=86
base.request.name=13
base.request.name=99
base.request.name=29
base.request.name=45
base.request.name=85
base.request.name=8
base.request.name=35
base.request.name=69
base.request.name=70
base.request.name=16
base.request.name=21
base.request.name=74
base.request.name=20
base.request.name=34
base.request.name=23
base.request.name=96
base.request.name=19
base.request.name=67
base.request.name=15
base.request.name=27
base.request.name=43
base.request.name=39
base.request.name=47
base.request.name=87
base.request.name=71
base.request.name=41
base.request.name=38
base.request.name=null
base.request.name=31
base.request.name=32
base.request.name=76
base.request.name=55
base.request.name=75
base.request.name=93
base.request.name=null
base.request.name=56
base.request.name=1
base.request.name=18
base.request.name=89
base.request.name=65
base.request.name=10
base.request.name=78
base.request.name=null
base.request.name=80
base.request.name=24
base.request.name=88
base.request.name=88
base.request.name=44
base.request.name=53
base.request.name=58
base.request.name=61
base.request.name=60
base.request.name=37
base.request.name=92
base.request.name=42
base.request.name=11
base.request.name=68
base.request.name=72
base.request.name=91
base.request.name=79
base.request.name=33
base.request.name=66
base.request.name=54
base.request.name=40
base.request.name=94
base.request.name=46
base.request.name=83
base.request.name=17
base.request.name=64
base.request.name=26
base.request.name=90
base.request.name=7
base.request.name=62
base.request.name=57
base.request.name=73
base.request.name=98
base.request.name=30
base.request.name=6
base.request.name=2
base.request.name=28
base.request.name=5
base.request.name=95
base.request.name=9
base.request.name=3
base.request.name=51
base.request.name=4
base.request.name=52
base.request.name=12
base.request.name=25
base.request.name=36
base.request.name=84
base.request.name=81
base.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.註冊需要忽略的依賴介面ServletContextAwareServletConfigAware。 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);
	}
複製程式碼
  1. 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;
	}
複製程式碼
  1. 很明顯,對我們的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);
		}
	}
複製程式碼
  1. 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");
複製程式碼
  1. 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

相關文章