如何基于配置开关条件性加载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注册,配合自定义判断逻辑就能解决问题:
- 编写两个判断系统属性的条件类:
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"); } }
- 去掉两个控制器类上的
@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(); } } }
- 更新配置类,按条件注册控制器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,可以通过组件扫描的过滤器来排除不需要的类:
- 保留控制器类的原有注解:
@Controller @RequestMapping("/bipbop") public class Bip implements BipBop { // 方法不变 } @Controller @RequestMapping("/bipbop") public class Bop implements BipBop { // 方法不变 }
- 编写两个用于排除的条件类:
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"); } }
- 在配置类中添加组件扫描,并配置过滤器:
@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
相关产品推荐
相关产品推荐

