天天看點

SpringBoot--Controller擷取HttpServletRequest

簡介

        本文介紹SpringBoot如何在Controller中擷取HttpServletRequest。

法1:Controller中加參數

簡介

        該方法實作的原理是,在Controller方法開始處理請求時,Spring會将request對象指派到方法參數中。除了request對象,可以通過這種方法擷取的參數還有很多。

        Controller中擷取request對象後,如果要在其他方法中(如service方法、工具類方法等)使用request對象,需要在調用這些方法時将request對象作為參數傳入。

代碼示例

這種方法實作最簡單,直接上Controller代碼:

package com.example.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping
public class HelloController {

    @GetMapping("/test1")
    public String test1(HttpServletRequest request) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(request);
        return request.toString();
    }
}      

線程安全性

是線程安全的。

測試:接口中睡眠,模拟多個請求同時處理

直接用“代碼示例”中的代碼

Postman開兩個視窗,都通路:​​http://localhost:8080/test1​​

結果:(兩個不同的request,可見線程安全的)

org.apache.catalina.connector.RequestFacade@4613eaaa
org.apache.catalina.connector.RequestFacade@492b9e2e      

注意:如果不加睡眠,結果可能是:兩個相同的request,因為如果它能處理過來,就會用同一個request去接收了。

法2:自動注入

代碼示例

package com.example.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping
public class HelloController {
    @Autowired
    private HttpServletRequest request;

    @GetMapping("/test1")
    public String test1() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(request);
        return request.toString();
    }
}      

線程安全性

        線程安全(不是測出來的,是看代碼得出的)。

        分析:在Spring中,Controller的scope是singleton(單例),也就是說在整個web系統中,隻有一個TestController;但是其注入的request卻是線程安全的。

測試:接口中睡眠,模拟多個請求同時處理

使用上邊“代碼示例”中的代碼

Postman開兩個視窗,都通路:​​http://localhost:8080/test1​​

結果:(這個結果很奇怪,隻能去追尋源碼。見:SpringMVC原理--Controller線程安全_IT利刃出鞘的部落格)

Current HttpServletRequest
Current HttpServletRequest      

法3:基類中自動注入

說明

        與方法2相比,将注入部分代碼放入到了基類中。

代碼示例

基類代碼:

public class BaseController {
    @Autowired
    protected HttpServletRequest request;     
}      

Controller代碼

        這裡列舉了BaseController的兩個派生類,由于此時測試代碼會有所不同,是以服務端測試代碼沒有省略;用戶端也需要進行相應的修改(同時向2個url發送大量并發請求)。

@Controller
public class TestController extends BaseController {
    // 存儲已有參數,用于判斷參數value是否重複,進而判斷線程是否安全
    public static Set<String> set = new ConcurrentSkipListSet<>();
    @RequestMapping("/test")
    public void test() throws InterruptedException {
        String value = request.getParameter("key");
        // 判斷線程安全
        if (set.contains(value)) {
            System.out.println(value + "\t重複出現,request并發不安全!");
        } else {
            System.out.println(value);
            set.add(value);
        }
        // 模拟程式執行了一段時間
        Thread.sleep(1000);
    }
}
@Controller
public class Test2Controller extends BaseController {
    @RequestMapping("/test2")
    public void test2() throws InterruptedException {
        String value = request.getParameter("key");
        // 判斷線程安全(與TestController使用一個set進行判斷)
        if (TestController.set.contains(value)) {
            System.out.println(value + "\t重複出現,request并發不安全!");
        } else {
            System.out.println(value);
            TestController.set.add(value);
        }
        // 模拟程式執行了一段時間
        Thread.sleep(1000);
    }
}      

線程安全性

測試結果:線程安全

分析:在了解了方法2的線程安全性的基礎上,很容易了解方法3是線程安全的:當建立不同的派生類對象時,基類中的域(這裡是注入的request)在不同的派生類對象中會占據不同的記憶體空間,也就是說将注入request的代碼放在基類中對線程安全性沒有任何影響;測試結果也證明了這一點。

優缺點

        與方法2相比,避免了在不同的Controller中重複注入request;但是考慮到java隻允許繼承一個基類,是以如果Controller需要繼承其他類時,該方法便不再好用。

        無論是方法2和方法3,都隻能在Bean中注入request;如果其他方法(如工具類中static方法)需要使用request對象,則需要在調用這些方法時将request參數傳遞進去。下面介紹的方法4,則可以直接在諸如工具類中的static方法中使用request對象(當然在各種Bean中也可以使用)。

法4:@ModelAttribute

代碼示例

下面這種方法及其變種(變種:将request和bindRequest放在子類中)在網上經常見到:

package com.example.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping
public class HelloController {
    protected HttpServletRequest request;

    @ModelAttribute
    public void bindreq(HttpServletRequest request) {
        this.request = request;
    }

    @GetMapping("/test1")
    public String test1() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(request);
        return request.toString();
    }
}      

線程安全性

        是線程安全的。

        分析:@ModelAttribute注解用在Controller中修飾方法時,其作用是Controller中的每個@RequestMapping方法執行前,該方法都會執行。是以在本例中,bindRequest()的作用是在test()執行前為request對象指派。

測試:接口中睡眠,模拟多個請求同時處理

org.apache.catalina.connector.RequestFacade@3bb8d854
org.apache.catalina.connector.RequestFacade@3bb8d854