通过Java配置定义Spring RestController

Mar*_*nik 11 java spring spring-mvc spring-boot

是否可以@RestController在Java配置中定义Spring RestController(带注释的类) (在标记为 的方法中带注释的类)?@Configuration@Bean

我有一个由 spring boot 管理的应用程序(就问题而言,版本并不重要,即使是最后一个可用的版本)。该应用程序通过 REST 公开一些端点,因此有多个 REST 控制器,它们依次调用服务(像往常一样)。

现在,根据配置(属性中的application.yml),我想避免启动一些服务,比如用@RestController注释注释的 2 个类,因为它们处理我想要排除的“功能 X”。

我想通过Java 配置来配置我的所有 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() {}   
}
Run Code Online (Sandbox Code Playgroud)

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

// no @Service / @Component annotation
public class ServiceForFeatureX1 {}
Run Code Online (Sandbox Code Playgroud)

现在我的问题是关于用注释注释的类@RestContoller。假设我有 2 个这样的控制器:

@RestController
public class FeatureXRestController1 {
  ...
}

@RestController
public class FeatureXRestController2 {
 ...
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,我也想在 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() {
    } 
Run Code Online (Sandbox Code Playgroud)

所以问题基本上是可以这样做吗?

RestController 是一个控制器,它又是一个组件,因此它受到组件扫描。因此,如果功能 X 被禁用,功能 X 的其余控制器仍将开始加载并失败,因为不会有“服务”——配置中排除的 beans,因此 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 {...}
Run Code Online (Sandbox Code Playgroud)

Mar*_*nik 7

我找到了一个相对优雅的解决方法,可能对社区有所帮助:我没有像我在问题中建议的那样使用专门的元注释,而是使用常规注释来注释功能 X 的控制器@RestController

@RestController
public class FeatureXController {
  ...
}
Run Code Online (Sandbox Code Playgroud)

Spring 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);
    }

}
Run Code Online (Sandbox Code Playgroud)

现在,由于我希望仅在启用功能 X 的情况下加载其余控制器,因此我在 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @TanmayNaik 只是“@Bean”注释中的“autowire”字段被弃用,而不是整个注释。 (2认同)

Ren*_*ink 5

您可以在 java 配置中使用本地类。厄

@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;
  } 
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以省略@RestController“真实”实现上的注释,但像@RequestMapping往常一样使用其他注释。

@RequestMapping(...)
public class FeatureXRestController1 {

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

由于FeatureXRestController1没有@RestController注释,它不再是一个@Component,因此不会通过组件扫描被拾取。

返回MyAppFeatureXConfiguration一个 bean,即@RestController. 这FeatureXRestController1Bean扩展了FeatureXRestController1并因此具有超类的所有方法和请求映射。

由于它FeatureXRestController1Bean是本地类,因此不包含在组件扫描中。这对我有用;)

编辑

我已经使用了这个解决方案并且工作得很好。不幸的是它从 SpringBoot 3.2.1 开始就停止工作了

我用SpringBoot 3.2.3测试过,它有效。

重现步骤

  1. 使用springinitializr创建一个简单的 Spring Web 项目

  2. 添加一个简单的控制器作为 pojo

     public class MyController {
         public String sayHello(String who) {
             return "Hello " + who;
         }
     }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 创建应用程序配置

     @Bean
     public MyController myRestController(){
    
         @RestController
         class SpringRestController extends MyController {
    
             @GetMapping(path = "sayHello")
             @Override
             public String sayHello(@RequestParam(name="who") String who) {
                 return super.sayHello(who);
             }
         }
    
         return new SpringRestController();
     }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 构建并运行应用程序

     ./mvnw spring-boot:run
    
    Run Code Online (Sandbox Code Playgroud)

    或者如果您创建了一个 gradle 项目

     ./gradlew bootRun
    
    Run Code Online (Sandbox Code Playgroud)
  5. 访问端点http://localhost:8080/sayHello?who=Ren%C3%A9

PS:我什至在我原来的答案中尝试了自动装配。它也起作用了。