经验贴:Spring Bean的scope你真的会用吗?

我们知道Spring中的Bean默认都是单例的,也就是singleton。另外一个常用的是prototype(原型),可以通过它实现多例模式。但是请问,直接使用@Scope注解加上prototype直接就能实现多例吗?参考下面的代码:

MyService Bean:

1
2
3
@Service
@Scope(value="prototype")
public class MyService

MyController Bean:

1
2
3
4
5
6
7
8
9
10
11
@RestController
@RequestMapping(/rest)
public class MyController {
@Autowired
private MyService service;
private static int num=0;
@GetMapping("scope1")
public void testScope1(){
System.out.println("num:"+ ++num +"次请求,"+ service);
}
}

上面定义了两个Bean,一个是服务,一个是控制器。我们把服务通过自动注入的方式注入控制器。

请问上面的实现方式可以确保实现多例模式吗?

有的朋友可能认为,我已经使用了@Scope(value="prototype"),理当直接就是多例模式,每次访问/rest/me API的时候都能创建新的Bean,打印不同的结果。但是事实真的如此吗?

实际上看到的输出是:

1
2
num:1次请求,com.xqtony.bean.scope.prototype.service.MyService@6bc394d2
num:2次请求,com.xqtony.bean.scope.prototype.service.MyService@6bc394d2

实际上,并没有实现多例!我们的运行结果会发现,两次请求打印出来的结果是一样的。原因是什么?是因为我们没有指定ScopeProxyMode。

ScopeProxyMode的值有: Default, NO, TARGET_CLASS, INTERFACE。

如果希望在这个例子中实现多例,必须加上ScopeProxyMode.TARGET_CLASS

MyService Bean:

1
2
3
@Service
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class MyService2

这时候会发现,service2和service一个是新创建了对象,另一个没有。

1
2
3
4
num:0次请求, service没加  ScopeProxyModecom.xqtony.bean.scope.prototype.service.MyService@2a80117c
num:0次请求, service加了 ScopeProxyMode.TARGET_CLASScom.xqtony.bean.scope.prototype.service.MyService2@203f807a
num:1次请求, service没加 ScopeProxyModecom.xqtony.bean.scope.prototype.service.MyService@2a80117c
num:1次请求, service加了 ScopeProxyMode.TARGET_CLASScom.xqtony.bean.scope.prototype.service.MyService2@391e383e

为什么我们知道要使用TARGET_CLASS呢?因为proxyMode的默认值是Default,而在源码中是这样描述的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Default typically equals {@link #NO}, unless a different default
* has been configured at the component-scan instruction level.
*/
DEFAULT,

/**
* Do not create a scoped proxy.
* <p>This proxy-mode is not typically useful when used with a
* non-singleton scoped instance, which should favor the use of the
* {@link #INTERFACES} or {@link #TARGET_CLASS} proxy-modes instead if it
* is to be used as a dependency.
*/
NO,

可见在使用Prototype(原型)的时候,是不应该使用DEFAULT的。

完整的项目代码请参考我的代码仓库:

Github项目地址

参考:
springboot中怎么获取多例(prototype)
spring的scope为prototype的bean的正确使用方法

文章目录