• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • spring bean中注入HttpServletRequest成員變量的思考

    主題
    在使用spring框架開發的時候,我們經常會碰到這種情況:

    @Controller
    public class SomeController {
    
        @RequestMapping("/test1")
        public String test1(HttpServletRequest request) {
            System.out.println(request.getQueryString());
            return "";
        }
    
        @RequestMapping("/test2")
        public String test2(HttpServletRequest request) {
            System.out.println(request.getQueryString());
            return "";
        }
    
        @RequestMapping("/test3")
        public String test3(HttpServletRequest request) {
            System.out.println(request.getQueryString());
            return "";
        }
    
    }
    

    即,一個@Controller或@Service中的多個方法都使用到了request這個參數,那么為了簡化代碼,我們會將request作為成員變量注入,改寫成如下形式:

    @Controller
    public class SomeController {
    
        @Resource
        private HttpServletRequest request;
    
        @RequestMapping("/test1")
        public String test1() {
            System.out.println(request.getQueryString());
            return "";
        }
    
        @RequestMapping("/test2")
        public String test2() {
            System.out.println(request.getQueryString());
            return "";
        }
    
        @RequestMapping("/test3")
        public String test3() {
            System.out.println(request.getQueryString());
            return "";
        }
    
    }
    

    這樣,我們就可以不必在每個用到request的方法的參數列表都寫一遍HttpServletRequest request這個參數了。工作中也經常是這樣做的。

    思考
    但是,仔細思考一下,我們會有這樣的疑問:

    spring中@Controller默認是單例的,其成員變量也是在bean初始化時注入好的,而HttpServletRequest這個變量對于每一次請求都是不同的,難道@Controller對每次請求都重新注入request這個成員變量?且不說這種做法不符合spring對bean管理的一般方式,即使這樣做了,線程安全如何保證?
    實現
    先上結論,上面的寫法,每次都可以取到正確的request對象,并且是線程安全的。

    那么,spring是如何做到的呢?

    首先debug看下兩種做法真實注入類的區別:

    區別很明顯,

    作為成員變量注入的時候注入的是代理對象,是 AutowireUtils.ObjectFactoryDelegatingInvocationHandler的實例。

    而作為方法參數注入的就是我們一般使用的Request對象。

    看下ObjectFactoryDelegatingInvocationHandler這個AutowireUtils的內部類:

         /**
         * Reflective InvocationHandler for lazy access to the current target object.
         */
        @SuppressWarnings("serial")
        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();
                }
            }
        }
    

    可以看到,當代理對象的方法被調用時,除去少數幾個方法,大部分的情況都是通過this.objectFactory.getObject()獲取被代理對象,再調用被代理對象的相應方法
    在這里插入圖片描述
    在這里插入圖片描述

    通過一步步debug,最終終于看到了熟悉的Request類,可以看到它是從requestAttributesHolder中取到的,那么requestAttributesHolder又是什么呢?

    在這里插入圖片描述
    看到這里答案已經很明了了,這個RequestContextHolder的ThreadLocal成員變量就是實現的關鍵所在,它存放了每個線程對應的Request對象,因此在@Controller中調用作為成員變量注入的代理類的方法時,最終可以取到當前線程相對應的Request對象,并調用Request對應的方法,這樣@Controller中的成員變量不需要重復注入(它一直都是最初bean初始化時注入的代理類),也避免了線程不安全的問題。

    spring是何時將Request放入這個ThreadLocal之中的呢?
    是在Springmvc的dispatcherServlet的父類FrameworkServlet里操作的:

    在這里插入圖片描述

    可以看到對Servlet的doGet、dePost等各種調用,最終都通過processRequest(request, response)處理

    在這里插入圖片描述
    該方法調用了initContextHolders(request, localeContext, requestAttributes);

    最終看到了將Request放入ThreadLocal的操作。

    總結
    在bean中注入作為成員變量的HttpServletRequest時,實際注入的是spring框架生成的代理對象,是ObjectFactoryDelegatingInvocationHandler的實例。在我們調用這個成員變量的方法時,最終是調用了objectFactory的getObject()對象的對應方法,在這里objectFactory是RequestObjectFactory這個類的對象。
    RequestObjectFactory的getObject方法是從RequestContextHolder的threadlocal中去取值的。
    請求剛進入springmvc的dispatcherServlet的時候會把request相關對象設置到RequestContextHolder的threadlocal中去.

    版權聲明:本文為q309572960原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/q309572960/article/details/102613552

    智能推薦

    spring中bean的創建與注入

    spring中bean的創建方式 有三種: 通過默認構造器創建bean,如果類中沒有默認構造器(即構造器被重載且沒有聲明默認的),則對象無法創建。 通過普通工廠方法中創建對象,即使用某個類的方法創建對象,并注入spring容器,適用于外部導入的jar包類對象 使用工廠中的靜態方法創建對象(使用某個類中的靜態方法創建對象,并存入spring容器),與第二種類似。 spring中注入bean的三種方式...

    spring注入bean的幾種策略模式

    上篇文章Spring IOC的核心機制:實例化與注入我們提到在有多個實現類的情況下,spring是如何選擇特定的bean將其注入到代碼片段中,我們討論了按照名稱注入和使用@Qualifier 注解輸入的兩種方式,本篇文章將結合之前提到的和spring的其他注入方式一起進行討論。 本文主題 我們將討論在一個接口或者抽象類在具有多個實現類的情況下,有多少種策略能夠讓我們在特定的代碼片段中注入想要的be...

    基于Spring容器Bean的動態注入

    首先對BeanPostProcessor,BeanFactoryPostProcessor,BeanDefinitionRegistryPostProcessor做個整理。 BeanPostProcessor: Spring容器Bean的后置回調接口,即每個Bean在實例化之前都會調用BeanPostProcessor里的postProcessBeforeInitialization和postPr...

    03spring-Bean的依賴注入

    3.5 Bean的依賴注入入門 ①創建 UserService,UserService 內部在調用 UserDao的save() 方法 ②將 UserServiceImpl 的創建權交給 Spring ③從 Spring 容器中獲得 UserService 進行操作 3.6 Bean的依賴注入概念 依賴注入(Dependency Injection):它是 Spring 框架核心 IOC 的具體實...

    spring 注入的bean不是代理對象

    最近需要在同一個類里面調用標注@Async 異步調用。所以,注入的類需要是代理對象。但注入的卻不是代理對象 我們常用的在本類中注入自己 是循環依賴 可以用 如何解決循環依賴處理 但這上面的方式注入的都是注入的沒有進行AOP增強的原始類。  看起來@Lazy的是增強的,但仔細一看不是。 @PostConstruct   最后執行但還不是代理類  這個方法是增強...

    猜你喜歡

    數組刪除其中某個對象的方法

    數組刪除其中的對象或元素,在前端是比較常見的需求。 我現在比較常用的方法如下: 這種方法只適合刪除具有唯一標識的對象。 有沒有想要脫單的小伙伴,加入我們的脫單星球,認識更多優秀的小哥哥小姐姐 特此聲明,星球是免費的,但是創建星球的時候說是必須輸入金額,所以只能先私聊,我再加你免費加入!...

    圖床搭建以及圖床工具的使用

    為什么要用圖床和圖床工具? 比較下面三種md中的圖片url地址(均免費),你會使用哪一種? 選1?由于是本地路徑,文檔分享后給其他人打開后很可能顯示圖片加載失敗。 選2?雖然分享后可以顯示圖片,但能保證加載速度? 選3?我肯定選這種,即兼容2的瀏覽器訪問,又能保證訪問速度。 這樣就可以回答上面的問題了!保證瀏覽器訪問要用圖床,保證加載速度要用圖床工具,又不花錢想想就開心。 除此之外本篇博客還會講解...

    并發編程理論篇

    一、必備知識回顧 計算機又叫電腦,即通電的大腦,發明計算機是為了讓他通電之后能夠像人一樣去工作,并且它比人的工作效率更高,因為可以24小時不間斷 計算機五大組成部分 控制器 運算器 存儲器 輸入設備 輸出設備 計算機的核心真正干活的是CPU(控制器+運算器=中央處理器) 程序要想被計算機運行,它的代碼必須要先由硬盤讀到內存,之后cpu取指再執行 并發 看起來像同時運行的就可以稱之為并發 并行 真正...

    Java LinkedHashMap

    Java LinkedHashMap 前言 Map是我們在實際使用過程中常用的集合,HashMap在Java的實際開發中出鏡率很高,它通過hash算法實現了高效的非線程安全的集合,它有一個缺點就是,用戶插入集合的數據時無序,在我們需要一些有序的map的時候,我們就需要引入另外一個集合:LinkedHashMap。 LinkedHashMap是一個有序的非線程安全的集合,它是HashMap的子類,基...

    Spark Streaming處理文件(本地文件以及hdfs上面的文件)

    標題介紹文件流之前先介紹一下Dstream 下面是來自官網一段的說明,Discretized Streams或DStream是Spark Streaming提供的基本抽象。它表示連續的數據流,可以是從源接收的輸入數據流,也可以是通過轉換輸入流生成的已處理數據流。在內部,DStream由一系列連續的RDD表示,這是Spark對不可變的分布式數據集的抽象(有關更多詳細信息,請參見Spark編程指南)。...

    精品国产乱码久久久久久蜜桃不卡