SpringMVC中出現(xiàn)的線程安全問(wèn)題分析

(ps:前幾個(gè)星期發(fā)生的事情)之前同事跟我說(shuō)不要使用@Autowired方式注入HttpServletRequest(ps:我們的代碼之前用的是第2種方式)。同事的意思大概是注入的HttpServletRequest對(duì)象是同一個(gè)而且存在線程安全問(wèn)題爆捞。我保持質(zhì)疑的態(tài)度,看了下源碼,證明了@Autowired方式不存在線程安全問(wèn)題借宵,而@ModelAttribute方式存在線程安全問(wèn)題弧轧。

觀看本文章之前,最好看一下我上一篇寫(xiě)的文章:
1.通過(guò)循環(huán)引用問(wèn)題來(lái)分析Spring源碼
2.你真的了解Spring MVC處理請(qǐng)求流程嗎?

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;
    }
}

線程安全測(cè)試

@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));
    }
}

通過(guò)JUC的CountDownLatch斑芜,模擬同一時(shí)刻100個(gè)并發(fā)請(qǐng)求杀迹。

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();
    }
}

通過(guò)觀看base.request.name的值并沒(méi)有null值和存在值重復(fù)的現(xiàn)象,很肯定的說(shuō)@Autowired注入的HttpServletRequest不存在線程安全問(wèn)題押搪。

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

很明顯發(fā)現(xiàn)base.request.name的值存在null或者重復(fù)的現(xiàn)象树酪,說(shuō)明通過(guò)@ModelAttribute注入的HttpServletRequest存在線程安全問(wèn)題。

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()方法會(huì)調(diào)用postProcessBeanFactory(beanFactory);续语。它是個(gè)模板方法,在BeanDefinition被裝載后(所有BeanDefinition被加載厦画,但是沒(méi)有bean被實(shí)例化)疮茄,提供一個(gè)修改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是模板方法力试,它會(huì)被子類AbstractRefreshableWebApplicationContext重寫(xiě)徙邻。在AbstractRefreshableWebApplicationContext的postProcessBeanFactory()做以下幾件事情。

1.注冊(cè)ServletContextAwareProcessor畸裳。
2.注冊(cè)需要忽略的依賴接口ServletContextAware缰犁、ServletConfigAware
3.注冊(cè)Web應(yīng)用的作用域和環(huán)境配置信息怖糊。

    @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注冊(cè)了request,application,session,globalSession作用域,也注冊(cè)了需要解決的依賴: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都實(shí)現(xiàn)了ObjectFactory的接口麦乞,注入的值其實(shí)是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()中獲取的值是從綁定當(dāng)前線程的RequestAttribute中獲取的,內(nèi)部實(shí)現(xiàn)是通過(guò)ThreadLocal去完成的姐直。看到這里付翁,你應(yīng)該明白了一點(diǎn)點(diǎn)简肴。

    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.我們?cè)賮?lái)捋一捋@Autowired注入HttpServletRequest對(duì)象的過(guò)程。這里以HttpServletRequest對(duì)象注入舉例百侧。首先調(diào)用DefaultListableBeanFactory中的findAutowireCandidates()方法砰识,判斷autowiringType類型是否和requiredType類型一致或者是autowiringType是否是requiredType的父接口(父類)。如果滿足條件的話佣渴,我們會(huì)從resolvableDependencies中通過(guò)autowiringType(對(duì)應(yīng)著上文的ServletRequest)拿到autowiringValue(對(duì)應(yīng)著上文的RequestObjectFactory)辫狼。然后調(diào)用AutowireUtils.resolveAutowiringValue()對(duì)我們的ObjectFactory進(jìn)行處理。

    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. 很明顯辛润,對(duì)我們的RequestObjectFactory進(jìn)行了JDK動(dòng)態(tài)代理膨处。原來(lái)我們通過(guò)@Autowired注入拿到的HttpServletRequest對(duì)象是代理對(duì)象。
    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.我們?cè)賮?lái)看SpringMVC是怎么把HttpServletRequest對(duì)象放入到ThreadLocal中砂竖。當(dāng)用戶發(fā)出請(qǐng)求后真椿,會(huì)經(jīng)過(guò)FrameworkServlet中的processRequest()方法做了一些騷操作,然后再交給子類DispatcherServlet中的doService()去處理這個(gè)請(qǐng)求乎澄。這些騷操作就包括把request,response對(duì)象包裝成ServletRequestAttributes對(duì)象突硝,然后放入到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()方法將當(dāng)前request和response對(duì)象包裝成ServletRequestAttributes對(duì)象置济。initContextHolders()負(fù)責(zé)把RequestAttributes對(duì)象放入到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會(huì)優(yōu)先執(zhí)行被@ModelAttribute注解的方法。也就是說(shuō)我們每一次請(qǐng)求护盈,都會(huì)去調(diào)用init()方法挟纱,對(duì)request,response腐宋,httpSession進(jìn)行賦值操作紊服,并發(fā)問(wèn)題也由此產(chǎn)生。
    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;
    }
}

2019-04-18更新

最近的面試脏款,讓我想到了3個(gè)問(wèn)題围苫。假如有2個(gè)請(qǐng)求過(guò)來(lái)裤园,一個(gè)是A請(qǐng)求撤师,一個(gè)是B請(qǐng)求。A請(qǐng)求和B請(qǐng)求所處于不同線程(Spring中的Controller是單例多線程拧揽,也可以Controller的作用域設(shè)置為prototype剃盾,這樣每次請(qǐng)求過(guò)來(lái)都會(huì)生成一個(gè)新的實(shí)例,不存在多線程問(wèn)題淤袜,但同時(shí)也帶來(lái)了更大的內(nèi)存消耗痒谴,Struts2就是這樣做的),這時(shí)候會(huì)出現(xiàn)以下情況:

  • 1.A請(qǐng)求和B請(qǐng)求的HttpServletRequest對(duì)象都是ThreadLocal(@Autowire方式最終調(diào)用RequestContextHolder.currentRequestAttributes().getRequest())中獲取到的铡羡。Tomcat的處理模型是Connector+Executor积蔚,最終是Executor復(fù)用線程來(lái)處理實(shí)際的業(yè)務(wù)。有沒(méi)有可能會(huì)出現(xiàn)這種情況:A請(qǐng)求所在線程是A烦周。當(dāng)A請(qǐng)求結(jié)束后尽爆,B請(qǐng)求復(fù)用線程A,此時(shí)拿到的HttpServletRequest對(duì)象其實(shí)是A的读慎。 其實(shí)仔細(xì)想一想就覺(jué)得不可能漱贱,當(dāng)A請(qǐng)求完成時(shí),應(yīng)該會(huì)調(diào)用ThreadLocal.remove()方法清除線程A中綁定的HttpServletRequest對(duì)象夭委》ǎ可以在RequestContextFilter看到一些貓膩!
RequestContextFilter.png
  • 2.我們的每次請(qǐng)求株灸,Tomcat中的Executor都會(huì)分配一個(gè)線程來(lái)進(jìn)行處理崇摄。每次請(qǐng)求都會(huì)創(chuàng)建HttpServletRequest 和 HttpServletResponse對(duì)象。正因?yàn)镃ontroller是單例多線程慌烧,上述通過(guò)@ModelAndView方法初始化HttpServletRequest對(duì)象逐抑,會(huì)造成HttpServletRequest覆蓋問(wèn)題(后一次的請(qǐng)求覆蓋前一次的請(qǐng)求)。對(duì)于在同一個(gè)Controller杏死,為什么每次請(qǐng)求中的HttpServletRequest對(duì)象的hashCode都相同泵肄?因?yàn)镠ttpServletRequest是通過(guò)@Autowire注入來(lái)的捆交,Controller中的HttpServletRequest實(shí)際上不是RequestFacade對(duì)象,而是Proxy(代理RequestObjectFactory)對(duì)象腐巢。所以這里的hashCode其實(shí)是Proxy對(duì)象在內(nèi)存中的地址品追,真相大白!
AutowireUtils.png
  • 3.以方法參數(shù)注入的HttpServletRequest是沒(méi)有線程安全問(wèn)題的冯丙。但是我們?cè)诰W(wǎng)頁(yè)上請(qǐng)求/4接口100次肉瓦,此時(shí)httpServletRequest對(duì)象的hashCode是一樣的,這是為什么呢胃惜?服務(wù)器中每個(gè)thread所申請(qǐng)的request的內(nèi)存空間在這個(gè)服務(wù)器啟動(dòng)的時(shí)候就是固定的泞莉,那么我每次請(qǐng)求,它都會(huì)在它所申請(qǐng)到的內(nèi)存空間(可能是類似數(shù)組這樣的結(jié)構(gòu))中新建一個(gè)request,(類似于數(shù)組的起點(diǎn)總是同一個(gè)內(nèi)存地址)船殉。那么我發(fā)起一個(gè)請(qǐng)求鲫趁,他就會(huì)在起始位置新建一個(gè)Request傳遞給Servlet并開(kāi)始處理,處理結(jié)束后就會(huì)銷毀利虫,那么他下一個(gè)請(qǐng)求所新建的Request挨厚。因?yàn)橹暗膔equest銷毀了,所以又從起始地址開(kāi)始創(chuàng)建糠惫,這樣一切就解釋得通了疫剃。
    image.png

我們先請(qǐng)求/4,再請(qǐng)求/5硼讽。兩次請(qǐng)求中的HttpServletRequest的hashCode肯定不一樣巢价,因?yàn)?code>/4處于阻塞狀態(tài),其HttpServletRequest不能被成功銷毀固阁。'/5'請(qǐng)求中的HttpServletRequest不能占據(jù)以前的內(nèi)存地址壤躲,遂導(dǎo)致2次請(qǐng)求中的HttpServletRequest的hashCode不一致。

image.png

/4.png
/5.png

尾言

大家好您炉,我是cmazxiaoma(寓意是沉夢(mèng)昂志的小馬)柒爵,希望和你們一起成長(zhǎng)進(jìn)步,感謝各位閱讀本文章赚爵。

小弟不才棉胀。
如果您對(duì)這篇文章有什么意見(jiàn)或者錯(cuò)誤需要改進(jìn)的地方,歡迎與我討論。
如果您覺(jué)得還不錯(cuò)的話,希望你們可以點(diǎn)個(gè)贊冀膝。
希望我的文章對(duì)你能有所幫助唁奢。
有什么意見(jiàn)、見(jiàn)解或疑惑窝剖,歡迎留言討論麻掸。

最后送上:心之所向,素履以往赐纱。生如逆旅脊奋,一葦以航熬北。


saoqi.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诚隙,隨后出現(xiàn)的幾起案子讶隐,更是在濱河造成了極大的恐慌,老刑警劉巖久又,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巫延,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡地消,警方通過(guò)查閱死者的電腦和手機(jī)炉峰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)脉执,“玉大人疼阔,你說(shuō)我怎么就攤上這事∈释撸” “怎么了竿开?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵谱仪,是天一觀的道長(zhǎng)玻熙。 經(jīng)常有香客問(wèn)我,道長(zhǎng)疯攒,這世上最難降的妖魔是什么嗦随? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮敬尺,結(jié)果婚禮上枚尼,老公的妹妹穿的比我還像新娘。我一直安慰自己砂吞,他們只是感情好署恍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蜻直,像睡著了一般盯质。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上概而,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天呼巷,我揣著相機(jī)與錄音,去河邊找鬼赎瑰。 笑死王悍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的餐曼。 我是一名探鬼主播压储,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鲜漩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了集惋?” 一聲冷哼從身側(cè)響起宇整,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芋膘,沒(méi)想到半個(gè)月后鳞青,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡为朋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年臂拓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片习寸。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胶惰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霞溪,到底是詐尸還是另有隱情孵滞,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布鸯匹,位于F島的核電站坊饶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏殴蓬。R本人自食惡果不足惜匿级,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望染厅。 院中可真熱鬧痘绎,春花似錦、人聲如沸肖粮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涩馆。三九已至行施,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凌净,已是汗流浹背悲龟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冰寻,地道東北人须教。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親轻腺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乐疆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容