Source: Programming Myths: Several Methods of Obtaining Requests in Spring and Analysis of Thread Safety

Several methods of obtaining requests in Spring and their thread safety analysis

Source: Programming Myths,
www.cnblogs.com/kismetv/p/8757260.html

foreword

This article will introduce several methods for obtaining the request object in the web system developed by Spring MVC, and discuss its thread safety.

Overview

When using Spring MVC to develop a web system, it is often necessary to use the request object when processing requests, such as obtaining the client IP address, the requested url, attributes in the header (such as cookies, authorization information), and data in the body. Since in Spring MVC, objects such as Controller and Service that process requests are all singletons, the most important issue to pay attention to when obtaining a request object is whether the request object is thread-safe: when there are a large number of concurrent requests, can the Ensure that different request objects are used in different requests/threads.

There is another problem to be noted here: where is the use of the request object "when processing the request" mentioned earlier? Considering that the methods of obtaining request objects are slightly different, they can be roughly divided into two categories:

  1. The request object is used in Spring Beans: it includes both MVC beans such as Controller, Service, and Repository, as well as common Spring Beans such as Component. For the convenience of description, Beans in Spring are all referred to as Beans in the following text.
  2. Use request objects in non-Beans: such as in the methods of ordinary Java objects, or in static methods of classes.

In addition, the discussion in this article is based on the request object representing the request, but the methods used are also applicable to the response object, InputStream/Reader, OutputStream/Writer, etc. Among them, InputStream/Reader can read the data in the request, and OutputStream/Writer can respond to the response data input.

Finally, the method of obtaining the request object is also related to the version of Spring and MVC; this article is based on Spring4 for discussion, and the experiments are done using version 4.1.1.

How to Test for Thread Safety

Since the thread safety of the request object needs special attention, in order to facilitate the following discussion, the following describes how to test whether the request object is thread-safe.

The basic idea of ​​​​the test is to simulate a large number of concurrent requests from the client, and then determine whether these requests use the same request object on the server.

The most intuitive way to determine whether the request objects are the same is to print out the address of the request objects. If they are the same, the same object is used. However, in almost all web server implementations, thread pools are used, so that two requests arriving one after the other may be processed by the same thread: after the previous request is processed, the thread pool reclaims the thread and sends The thread is reassigned to a later request. In the same thread, the request object used is likely to be the same (same address, different attributes). Therefore, even for thread-safe methods, different requests may use the same request object address.

To avoid this problem, one way is to sleep the threads for a few seconds during request processing, which allows each thread to work long enough to avoid assigning the same thread to different requests; another way is to use Other attributes of the request (such as parameters, header, body, etc.) are used as the basis for whether the request is thread-safe, because even if different requests use the same thread (the request object address is the same), as long as different attributes are used to construct twice request object, then the use of the request object is thread-safe. This article uses the second method for testing.

The client test code is as follows (create 1000 threads to send requests separately):

public class Test {
    public static void main(String[] args) throws Exception {
        String prefix = UUID.randomUUID().toString().replaceAll("-", "") + "::";
        for (int i = 0; i < 1000; i++) {
            final String value = prefix + i;
            new Thread() {
                @Override
                public void run() {
                    try {
                        CloseableHttpClient httpClient = HttpClients.createDefault();
                        HttpGet httpGet = new HttpGet("http://localhost:8080/test?key=" + value);
                        httpClient.execute(httpGet);
                        httpClient.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
}

The Controller code in the server is as follows (the code for obtaining the request object is temporarily omitted):

@Controller
public class TestController {

    // 存储已有参数,用于判断参数是否重复,从而判断线程是否安全
    public static Set<String> set = new HashSet<>();

    @RequestMapping("/test")
    public void test() throws InterruptedException {

        // …………………………通过某种方式获得了request对象………………………………

        // 判断线程安全
        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);
    }
}

If the request object is thread-safe, the result printed in the server is as follows:
write picture description here

If there is a thread safety problem, the print result in the server may be as follows:
write picture description here
Unless otherwise specified, the test code will be omitted from the code later in this article.

Method 1: Add parameters to the Controller

code example

This method is the easiest to implement, just go directly to the Controller code:

@Controller
public class TestController {
    @RequestMapping("/test")
    public void test(HttpServletRequest request) throws InterruptedException {
        // 模拟程序执行了一段时间
        Thread.sleep(1000);
    }
}

The principle of this method is that when the Controller method starts to process the request, Spring will assign the request object to the method parameter. In addition to the request object, there are many parameters that can be obtained by this method. For details, please refer to:

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods

After obtaining the request object in the Controller, if you want to use the request object in other methods (such as service method, tool class method, etc.), you need to pass the request object as a parameter when calling these methods.

thread safety

Test Results: Thread Safety

Analysis: At this time, the request object is a method parameter, which is equivalent to a local variable and is undoubtedly thread-safe.

Advantages and disadvantages

The main disadvantage of this method is that the request object is too redundant to write, which is mainly reflected in two points:

  1. If the request object is required in multiple controller methods, then the request parameter needs to be added to each method
  2. The request object can only be obtained from the controller. If the place where the request object is used is deep in the function call level, then all methods on the entire call chain need to add the request parameter

In fact, the request object runs through the entire request processing process; that is, except for special cases such as timers, the request object is equivalent to a global variable inside the thread. And this method is equivalent to passing this global variable around.

Method 2: Automatic injection

code example

@Controller
public class TestController{

    @Autowired
    private HttpServletRequest request; //自动注入request

    @RequestMapping("/test")
    public void test() throws InterruptedException{
        //模拟程序执行了一段时间
        Thread.sleep(1000);
    }

thread safety

Test Results: Thread Safety

Analysis: In Spring, the scope of the Controller is singleton (singleton), which means that there is only one TestController in the entire web system; but the injected request is thread-safe because:

In this way, when the Bean (TestController in this example) is initialized, Spring does not inject a request object, but a proxy (proxy); when the Bean needs to use the request object, the request object is obtained through the proxy.

Advantages and disadvantages

The main advantages of this method:

  1. Injection is not limited to the Controller: in method 1, the request parameter can only be added to the Controller. For method 2, it can be injected not only in the Controller, but also in any Bean, including Service, Repository and ordinary Beans.
  2. The injected objects are not limited to requests: in addition to injecting request objects, this method can also inject other objects whose scope is request or session, such as response objects, session objects, etc.; and ensure thread safety.
  3. Reduce code redundancy: You only need to inject the request object into the bean that needs the request object, and you can use it in each method of the bean, which greatly reduces code redundancy compared with method 1.
    However, this method also has code redundancy. Consider this scenario: there are many controllers in the web system, and each controller will use the request object (this scenario is actually very frequent), then you need to write the code to inject the request many times; if you also need to inject the response, the code is more cumbersome. The following describes the improvement method of the automatic injection method, and analyzes its thread safety, advantages and disadvantages.

Method 3: Automatic injection in the base class

code example

Compared with method 2, the injection part of the code is put into the base class.

Base class code:

public class BaseController {
    @Autowired
    protected HttpServletRequest request;     
}

The controller code is as follows; here are two derived classes of BaseController. Since the test code will be different at this time, the server-side test code is not omitted; the client also needs to make corresponding modifications (send a large number of concurrent requests to two urls at the same time) ).

@Controller
public class TestController extends BaseController {

    // 存储已有参数,用于判断参数value是否重复,从而判断线程是否安全
    public static Set<String> set = new HashSet<>();

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

thread safety

Test Results: Thread Safety

Analysis: On the basis of understanding the thread safety of method 2, it is easy to understand that method 3 is thread-safe: when creating different derived class objects, the fields in the base class (here is the injected request) are in different derived classes. Class objects occupy different memory spaces, which means that placing the code injected into the request in the base class has no effect on thread safety; the test results also prove this.

Advantages and disadvantages

Compared with method 2, repeated injection of requests in different Controllers is avoided; however, considering that java only allows inheritance of one base class, this method is no longer easy to use if the Controller needs to inherit other classes.

Both method 2 and method 3 can only inject request into the bean; if other methods (such as static methods in tool classes) need to use the request object, you need to pass the request parameter when calling these methods. In the method 4 described below, the request object can be used directly in static methods such as tool classes (of course, it can also be used in various beans).

Method 4: Manual call

code example

@Controller
public class TestController {
    @RequestMapping("/test")
    public void test() throws InterruptedException {
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        // 模拟程序执行了一段时间
        Thread.sleep(1000);
    }
}

thread safety

Test Results: Thread Safety

Analysis: This method is similar to method 2 (automatic injection), except that method 2 is implemented by automatic injection, and this method is implemented by manual method invocation. Therefore this method is also thread safe.

Advantages and disadvantages

Advantages: can be obtained directly in non-Bean. Disadvantage: The code is very cumbersome if used in many places; therefore can be used in conjunction with other methods.

Method 5: @ModelAttribute method

code example

The following method and its variants (variant: subclassing request and bindRequest) are commonly seen online:

@Controller
public class TestController {
    private HttpServletRequest request;
    @ModelAttribute
    public void bindRequest(HttpServletRequest request) {
        this.request = request;
    }
    @RequestMapping("/test")
    public void test() throws InterruptedException {
        // 模拟程序执行了一段时间
        Thread.sleep(1000);
    }
}

thread safety

Test result: thread unsafe

Analysis: When the @ModelAttribute annotation is used to decorate a method in the Controller, its function is that the method will be executed before each @RequestMapping method in the Controller is executed. So in this example, the role of bindRequest() is to assign a value to the request object before test() is executed. Although the parameter request in bindRequest() is thread-safe, because TestController is a singleton, request, as a domain of TestController, cannot guarantee thread-safety.

Summarize

In summary, adding parameters to the Controller (method 1), automatic injection (method 2 and method 3), and manual invocation (method 4) are all thread-safe and can be used to obtain the request object. If the request object in the system is used less, either method can be used; if it is used more, it is recommended to use automatic injection (method 2 and method 3) to reduce code redundancy. If you need to use the request object in a non-bean, you can either pass it in through parameters when the upper layer is called, or you can get it directly in the method by calling it manually (method 4).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324607017&siteId=291194637