
一、Profile的基本使用第一步没有Profile的时候假设你有三个环境开发dev、测试test、生产prod。每个环境的数据源配置都不一样Configuration public class DataSourceConfig { Bean public DataSource dataSource() { // 开发环境连本地 MySQL HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/dev_db); config.setUsername(root); config.setPassword(dev123); return new HikariDataSource(config); } }问题你要把项目部署到测试服务器得手动改 URL、用户名、密码部署到生产环境又得改一遍更麻烦的是有些 Bean 只在开发环境需要比如内存缓存、Mock 服务有些只在生产环境需要比如监控、报警团队里每个人本地开发时配置文件互相冲突提交代码时经常把别人的配置覆盖掉早期 workaroundConfiguration public class DataSourceConfig { Value(${env}) private String env; Bean public DataSource dataSource() { if (dev.equals(env)) { // 开发配置 } else if (test.equals(env)) { // 测试配置 } else if (prod.equals(env)) { // 生产配置 } // ... } }这有什么问题一个方法里塞三套逻辑违反单一职责新增环境要改源码重新编译只有开发环境需要的 Bean比如 H2 控制台也会被注册到生产环境只是没被用到浪费资源第二步Profile被设计出来Spring 意识到不同环境需要的 Bean 集合是不同的应该在容器启动时就决定哪些 Bean 要注册哪些要跳过。Profile的核心设计意图让 Bean 的注册与环境绑定。只有当前激活的 Profile 匹配时这个 Bean 才会被创建并放入 Spring 容器。第三步Profile的三种用法用法 1加在Configuration类上Configuration Profile(dev) public class DevConfig { Bean public DataSource dataSource() { // 开发环境连本地数据库 HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/dev_db); return new HikariDataSource(config); } Bean public MockEmailService mockEmailService() { // 开发环境不发真实邮件用 Mock return new MockEmailService(); } }Configuration Profile(prod) public class ProdConfig { Bean public DataSource dataSource() { // 生产环境连线上数据库配连接池参数 HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://prod-db.internal:3306/mydb); config.setMaximumPoolSize(50); return new HikariDataSource(config); } Bean public RealEmailService realEmailService() { // 生产环境发真实邮件 return new RealEmailService(); } }效果当spring.profiles.activedev时只有DevConfig里的 Bean 会被注册ProdConfig整个类被跳过。用法 2加在Bean方法上Configuration public class DataSourceConfig { Bean Profile(dev) public DataSource devDataSource() { return new HikariDataSource(devConfig()); } Bean Profile(prod) public DataSource prodDataSource() { return new HikariDataSource(prodConfig()); } }适用场景同一个配置类里大部分 Bean 是通用的只有个别 Bean 需要环境区分。用法 3加在Component类上Component Profile(dev) public class DevDebugInterceptor implements HandlerInterceptor { // 只在开发环境注册的拦截器比如打印详细请求日志 }Component Profile(prod) public class ProdSecurityFilter implements Filter { // 只在生产环境注册的安全过滤器 }第四步如何激活 Profile方式 1application.yml里配置最常用spring: profiles: active: dev方式 2启动参数java -jar myapp.jar --spring.profiles.activeprod方式 3JVM 参数java -Dspring.profiles.activeprod -jar myapp.jar方式 4IDE 里设置在 IDEA 的 Run Configuration 里Environment 标签页设置SPRING_PROFILES_ACTIVEdev。第五步Profile的进阶特性1. 取反!Bean Profile(!dev) // 除了 dev 环境其他环境都生效 public CacheManager cacheManager() { // 生产环境用 Redis 缓存 return new RedisCacheManager(); }2. 多 Profile 匹配or关系Bean Profile({dev, test}) // dev 或 test 环境都生效 public DataSource h2DataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .build(); }3. Profile 的继承spring.profiles.includespring: profiles: active: prod --- spring: config: activate: on-profile: prod profiles: include: common, monitoring # prod 环境自动包含 common 和 monitoring 的配置第六步Profile的底层原理Profile底层是Conditional的一种特化。Spring 在解析 Bean 定义时会检查Conditional(ProfileCondition.class) public interface Profile { String[] value(); }ProfileCondition的实现逻辑很简单class ProfileCondition implements Condition { Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 获取当前激活的所有 Profile String[] activeProfiles context.getEnvironment().getActiveProfiles(); // 获取 Profile 注解里声明的 Profile String[] requiredProfiles // ... 从 metadata 读取 ... // 如果有交集条件匹配Bean 注册否则跳过 return Arrays.stream(requiredProfiles) .anyMatch(p - Arrays.asList(activeProfiles).contains(p)); } }关键点这个判断发生在容器启动阶段不是运行时。如果条件不匹配这个 Bean 根本不会被创建不会占用任何资源。第七步Profile和配置文件的配合Spring Boot 支持按 Profile 分配置文件application.yml # 公共配置 application-dev.yml # dev 环境专属 application-test.yml # test 环境专属 application-prod.yml # prod 环境专属当spring.profiles.activedev时先加载application.yml再加载application-dev.yml同名属性覆盖公共配置# application.yml spring: profiles: active: dev server: port: 8080 --- # application-dev.yml server: port: 9090 # 覆盖为 9090 spring: datasource: url: jdbc:mysql://localhost:3306/dev_db逻辑链条总结不同环境需要不同的 Bean 和配置 → 手动 if-else 判断代码臃肿且易错 ↓ Spring 设计 Profile把 Bean 注册与环境绑定 ↓ 容器启动时检查当前激活的 Profile不匹配的 Bean 直接跳过 ↓ 配合 application-{profile}.yml 实现配置文件的物理隔离 ↓ 最终效果代码一套配置多套部署时只需改一个参数一句话记住Profile是 Spring 的环境开关它在容器启动阶段决定哪些 Bean 该注册、哪些该忽略。没有它你得在代码里写一堆 if-else 来区分环境有了它不同环境的 Bean 各自独立存在由 Spring 根据spring.profiles.active自动筛选。二、与多环境的application-{xxx}.yml对比核心区别维度application-{profile}.ymlProfile管什么配置属性的值字符串、数字、布尔Bean 是否注册到容器作用对象application.yml里的key: valueComponent、Bean、Configuration类本质数据替换组件开关场景一只有配置值不同 → 只用 yml 就够了# application-dev.yml server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/dev_db # application-prod.yml server: port: 80 spring: datasource: url: jdbc:mysql://prod-db.internal:3306/mydb这种情况下不需要Profile。Spring Boot 自动根据spring.profiles.active加载对应的 yml同名属性覆盖即可。场景二需要不同的 Bean 实现类 → 必须用Profile假设开发环境不发真实邮件用 Mock生产环境发真实邮件public interface EmailService { void send(String to, String subject, String content); } Component Profile(dev) public class ConsoleEmailService implements EmailService { Override public void send(String to, String subject, String content) { System.out.println([Mock邮件] to to , subject subject); } } Component Profile(prod) public class SmtpEmailService implements EmailService { Override public void send(String to, String subject, String content) { // 真实调用 SMTP 服务器发邮件 javaMailSender.send(...); } }这个场景 yml 能替代吗你或许会想我在 yml 里配一个email.mocktrue然后在代码里判断Service public class EmailServiceFactory { Value(${email.mock:false}) private boolean mock; Bean public EmailService emailService() { if (mock) { return new ConsoleEmailService(); } else { return new SmtpEmailService(); } } }这能工作但有两个问题问题 1两个实现类都编译进了 jar 包SmtpEmailService里可能引入了javax.mail依赖开发环境其实根本不需要这个依赖。但因为代码里直接new SmtpEmailService()编译期就必须把相关类都引进来。而用Profile不匹配的 Bean 在启动阶段就被跳过连类都不会被加载前提是合理拆分配置类。问题 2更复杂的场景下yml 判断会爆炸假设你有 10 个 Bean每个都在不同环境下有不同的实现。用 yml 判断你的配置类会变成这样Configuration public class EnvConfig { Value(${env}) private String env; Bean public AService aService() { if (dev.equals(env)) return new AServiceDev(); if (test.equals(env)) return new AServiceTest(); if (prod.equals(env)) return new AServiceProd(); // ... 10 个 Bean 都这样写 } }这就是把Profile该干的事退化成了手动 if-else回到了没有Profile时代的痛苦。场景三两者配合 → 最佳实践实际项目中它们是一起用的Configuration Profile(prod) public class ProdConfig { Bean public DataSource dataSource( Value(${spring.datasource.url}) String url, Value(${spring.datasource.username}) String username) { // 生产环境用连接池参数从 yml 读 HikariConfig config new HikariConfig(); config.setJdbcUrl(url); config.setUsername(username); config.setMaximumPoolSize(50); return new HikariDataSource(config); } }# application-prod.yml spring: datasource: url: jdbc:mysql://prod-db.internal:3306/mydb username: prod_user分工Profile(prod)决定这个配置类只在生产环境生效application-prod.yml决定生产环境的数据库地址是什么一句话总结application-{profile}.yml管值是什么Profile管这个组件要不要存在。yml 无法让一个 Bean 在容器里消失也无法切换不同的实现类。只有配置值差异时用 yml有组件差异时必须用Profile复杂项目两者配合。