Spring MVC 通过Java配置定义Spring RestController

q35jwt9p  于 2022-11-14  发布在  Spring
关注(0)|答案(3)|浏览(158)

是否可以在Java配置中 * 单独 * 定义Spring RestController(@RestController注解类)(在标记为@Bean的方法中注解了@Configuration的类)?
我有一个由spring Boot 管理的应用程序(为了回答这个问题,版本并不重要,即使是最后一个可用的版本)。这个应用程序使用REST公开了一些端点,所以有几个rest控制器,它们依次调用服务(像往常一样)。
现在,根据配置(application.yml中的属性),我希望避免启动一些服务,比如说,使用@RestController注解进行注解的2个类,因为它们处理我希望排除的“特性X”。
我想通过 Java configuration 来配置我所有的bean,这是一个要求。所以我最初的方法是在一个单独的配置中定义所有的bean(控制器和服务),这个配置在扫描过程中由spring Boot 找到),并在配置中放置一个@ConditionalOnProperty,这样它就出现在一个地方:

@Configuration
public class MyAppGeneralConfiguration {
  // here I define all the beans that are not relevant for "feature X"
  @Bean
  public ServiceA serviceA() {}
  ...
}

@Configuration 
@ConditionalOnProperty(name = "myapp.featureX.enabled", havingValue = "true")
public class MyAppFeatureXConfiguration {
   // here I will define all the beans relevant for feature X:

  @Bean
  public ServiceForFeatureX1 serviceForFeatureX1() {}   

  @Bean
  public ServiceForFeatureX2 serviceForFeatureX2() {}   
}

使用这种方法,我的服务根本没有任何spring注解,我也不使用@Autowired注解,因为所有内容都是通过@Configuration类中的构造函数注入的:

// no @Service / @Component annotation
public class ServiceForFeatureX1 {}

现在我的问题是关于用@RestContoller标注的类。

@RestController
public class FeatureXRestController1 {
  ...
}

@RestController
public class FeatureXRestController2 {
 ...
}

理想情况下,我也希望在Java配置中定义它们,这样当我禁用该特性时,这两个控制器甚至不会加载:

@ConditionalOnProperty(name = "myapp.featureX.enabled", havingValue = "true", matchIfMissing=true)
public class MyAppFeatureXConfiguration {

    @Bean
    @RestController // this doesn't work because the @RestController has target Type and can't be applied 
                    // to methods
    public FeatureXRestController1 featureXRestController1() {
    }

所以问题基本上是这样做有可能吗?
RestController是一个控制器,它又是一个组件,因此它要接受组件扫描。因此,如果功能X被禁用,功能X的其余控制器仍然会开始加载并失败,因为没有“服务”-配置中排除的bean,所以Spring Boot 将无法注入。
我想到的一种方法是定义一个特殊的注解,如@FeatureXRestController,并将其设置为@RestController,然后将@ConditionalOnProperty放在那里,但它仍然有两个位置,这是我能想到的最佳解决方案:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RestController
@ConditionalOnProperty(name = "myapp.featureX.enabled", havingValue = "true", matchIfMissing=true)
public @interface FeatureXRestController {
}

...
@FeatureXRestController
public class FeatureXRestController1 {...}

@FeatureXRestController
public class FeatureXRestController2 {...}
rdlzhqv9

rdlzhqv91#

我发现了一个相对优雅的解决方案,可能对社区有帮助:我没有像问题中建议的那样使用专门的 meta注解,而是使用常规的@RestController注解来注解Feature X的控制器:

@RestController
public class FeatureXController {
  ...
}

Sping Boot 应用程序类可以被“指示”在组件扫描排除过滤器期间不加载RestControllers。为了示例起见,我将在答案中使用内置的注解过滤器,但通常可以为更复杂(真实的)的情况创建自定义过滤器:

// Note the annotation - component scanning process won't recognize classes annotated with RestController, so from now on all the rest controllers in the application must be defined in `@Configuration` classes.
@ComponentScan(excludeFilters = @Filter(RestController.class))
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

现在,由于我希望仅在启用Feature X时才加载rest控制器,因此我在FeatureXConfiguration中创建了相应的方法:

@Configuration
@ConditionalOnProperty(value = "mayapp.featureX.enabled", havingValue = "true", matchIfMissing = false)
public class FeatureXConfiguration {

    @Bean
    public FeatureXService featureXService () {
        return new FeatureXService();
    }

    @Bean
    public FeatureXRestController featureXRestController () {
        return new FeatureXRestController(featureXService());
    }
}

虽然组件扫描过程不加载其余控制器,但显式bean定义“覆盖”了此行为,并且在启动过程中创建了其余控制器的bean定义。然后Spring MVC引擎对其进行分析,由于存在@RestController注解,它像往常一样公开了相应的端点。

c0vxltue

c0vxltue2#

您可以在java config. E. e中使用本地类。

@Configuration
public class MyAppFeatureXConfiguration  {

  @Bean
  public FeatureXRestController1 featureXRestController1(AutowireCapableBeanFactory beanFactory) {

     @RestController
     class FeatureXRestController1Bean extends FeatureRestController1 {
     }

     FeatureXRestController1Bean featureBean = new FeatureXRestController1Bean();

     // You don't need this line if you use constructor injection
     autowireCapableBeanFactory.autowireBean(featureBean);
  
     return featureBean;
  } 
}

然后,您可以省略“真实的”实现上的@RestController注解,但照常使用其他注解,如@RequestMapping

@RequestMapping(...)
public class FeatureXRestController1 {

    @RequestMapping(value="/somePath/{someId}", method=RequestMethod.GET)
    public String findSomething(@PathVariable String someId) {
       ...
    }
}

由于FeatureXRestController1没有@RestController注解,因此它不再是@Component,因此将不会通过组件扫描获得。
MyAppFeatureXConfiguration会传回@RestController的Bean。此FeatureXRestController1Bean会扩充FeatureXRestController1,因此具有超类别的所有方法和请求对映。
因为FeatureXRestController1Bean是一个本地类,所以它不包含在组件扫描中。)

a9wyjsp7

a9wyjsp73#

我喜欢上面提出的两个解决方案。但是,我想出了另一个对我有效的解决方案,而且相当干净。
因此,我决定只使用@Configuration类创建bean,并完全放弃@RestController注解。我将Web控制器的@Configuration类放在一个单独的包中,以便在需要创建控制器bean时,可以将类描述符传递给@ComponentScan或@ContextConfiguration注解。所有控制器类的ResponseBody注解,在类名上面,以保留REST控制器属性。非常干净。
缺点是控制器类不能被@WebMvcTest注解识别,并且每次我对一个控制器进行MockMvc测试时,我都需要为我所有的控制器创建bean。

相关问题