平常我们用 Spring
管理 Bean
,都是使用的默认作用域,也就是 SCOPE_SINGLETON
- 单例作用域:定义的 Bean
在整个应用程序中只有一个实例。Spring
还包含另一个不常用的作用域 SCOPE_PROTOTYPE
- 原型作用域:每次请求该 Bean
时,都会创建一个新的实例。
问题复现
最近项目中就用到了 SCOPE_PROTOTYPE
来定义 Bean
,但是测试发现没有生效,下面通过一段测试代码来还原这个问题:
1 |
|
1 |
|
每次请求 /test
,打印出的 InvalidManage
都是同一个,定义的 SCOPE_PROTOTYPE
并没有生效:
解决办法
上面测试中,尽管 InvalidManager
的作用域是原型(prototype
),但在被注入到 Test
中时,InvalidManager
的实例只会在 Test
的单例创建时创建一次,并在整个 Test
的生命周期内保持相同的实例。
如果想要让 InvalidManager
的 原型(prototype
)作用域生效,那么可以通过下面几种方式处理:
入口类作用域修改为原型(prototype
)
1 |
|
不过这样每次请求 /test
都会创建一个新的 Test
实例。
使用 ApplicatioContext
1 |
|
这样每次请求 /test
都会重新注入 InvalidManager
:
使用 ObjectFactory
1 |
|
ObjectFactory
会根据对象的 scope
来选择是否需要创建对象,比如上面 InvalidManager
的作用域是原型(prototype
),那么每次调用 ObjectFactory.getObject()
都会返回一个新的对象。而如果对象的作用域是单例(singleton
),那每次调用 ObjectFactory.getObject()
都会返回相同的实例。
使用 Lookup
注解
1 |
|
最终输出如下:
注意上面的 Lookup
注解修饰的 getInvalidManager()
方法,最终的返回值是 null
,但实际上 getInvalidManager()
的具体实现无关紧要,因为被 Lookup
注解修饰的方法的具体实现不会被调用。因此可以看到上面 getInvalidManager()
中虽然有打印日志的代码,但是最终也没有输出,因为方法没有被调用。被 Lookup
修饰的方式最终会通过 BeanFactory
来获取 Bean
,比如上例中就是获取 InvalidManager
实例。
而如果 Lookup
注解标记的方法获取的对象作用域是单例(singleton
)的话,那么每次调用方法获取到的实例都会是同一个。
在 Lookup
的官方文档中有一个提示:
Concrete limitations in typical Spring configuration scenarios: When used with component scanning or any other mechanism that filters out abstract beans, provide stub implementations of your lookup methods to be able to declare them as concrete classes. And please remember that lookup methods won’t work on beans returned from @Bean methods in configuration classes; you’ll have to resort to @Inject Provider
or the like instead. 典型的Spring配置场景中的具体限制:当与组件扫描或任何其他过滤抽象bean的机制一起使用时,请提供查找方法的存根实现,以便将其声明为具体类。请记住,查找方法不适用于配置类中的@Bean方法返回的bean;您将不得不转而使用@Inject Provider或类似的方法。
也就是说,如果 ClassA
中使用了 Lookup
修饰了某个方法,那么 ClassA
必须使用注解(比如 Component
)来修饰以便 ClassA
是通过扫描的方式被注册为 Bean
的,如果 ClassA
是通过 @Bean
方式来注册成 Bean
的,那 Lookup
注解将失效。我们用一段代码测试下:
ValidManager
中通过 @Lookup
获取 InvalidManager
实例,但是 ValidManager
是通过 @Bean
将自己注册为 Bean
的:
1
2
3
4
5
6
7
8
9
10
11public class ValidManager {
public InvalidManager getInvalidManager() {
System.out.println("getInvalidManger方法被调用啦.....");
return null;
}
public void print() {
System.out.println(getInvalidManager());
}
}
1
2
3
4
public ValidManager validManager() {
return new ValidManager();
}
在 Test
中调用 ValidManager
:
1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
private ValidManager validManager;
public void test() {
System.out.println("==================");
validManager.print();
}
}
请求 /test
,结果如下,可以看到 Lookup
注解并未生效:
将 ValidManager
更改为使用注解的方法注册为 Bean
:
1
2
public class ValidManager {}
可以看到 Lookup
生效了:
另外还有一个注意点,被
Lookup
修饰的方法必须要不能是private
方法,否则Lookup
注解也会失效。