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)
我找到了一个相对优雅的解决方法,可能对社区有所帮助:我没有像我在问题中建议的那样使用专门的元注释,而是使用常规注释来注释功能 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它像平常一样公开相应的端点。
您可以在 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测试过,它有效。
重现步骤
使用springinitializr创建一个简单的 Spring Web 项目
添加一个简单的控制器作为 pojo
public class MyController {
public String sayHello(String who) {
return "Hello " + who;
}
}
Run Code Online (Sandbox Code Playgroud)
创建应用程序配置
@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)
构建并运行应用程序
./mvnw spring-boot:run
Run Code Online (Sandbox Code Playgroud)
或者如果您创建了一个 gradle 项目
./gradlew bootRun
Run Code Online (Sandbox Code Playgroud)
访问端点http://localhost:8080/sayHello?who=Ren%C3%A9
PS:我什至在我原来的答案中尝试了自动装配。它也起作用了。
| 归档时间: |
|
| 查看次数: |
7633 次 |
| 最近记录: |