SpringBoot整合MinIO实现全场景文件操作管理

 更新时间:2025年06月03日 09:54:00   作者:天天摸鱼的java工程师  
这篇文章主要为大家介绍了SpringBoot 整合 MinIO 的全过程,从为什么选择 MinIO,到各种文件操作的实现,包括简单的文件上传,批量上传,文件下载,文件预览等功能,需要的可以参考下

最近项目中需要处理大量文件存储和管理的需求,对比了 Nginx、FastDFS、阿里云 OSS 等多种方案后,最终选择了 MinIO。今天就来和大家分享一下 SpringBoot 整合 MinIO 的全过程,从为什么选择 MinIO,到各种文件操作的实现,包括简单的文件上传、批量上传、文件下载、文件预览,再到大文件分片上传和秒传功能。

一、为什么选择 MinIO

在选择文件存储方案时,我们需要考虑多个因素,如功能、性能、成本、扩展性等。对比其他常见的文件存储方案,MinIO 具有以下优势:

1. 功能丰富

MinIO 支持标准的 S3 协议,可以与其他支持 S3 协议的工具和服务无缝集成。同时,它还提供了丰富的 API,包括文件上传、下载、预览、删除、版本控制等,满足各种文件管理需求。

2. 高性能

MinIO 专为高性能设计,采用分布式架构,可以横向扩展,支持 PB 级数据存储。在读写性能方面,MinIO 表现出色,尤其适合大文件的存储和处理。

3. 开源免费

MinIO 是开源项目,采用 AGPL v3 许可证,企业可以免费使用。对于中小企业来说,这无疑是一个很大的优势。

4. 易于部署和管理

MinIO 提供了简单易用的命令行工具和 Web 界面,部署和管理都非常方便。可以在几分钟内完成部署,并开始使用。

5. 数据安全

MinIO 支持数据加密、访问控制、多因素认证等安全功能,保障数据的安全性和隐私性。

对比其他方案

  • Nginx:主要用于静态文件服务,不支持分布式存储和大规模文件管理。
  • FastDFS:功能相对简单,缺乏统一的管理界面,扩展性有限。
  • 阿里云 OSS:云服务成本较高,依赖于网络环境,不适合对数据隐私要求较高的场景。

综上所述,MinIO 是一个功能强大、性能出色、易于部署和管理的文件存储方案,非常适合作为企业级文件存储系统。

二、环境准备

1. 安装 MinIO

可以通过 Docker 快速安装 MinIO:

docker run -p 9000:9000 -p 9001:9001 \
  --name minio \
  -v /data/minio/data:/data \
  -v /data/minio/config:/root/.minio \
  -e "MINIO_ROOT_USER=minioadmin" \
  -e "MINIO_ROOT_PASSWORD=minioadmin" \
  minio/minio server /data --console-address ":9001"

安装完成后,可以通过访问http://localhost:9001进入 MinIO 管理界面,使用用户名minioadmin和密码minioadmin登录。

2. 创建 SpringBoot 项目

使用 Spring Initializr 创建一个 SpringBoot 项目,添加以下依赖:

  • Spring Web
  • Lombok
  • MinIO Client

三、整合 MinIO

1. 添加依赖

pom.xml中添加 MinIO 客户端依赖:

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.5.5</version>
</dependency>

2. 配置 MinIO 连接信息

application.yml中添加 MinIO 配置信息:

minio:
  endpoint: http://localhost:9000
  access-key: minioadmin
  secret-key: minioadmin
  bucket-name: test-bucket

3. 创建 MinIO 配置类

创建一个配置类,用于创建 MinIO 客户端:

import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MinIOConfig {

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.access-key}")
    private String accessKey;

    @Value("${minio.secret-key}")
    private String secretKey;

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}

四、创建 MinIO 工具类

为了方便使用 MinIO 的各种功能,我们创建一个工具类,封装 MinIO 的常用操作:

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class MinioUtil {

    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucket-name}")
    private String defaultBucketName;

    /**
     * 检查存储桶是否存在
     * @param bucketName 存储桶名称
     * @return 是否存在
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建存储桶
     * @param bucketName 存储桶名称
     */
    @SneakyThrows
    public void makeBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 获取所有存储桶
     * @return 存储桶列表
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 删除存储桶
     * @param bucketName 存储桶名称
     */
    @SneakyThrows
    public void removeBucket(String bucketName) {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 简单文件上传
     * @param file       文件
     * @param bucketName 存储桶名称
     * @return 文件信息
     */
    @SneakyThrows
    public Map<String, String> uploadFile(MultipartFile file, String bucketName) {
        if (file == null || file.isEmpty()) {
            return null;
        }

        if (!bucketExists(bucketName)) {
            makeBucket(bucketName);
        }

        String originalFilename = file.getOriginalFilename();
        String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

        minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(fileName)
                .contentType(file.getContentType())
                .stream(file.getInputStream(), file.getSize(), -1)
                .build());

        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("fileName", fileName);
        resultMap.put("originalFilename", originalFilename);
        resultMap.put("url", getObjectUrl(bucketName, fileName, 7));

        return resultMap;
    }

    /**
     * 简单文件上传(使用默认存储桶)
     * @param file 文件
     * @return 文件信息
     */
    public Map<String, String> uploadFile(MultipartFile file) {
        return uploadFile(file, defaultBucketName);
    }

    /**
     * 批量文件上传
     * @param files      文件列表
     * @param bucketName 存储桶名称
     * @return 文件信息列表
     */
    public List<Map<String, String>> uploadFiles(List<MultipartFile> files, String bucketName) {
        return files.stream()
                .map(file -> uploadFile(file, bucketName))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    /**
     * 批量文件上传(使用默认存储桶)
     * @param files 文件列表
     * @return 文件信息列表
     */
    public List<Map<String, String>> uploadFiles(List<MultipartFile> files) {
        return uploadFiles(files, defaultBucketName);
    }

    /**
     * 下载文件
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @return 输入流
     */
    @SneakyThrows
    public InputStream downloadFile(String bucketName, String objectName) {
        return minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
    }

    /**
     * 下载文件(使用默认存储桶)
     * @param objectName 对象名称
     * @return 输入流
     */
    public InputStream downloadFile(String objectName) {
        return downloadFile(defaultBucketName, objectName);
    }

    /**
     * 删除文件
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     */
    @SneakyThrows
    public void deleteFile(String bucketName, String objectName) {
        minioClient.removeObject(RemoveObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
    }

    /**
     * 删除文件(使用默认存储桶)
     * @param objectName 对象名称
     */
    public void deleteFile(String objectName) {
        deleteFile(defaultBucketName, objectName);
    }

    /**
     * 批量删除文件
     * @param bucketName  存储桶名称
     * @param objectNames 对象名称列表
     * @return 删除错误列表
     */
    @SneakyThrows
    public List<DeleteError> deleteFiles(String bucketName, List<String> objectNames) {
        List<DeleteObject> objects = objectNames.stream()
                .map(DeleteObject::new)
                .collect(Collectors.toList());

        Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
                .bucket(bucketName)
                .objects(objects)
                .build());

        List<DeleteError> errors = new ArrayList<>();
        for (Result<DeleteError> result : results) {
            errors.add(result.get());
        }
        return errors;
    }

    /**
     * 批量删除文件(使用默认存储桶)
     * @param objectNames 对象名称列表
     * @return 删除错误列表
     */
    public List<DeleteError> deleteFiles(List<String> objectNames) {
        return deleteFiles(defaultBucketName, objectNames);
    }

    /**
     * 获取文件URL
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param expires    过期时间(天)
     * @return 文件URL
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName, int expires) {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucketName)
                .object(objectName)
                .expiry(expires, TimeUnit.DAYS)
                .build());
    }

    /**
     * 获取文件URL(使用默认存储桶)
     * @param objectName 对象名称
     * @param expires    过期时间(天)
     * @return 文件URL
     */
    public String getObjectUrl(String objectName, int expires) {
        return getObjectUrl(defaultBucketName, objectName, expires);
    }

    /**
     * 检查文件是否存在
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @return 是否存在
     */
    @SneakyThrows
    public boolean objectExists(String bucketName, String objectName) {
        try {
            minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 检查文件是否存在(使用默认存储桶)
     * @param objectName 对象名称
     * @return 是否存在
     */
    public boolean objectExists(String objectName) {
        return objectExists(defaultBucketName, objectName);
    }

    /**
     * 列出存储桶中的所有对象
     * @param bucketName 存储桶名称
     * @return 对象列表
     */
    @SneakyThrows
    public List<Item> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                .bucket(bucketName)
                .build());

        List<Item> items = new ArrayList<>();
        for (Result<Item> result : results) {
            items.add(result.get());
        }
        return items;
    }

    /**
     * 列出存储桶中的所有对象(使用默认存储桶)
     * @return 对象列表
     */
    public List<Item> listObjects() {
        return listObjects(defaultBucketName);
    }

    /**
     * 创建分片上传
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @return 上传ID
     */
    @SneakyThrows
    public String createMultipartUpload(String bucketName, String objectName) {
        CreateMultipartUploadResponse response = minioClient.createMultipartUpload(CreateMultipartUploadArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
        return response.result().uploadId();
    }

    /**
     * 上传分片
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param uploadId   上传ID
     * @param partNumber 分片编号
     * @param stream     输入流
     * @param size       大小
     * @return 分片ETag
     */
    @SneakyThrows
    public String uploadPart(String bucketName, String objectName, String uploadId, int partNumber, InputStream stream, long size) {
        UploadPartResponse response = minioClient.uploadPart(UploadPartArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .uploadId(uploadId)
                .partNumber(partNumber)
                .stream(stream, size, -1)
                .build());
        return response.etag();
    }

    /**
     * 完成分片上传
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param uploadId   上传ID
     * @param etags      分片ETag列表
     */
    @SneakyThrows
    public void completeMultipartUpload(String bucketName, String objectName, String uploadId, List<String> etags) {
        List<CompletePart> completeParts = new ArrayList<>();
        for (int i = 0; i < etags.size(); i++) {
            completeParts.add(new CompletePart(i + 1, etags.get(i)));
        }

        minioClient.completeMultipartUpload(CompleteMultipartUploadArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .uploadId(uploadId)
                .parts(completeParts)
                .build());
    }

    /**
     * 生成文件哈希值(用于秒传判断)
     * @param file 文件
     * @return 哈希值
     */
    @SneakyThrows
    public String generateFileHash(MultipartFile file) {
        // 这里使用简单的文件大小和修改时间作为哈希值,实际应用中应使用MD5或SHA-1等算法
        return file.getSize() + "-" + file.getOriginalFilename();
    }
}

五、创建 Controller

接下来,我们创建一个 Controller,提供各种文件操作的接口:

import io.minio.messages.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/minio")
public class MinioController {

    @Autowired
    private MinioUtil minioUtil;

    /**
     * 简单文件上传
     */
    @PostMapping("/upload")
    public ResponseEntity<Map<String, Object>> uploadFile(@RequestParam("file") MultipartFile file) {
        Map<String, Object> result = new HashMap<>();
        try {
            Map<String, String> fileInfo = minioUtil.uploadFile(file);
            if (fileInfo != null) {
                result.put("code", 200);
                result.put("message", "上传成功");
                result.put("data", fileInfo);
                return ResponseEntity.ok(result);
            } else {
                result.put("code", 500);
                result.put("message", "上传失败");
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
            }
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "上传异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 批量文件上传
     */
    @PostMapping("/upload/batch")
    public ResponseEntity<Map<String, Object>> uploadFiles(@RequestParam("files") List<MultipartFile> files) {
        Map<String, Object> result = new HashMap<>();
        try {
            List<Map<String, String>> fileInfos = minioUtil.uploadFiles(files);
            result.put("code", 200);
            result.put("message", "上传成功");
            result.put("data", fileInfos);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "上传异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 文件下载
     */
    @GetMapping("/download/{fileName}")
    public ResponseEntity<byte[]> downloadFile(@PathVariable("fileName") String fileName) {
        try {
            InputStream inputStream = minioUtil.downloadFile(fileName);
            byte[] bytes = inputStream.readAllBytes();
            inputStream.close();

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", fileName);

            return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(null, null, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 文件预览
     */
    @GetMapping("/preview/{fileName}")
    public ResponseEntity<Map<String, Object>> previewFile(@PathVariable("fileName") String fileName) {
        Map<String, Object> result = new HashMap<>();
        try {
            String url = minioUtil.getObjectUrl(fileName, 1);
            result.put("code", 200);
            result.put("message", "获取成功");
            result.put("url", url);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "获取异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 删除文件
     */
    @DeleteMapping("/delete/{fileName}")
    public ResponseEntity<Map<String, Object>> deleteFile(@PathVariable("fileName") String fileName) {
        Map<String, Object> result = new HashMap<>();
        try {
            minioUtil.deleteFile(fileName);
            result.put("code", 200);
            result.put("message", "删除成功");
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "删除异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 列出所有文件
     */
    @GetMapping("/list")
    public ResponseEntity<Map<String, Object>> listFiles() {
        Map<String, Object> result = new HashMap<>();
        try {
            List<Item> items = minioUtil.listObjects();
            result.put("code", 200);
            result.put("message", "获取成功");
            result.put("data", items);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "获取异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 初始化分片上传
     */
    @PostMapping("/multipart/init")
    public ResponseEntity<Map<String, Object>> initMultipartUpload(@RequestParam("fileName") String fileName) {
        Map<String, Object> result = new HashMap<>();
        try {
            String uploadId = minioUtil.createMultipartUpload("test-bucket", fileName);
            result.put("code", 200);
            result.put("message", "初始化成功");
            result.put("uploadId", uploadId);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "初始化异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 上传分片
     */
    @PostMapping("/multipart/upload")
    public ResponseEntity<Map<String, Object>> uploadPart(
            @RequestParam("fileName") String fileName,
            @RequestParam("uploadId") String uploadId,
            @RequestParam("partNumber") int partNumber,
            @RequestParam("file") MultipartFile file) {
        Map<String, Object> result = new HashMap<>();
        try {
            String etag = minioUtil.uploadPart("test-bucket", fileName, uploadId, partNumber, file.getInputStream(), file.getSize());
            result.put("code", 200);
            result.put("message", "分片上传成功");
            result.put("etag", etag);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "分片上传异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 完成分片上传
     */
    @PostMapping("/multipart/complete")
    public ResponseEntity<Map<String, Object>> completeMultipartUpload(
            @RequestParam("fileName") String fileName,
            @RequestParam("uploadId") String uploadId,
            @RequestParam("etags") List<String> etags) {
        Map<String, Object> result = new HashMap<>();
        try {
            minioUtil.completeMultipartUpload("test-bucket", fileName, uploadId, etags);
            result.put("code", 200);
            result.put("message", "分片合并成功");
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "分片合并异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 文件秒传检查
     */
    @PostMapping("/check")
    public ResponseEntity<Map<String, Object>> checkFile(@RequestParam("file") MultipartFile file) {
        Map<String, Object> result = new HashMap<>();
        try {
            String fileHash = minioUtil.generateFileHash(file);
            // 这里应该查询数据库或缓存,检查是否存在相同哈希值的文件
            // 为简化示例,直接返回不存在
            boolean exists = false;

            result.put("code", 200);
            result.put("message", "检查成功");
            result.put("exists", exists);
            result.put("fileHash", fileHash);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "检查异常:" + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }
}

六、大文件分片上传和秒传实现原理

1. 大文件分片上传

大文件分片上传是将一个大文件分成多个小片段,分别上传这些片段,最后在服务器端将这些片段合并成一个完整的文件。实现步骤如下:

前端:将文件切成固定大小的片段(如 1MB / 片),为每个片段生成唯一标识(如序号),按顺序上传这些片段。

后端

  • 接收前端上传的片段,保存到临时目录。
  • 记录已上传的片段信息(如文件名、片段序号、ETag 等)。
  • 当所有片段上传完成后,按顺序合并这些片段。

2. 秒传功能

秒传功能是指当用户上传一个文件时,系统首先检查该文件是否已经存在,如果存在则直接返回文件链接,无需重新上传。实现步骤如下:

前端:计算文件的哈希值(如 MD5、SHA-1),并将哈希值发送给后端。

后端

  • 根据哈希值查询数据库或缓存,检查是否存在相同哈希值的文件。
  • 如果存在,返回文件链接;如果不存在,通知前端正常上传。

七、测试与验证

1. 简单文件上传测试

使用 Postman 或其他工具,向/api/minio/upload接口发送 POST 请求,上传一个文件,验证是否能成功上传并返回文件信息。

2. 批量文件上传测试

/api/minio/upload/batch接口发送 POST 请求,上传多个文件,验证是否能成功批量上传。

3. 文件下载测试

访问/api/minio/download/{fileName}接口,验证是否能成功下载文件。

4. 文件预览测试

访问/api/minio/preview/{fileName}接口,验证是否能获取文件预览链接。

5. 大文件分片上传测试

使用前端工具(如 webuploader、plupload 等)实现大文件分片上传功能,调用后端提供的分片上传接口,验证大文件是否能成功上传。

6. 秒传功能测试

上传一个文件,记录文件哈希值,再次上传相同文件,验证是否能秒传成功。

到此这篇关于SpringBoot整合MinIO实现全场景文件操作管理的文章就介绍到这了,更多相关SpringBoot MinIO文件操作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 如何利用MyBatisX插件自动生成代码

    如何利用MyBatisX插件自动生成代码

    这篇文章主要介绍了如何利用MyBatisX插件自动生成代码,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • java 多线程实现在线咨询(udp)

    java 多线程实现在线咨询(udp)

    这篇文章主要介绍了java 多线程实现在线咨询(udp)的示例,帮助大家更好的理解和学习Java 网络编程的相关内容,感兴趣的朋友可以了解下
    2020-11-11
  • Java重写(Override)与重载(Overload)区别原理解析

    Java重写(Override)与重载(Overload)区别原理解析

    这篇文章主要介绍了Java重写(Override)与重载(Overload)区别原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • springboot切面添加日志功能实例详解

    springboot切面添加日志功能实例详解

    在本篇文章里小编给大家整理的是关于springboot 切面添加日志功能的相关知识点内容,有需要的朋友们可以参考下。
    2019-09-09
  • Java 中函数 Function 的使用和定义示例小结

    Java 中函数 Function 的使用和定义示例小结

    这篇文章主要介绍了Java 中函数 Function 的使用和定义小结,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-07-07
  • 浅谈MyBatis Plus主键设置策略

    浅谈MyBatis Plus主键设置策略

    本文主要介绍了MyBatis Plus主键设置策略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Java中二叉树的先序、中序、后序遍历以及代码实现

    Java中二叉树的先序、中序、后序遍历以及代码实现

    这篇文章主要介绍了Java中二叉树的先序、中序、后序遍历以及代码实现,一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成,需要的朋友可以参考下
    2023-11-11
  • springboot3+r2dbc响应式编程实践

    springboot3+r2dbc响应式编程实践

    本文主要介绍了springboot3+r2dbc响应式编程实践,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • SpringBoot项目中如何解决跨域问题的最新方案?

    SpringBoot项目中如何解决跨域问题的最新方案?

    跨域问题是浏览器为了保护用户的信息安全,实施了同源策略(Same-Origin Policy),即只允许页面请求同源(相同协议、域名和端口)的资源,当 JavaScript 发起的请求跨越了同源策略,即请求的目标与当前页面的域名、端口、协议不一致时,浏览器会阻止请求的发送或接收
    2025-03-03
  • 通过实例学习Spring @Required注释原理

    通过实例学习Spring @Required注释原理

    这篇文章主要介绍了通过实例学习Spring @Required注释原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03

最新评论