-
Notifications
You must be signed in to change notification settings - Fork 38.6k
Description
Motivation:
There are several standard framework annotations already marked with @Component
annotation.
E.g. @Controller
, @ControllerAdvice
etc.
These annotations have more than one responsibility:
- Main responsibility: They make some other framework's part to understand that this class should be used for something (depending on the annotation). E.g.
@Controller
will be registered inDispatcherServlet
;@ExceptionHandler
methods from@ControllerAdvice
will be registered inExceptionHandlerExceptionResolver
etc. - Second responsibility: They make the class to be subject for component-scan (and be registered as a bean).
Usually this is not a problem. But there are cases when you need to create such components conditionally using @Bean
annotation. E.g. if you create spring-boot-starter
with auto-configurations. In this case you usually explicitly define a @Configuraton
with some @ConditionalOn*
annotations that creates such beans.
Usually this works fine as these @Controller
s or @ControllerAdvice
s are not in a package, that is a sub-package of @SpringBootApplication
's package of consumer app, that uses this spring-boot-starter
. So, they just don't fall under the package scan of the consumer app.
But:
- As a developer of some
spring-boot-starter
library, you couldn't guarantee what package will be used by a consumer app. So, even if it's unlikely, it's still possible that package of your@ControllerAdvice
s will accidentally be package-scanned. This will makespring-boot-starter
's auto-configurations work unexpectedly (usually creating very subtle bugs). - As a developer of some
spring-boot-starter
library, to prevent these components to be package-scanned in your integration tests, you should use a different package for auto-configuration tests (could be problematic as it hides non-public classes from tests) or exclude potentially dangerous classes explicitly from component-scanning in your tests. Not doing this leads to subtle bugs in tests (e.g. false-positive tests).
For @Controller
annotation there is a workaround. You could just not mark a class with @Controller
annotation and mark it with @RequestMapping
instead. This will make DispatcherServlet
to catch the bean as a controller (as it searches for both @Controller
and @RequestMapping
as a marker).
For @ControllerAdvice
I didn't find any workarounds. This is the only annotation that makes ExceptionHandlerExceptionResolver
to find that bean and use it for setup exception handling.
There is similar issue spring-projects/spring-boot#7168. But it's easier to check if @Configuration
is actually an auto-configuration.
I think it's impossible to automatically check if @Component
is actually not intended to be a component, but a bean that is conditionally created by some (auto-)configuration (and is marked with @Component
as an unwelcome side effect of other annotation).
Possible solutions
This is why I think about some dedicated annotation that will handle as an ignore-marker for component scan. As a developer of some spring-boot-starter
library, you will be able to mark any meta-annotated @Component
with this new annotation and be sure this class will never be picked up by component scan of consumer application or your integration tests.
Another solution for a concrete problem with @ControllerAdvice
is to add some other mechanism to register controller advice @Bean
without @ControllerAdvice
annotation. Maybe a marker interface or some new annotation (@ControllerAdviceNoComponent
? :-) ), that is not annotated with @Component
.
But this is obviously will solve only one exact case.
I want a good universal way to allow a library developer to make sure a class will never be catched up by a component scan of consuming app, like it's not annotated with @Component
at all. This will help not only with standard spring-framework annotations (like @ControllerAdvice
), but also with any 3rd party annotations, that also marked with @Component
for one reason or another.
Possible workaround
Add @Conditional
with always-false condition to a class that is annotated (directly or indirectly) with @Component
annotation.
E.g.:
@ConditionalOnExpression("false")
@ControllerAdvice
public class MyExceptionHandlerControllerAdvice {}
This makes @Component
to be ignored by component scan, but when you explicitly create @Bean
with this type, the @Conditional
on the type is ignored (only @Conditional
on bean-factory-method or parent @Configuration
is considered). So you are able to create a @Bean
, if needed.
But it also interferes with some other mechanisms, e.g. if you try to @Import
such component, it will not be imported (bean will not be created), while a simple class without any annotations will be imported. This makes a class that is annotated with both @Component
and @ConditionalOnExpression("false")
not exactly equal to a class that is not annotated at all. So, a dedicated annotation that only affects component scan and not interferes with @Import
or any other mechanisms will be preferred.
Context
I tried to fix this problem zalando/problem-spring-web#802 and found out that the problem was not noticed earlier because of false-positive tests that should be failed long time ago, but was successful because @ControllerAdvice
was registered in tests earlier than it is registered in a real application, that consumes problem-spring-web-starter. In a real application @ControllerAdvice
was registered by auto-configuration (that fires later), while in tests it was registered by component scan and this fact itself leads to ordering problem, that in the end leads to early ExceptionHandlerExceptionResolver
registration (when @ControllerAdvice
from auto-configuration is not loaded yet).