The bean scope is singleton (singleton mode) causing multi-thread safety issues

HUAWEI CLOUD OBS integrates Ueditor, but when uploading images in batches, only partial uploads are successful, and many files will fail to upload. After analysis, it is found that when the bean scope is singleton mode, there will only be one shared bean in the Spring IoC container. Instances, no matter how many Beans refer to it, always point to the same object. This mode is unsafe under multi-threading, and is hereby recorded.
Insert picture description here
error code:

@Service
@Slf4j
public class FileServiceImpl implements FileService {

	@Value("${files.path}")
	private String filesPath;

	@Value("${files.prefix}")
	private String FilesPrefix;

	@Value("${huaWeiObs.AccessKeyId}")
	private String AccessKeyId;

	@Value("${huaWeiObs.AccessKeySecret}")
	private String AccessKeySecret;

	@Value("${huaWeiObs.BucketName}")
	private String BucketName;

	@Value("${huaWeiObs.Endpoint}")
	private String Endpoint;

	@Value("${huaWeiObs.ObsFilesPath}")
	private String ObsFilesPath;

    private ObsClient obsClient;

	@Autowired
	private FileInfoMapper fileInfoMapper;

	@Override
	public FileInfo huaWeiObsUpload(MultipartFile file)  {


		PutObjectResult putObjectResult = null;
		String md5                                    = null;

		try {

			//验证文件格式
			String fileOrigName = file.getOriginalFilename();
			if (!fileOrigName.contains(".")) {
				throw new IllegalArgumentException("缺少后缀名");
			}

			//对文件流进行MD5加密
			md5               = FileUtil.fileMd5(file.getInputStream());
			FileInfo fileInfo = fileInfoMapper.getById(md5);

			//根据MD字符串查看文件是否已经上传,已经上传的文件直接返回文件信息不用调取华为云上传接口
			if (fileInfo != null) {
				fileInfo.setFilePrefix(FilesPrefix);
				fileInfoMapper.update(fileInfo);
				return fileInfo;
			}

			String fileSuffix    =  fileOrigName.substring(fileOrigName.lastIndexOf("."));
			String pathname      =  ObsFilesPath+FileUtil.getPath()+md5+fileSuffix;
           
           //实例化ObsClient,并将实例化引用赋值给成员变量obsClient
			obsClient                 =  new ObsClient(AccessKeyId,AccessKeySecret,Endpoint);
			putObjectResult      =  obsClient.putObject(BucketName, pathname, file.getInputStream());

			//保存上传记录到数据库
			long size          = file.getSize();
			String contentType = file.getContentType();
			String fullPath    = putObjectResult.getObjectUrl();
			fileInfo           = new FileInfo();
			fileInfo.setId(md5);
			fileInfo.setContentType(contentType);
			fileInfo.setSize(size);
			fileInfo.setPath(fullPath);
			fileInfo.setUrl(pathname);
			fileInfo.setType(contentType.startsWith("image/") ? 1 : 0);
			fileInfo.setFilePrefix(FilesPrefix);
			fileInfo.setOriginalName(fileOrigName);
			fileInfo.setSuffix(fileSuffix);
			fileInfoMapper.save(fileInfo);
			log.info("文件上传成功{}", fullPath);

			//返回文件对象
			return fileInfo;

		} catch (ObsException e) {
			log.info("Response Code: {}",e.getResponseCode());
			log.info("Error Message: {}",e.getErrorMessage());
			log.info("Error Code: {}",e.getErrorCode());
			log.info("Request ID: {}",e.getErrorRequestId());
			log.info("Host ID: {}",e.getErrorHostId());
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			//不管有没有返回,最后一定会执行
			if (obsClient != null) {
				try {
					//关闭最后连接
					obsClient.close();
				} catch (IOException e) {

				}
			}
		}

		//返回空对象
		return null;
	}
	
}

The reason for the error: The
default scope of the bean instance is the singleton mode. There will only be one shared bean instance in the Spring IoC container. No matter how many beans refer to it, it will always point to the same object. This mode is not safe under multithreading The same bean object may be injected into different objects. In the case of high concurrency, the dependent object may be shared by multiple threads, which will cause multi-thread safety issues.

For example: the FileService class is dependent on the FileController class, and the FileService bean object is injected into the FileController object through @Autowired. Because the bean instance of FileService is in singleton mode, in the case of high concurrency, the same FileService bean object instance will be injected into different FileController objects. Therefore, the FileService bean object is shared by multiple threads, causing security issues. For example: a thread is uploading a file using the obsClient.putObject() member variable of the FileService bean object, and another thread is using the obsClient.close() member of the FileService bean object to close the link, which causes part of the file upload to fail.

Solution:
1. Do not declare variables like obsClient as member variables, but as local variables to avoid obsClient being shared by multiple threads to cause security problems.
2. Use @Scope("prototype") to define the FileService bean scope as the prototype mode. Every time a bean defined by the prototype is obtained through the Spring container, the container will create
a new Bean instance, which will not cause multi-threaded safety issues.
2. Use sychornized to modify the upload file code and lock the object instance, for example:
Synchronized(this){ //Upload code //Because the same FileService bean object is injected into a thread unlike a thread, it can be locked }


However, this method of locking may cause serious blocking and performance overhead, so this method is not recommended.

Code for method 1:

@Service
@Slf4j
public class FileServiceImpl_bak implements FileService {

	@Value("${files.path}")
	private String filesPath;

	@Value("${files.prefix}")
	private String FilesPrefix;

	@Value("${huaWeiObs.AccessKeyId}")
	private String AccessKeyId;

	@Value("${huaWeiObs.AccessKeySecret}")
	private String AccessKeySecret;

	@Value("${huaWeiObs.BucketName}")
	private String BucketName;

	@Value("${huaWeiObs.Endpoint}")
	private String Endpoint;

	@Value("${huaWeiObs.ObsFilesPath}")
	private String ObsFilesPath;

	@Autowired
	private FileInfoMapper fileInfoMapper;

	@Override
	public FileInfo huaWeiObsUpload(MultipartFile file)  {

		//局部变量,不被多个线程共享,这避免了多线程导致的安全问题
		ObsClient obsClient             = null;
		PutObjectResult putObjectResult = null;
		String md5                      = null;

		try {

			//验证文件格式
			String fileOrigName = file.getOriginalFilename();
			if (!fileOrigName.contains(".")) {
				throw new IllegalArgumentException("缺少后缀名");
			}

			//对文件流进行MD5加密
			md5               = FileUtil.fileMd5(file.getInputStream());
			FileInfo fileInfo = fileInfoMapper.getById(md5);
			//根据MD5字符串查看文件是否已经上传,已经上传的文件直接返回文件信息,不用调取华为云上传接口
			if (fileInfo != null) {
				fileInfo.setFilePrefix(FilesPrefix);
				fileInfoMapper.update(fileInfo);
				return fileInfo;
			}

			//文件后缀名与上传路径
			String fileSuffix    =  fileOrigName.substring(fileOrigName.lastIndexOf("."));
			String pathname      =  ObsFilesPath+FileUtil.getPath()+md5+fileSuffix;

			obsClient            =  new ObsClient(AccessKeyId,AccessKeySecret,Endpoint);
			putObjectResult      =  obsClient.putObject(BucketName, pathname, file.getInputStream());

			//保存上传记录到数据库
			long size          = file.getSize();
			String contentType = file.getContentType();
			String fullPath    = putObjectResult.getObjectUrl();
			fileInfo           = new FileInfo();
			fileInfo.setId(md5);
			fileInfo.setContentType(contentType);
			fileInfo.setSize(size);
			fileInfo.setPath(fullPath);
			fileInfo.setUrl(pathname);
			fileInfo.setType(contentType.startsWith("image/") ? 1 : 0);
			fileInfo.setFilePrefix(FilesPrefix);
			fileInfo.setOriginalName(fileOrigName);
			fileInfo.setSuffix(fileSuffix);
			fileInfoMapper.save(fileInfo);
			log.info("文件上传成功{}", fullPath);

			//返回文件对象
			return fileInfo;

		} catch (ObsException e) {
			log.info("Response Code: {}",e.getResponseCode());
			log.info("Error Message: {}",e.getErrorMessage());
			log.info("Error Code: {}",e.getErrorCode());
			log.info("Request ID: {}",e.getErrorRequestId());
			log.info("Host ID: {}",e.getErrorHostId());
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			//不管有没有返回,最后一定会执行
			if (obsClient != null) {
				try {
					//关闭最后连接
					obsClient.close();
				} catch (IOException e) {

				}
			}
		}

		return null;

	}

}

Summary:
Spring 3 defines 5 scopes for Bean, namely singleton (singleton), prototype (prototype), request, session and global session. Different application scenarios need to choose different scopes to reduce overhead and ensure thread safety When springboot defines the scope of the bean, use the @Scope annotation to decorate the class. E.g:

//定义单例模式
@Scope("singleton")
@Component
public class SingleScopeTest {
}
//定义原型模式
@Scope("prototype")
@Component
public class PrototypeScoreTest {
}

Guess you like

Origin blog.csdn.net/u011582840/article/details/107939151