本文目录导读:

在Java中实现“服务随境”通常有两种理解:
- 服务随环境切换:开发、测试、生产等不同环境自动切换配置
- 服务随上下文变化:运行时根据不同上下文动态调用不同实现
下面分别给出实现方案。
服务随环境切换(环境配置)
方案1:Spring Boot多环境配置
application.yml 多文档块方式:
spring:
profiles:
active: @profile.active@ # Maven打包时动态注入
---
# 开发环境
spring:
profiles: dev
server:
port: 8080
database:
url: jdbc:mysql://localhost:3306/dev_db
---
# 生产环境
spring:
profiles: prod
server:
port: 80
database:
url: jdbc:mysql://prod-server:3306/prod_db
或者多配置文件方式:
application-dev.yml
application-prod.yml
启动指定环境:
java -jar app.jar --spring.profiles.active=prod
Maven配置动态环境:
<profiles>
<profile>
<id>dev</id>
<properties>
<profile.active>dev</profile.active>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<profile.active>prod</profile.active>
</properties>
</profile>
</profiles>
方案2:环境变量+配置中心
使用Nacos或Apollo配置中心,运行时动态刷新配置:
@RefreshScope
@Service
public class DynamicService {
@Value("${service.mode:normal}")
private String serviceMode;
public void execute() {
if ("fast".equals(serviceMode)) {
// 快速模式
} else {
// 常规模式
}
}
}
服务随上下文变化(策略模式)
方案1:Spring IOC + 策略模式
// 定义策略接口
public interface PaymentStrategy {
String getType();
void pay(BigDecimal amount);
}
// 实现不同策略
@Service
@Order(1)
public class WechatPayStrategy implements PaymentStrategy {
@Override
public String getType() { return "wechat"; }
@Override
public void pay(BigDecimal amount) {
System.out.println("微信支付:" + amount);
}
}
@Service
@Order(2)
public class AlipayStrategy implements PaymentStrategy {
@Override
public String getType() { return "alipay"; }
@Override
public void pay(BigDecimal amount) {
System.out.println("支付宝支付:" + amount);
}
}
// 策略上下文
@Component
public class PaymentContext {
@Autowired
private List<PaymentStrategy> strategies;
public PaymentStrategy getStrategy(String type) {
return strategies.stream()
.filter(s -> s.getType().equals(type))
.findFirst()
.orElseThrow(() -> new RuntimeException("不支持的支付类型"));
}
}
// 使用
@RestController
public class PaymentController {
@Autowired
private PaymentContext context;
@PostMapping("/pay")
public String pay(@RequestParam String type, @RequestParam BigDecimal amount) {
PaymentStrategy strategy = context.getStrategy(type);
strategy.pay(amount);
return "success";
}
}
方案2:Map注入 + @PostConstruct
@Component
public class ServiceRouter {
@Autowired
private List<ServiceHandler> handlers;
private Map<String, ServiceHandler> handlerMap;
@PostConstruct
public void init() {
handlerMap = handlers.stream()
.collect(Collectors.toMap(ServiceHandler::getType, Function.identity()));
}
public ServiceHandler getHandler(String type) {
return handlerMap.get(type);
}
}
方案3:注解+反射(更灵活)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceType {
String value();
}
@ServiceType("vip")
@Service
public class VipUserService implements UserService {
// ...
}
@Component
public class ServiceFactory {
@Autowired
private ApplicationContext context;
public <T> T getService(String type, Class<T> clazz) {
Map<String, Object> beans = context.getBeansWithAnnotation(ServiceType.class);
for (Object bean : beans.values()) {
ServiceType annotation = bean.getClass().getAnnotation(ServiceType.class);
if (annotation.value().equals(type)) {
return clazz.cast(bean);
}
}
return null;
}
}
实战:动态租户切换(多租户)
@Component
public class TenantContextHolder {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public static void setTenant(String tenant) {
CURRENT_TENANT.set(tenant);
}
public static String getTenant() {
return CURRENT_TENANT.get();
}
public static void clear() {
CURRENT_TENANT.remove();
}
}
// 拦截器自动设置租户
@Component
public class TenantInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String tenant = request.getHeader("X-Tenant-ID");
TenantContextHolder.setTenant(tenant);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
TenantContextHolder.clear();
}
}
// 数据源动态切换
@Aspect
@Component
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class DataSourceAspect {
@Around("@annotation(switchDataSource)")
public Object around(ProceedingJoinPoint point, SwitchDataSource switchDataSource) throws Throwable {
String tenant = TenantContextHolder.getTenant();
DynamicDataSource.setDataSource(tenant);
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
}
}
}
选择建议
| 场景 | 推荐方案 |
|---|---|
| 不同环境配置切换 | Spring Profiles + 配置中心 |
| 运行时代码逻辑切换 | 策略模式 + Spring IOC |
| 多租户数据隔离 | ThreadLocal + 动态数据源 |
| A/B测试流量切换 | 流量染色 + 灰度发布平台 |
最佳实践:将环境切换和业务逻辑切换分离,环境切换通过配置/部署实现,业务逻辑切换通过代码设计模式实现,避免混合使用导致维护困难。