@Scope(“prototype“)无法生效 | springboot中prototype无法生效 的原因和解决方案

背景

在我的SpringBoot项目中,类ExcelController调用ExcelServiceImpl类,而ExcelServiceImpl类有一个私有对象excelResList,是个有状态的类。这在默认单例的情况下ExcelServiceImpl类线程不安全。

因此,为了避免并发场景下,单例的ExcelServiceImpl类的excelResList对象被共享,需要将ExcelServiceImpl类设置为多例。代码如下:

@RestController
@RequestMapping("/money/excel")
public class ExcelController {
    @Autowired
    private ExcelService excelService;
    @ApiOperation(value = "Excel批量导入数据")
    @PostMapping("import/{userId}")
    public Result batchImport(
            @ApiParam(value = "用户id", required = true)
            @PathVariable("userId") Integer userId,
            @ApiParam(value = "Excel文件", required = true)
            @RequestParam("file") MultipartFile file)
            throws DemoException {
        InputStream inputStream = null;
        try {
            inputStream = file.getInputStream();
            List<Bill> bills = excelService.batchImport(inputStream, userId);
            return Result.success("批量导入成功", bills);
        } catch (Exception e) {
            e.printStackTrace();
            throw new DemoException("Excel数据导入错误");
        }
    }
}
@Service
//由于该类有状态,需要设置成多例
@Scope("prototype")
public class ExcelServiceImpl implements ExcelService {
	//每个对象都有自己的excelResList对象
    private List<Bill> excelResList = new ArrayList<>();
    @Override
    public void addExcelResList(List<Bill> bills){
        excelResList.addAll(bills);
    }
	...
}

问题

但是,测试时发现ExcelServiceImpl类仍然是单例的。即@Scope(“prototype”)无法生效。

原因

原来,单纯在ExcelServiceImpl类上声明原型域,是无法实现多例的。因为调用者ExcelController是单例的,在实例化时只有一次设置属性ExcelServiceImpl的机会,在运行时是无法创建新的bean的。所以ExcelServiceImpl类仍然是单例的。

解决方式

我的解决方法是:在ExcelController类上加@Scope(“request”)
但这种方式会导致ExcelController实现了请求域,即变为了多例,一定程度上浪费了资源。

所以第二种解决方式是:Method injection。官方文档: Method injection

这种方法使用Lookup方法注入,当需要使用ExcelServiceImpl类时新创建一个实例。代码修改后的如下(注释处是关键代码):

@RestController
@RequestMapping("/money/excel")
//将类声明为abstract
public abstract class ExcelController {
    //添加Lookup方法注入,该方法可以新创建ExcelServiceImpl实例。
    @Lookup("excelServiceImpl")
    protected abstract ExcelService createExcelService();
    
    @ApiOperation(value = "Excel批量导入数据")
    @PostMapping("import/{userId}")
    public Result batchImport(
        @ApiParam(value = "用户id", required = true)
        @PathVariable("userId") Integer userId,
        @ApiParam(value = "Excel文件", required = true)
        @RequestParam("file") MultipartFile file)
    throws DemoException {
        InputStream inputStream = null;
        try {
            inputStream = file.getInputStream();
            //在这里调用方法,由于ExcelServiceImpl是原型域,所以每次都是新的实例
            List<Bill> bills = createExcelService().batchImport(inputStream, userId);
            return Result.success("批量导入成功", bills);
        } catch (Exception e) {
            e.printStackTrace();
            throw new DemoException("Excel数据导入错误");
        }
    }
}
@Service
//同样也需要设置多例
@Scope("prototype")
public class ExcelServiceImpl implements ExcelService {
    //使得每个对象都有自己的excelResList对象
    private List<Bill> excelResList = new ArrayList<>();
    @Override
    public void addExcelResList(List<Bill> bills){
        excelResList.addAll(bills);
    }
    ...
}

经过测试,这两种方法都能解决并发问题。

多例Bean销毁问题

SpringBoot会将单例Bean进行自动销毁,而不会对多例Bean进行销毁。多例Bean留给JVM进行销毁,待多例Bean没有被引用后就会被垃圾回收,正常情况下不用我们手动销毁多例Bean。

总结

第二种方法较第一种方式好处在于减少了调用者ExcelController的多例,减少了资源浪费。因此,最好的方法是Method injection。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值