Spring Boot 核心机制详解:可执行 JAR、CORS、静态资源与配置绑定

Spring Boot 简化了 Java Web 应用的构建与配置,但"开箱即用"背后仍有一套明确机制。本文集中讲解四个常见问题:可执行 JAR 为什么能直接启动、后端如何配置 CORS、静态资源如何映射,以及 @ConfigurationProperties@Value 应该如何选择。

一、Spring Boot JAR 为什么可以直接运行?

普通类库 JAR 通常只包含当前项目编译后的类,第三方依赖需要额外放入 classpath。Spring Boot Maven 或 Gradle 插件会重新打包应用,将代码、依赖和启动器放入同一个可执行 JAR。

典型结构如下:

复制代码
app.jar
├── META-INF/MANIFEST.MF
├── org/springframework/boot/loader/...
└── BOOT-INF
    ├── classes    # 项目类和资源
    └── lib        # 第三方依赖 JAR

MANIFEST.MF 中通常包含:

复制代码
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: com.example.Application

Main-Class 并不是业务启动类,而是 Spring Boot Loader 提供的 JarLauncherStart-Class 才是包含 main() 方法的应用类。

执行:

复制代码
java -jar app.jar

JVM 首先运行 JarLauncher。它建立能够访问 BOOT-INF/classesBOOT-INF/lib 中嵌套依赖的类加载环境,然后调用 Start-Classmain() 方法。随后 SpringApplication.run() 创建 Spring 容器并启动内嵌 Tomcat。因此,一个文件中同时具备应用代码、依赖、加载器和 Web 容器,部署时不需要单独安装 Tomcat。

整个过程可以概括为:

复制代码
java -jar app.jar
        ↓
JVM 读取 MANIFEST.MF
        ↓
执行 Main-Class,也就是 JarLauncher
        ↓
建立嵌套 JAR 类加载环境
        ↓
调用 Start-Class.main()
        ↓
创建 Spring 容器并启动内嵌服务器

这也解释了为什么不能简单地把 Spring Boot JAR 当成普通依赖 JAR 使用:它经过了专门的目录重组。构建插件一般还会保留一个未重新打包的原始 JAR,具体名称取决于构建配置。排查"本地能运行、打包后不能运行"时,应先检查使用的是否为重新打包后的文件,再检查清单中的启动类和 BOOT-INF/lib 依赖是否完整。

可以通过以下命令观察内部结构:

复制代码
jar -tf target/app.jar
unzip -p target/app.jar META-INF/MANIFEST.MF

二、Spring Boot 如何支持跨域请求?

浏览器的同源策略会限制页面脚本访问不同协议、域名或端口的资源。例如前端运行在 http://localhost:5173,后端运行在 http://localhost:8080,二者端口不同,属于跨域。

CORS 的本质是后端在响应中加入 Access-Control-Allow-* 等响应头,告诉浏览器哪些来源、方法和请求头可以访问。它不是关闭浏览器的安全策略。

1. 局部配置:@CrossOrigin

只允许某个 Controller 跨域时,可以使用:

复制代码
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "https://www.example.com")
public class UserController {

    @GetMapping
    public List<String> list() {
        return List.of("Tom", "Jerry");
    }
}

也可以把 @CrossOrigin 放在单个方法上。它适合少量特殊接口,但接口较多时容易分散配置。

2. 全局配置:WebMvcConfigurer

复制代码
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://www.example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

其中 maxAge 表示浏览器缓存预检结果的时间。对于非简单跨域请求,浏览器通常先发送 OPTIONS 预检请求,确认服务器允许后再发送真实请求。

如果开启 allowCredentials(true) 以携带 Cookie 等凭证,不能同时把 allowedOrigins 配置为 "*",应明确列出可信域名。生产环境也不应为了快速解决报错而无条件允许所有来源。

三、Spring Boot 如何处理静态资源映射?

Spring Boot 默认会从 classpath 下的以下目录提供静态资源:

复制代码
src/main/resources/static
src/main/resources/public
src/main/resources/resources
src/main/resources/META-INF/resources

例如文件:

复制代码
src/main/resources/static/images/logo.png

默认可通过下面的地址访问:

复制代码
http://localhost:8080/images/logo.png

Spring MVC 使用 ResourceHttpRequestHandler 处理这些请求,默认路径模式是 /**。需要统一增加访问前缀时,可以配置:

复制代码
spring:
  mvc:
    static-path-pattern: /assets/**

此时资源访问路径变成 /assets/images/logo.png。如果希望更换资源存放位置,可以使用:

复制代码
spring:
  web:
    resources:
      static-locations: classpath:/web/,file:/opt/app/uploads/

还可以通过 Java 配置映射外部目录:

复制代码
@Configuration
public class ResourceConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/uploads/**")
                .addResourceLocations("file:/opt/app/uploads/");
    }
}

四、@ConfigurationProperties@Value 的区别

两者都能读取 Spring Environment 中的配置,但定位不同。

1. 使用 @Value

@Value 适合注入少量、独立的配置:

复制代码
payment:
  timeout: 3s

@Component
public class PaymentClient {

    @Value("${payment.timeout:5s}")
    private Duration timeout;
}

它写法直接,支持占位符和 SpEL,但配置一多,注解会散落在多个类中,不便于统一管理和校验。

2. 使用 @ConfigurationProperties

对于一组成体系的配置,更适合绑定为类型安全对象:

复制代码
payment:
  base-url: https://pay.example.com
  timeout: 3s
  retry-count: 2

@Validated
@ConfigurationProperties(prefix = "payment")
public record PaymentProperties(
        @NotBlank String baseUrl,
        @NotNull Duration timeout,
        @Min(0) int retryCount) {
}

在启动类上启用扫描:

复制代码
@SpringBootApplication
@ConfigurationPropertiesScan
public class Application {
}

它支持层级对象、集合、类型转换、校验和宽松绑定。例如 base-url 可以绑定到 Java 的 baseUrl,环境变量也可以按规则映射。

除了 @ConfigurationPropertiesScan,还可以通过 @EnableConfigurationProperties(PaymentProperties.class) 明确注册配置类。前者适合统一扫描项目内多个配置对象,后者适合显式启用少量配置。配置类注册为 Bean 后,应通过构造器注入到业务组件中,而不是到处重复读取字符串配置:

复制代码
@Service
public class PaymentService {

    private final PaymentProperties properties;

    public PaymentService(PaymentProperties properties) {
        this.properties = properties;
    }
}

使用专门的配置对象还能在应用启动时集中发现缺失值或非法值。相比之下,多个类各自使用 @Value,很容易出现键名重复、默认值不一致和配置含义分散的问题。

参考资料

相关推荐
Sayuanni%31 小时前
SpringBoot 从注解到源码:核心知识点总结
java·spring boot·后端
陈随易2 小时前
Bun v1.4 更新总结:把浏览器、图片、定时任务和工程工具都装进一个运行时
前端·后端·程序员
人间凡尔赛3 小时前
2026 后端架构三驾马车:Wasm 容器上 K8s、存算分离与 AI 原生
后端·云原生·架构
m0_587383003 小时前
社区家政系统开发实战:从需求分析到上线部署全指南
java·spring boot·spring·需求分析
凤山老林3 小时前
动态 i18n 体系落地:Spring Boot 多租户热加载与前后端协同实践
java·spring boot·后端·i18n
东风破_4 小时前
TypeScript 高级类型进阶:keyof、Exclude、Record 与类型组合思想
前端·后端·typescript
【赫兹威客】浩哥4 小时前
基于SpringBoot+Vue3的健身运动管理系统|WebSocket实时消息 健身房毕设项目
spring boot·websocket·课程设计
凤山老林4 小时前
数据库读写分离与动态路由实战:Spring Boot + ShardingSphere-JDBC 生产配置
数据库·spring boot·后端·分库分表·sharding-jdbc
Zane19945 小时前
调用了 async 函数却没执行?一文讲透协程、event loop 与 await
后端·python