Florian
Florian

Reputation: 5091

Where should I define constants for Spring's @Qualifier annotations?

Spring's dependency injection allows distinguishing multiple implementations for the same interface with the @Qualifier("<flavor>") annotation.

For example, we could have:

public class Consumer {
  @Autowired("b") private SomeInterface dependency;
}

public interface SomeInterface { }

@Component @Qualifier("a")
public class VariantA implements SomeInterface { }

@Component @Qualifier("b")
public class VariantB implements SomeInterface { }

This requires that both, the consumer, and the implementations, use the same constant Strings to identify the variants, in this example "a" and "b". As a clean coder, my first impulse is to create constants for these Strings.

But where should I put them?

A. Consumer

Putting them in the Consumer is out of the question, because that would invert the dependency in an illegal direction, requiring the implementations to know their consumers:

public class Consumer {
  public final static String VARIANT_B = "b";
  @Autowired(Consumer.VARIANT_B) private SomeInterface dependency;
}

@Component @Qualifier(Consumer.VARIANT_B)
public class VariantB implements SomeInterface { }

B. Implementation

Putting them in the implementations seems also to be besides the point, reintroducing the consumer-knows-implementation dependency that dependency injection wants to remove in the first place:

public class Consumer {
  @Autowired(VariantB.VARIANT_B) private SomeInterface dependency;
}

@Component @Qualifier(VariantB.VARIANT_B)
public class VariantB implements SomeInterface {
  public final static String VARIANT_B = "b";
}

C. Interface

Putting them in the interface seems to be an option, but is only possible if the interface is mine and I am free to change it. Also, the notion of having the interface "know" all its implementation variants beforehand sounds a little strange.

public class Consumer {
  @Autowired(SomeInterface.VARIANT_B) private SomeInterface dependency;
}

public interface SomeInterface {
  public final static String VARIANT_A = "a";
  public final static String VARIANT_B = "b";
}

@Component @Qualifier(SomeInterface.VARIANT_A)
public class VariantA implements SomeInterface { }

@Component @Qualifier(SomeInterface.VARIANT_B)
public class VariantB implements SomeInterface { }

D. Constants class

The cleanest solution seems to be putting the constants into a separate development object. This looks a little overhead-y, however:

public class Consumer {
  @Autowired(ImplementationVariant.B) private SomeInterface dependency;
}

public interface SomeInterface { }

public class ImplementationVariant {
  public final static String A= "a";
  public final static String B= "b";
}

@Component @Qualifier(ImplementationVariant.A)
public class VariantA implements SomeInterface { }

@Component @Qualifier(ImplementationVariant.B)
public class VariantB implements SomeInterface { }

Upvotes: 5

Views: 1671

Answers (2)

Datz
Datz

Reputation: 3881

E. Use you own annotations

See my answer here: Typesafe @Qualifier for Spring Boot to avoid errors with strings for details.

Create you own annotations like

@Qualifier("a")
@Retention(RetentionPolicy.RUNTIME)
public @interface VariantA {}

@Qualifier("b")
@Retention(RetentionPolicy.RUNTIME)
public @interface VariantB {}

Then use them like this:

@Autowired
@VariantB
private SomeInterface dependency;

@Component
@VariantA
public class VariantA implements SomeInterface { }

@Component
@VariantB
public class VariantB implements SomeInterface { }

Upvotes: 2

davidxxx
davidxxx

Reputation: 131496

I would choose E : the standard way.

Adding this indirection to identify beans names adds boiler plate code, is not standard (your peers will be surprised) and is not robust either.
Why no robust ? A qualifier expects a string and not an enum value that is strongly bounded. It means that you could pass an incorrect string such as @Component @Qualifier(OtherImplementationVariant.B) and it will still compile fine.

A more robust alternative to achieve that would be to favor the @Bean method factory for beans that have and that use @Qualifier and so to set dependencies between beans by invoking the @Bean method. But there still, that is verbose and not standard at all to generalize them for such a requirement. So not a good idea either.

So I think that you should just stick to standards because if a mismatch occurs and that a dependency cannot be resolved, the issue will be detected as soon as the spring boot startup.

Upvotes: 0

Related Questions