Spring Boot 中 @Value
加载资源文件
一、原理概述
@Value
注解可以注入来自配置文件(application.properties
/ application.yml
)的属性值,也能通过 classpath:
、file:
、http:
等前缀直接加载外部资源。
- 当注入类型为
org.springframework.core.io.Resource
时,Spring 会根据前缀自动创建相应的 Resource
实例,方便后续通过 getInputStream()
等方法读取资源内容。
二、常见资源前缀
前缀 | 说明 |
---|
classpath: | 从 classpath 加载(通常放在 resources 目录下) |
file: | 从文件系统路径加载 |
http: | 从远程 HTTP URL 加载 |
无前缀 | 默认等同于 classpath: |
三、注入 Resource
示例
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@RestController
@RequestMapping
public class IndexController {
// 注入位于 classpath:static/logo.svg 的资源
@Value("classpath:static/logo.svg")
private Resource logoPath;
// 注入基于http的远程资源
private Resource remoteText;
@RequestMapping(value = "/favicon.ico")
public void favicon(HttpServletResponse response) throws IOException {
// 1. 设置正确的 MIME 类型
response.setContentType("image/svg+xml");
// 2. 可选:浏览器缓存一年
response.setHeader("Cache-Control", "max-age=31536000");
// 3. 读取资源并输出到响应流
try (InputStream in = logoPath.getInputStream()) {
FileCopyUtils.copy(in, response.getOutputStream());
}
}
/**
* 访问 /remote/json 时,读取远程文本资源并以json返回
*/
@GetMapping(value = "/remote/json", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> fetchRemoteText() throws IOException {
String content = StreamUtils.copyToString(
remoteText.getInputStream(),
StandardCharsets.UTF_8
);
return ResponseEntity.ok(content);
}
}
- 用途:将自定义的 SVG Logo 作为 Favicon 提供给浏览器,避免默认的
/favicon.ico
404。
- 关键点:
Resource#getInputStream()
获取资源输入流。
FileCopyUtils.copy(...)
简化流复制操作。
@SaIgnore
(来自 Sa-Token)用于跳过权限验证。
四、加载证书文件并构建 Bean
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAPublicKeyConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RSAPublicKeyNotificationConfig;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 微信支付配置类
*/
@Data
@Configuration
public class WechatPayConfigProperties {
@Value("${wechat.pay.app-id}")
private String appId;
@Value("${wechat.pay.mch-id}")
private String mchId;
@Value("${wechat.pay.mch-serial-number}")
private String mchSerialNumber;
@Value("${wechat.pay.api-v3-key}")
private String apiV3Key;
@Value("classpath:cert/apiclient_key.pem")
private Resource privateKeyPath;
@Value("classpath:cert/pub_key.pem")
private Resource publicKeyPath;
@Value("${wechat.pay.public-key-id}")
private String publicKeyId;
@Value("${wechat.pay.notify-url}")
private String notifyUrl;
@Bean
public Config wechatPayConfig() throws IOException {
// 使用微信支付公钥的RSA配置
String privateKeyContent = StreamUtils.copyToString(
privateKeyPath.getInputStream(),
StandardCharsets.UTF_8
);
String publicKeyContent = StreamUtils.copyToString(
publicKeyPath.getInputStream(),
StandardCharsets.UTF_8
);
return new RSAPublicKeyConfig.Builder()
.merchantId(mchId) //微信支付的商户号
.privateKey(privateKeyContent) // 商户API证书私钥的存放路径
.publicKey(publicKeyContent) //微信支付公钥的存放路径
.publicKeyId(publicKeyId) //微信支付公钥ID
.merchantSerialNumber(mchSerialNumber) //商户API证书序列号
.apiV3Key(apiV3Key) //APIv3密钥
.build();
}
@Bean
public NotificationParser notificationParser() throws IOException {
String publicKeyContent = StreamUtils.copyToString(
publicKeyPath.getInputStream(),
StandardCharsets.UTF_8
);
RSAPublicKeyNotificationConfig config = new RSAPublicKeyNotificationConfig.Builder()
.publicKey(publicKeyContent)
.publicKeyId(publicKeyId)
.apiV3Key(apiV3Key)
.build();
return new NotificationParser(config);
}
}
- 说明:
- 将 PEM 文件以
Resource
方式注入,便于随项目一起打包。
- 使用
StreamUtils.copyToString(...)
将流内容转为字符串。
- 配置中的商户号、序列号等敏感信息仍通过
${}
从配置文件注入,保持灵活与安全。
五、整合 @Value("classpath:static/privacyPolicy.html")
的示例
@RestController
@RequestMapping("/docs")
public class DocController {
@Value("classpath:static/privacyPolicy.html")
private Resource privacyPolicyHtml;
@GetMapping("/privacy")
public ResponseEntity<String> privacyPolicy() throws IOException {
String html = StreamUtils.copyToString(
privacyPolicyHtml.getInputStream(),
StandardCharsets.UTF_8
);
// 返回 HTML 内容,并设置正确的 Content-Type
return ResponseEntity.ok()
.contentType(MediaType.TEXT_HTML)
.body(html);
}
}
- 效果:访问
/docs/privacy
时,返回 static/privacyPolicy.html
的原始 HTML 内容,方便展示隐私策略页面。
六、注意事项与最佳实践
- 资源路径
- 放在
src/main/resources
下,对应运行后 classpath:
。
- 避免使用相对路径或硬编码文件系统路径,增强可移植性。
- 字符编码
- 读取文本类资源时,务必显式指定编码(如
StandardCharsets.UTF_8
),防止乱码。
- 大文件处理
- 若资源体积较大,可考虑使用
InputStreamResource
或分块读取,避免一次性加载至内存。
- 热更新
- 打包后
classpath:
资源随 Jar 打包,不支持运行时更改。若需动态更新,可使用文件系统路径或外部化配置。
- 异常处理
- 建议统一捕获
IOException
并返回友好错误信息,或在全局异常处理器中处理。