@ConditionalOnProperty注解使用详解

1,810 阅读5分钟

在开发基于SpringBoot框架的项目时,会用到下面的条件注解,有时会有需要控制配置类是否生效或注入到Spring上下文中的场景,可以使用@ConditionalOnProperty注解来控制@Configuration的注解是否生效。

实现原理

@ConditionalOnProperty通过havingValue与配置文件中的值进行对比,如果对比值返回TRUE则配置类生效,反之失效。

源码分析

@Retention(RetentionPolicy.RUNTIME)  
@Target({ ElementType.TYPE, ElementType.METHOD })  
@Documented  
@Conditional(OnPropertyCondition.class)  
public @interface ConditionalOnProperty {  
    
    // 配置项值
    String[] value() default {};  
    
    // 配置项前缀   
    String prefix() default "";  
    
    // 配置项名
    String[] name() default {};  
    
    // havingValue的值与配置项的值进行比较,一致返回TRUE,不一致返回FALSE
    String havingValue() default "";  

    // 如果配置文件中, 没有该配置项, 判断是否加载BEAN, 默认为false。
    boolean matchIfMissing() default false;  
  
}

注解使用实例

注解实例1

  • matchIfMissing值为TRUE,即使配置文件中没有file.service.type配置,依然会加载UploadFile
@Configuration
@ConditionalOnProperty(name = "file.service.type", matchIfMissing = true)
public class UploadFile {
}

注解实例2

file.service.type=true
  • 既指定prefix也指定name,配置项的值为TRUE,因此依然加载UploadFile
@Configuration @ConditionalOnProperty(prefix = "file",name = "service.type") 
public class UploadFile { 
}

注解实例3

file.service.type=true
  • 只指定namevalue,配置项的值为true,因此加载UploadFile
@Configuration @ConditionalOnProperty(name = "file.service.type") 
public class UploadFile { 
}

注解实例4

file.service.type=true
  • 只指定namevalue,配置项的值为true,但havingValue为false;因为havingValue的值为false所以不会加载UploadFile
@Configuration @ConditionalOnProperty(name = "file.service.type",havingValue = "false") 
public class UploadFile { 
}

注解实例5

file.service.type=local
  • 指定name也指定havingValue,name配置项的值与havingValue的值相同,因此加载UploadFile
@Configuration @ConditionalOnProperty(name = "file.service.type",havingValue = "local") 
public class UploadFile { 
}

注解总结

  • 当配置项为true,havingValue的值为true;对比配置项与havingValue的值相同该类会加载
  • 当配置项为false,havingValue的值为false;对比配置项与havingValue的值相同该类会加载
  • 当配置项为true,havingValue的值为false;对比配置项与havingValue的值相同该类不会加载
  • 当配置项为false,havingValue的值为true;对比配置项与havingValue的值相同该类不会加载
  • havingValue不设置值时,是否加载有配置项决定
    • 配置项为true,该类会加载
    • 配置项为false,该类不会加载

image.png

工作中实际应用

工作需求

根据配置文件中file.service.type配置项,来决定文件上传到本地还是minio。

#文件服务类型分为 minio 和 local
file.service.type=local
# file.service.type=minio

代码示例

  1. 创建上传文件Service
import org.springframework.web.multipart.MultipartFile;   
import javax.servlet.http.HttpServletResponse;  
import java.io.InputStream;  

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2fab80e8a65244b48bcca8ce9603781c~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=838&h=555&s=54194&e=png&b=c6eccc)
public interface FileService {  
    
    /**  
    * 上传文件  
    * @param uploadfile  
    * @param pathName  
    * @throws Exception  
    */  
    void fileUpload(MultipartFile uploadfile, String pathName) throws Exception;  

    /**  
    * 下载文件  
    * @param wholePath  
    * @param response  
    * @param fileName  
    * @throws Exception  
    */  
    void downloadFile(String wholePath, HttpServletResponse response, String fileName) throws Exception;  

    /**  
    * 删除文件  
    * @param wholePath  
    * @throws Exception  
    */  
    void deleteFile(String wholePath) throws Exception;  

    /**  
    * 生成InputStream  
    * @param wholePath  
    * @return  
    * @throws Exception  
    */  
    InputStream getFileStream(String wholePath) throws Exception;  
}
  1. 创建本地文件上传Service,本地文件上传Service继承FileService,编写接口实现类
import com.zmy.lib.file.FileService;  
import org.springframework.web.multipart.MultipartFile;  
import javax.servlet.http.HttpServletResponse;  
import java.io.InputStream;  
  
public interface LocalFileService extends FileService {  
  
    String LOCAL = "local";  
}

创建LocalFileService接口实现类LocalFileServiceImpl

import cn.hutool.core.io.IoUtil;  
import cn.hutool.core.util.StrUtil;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;  
import org.springframework.stereotype.Service;  
import org.springframework.web.multipart.MultipartFile;  
import javax.servlet.http.HttpServletResponse;  
import java.io.*;  
import java.net.URLEncoder;  
import java.util.Arrays;  
import java.util.LinkedList;  

@Service  
@Slf4j  
@ConditionalOnProperty(name = "file.service.type", havingValue = LocalFileService.LOCAL)  
public class LocalFileServiceImpl implements LocalFileService {  
    @Value("${local.file.path.prefix}")  
    private String localPath;  

    @Override  
    public void fileUpload(MultipartFile uploadfile, String pathName) {  
        String[] split = pathName.split(StrUtil.SLASH);  
        LinkedList<String> list = new LinkedList<>(Arrays.asList(split));  
        String newName = list.get(list.size() - 1);  
        list.removeLast();  
        pathName = StrUtil.join(StrUtil.SLASH, list);  
        File file = new File(localPath + pathName);  
        if (!file.exists()) {  
            boolean mkdirs = file.mkdirs();  
            if (!mkdirs) {  
                log.error("创建文件夹异常!");  
                throw new RuntimeException(("创建文件夹异常!"));  
            }  
        }  

        try (InputStream inputStream = uploadfile.getInputStream();  
        FileOutputStream fileOutputStream = new FileOutputStream(localPath + pathName + StrUtil.SLASH + newName)) {  
            IoUtil.copy(inputStream, fileOutputStream);  
        } catch (Exception e) {  
            log.error("文件上传异常!", e);  
            throw new RuntimeException(("文件上传失败!"));  
        }  
    }  

    @Override  
    public void downloadFile(String wholePath, HttpServletResponse response, String fileName) throws Exception {  
        String filePathName = localPath + wholePath;  

        BufferedInputStream bis = null;  
        BufferedOutputStream bos = null;  
        try {  
            response.reset();  
            File file = new File(filePathName);  
            if (!file.exists()) {  
                log.error("要下载的文件不存在:{}", filePathName);  
                return;  
            }  
            bis = new BufferedInputStream(new FileInputStream(filePathName));  
            bos = new BufferedOutputStream(response.getOutputStream());  
            if (fileName != null) {  
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));  
            } else {  
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(wholePath.substring(wholePath.lastIndexOf(StrUtil.SLASH) + 1), "UTF-8"));  
            }  
            response.setContentType("application/form-data");  
            IoUtil.copy(bis, bos);  
        } catch (IOException e) {  
            log.error("文件下载异常!", e);  
            throw new RuntimeException("文件下载失败!");  
        } finally {  
            if (bis != null) {  
                bis.close();  
            }  
            if (bos != null) {  
                bos.close();  
            }  
        }  
    }  

    @Override  
    public void deleteFile(String wholePath) {  
        File file = new File(localPath + wholePath);  
        if (file.exists()) {  
            boolean delete = file.delete();  
            if (!delete) {  
                throw new RuntimeException("文件删除失败! 请关闭文件后再进行删除!");  
            }  
            log.info("已删除文件:{}", localPath + wholePath);  
        } else {  
            log.warn("文件不存在:{}", localPath + wholePath);  
        }  
    }  

    @Override  
    public InputStream getFileStream(String wholePath) {  
        try {  
            File file = new File(localPath + wholePath);  
            return new FileInputStream(file);  
        } catch (FileNotFoundException e) {  
            throw new RuntimeException("文件不存在,请重新上传!");  
        }  
    }  
  
}

3.创建minio上传Service,minio文件上传Service继承FileService编写接口实现类

  • 首先配置Minio
public class MinioConfig {  
  
    @Value("${minio.url}")  
    private String url;  
    @Value("${minio.accessKey}")  
    private String accessKey;  
    @Value("${minio.secretKey}")  
    private String secretKey;  

    @Bean  
    public MinioClient getMinioClient() {  
        return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();  
    }  
  
}
  • 编写Minio文件上传工具类
import com.zmy.lib.minio.model.FileInfo;  
import io.minio.*;  
import io.minio.messages.Bucket;  
import io.minio.messages.Item;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  
import java.io.InputStream;  
import java.util.ArrayList;  
import java.util.List;  
  
@Component  
public class MinioUtil {  
@Autowired  
private MinioClient minioClient;  
  
    /**  
    * 创建一个桶  
    */  
    public void createBucket(String bucket) throws Exception {  
        boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());  
        if (!found) {  
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());  
        }  
    }  

    /**  
    * 上传一个文件  
    */  
    public void uploadFile(InputStream stream, String bucket, String objectName) throws Exception {  
        minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName).stream(stream, -1, 10485760).build());  
    }  

    /**  
    * 列出所有的桶  
    */  
    public List<String> listBuckets() throws Exception {  
        List<Bucket> list = minioClient.listBuckets();  
        List<String> names = new ArrayList<>();  
        list.forEach(b -> {  
            names.add(b.name());  
        });  
        return names;  
    }  

    /**  
    * 判断Bucket是否存在  
    *  
    * @return true:存在,false:不存在  
    */  
    public boolean bucketExists(String bucketName) throws Exception {  
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());  
    }  

    /**  
    * 下载一个文件  
    */  
    public InputStream download(String bucket, String objectName) throws Exception {  
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(objectName).build());  
    }  

    /**  
    * 删除一个桶  
    */  
    public void deleteBucket(String bucket) throws Exception {  
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucket).build());  
    }  

    /**  
    * 删除一个对象  
    */  
    public void deleteObject(String bucket, String objectName) throws Exception {  
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectName).build());  
    }  

    /**  
    * 下载一个文件  
    */  
    public GetObjectResponse getObject(String bucket, String objectName) throws Exception {  
        GetObjectResponse stream = minioClient.getObject(  
        GetObjectArgs.builder().bucket(bucket).object(objectName).build());  
        return stream;  
    }  
}

创建MinioServiceMinioService接口实现类MinioServiceImpl

  • 创建MinioService文件上传接口并继承FileService
import com.zmy.lib.file.FileService;  
  
public interface MinioService extends FileService {  
  
    String MINIO = "minio";  
}
  • 创建MinioService文件实现类
import com.zmy.lib.minio.util.MinioUtil;  
import io.minio.BucketExistsArgs;  
import io.minio.GetPresignedObjectUrlArgs;  
import io.minio.MinioClient;  
import io.minio.http.Method;  
import lombok.extern.slf4j.Slf4j;  
import org.apache.commons.io.IOUtils;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;  
import org.springframework.stereotype.Service;  
import org.springframework.web.multipart.MultipartFile;  
  
import javax.servlet.ServletOutputStream;  
import javax.servlet.http.HttpServletResponse;  
import java.io.InputStream;  
import java.net.URLEncoder;  

@Service  
@Slf4j  
@ConditionalOnProperty(name="file.service.type",havingValue = MinioService.MINIO)  
public class MinioServiceImpl implements MinioService {  
  
    @Autowired  
    MinioUtil minioUtil;  

    @Autowired  
    MinioClient minioClient;  

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

    /**  
    * 上传一个文件  
    *  
    * @param uploadfile 上传的文件  
    * @param pathName 文件所在目录及文件名  
    * @throws Exception  
    */  
    @Override  
    public void fileUpload(MultipartFile uploadfile, String pathName) throws Exception {  
        if (pathName != null) {  
            minioUtil.uploadFile(uploadfile.getInputStream(), bucketName, pathName);  
        } else {  
            minioUtil.uploadFile(uploadfile.getInputStream(), bucketName, uploadfile.getOriginalFilename());  
        }  
    }  

    /**  
    * 下载一个文件  
    * <p>  
    * fileName可以为空为空时使用保存的文件名  
    *  
    * @param wholePath 文件存储的目录及文件名  
    * @param fileName 下载下来的文件名  
    * @throws Exception  
    */  
    @Override  
    public void downloadFile(String wholePath, HttpServletResponse response, String fileName) throws Exception {  
        InputStream stream = minioUtil.download(bucketName, wholePath);  
        ServletOutputStream output = response.getOutputStream();  
        if (fileName != null) {  
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));  
        } else {  
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(wholePath.substring(wholePath.lastIndexOf("/") + 1), "UTF-8"));  
        }  
        response.setContentType("application/octet-stream");  
        response.setCharacterEncoding("UTF-8");  
        IOUtils.copy(stream, output);  
    }  

    /**  
    * 删除一个文件  
    *  
    * @param wholePath 文件的目录加名字  
    * @throws Exception  
    */  
    @Override  
    public void deleteFile(String wholePath) throws Exception {  
        minioUtil.deleteObject(bucketName, wholePath);  
    }  

    @Override  
    public InputStream getFileStream(String wholePath) throws Exception {  
        return minioUtil.download(bucketName, wholePath);  
    }  

    /**  
    * 生成一个GET请求的分享链接。  
    * 失效时间默认是7天。  
    *  
    * @param bucketName 存储桶名称  
    * @param wholePath 文件的目录加名字  
    * @param expires    失效时间(以秒为单位),默认是7天,不得大于七天  
    * @return  
    *      
    */  
    @Override  
    public String presignedGetObject(String wholePath, Integer expires) throws Exception {  
        BucketExistsArgs bucketArgs = BucketExistsArgs.builder().bucket(bucketName).build();  
        boolean bucketExists = minioClient.bucketExists(bucketArgs);    
        String url = "";  
        if (bucketExists) {  
            try {  
                if (expires == null) {  
                    expires = 604800;  
                }  

                GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()  
                .method(Method.GET)  
                .bucket(bucketName)  
                .object(wholePath)  
                .build();  
                url = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);  
                log.info("*******url2:{}", url);  
            } catch (Exception e) {  
                log.info("presigned get object fail:{}", e);  
            }  
        }  
        return url;  
    }  
  
}

备注说明

在本地上传LocalFileService实现类和MinioFileService实现类中使用@ConditionalOnProperty注解,Spring容器会根据name配置的值与havingValue的值进行对比,如果值相同则havingValue为TRUE,便会将该实现类注入Spring容器;进而实现根据配置文件来选择上传到本地或者Minio

补充

类似ConditionalOnProperty有条件的类加载注解,SpringBoot提供了14个,在org.springframework.boot.autoconfigure.condition包下

image.png