Spring 的 Controller 是单例还是多例?

简介: 在笔试面试的时候经常会遇到的一个问题:Spring 的 Controller 是单例还是多例?

1 前言


在笔试面试的时候经常会遇到的一个问题:Spring 的 Controller 是单例还是多例?


首先答案是:controller默认是单例的,不要使用非静态的成员变量,否则会发生数据逻辑混乱。而且正因为单例,所以它也不是线程安全的。


那么既然不是线程安全的,那么spring怎么保证做到并发的安全性呢?


2 正文


首先我们来看下面的例子:


package com.springboot.springbootdemo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ScopeController {
    public int number = 0;
    @RequestMapping("/text1")
    public int test1(){
        number=number+1;
        return number;
    }
    @RequestMapping("/text2")
    public int test2(){
        number=number+1;
        return ++number;
    }
}
复制代码


e7471f920132463b931f0ad5643e89b5~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


539ed0ab6b5c45a4bb5a549153dc743c~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


可以发现我们首先访问 http://localhost:8080/text1,得到的答案是1;然后我们再访问http://localhost:8080/text2,得到的答案是 2。所以得到的不同的值,证明这是线程不安全的。


说到这个问题,我们就需要说到之前提到的一个问题,那就是关于spring的bean作用域,关于spring的作用域可以参考之前的一篇文章:传送门Spring注解(三):@scope设置组件作用域


在Spring注解开发中@Scope注解可以用于设置组件的作用域,通过@Scope源码,可以发现@Scope注解有五种作用域,即:


SINGLETON:单例模式,默认模式,不写的时候默认是SINGLETON
PROTOTYPE:原型模式
REQUEST:同一次请求则只创建一次实例
SESSION:同一个session只创建一次实例
GLOBAL SESSION:全局的web域,类似于servlet中的application。
复制代码


在默认的情况下,controller是单例模式singleton,单例是不安全的,因为它会导致属性重复使用。


接下来我们再来给controller增加作用多例 @Scope("prototype")


package com.springboot.springbootdemo.controller;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Scope("prototype")
public class ScopeController {
    public int number = 0;
    @RequestMapping("/text1")
    public String test1(){
        number=number+1;
        return String.valueOf(number);
    }
    @RequestMapping("/text2")
    public String test2(){
        number=number+1;
        return String.valueOf(number);
    }
}
复制代码


3daff66d5bc5442d9e8b303a0bd4a544~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


b207a4c9fcab4f26bd7c8f70f453c2ba~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


可以发现我们首先访问 http://localhost:8080/text1,得到的答案是1;然后我们再访问http://localhost:8080/text2,得到的答案是 1。这时候就是线程安全的了。


3 总结


通过上面的例子,可以得出结论:


1、spring的controller默认是单例模式,而这种模式下是线程不安全的,所以在这种模式下不要在controller种定义成员变量;


2、在单例模式下可以通过使用ThreadLocal 解决线程安全问题。


线程安全问题主要是全局变量和静态变量引起的。若每个线程中对全局变量、静态变量读操作,而无写操作,一般来说这个全局变量是线程安全的。若多个线程同时执行写操作,需要考虑线程同步问题,否则影响线程安全。spring 使用ThreadLocal 实现高并发下 共享资源的同步。


使用ThreadLocal 为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线 程都完全拥有该变量。


ThreadLocal 为每一个变量维护变量的副本的原理如下:


public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
复制代码


3、通过@Scope(“prototype”)注解,将默认的作用域改为多例模式,这时候就能够在controller种定义非静态的成员变量了。


在spring的controller默认是单例,原因有两点:


(1)为了性能:单例不用每次都创建


(2)不需要多例:只要controller中不定义属性,那么单例完全是安全可用的,如果定义了,那单例肯定会出现竞争访问;非要定义,则通过注解@Scope("prototype"),将其设置为多例模式。

目录
相关文章
|
5月前
|
缓存 算法 安全
Spring 为啥默认把bean设计成单例的?这篇讲的明明白白的
Spring 为啥默认把bean设计成单例的?这篇讲的明明白白的
77 0
|
5月前
|
安全 Java Spring
Spring框架中的单例Bean是线程安全的吗?
Spring框架中的单例Bean是线程安全的吗?
67 1
|
2月前
|
安全 Java C#
Spring创建的单例对象,存在线程安全问题吗?
Spring框架提供了多种Bean作用域,包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)、全局会话(GlobalSession)等。单例是默认作用域,保证每个Spring容器中只有一个Bean实例;原型作用域则每次请求都会创建一个新的Bean实例;请求和会话作用域分别与HTTP请求和会话绑定,在Web应用中有效。 单例Bean在多线程环境中可能面临线程安全问题,Spring容器虽然确保Bean的创建过程是线程安全的,但Bean的使用安全性需开发者自行保证。保持Bean无状态是最简单的线程安全策略;
|
4月前
|
Java Spring 容器
解读spring5源码中实例化单例bean的调用链
解读spring5源码中实例化单例bean的调用链
|
4月前
|
安全 Java Spring
spring的controller是单例还是多例,怎么保证并发的安全
spring的controller是单例还是多例,怎么保证并发的安全
39 0
|
4月前
|
XML JSON Java
图文并茂:解析Spring Boot Controller返回图片的三种方式
图文并茂:解析Spring Boot Controller返回图片的三种方式
393 0
|
5月前
|
安全 Java Spring
Spring 的 Controller 是单例还是多例?怎么保证并发的安全
Spring 的 Controller 是单例还是多例?怎么保证并发的安全
30 0
|
缓存 Java Spring
Spring 获取单例流程(三)
读完这篇文章你将会收获到 • Spring 何时将 bean 加入到第三级缓存和第一级缓存中 • Spring 何时回调各种 Aware 接口、BeanPostProcessor 、InitializingBean 等
121 0
|
缓存 Java Spring
Spring 获取单例流程(二)
读完这篇文章你将会收获到 • Spring 中 prototype 类型的 bean 如何做循环依赖检测 • Spring 中 singleton 类型的 bean 如何做循环依赖检测
77 0
|
XML 缓存 Java
Spring 获取单例流程(一)
读完这篇文章你将会收获到 • 在 getBean 方法中, Spring 处理别名以及 factoryBean 的 name • Spring 如何从多级缓存中根据 beanName 获取 bean • Spring 如何处理用户获取普通 bean 和 factoryBean
220 0
下一篇
无影云桌面