Web應用中Service層獲取request對象 | RequestContextHolder的使用


參考地址 : https://my.oschina.net/ojeta/blog/801640

自身理解 : Web應用流程(以SSM為例) :

      請求 ➡ Controller  ---Controller中可以獲取到當前的request,response,session,SrevletContext等

                ⬇

      Controller中調用Service層,Service層調用Dao層后進行數據處理

                ⬇  ➡問題: Controller中可以獲取到request等,但在Service中要使用當前線程中的request等,怎么辦?

      請求處理並響應過程中從 M到V到C 過程中都是在一個線程內的

      而此時要達到的目的是 : 線程中共享 線程間隔離

                

          此時就想到了ThreadLocal

                ⬇

      定義一個類實現RequestListener監聽器,類中有ThreadLocal,當一個請求生成時就裝入ThreadLocal,

      在此線程內,不論Dao,Service,Controller都可以通過此類而獲取到

      實現了線程間隔離,線程內共享

 


 

      SpringMVC中就實現了此種思想的功能

  public static ServletRequestAttributes getServletRequestAttributes() {

return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

}

  RequestContextHodler就是上面所說的那個類,而內部有ThreadLocal屬性,可通過getRequestAttributes()方法獲取到此線程的req


 工具類

public class WebUtils {
    public static ServletRequestAttributes getServletRequestAttributes() {
        return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    }

    /**
     * 得到當前線程的請求對象
     *
     * @return
     */
    public static HttpServletRequest getHttpServletRequest() {
        return getServletRequestAttributes().getRequest();
    }

    /**
     * 得到訪問的ip地址
     *
     * @return
     */
    public static String getRequestIp() {
        HttpServletRequest request = getHttpServletRequest();
        String ip = null;
        ip = request.getHeader("x-forwarded-for");
        if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) {
            ip = request.getRemoteAddr();
            if (ip.equals("127.0.0.1")) {
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ip = inet.getHostAddress();
            }
        }
        if ((ip != null) && (ip.length() > 15)) {
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;
    }

    /**
     * 得到當前線程的響應對象
     */
    public static HttpServletResponse getHttpServletResponse() {
        return getServletRequestAttributes().getResponse();
    }

    /**
     * 得到session對象
     */
    public static HttpSession getHttpSession() {
        return getHttpServletRequest().getSession();
    }

    /**
     * 得到servletContext對象
     */
    public static ServletContext getServletContext() {
        return getHttpServletRequest().getServletContext();
    }
}

 


 

問題

朋友遇到一個問題:他想在Service方法中使用HttpServletRequest的API,但是又不想把HttpServletRequest對象當作這個Service方法的參數傳過來,原因是這個方法被N多Controller調用,加一個參數就得改一堆代碼。一句話:就是他懶。不過,這個問題該這么解決呢?

思考

不把HttpServletRequest當作參數傳過來,這意味着要在Service的方法中直接獲取到HttpServletRequest對象。
我們知道,一次請求,Web應用服務器就會分配一個線程去處理。也就是說,在Service方法中獲取到的HttpServletRequest對象需要滿足:線程內共享,線程間隔離
這恰恰是ThreadLocal的應用場景。

思路

那么,就需要在請求執行之前獲取到HttpServletRequest,把它set()到某個類的ThreadLocal類型的靜態成員中,使用的時候直接通過靜態方式訪問到這個ThreadLocal對象,調用它的get()方法,即可獲取到線程隔離的HttpServletRequest了。最后,在請求結束后,要調用ThreadLocalremove()方法,清理資源引用。

實現

方式一 利用ServletRequestListener實現

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;

public class RequestHolder implements ServletRequestListener {

    private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = 
            new ThreadLocal<HttpServletRequest>();
    
    @Override
    public void requestInitialized(ServletRequestEvent requestEvent) {
        HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
        httpServletRequestHolder.set(request); // 綁定到當前線程
    }
    
    @Override
    public void requestDestroyed(ServletRequestEvent requestEvent) {
        httpServletRequestHolder.remove(); // 清理資源引用
    }
    
    public static HttpServletRequest getHttpServletRequest() {
        return httpServletRequestHolder.get();
    }
    
}

方式二 利用Filter實現

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class RequestHolder implements Filter {

    private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = 
            new ThreadLocal<HttpServletRequest>();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        httpServletRequestHolder.set((HttpServletRequest) request); // 綁定到當前線程
        try {
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        } finally {
            httpServletRequestHolder.remove(); // 清理資源引用
        }
    }

    @Override
    public void destroy() {
    }

    public static HttpServletRequest getHttpServletRequest() {
        return httpServletRequestHolder.get();
    }
    
}

方式三 利用SpringMVC的攔截器實現

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class RequestHolder extends HandlerInterceptorAdapter {

    private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = 
            new ThreadLocal<HttpServletRequest>();
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        httpServletRequestHolder.set(request); // 綁定到當前線程
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        httpServletRequestHolder.remove(); // 清理資源引用
    }
    
    public static HttpServletRequest getHttpServletRequest() {
        return httpServletRequestHolder.get();
    }
    
}

調用

無論是哪種方式,都可以直接在Service的方法中執行

HttpServletRequest request = RequestHolder.getHttpServletRequest();

即可直接獲取到線程隔離的HttpServletRequest了。

延伸

類似的功能,在SpringMVC中就有開箱即用的實現。代碼是

HttpServletRequest request = 
    ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

那么SpringMVC是如何實現的呢?
先看一下RequestContextHolder的源碼(精簡了一下)

public abstract class RequestContextHolder {

    private static final ThreadLocal<RequestAttributes> requestAttributesHolder = 
            new NamedThreadLocal<RequestAttributes>("Request attributes"); // 重點
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = 
            new NamedInheritableThreadLocal<RequestAttributes>("Request context");

    public static void resetRequestAttributes() {
        requestAttributesHolder.remove(); // 重點
        inheritableRequestAttributesHolder.remove();
    }

    public static void setRequestAttributes(RequestAttributes attributes) {
        setRequestAttributes(attributes, false);
    }

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

    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = requestAttributesHolder.get(); // 重點
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }
}

主要代碼就是把RequestAttributes對象ThreadLocal化,然后提供了setRequestAttributes()getRequestAttributes()等靜態方法,來放入或取出ThreadLocal中線程隔離的RequestAttributes
接下來看一下setRequestAttributes()方法是在什么時候調用的呢?
setRequestAttributes()
可以看到setRequestAttributes()initContextHolders()調用,initContextHolders()又被processRequest()調用,而processRequest()在每次請求時都會被調用,無論是GET、POST、PUT、DELETE還是TRACE、OPTIONS等等。
先來看一下processRequest()方法

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(); // 重點1
    ServletRequestAttributes requestAttributes = 
            buildRequestAttributes(request, response, previousAttributes); // 重點2

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes); // 重點3

    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); // 重點4
        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, startTime, failureCause); // 發布請求處理完成事件
    }
}

重點1

set之前就先get,通常為null

重點2

直接看buildRequestAttributes()方法的實現

protected ServletRequestAttributes buildRequestAttributes(HttpServletRequest request, HttpServletResponse response, RequestAttributes previousAttributes) {
    if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
        return new ServletRequestAttributes(request); // 重點
    }
    else {
        return null;  // preserve the pre-bound RequestAttributes instance
    }
}

ServletRequestAttributes的代碼不再去看了,它就是RequestAttributes接口的實現類,只是對HttpServletRequest對象(還有HttpSession)的一個包裝。

重點3

直接看initContextHolders()方法的實現

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

調用RequestContextHolder.setRequestAttributes()方法,把requestAttributes對象放入。this.threadContextInheritable默認是false
即把HttpServletRequest的封裝對象ServletRequestAttributes與當前線程綁定。

重點4

private void resetContextHolders(HttpServletRequest request, LocaleContext prevLocaleContext, RequestAttributes previousAttributes) {
    LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
    RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable); // 重點
    if (logger.isTraceEnabled()) {
        logger.trace("Cleared thread-bound request context: " + request);
    }
}

在請求執行完畢后,再次調用RequestContextHolder.setRequestAttributes(),但由於previousAttributesnull,所以,這里相當於調用RequestContextHolder.setRequestAttributes(null, false)
再回顧一下setRequestAttributes()方法。

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

參數attributesnull,就會調用resetRequestAttributes(),來清理當前線程引用的RequestAttributes

至此,SpringMVC是如何實現直接獲取HttpServletRequest對象的源碼,就分析完了。和我們自己實現的思路差不多,只不過多繞了幾個彎而已。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM