每个添加@RestController或@Controller的控制器,默认是单例(singleton),这也是Spring Bean的默认作用域。
下面代码示例参考了 Building a RESTful Web Service ,该教程搭建基于Spring Boot的web项目,源代码可参考 spring-guides/gs-rest-service
GreetingController.java代码如下:
package com.example.controller; import java.util.concurrent.atomic.AtomicLong; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class GreetingController { private static final String template = "Hello, %s!"; private final AtomicLong counter = new AtomicLong(); @GetMapping("/greeting") public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) { Greeting greet = new Greeting(counter.incrementAndGet(), String.format(template, name)); System.out.println("id=" + greet.getId() + ", instance=" + this); return greet; } }
我们使用HTTP基准工具 wrk 来生成大量HTTP请求。在终端输入如下命令来测试:
wrk -t12 -c400 -d10s http://127.0.0.1:8080/greeting
在服务端的标准输出中,可以看到类似日志。
id=162440, instance=com.example.controller.GreetingController@368b1b03 id=162439, instance=com.example.controller.GreetingController@368b1b03 id=162438, instance=com.example.controller.GreetingController@368b1b03 id=162441, instance=com.example.controller.GreetingController@368b1b03 id=162442, instance=com.example.controller.GreetingController@368b1b03 id=162443, instance=com.example.controller.GreetingController@368b1b03 id=162444, instance=com.example.controller.GreetingController@368b1b03 id=162445, instance=com.example.controller.GreetingController@368b1b03 id=162446, instance=com.example.controller.GreetingController@368b1b03
日志中所有GreetingController实例的地址都是一样的,说明多个请求对同一个 GreetingController 实例进行处理,并且它的AtomicLong类型的counter字段正按预期在每次调用时递增。
如果我们在@RestController注解上方增加@Scope("prototype")注解,使bean作用域变成原型作用域,其它内容保持不变。
... @Scope("prototype") @RestController public class GreetingController { ... }
服务端的标准输出日志如下,说明改成原型作用域后,每次请求都会创建新的bean,所以返回的id始终是1,bean实例地址也不同。
id=1, instance=com.example.controller.GreetingController@2437b9b6 id=1, instance=com.example.controller.GreetingController@c35e3b8 id=1, instance=com.example.controller.GreetingController@6ea455db id=1, instance=com.example.controller.GreetingController@3fa9d3a4 id=1, instance=com.example.controller.GreetingController@3cb58b3
如果我们在greeting()方法中增加休眠时间,来看下每个http请求是否会串行调用控制器里面的方法。
@RestController public class GreetingController { private static final String template = "Hello, %s!"; private final AtomicLong counter = new AtomicLong(); @GetMapping("/greeting") public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) throws InterruptedException { Thread.sleep(1000); // 休眠1s Greeting greet = new Greeting(counter.incrementAndGet(), String.format(template, name)); System.out.println("id=" + greet.getId() + ", instance=" + this); return greet; } }
还是使用wrk来创建大量请求,可以看出即使服务端的方法休眠1秒,导致每个请求的平均延迟达到1.18s,但每秒能处理的请求仍达到166个,证明HTTP请求在Spring MVC内部是并发调用控制器的方法,而不是串行。
wrk -t12 -c400 -d10s http://127.0.0.1:8080/greeting Running 10s test @ http://127.0.0.1:8080/greeting 12 threads and 400 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.18s 296.41ms 1.89s 85.22% Req/Sec 37.85 38.04 153.00 80.00% 1664 requests in 10.02s, 262.17KB read Socket errors: connect 155, read 234, write 0, timeout 0 Requests/sec: 166.08 Transfer/sec: 26.17KB
package com.demo.designpattern; import java.util.concurrent.atomic.AtomicInteger; public class Singleton { private volatile static Singleton singleton; private int counter = 0; private AtomicInteger atomicInteger = new AtomicInteger(0); private Singleton() { } public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } public int getUnsafeNext() { return ++counter; } public int getUnsafeCounter() { return counter; } public int getSafeNext() { return atomicInteger.incrementAndGet(); } public int getSafeCounter() { return atomicInteger.get(); } }
package com.demo.designpattern; import java.util.*; import java.util.concurrent.*; public class SingletonTest { public static void main(String[] args) { // 定义可返回计算结果的非线程安全的Callback实例 Callable<Integer> unsafeCallableTask = () -> Singleton.getSingleton().getUnsafeNext(); runTask(unsafeCallableTask); // unsafe counter may less than 1000, i.e. 984 System.out.println("current counter = " + Singleton.getSingleton().getUnsafeCounter()); // 定义可返回计算结果的线程安全的Callback实例(基于AtomicInteger) Callable<Integer> safeCallableTask = () -> Singleton.getSingleton().getSafeNext(); runTask(safeCallableTask); // safe counter should be 1000 System.out.println("current counter = " + Singleton.getSingleton().getSafeCounter()); } public static void runTask(Callable<Integer> callableTask) { int cores = Runtime.getRuntime().availableProcessors(); ExecutorService threadPool = Executors.newFixedThreadPool(cores); List<Callable<Integer>> callableTasks = new ArrayList<>(); for (int i = 0; i < 1000; i++) { callableTasks.add(callableTask); } Map<Integer, Integer> frequency = new HashMap<>(); try { List<Future<Integer>> futures = threadPool.invokeAll(callableTasks); for (Future<Integer> future : futures) { frequency.put(future.get(), frequency.getOrDefault(future.get(), 0) + 1); //System.out.printf("counter=%s/n", future.get()); } } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } threadPool.shutdown(); } }
范围 |
描述 |
---|---|
singleton(单例) |
(默认值)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。 换句话说,当您定义一个bean并且其作用域为单例时,Spring IoC容器将为该bean所定义的对象创建一个实例。该单例存储在单例beans的高速缓存中,并且对该命名bean的所有后续请求和引用都返回该高速缓存的对象。 |
prototype(原型) |
将单个bean定义的作用域限定为任意数量的对象实例。 每次对特定bean发出请求时,bean原型作用域都会创建一个新bean实例。也就是说,将Bean注入到另一个Bean中,或者您可以调用容器上的getBean()方法来请求它。通常,应将原型作用域用于所有有状态Bean,将单例作用域用于无状态Bean。 |
request | 将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有一个在单个bean定义后创建的bean实例。仅在web-aware的Spring ApplicationContext上下文有效。 |
session | 将单个bean定义的范围限定为HTTP Session的生命周期。仅在基于web的Spring ApplicationContext上下文有效。 |
application | 将单个bean定义的范围限定为ServletContext的生命周期。仅在基于web的Spring ApplicationContext上下文有效。 |
websocket | 将单个bean定义的作用域限定为WebSocket的生命周期。仅在基于web的Spring ApplicationContext上下文有效。 |