You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于配置开关条件性加载Spring的@Controller?

基础Spring中根据系统属性动态选择控制器Bean的实现

问题描述

我希望在基础Spring框架中,根据系统属性从两个类中选择一个作为@Bean实例。简化后的代码示例如下:

@Configuration(value = "BipBop")
@EnableWebMvc
public class BipBopConfiguration implements WebMvcConfigurer {
    
    @Bean
    public BipBop getBipBop() {
        if (Boolean.getBoolean("isBip")) {
            return new Bip();
        } else {
            return new Bop();
        }
    }
}

@Controller
@RequestMapping("/bipbop")
public class Bip implements BipBop {

    @RequestMapping(method = RequestMethod.GET)
    public void bipBop(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream()))) {
            pw.println("Bip");
            pw.flush();
        }
    }
}

@Controller
@RequestMapping("/bipbop")
public class Bop implements BipBop {

    @RequestMapping(method = RequestMethod.GET)
    public void bipBop(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream()))) {
            pw.println("Bop");
            pw.flush();
        }
    }
}

当前遇到的问题:

  • 虽然@Bean能正确选择实现类,但Spring不会将选中的类识别为@Controller,导致访问/bipbop路径时出现404错误;
  • 若将两个类都置于扫描路径下,Spring会识别它们为控制器,但会报/bipbop路径存在两个实现的冲突。

已知Spring Boot的@ConditionalOnProperty注解可以实现类似功能,但该注解仅适用于Spring Boot,请问在基础Spring中该如何实现需求?

解决方案

方法一:自定义条件判断,手动注册控制器Bean

基础Spring自带的@Conditional注解可以实现条件化Bean注册,配合自定义判断逻辑就能解决问题:

  1. 编写两个判断系统属性的条件类:
public class BipCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 当系统属性isBip为true时,满足该条件
        return Boolean.getBoolean("isBip");
    }
}

public class BopCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 当系统属性isBip为false时,满足该条件
        return !Boolean.getBoolean("isBip");
    }
}
  1. 去掉两个控制器类上的@Controller和@RequestMapping注解(防止Spring自动扫描时冲突):
public class Bip implements BipBop {
    // 保留原有的请求处理方法
    @RequestMapping(method = RequestMethod.GET)
    public void bipBop(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream()))) {
            pw.println("Bip");
            pw.flush();
        }
    }
}

public class Bop implements BipBop {
    @RequestMapping(method = RequestMethod.GET)
    public void bipBop(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream()))) {
            pw.println("Bop");
            pw.flush();
        }
    }
}
  1. 更新配置类,按条件注册控制器Bean,并手动绑定请求路径:
@Configuration
@EnableWebMvc
public class BipBopConfiguration implements WebMvcConfigurer {

    @Bean
    @Conditional(BipCondition.class)
    public Bip bipController() {
        return new Bip();
    }

    @Bean
    @Conditional(BopCondition.class)
    public Bop bopController() {
        return new Bop();
    }

    // 注入Spring的请求映射处理器,手动注册路径
    @Autowired
    private RequestMappingHandlerMapping handlerMapping;

    @PostConstruct
    public void setupRequestMapping() throws NoSuchMethodException {
        RequestMappingInfo mapping = RequestMappingInfo.paths("/bipbop").methods(RequestMethod.GET).build();
        if (Boolean.getBoolean("isBip")) {
            // 注册Bip的请求映射
            Method method = Bip.class.getMethod("bipBop", HttpServletRequest.class, HttpServletResponse.class);
            handlerMapping.registerMapping(mapping, bipController(), method);
        } else {
            // 注册Bop的请求映射
            Method method = Bop.class.getMethod("bipBop", HttpServletRequest.class, HttpServletResponse.class);
            handlerMapping.registerMapping(mapping, bopController(), method);
        }
    }
}

方法二:用组件扫描过滤器,排除不符合条件的控制器

如果不想改动控制器类上的@Controller和@RequestMapping,可以通过组件扫描的过滤器来排除不需要的类:

  1. 保留控制器类的原有注解:
@Controller
@RequestMapping("/bipbop")
public class Bip implements BipBop {
    // 方法不变
}

@Controller
@RequestMapping("/bipbop")
public class Bop implements BipBop {
    // 方法不变
}
  1. 编写两个用于排除的条件类:
public class ExcludeBipCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 当isBip为false时,排除Bip类
        return !Boolean.getBoolean("isBip");
    }
}

public class ExcludeBopCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 当isBip为true时,排除Bop类
        return Boolean.getBoolean("isBip");
    }
}
  1. 在配置类中添加组件扫描,并配置过滤器:
@Configuration
@EnableWebMvc
@ComponentScan(
        basePackages = "your.controller.package", // 替换成你的控制器所在包
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.CONDITION, value = ExcludeBipCondition.class),
                @ComponentScan.Filter(type = FilterType.CONDITION, value = ExcludeBopCondition.class)
        }
)
public class BipBopConfiguration implements WebMvcConfigurer {
    // 无需额外代码,Spring会自动扫描并保留符合条件的控制器
}

注意事项

  • 两种方法的核心都是确保同一时刻只有一个控制器Bean存在于Spring容器中,避免路径冲突;
  • @Conditional是基础Spring的核心注解,完全不需要依赖Spring Boot;
  • 方法一的手动注册映射更灵活,适合复杂场景;方法二更简洁,适合保留原有控制器注解的情况。

内容的提问来源于stack exchange,提问作者MeBigFatGuy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 22:58:11