Are spring components instantiated when not autowired/injected?

Viewed 506

I have a @Service that's injected into a conditional bean. If the bean conditions is false, would the @Service be instantiated anyway? Do I need to mark it @Lazy?

Example:

@Service
class MyService {
  public String doWork() {
    return "work";
  };
}
interface MyComponent {
  String handle();
}
@Component
@ConditionalOnProperty(value = "features.enabled", havingValue = "false", matchIfMissing = true)
class MyNoopComponent implements MyComponent {
  @Override
  public String handle() {
    return "";
  };
}
@Component
@ConditionalOnProperty(value = "features.enabled", havingValue = "true")
class MyDefaultComponent implements MyComponent {
  private final Service service;

  @Autowired
  MyDefaultComponent(MyService service) {
    this.service = service;
  }

  @Override
  public String handle() {
    return service.doWork();
  };
}
# application.yaml
features:
  enabled: false
2 Answers

I wouldn't recommend defining the MyService bean as lazy for the purpose you describe, as you either need the bean or you don't. And although eager instantiation on a lazy bean would be skipped when the feature is disabled, once the feature is enabled the first request requiring the bean would have to wait until the bean is fully instantiated.

Your ConditionalOnProperty works fine, why not apply it twice by adding it to the MyService as well? Or you can create your own meta-annotation to prevent having to duplicate the annotation's attributes:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnProperty(value = "features.enabled", havingValue = "true")
public @Interface ActiveWhenFeatureFlagEnabled {}
@Component
@ActiveWhenFeatureFlagEnabled
class MyDefaultComponent implements MyComponent { ... }
@Service
@ActiveWhenFeatureFlagEnabled
class MyService { ... }

I have a @Service that's injected into a conditional bean. If the bean conditions is false, would the @Service be instantiated anyway?

Yes, if you mark it as @Service and spring IOC container finds it during the startup (because it's a subject to component scanning) then it will load it, create the bean definition out of it, initialize the object, inject it's own dependencies and put onto the application context. From this standpoint it doesn't matter whether the bean is injected into other beans or not.

Do I need to mark it @Lazy?

Usually lazy is used to "postpone" the initialization of bean till the point when its called first (read, its methods are invoked).

If you do not call this bean's method - you basically do not need to create. @Michiel already provided one way to avoid loading this bean with annotation, another method is "unify" all the relevant beans in java configuration:

@Configuration
@ConditionalOnProperty(...)
public class MyConfig {

   @Bean
   public MyService myService() {
      return new MyService();
   }
   @Bean
   public MyDefaultComponent myDefaultComponent(MyService myService) {
        return new MyDefaultComponent(myService);
   }
}

This method allows specifying the conditional only once, so that if "tomorrow" you'll have even more beans to load - you'll know where to add them so that they will be loaded only upon certain value of your condition.

Update 1

Roughly the same effect can be achieved by using a custom stereotype annotation See this tutorial for example

You can create your own annotation, say, @FeatureXService and annotated it with both @ConditionalOnProperty and regular @Service

Then you'll have to mark all the relevant beans with this annotation instead of regular @Service or @Component. The annotation looks like this:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
@ConditionalOnProperty(name = "feature.x.enabled", havingValue = "true")
public @interface FeatureXService {
}

And the use it:

@FeatureXService
class MyService {...}

@FeatureXService
class MyDefaultComponent implements MyComponent {...}

This also allows specifying the @ConditionalOnProperty only once.

Related