挠头~?加了@Scope(value="prototype")为何还是单例?
- 我们知道spring的Bean的作用域分为singleton,prototype,session,reques,默认作用域为singleton。
- 但有些时候场景则要求创建新的bean,比如多线程连接服务器执行shell、SFTP服务等,若为单例bean,第一个线程执行完命令后close连接,其他的线程都没法玩了...
- 作用域为prototype时,每次获取Bean都会有一个新的实例。
例子
@Component
//配置bean作用域为多例
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ShellUtil{
private static Connection conn = new Connection();
}
@RestController
public class ExecController {
@Autowired
private ShellUtil shell;
@GetMapping("test")
public void test(){
System.out.println("Thaad-Name:"+Thread.currentThread().getName());
System.out.println("ObjHashCode:"+shell.hashCode());
}
)
执行结果:
拿到的还是同一个bean
???挠头~
分析:上面的例子中虽然ShellUtil虽然设置为了多例,但是他被单例对象ExecController依赖,ExecController在初始化Bean的时候ShellUtil已经注入,并且注入时只创建一次。
方法1
- 让ExecController也变成多例对象
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@RestController
public class ExecController {
@Autowired
private ShellUtil shell;
@GetMapping("test")
public void test(){
System.out.println("Thaad-Name:"+Thread.currentThread().getName());
System.out.println("ObjHashCode:"+shell.hashCode());
}
)
执行结果:
效果是达到了,但是ExecController失去了单例的优势,若bean的调用链很长则每个被依赖的bean都需要配置为多例。
方法2
- 通过getBean方法直接从bean工厂获取对象,而不是使用自动注入的方式。
@RestController
public class ExecController {
/**
* 获取spring上下文
*/
@Autowired
private WebApplicationContext applicationContext;
@GetMapping("test")
public void test() {
System.out.println("Thaad-Name:"+Thread.currentThread().getName());
System.out.println("ObjHashCode:"+shell.hashCode());
}
)
执行结果:
方法3
- 对于此类连接工具我们只关心Connection对象是否多例,因此可以使用TheardLocal装配Connection,让每个线程拥有单独的Connection对象,互不干扰。
@Component
public class ShellUtil{
//使用ThreadLocal装配Connection对象
private static ThreadLocal<Connection> conn = new ThreadLocal<>();
public void close() {
if (conn != null) {
conn.get().close()
//注意:连接关闭后,记得回收ThreadLocal对象
conn.remove();
}
}
结论:
- 使用@Scope(value="prototype")配置多例时要确认是否存在Bean依赖链,若存在则使用getBean的方式,
- 使用连接类工具可以用TheardLocal装配Connection对象,保证现在拥护单独对象。
网友评论