Alibaba Cloud OSS client implements singleton mode running account
"TLDR: This article mainly introduces the Alibaba Cloud OSS client to implement singleton mode running account. In the original code, every user request for a file inside the bucket would be created and destroyed on the backend, causing a huge waste of performance. The method used to download files is: oss downloads files to the backend, and the backend transmits the file content to the frontend through the input and output streams, which also puts great pressure on the backend. The flow chart of the original code is as follows:"
Today in the back-end system in the maintenance team, we found a lot of code that caused brain hemorrhage, mainly the CRUD part of Alibaba Cloud oss.
First look at the original code:
Controller layer:
@ApiOperation("Download the specified file")
@GetMapping("/download")
public void download(@RequestParam String url, HttpSession session, HttpServletResponse response)
throws IOException {
try {
InputStream inputStream = new ByteArrayInputStream(
ossService.newfileDownload(String.valueOf(session.getAttribute("accessKeyId")),
String.valueOf(session.getAttribute("accessKeySecret")),
String.valueOf(session.getAttribute("endpoint")),
url));
OutputStream outputStream = response.getOutputStream();
String[] parts = url.split("/");
String filename = parts[parts.length - 1];
response.setContentType("application/x-download");
String saveName = new String(filename.getBytes("GBK"), "iso8859-1");
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + saveName);
IoUtil.copy(inputStream, outputStream);
} catch (IORuntimeException | IOException e) {
// Solve the problem of error reporting after each download without affecting actual applications
}
// return new ResultWrapper(res);
}
@ApiOperation("Delete the specified file")
@PostMapping("/delete")
public ResultWrapper delete(@RequestParam String url, HttpSession session) {
boolean result = ossService.deleteFile(String.valueOf(session.getAttribute("accessKeyId")),
String.valueOf(session.getAttribute("accessKeySecret")),
String.valueOf(session.getAttribute("endpoint")),
url);
return new ResultWrapper(result);
}
@ApiOperation("Get all buckets")
@PostMapping("/buckets")
public ResultWrapper getAllBuckets(HttpSession session) {
List<Bucket> result = ossService.listBuckets(String.valueOf(session.getAttribute("accessKeyId")),
String.valueOf(session.getAttribute("accessKeySecret")));
return new ResultWrapper(result);
}
Service layer:
@Override
public String fileUpload(byte[] bytes, String dir, String name) {
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
//Logic for uploading code
} catch (OSSException oe) {
// Exception handling logic
} catch (ClientException ce) {
// Exception handling logic
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return name;
}
@Override
public byte[] fileDownload(String dir, String fileName) throws IOException {
File template = null;
String[] split = fileName.split("\\.");
template = File.createTempFile(split[0], "." + split[1]);
//Create an OSSClient instance.
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// Download the Object to a local file and save it to the specified local path. If the specified local file exists, it will be overwritten. If it does not exist, it will be created.
ossClient.getObject(new GetObjectRequest(bucketName, dir + "/" + fileName), template);
//Close OSSClient.
ossClient.shutdown();
return IoUtil.readBytes(IoUtil.toStream(template));
}
@Override
public List<Bucket> listBuckets(String accessKeyId, String accessKeySecret) {
//Create OSSClient instance
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
List<Bucket> bucketList = new ArrayList<>();
try {
//The logic of getting the Bucket list
} catch (Exception e) {
e.printStackTrace();
} finally {
//Close OSSClient
ossClient.shutdown();
}
return bucketList;
}
The summary is as follows:
-
Every time a user requests a file inside the bucket, it will be created and destroyed on the backend, causing a huge waste of performance.
-
The method used to download files is: oss downloads the file to the backend, and the backend transmits the file content to the frontend through the input and output streams, which also puts great pressure on the backend.
The flow chart of the original code is as follows:

For requests for the same bucket, whether it is addition, deletion, modification or query, you can actually reuse an instance to avoid frequent creation and destruction of objects, so use the singleton mode instead:

However, if we think more deeply, if multiple users are adding, deleting, modifying, and checking multiple buckets, we cannot reuse only one oss client instance at this time, because one oss client instance is only bound to one bucket.
Therefore, it needs to be changed to the following mode:

For uploading and downloading, there is no need to use back-end transfer, but key and secret cannot be stored directly in the front-end, as this may cause security risks. Therefore, the backend is used to issue temporary URLs for the frontend to upload/download.
The final modified code is as follows:
Factory class (used to implement singleton pattern)
public class OssClientFactory {
// Volatile is a lightweight synchronization mechanism provided by Java, and it also plays an important role in concurrent programming.
//Compared with synchronized (synchronized is often called heavyweight lock), volatile is more lightweight and compared to using
// The huge overhead caused by synchronized, if volatile can be used appropriately and reasonably, it will be wonderful
private volatile static OSSClientBuilder ossClientBuilder;
// ConcurrentHashMap is a thread-safe HashMap. Its thread safety is achieved through segmentation locks. Its efficiency is higher than Hashtable.
private static ConcurrentHashMap <String, OSSClient> clientMap = new ConcurrentHashMap();
public OssClientFactory() {
}
@Bean
@Scope("prototype")
public static OSS getOSSClient(String endpoint, String accessKeyId, String accessKeySecret) {
if (clientMap.containsKey(endpoint)) {
OSSClient client = clientMap.get(endpoint);
return client;
}
synchronized (OssClientFactory.class) {
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
//Set the maximum number of HTTP connections allowed to be opened by OSSClient. The default is 1024.
conf.setMaxConnections(200);
// Set the timeout for Socket layer data transmission, the default is 50000 milliseconds.
conf.setSocketTimeout(10000);
//Set the timeout for establishing a connection, the default is 50000 milliseconds.
conf.setConnectionTimeout(10000);
//Set the timeout for obtaining connections from the connection pool (unit: milliseconds), the default is no timeout.
conf.setConnectionRequestTimeout(1000);
//Set the connection idle timeout. The connection is closed when the timeout occurs, and the default is 60000 milliseconds.
conf.setIdleConnectionTime(10000);
//Set the number of retries for failed requests, the default is 3 times.
conf.setMaxErrorRetry(5);
OSSClient client = (OSSClient) getOSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret, conf);
clientMap.put(endpoint, client);
}
return clientMap.get(endpoint);
}
public static void closeOSSClient(String endpoint) {
if (clientMap.containsKey(endpoint)) {
clientMap.get(endpoint).shutdown();
clientMap.remove(endpoint);
}
}
public static OSSClientBuilder getOSSClientBuilder() {
System.out.println("Get OSSClientBuilder");
if (ossClientBuilder == null) {
System.out.println("OSSClientBuilder is empty and is being created");
synchronized (OssClientFactory.class) {
if (ossClientBuilder == null) {
System.out.println("Enter synchronous instantiation of OSSClientBuilder");
ossClientBuilder = new OSSClientBuilder();
}
}
}
return ossClientBuilder;
}
}
@Override
public String fileUpload(byte[] bytes, String dir, String name) {
//Create instance
OSS ossClient = OssClientFactory.getOSSClient(endpoint, accessKeyId, accessKeySecret);
try {
ossClient.putObject(bucketName, dir + "/" + name, new ByteArrayInputStream(bytes));
} catch (OSSException oe) {
// do something
} finally {
if (ossClient != null) {
// Close the instance
OssClientFactory.closeOSSClient(endpoint);
}
}
return name;
}
// Generate a temporary signed URL. This URL can be directly uploaded/downloaded by the front end.
@Override
public String generatePresignedUrl(String bucketName, String objectName, String endpoint, Long expireTime,
HttpMethod method) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, method);
Date expiration = new Date(System.currentTimeMillis() + expireTime);
request.setExpiration(expiration);
//Create an OSSClient instance.
OSS ossClient = OssClientFactory.getOSSClient(endpoint, accessKeyId, accessKeySecret);
try {
// Generate signed URL.
return ossClient.generatePresignedUrl(request).toString();
} finally {
//Close OSSClient.
OssClientFactory.closeOSSClient(endpoint);
}
}