I have an interface IInterface.java like below:
public interface IInterface {
void printIt();
}
And there are two implementation classes for this: ImplementationA.java and ImplementationB.java
#Component
public class ImplementationA implements IInterface {
#Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
#Component
public class ImplementationB implements IInterface {
#Override
public void printIt() {
System.out.println("Inside ImplementationB");
}
}
Now I have a listener class, which has this IInterface as a member:
#Component
#AllArgsConstructor
public class Listener {
IInterface iInterface;
public void doStuff(){
iInterface.printIt();
}
}
Now, my requirement is to inject either of ImplementationA.java or ImplementationB.java in the iInterface member of Listener.java based on certain condition.
After some research I started using the #Conditional annotation.
I added two classes ConditionA.java and ConditionB.java:
public class ConditionA implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
public class ConditionB implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
And I also changed my implementation classes as below(added the Conditional annotation):
#Component
#Conditional(ConditionA.class)
public class ImplementationA implements IInterface {
#Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
#Component
#Conditional(ConditionB.class)
public class ImplementationB implements IInterface {
#Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
This seems to work like a charm for me. Whichever implementation class I need to inject I just return true from its corresponding Condition class and return false from rest of the implementation class's Condition class.
However this next part is where I am facing the challenge:
So from the above solution, I was hardcoding the return true or return false from the matches method of the corresponding Condition class. What if I need to return a dynamic value based on another component.
Lets say I have a spring Component class MyCustomConfig which has a member customFlag and if this member is set to true, we need to inject ImplementationA.class.
I had tried the below(made the class #Component and also autowired MyCustomConfig):
#Component
public class ConditionA implements Condition {
#Autowired
MyCustomConfig myCustomConfig;
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return myCustomConfig.getCustomFlag();
}
}
However this simply does not work. myCustomConfig is not autowired and I get a null pointer exception.
Could someone please help me with this.
I think there is no chance for what you need with the use of implements Condition.
If you inspect the documentation for interface Condition which is what the class that you provide to #Conditional should do, in your case ConditionA and ConditionB it says:
Conditions must follow the same restrictions as
BeanFactoryPostProcessor and take care to never interact with bean
instances. For more fine-grained control of conditions that interact
with #Configuration beans consider implementing the
ConfigurationCondition interface.
Maybe this answer would provide a workaround for you as it mentioned above in documentation that in the case that you want to interfere with other beans you should implement your own custom ConfigurationCondition.
Just check that the phase where this ConfigurationCondition will run will be after the beans that you need are registered
I would register the bean using a configuration class and a #Bean as follows:
#Configuration
public class ImplementationConfig {
#Bean
public IInterface implementation(MyCustomConfig myCustomConfig) {
if (myCustomConfig.getCustomFlag()) {
return new ImplementationA();
} else {
return new ImplementationB();
}
}
}
You simply drop the Spring-related annotations in both ImplementationA and ImplementationB:
public class ImplementationA implements IInterface {
#Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
public class ImplementationB implements IInterface {
#Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
And finally, you can drop the Condition classes.
Check this link https://stackoverflow.com/a/70351394/7237884 here.
The docs say
Conditions must follow the same restrictions as BeanFactoryPostProcessor and take care to never interact with bean instances. For more fine-grained control of conditions that interact with #Configuration beans consider implementing the ConfigurationCondition interface.
Related
Having the following class structure:
public abstract class A {
String someProperty = "property"
public abstract void doSomething();
}
#Service
public class Aa extends A {
#Override
public abstract void doSomething() {
System.out.println("I did");
}
}
#Service
public class Ab extends A {
#Override
public abstract void doSomething() {
System.out.println("I did something else");
}
}
I need a way to tell Spring which A concrete class to Autowire in my Foo service, based on a property in a properties file.
#Service
public class Foo {
#Autowire
private A assignMeAConcreteClass;
}
And in my properties file I have this:
should-Aa-be-used: {true, false}
Remove the #Service annotation, instead write a #Bean-annotated method in a configuration class that reads the properties, and returns the appropriate A instance.
Not a new way but in your case I think that a possible suitable way would be to use
FactoryBean in the class that wants to inject the bean conditionally.
The idea is simple : you implement FactoryBean by parameterizing it with the interface of the bean that you want to inject and override getObject() to inject the wished implementation :
public class FactoryBeanA implements FactoryBean<A> {
#Autowired
private ApplicationContext applicationContext;
#Value("${should-Aa-be-used}")
private boolean shouldBeUsed;
#Override
public A getObject() {
if (shouldBeUsed) {
return applicationContext.getBean(Aa.class));
return applicationContext.getBean(Ab.class));
}
}
But FactoryBean instances are not classic beans. You have to configure it specifically.
You could configure it in a Spring Java configuration in this way :
#Configuration
public class FactoryBeanAConfiguration{
#Bean(name = "factoryBeanA")
public FactoryBeanA factoryBeanA() {
return new FactoryBeanA();
}
#Bean
public beanA() throws Exception {
return factoryBeanA().getObject();
}
}
I have a method which has a configurable transaction behaviour, like based on a boolean value it should apply #Transactional on that method. Is there something like this:
#Transactional(if myBooleanVariable is true)
public boolean executeQueries(List<String> queries) {
Iterator<String> it = queries.iterator();
}
Yes, the injecting beans conditionally are possible. There are lots of ways, however, I recommend you to use profiles.
It allows you to have more unnamed beans of the same time based on the profile.
#Bean
#Profile("!dev")
public class MyClass1 implements MyInterface {}
#Bean
#Profile("dev")
public class MyClass2 implements MyInterface {}
You can configure the profile using in the Java Configuration class:
#Configuration
public class MyWebApplicationInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter("spring.profiles.active", "dev");
}
}
Then the annotation #Autowired will find the proper implementation to inject based on the active profile. Read more on the Baeldung's websize, where I took the example from.
The second way is to use the annotation #Conditional, which injects the bean conditionally based on the return method of the implemented interface Condition.
#Bean
#Conditional(MyClass1Condition.class)
public class MyClass1 implements MyInterface {}
#Bean
#Conditional(MyClass2Condition.class)
public class MyClass2 implements MyInterface {}
Here is how the implementation skeleton would look like:
public class MyClass1Condition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// perform your logic
}
}
So this approach may not be viable and I'm misunderstanding the construction.
I'm using Spring and I have an interface which I'd like to use for two different types of #Conditional checks. I declare my inner classes and specify the #ConditonalOnProperty environment values I'm looking for.
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
public interface CloudEnvironments {
#ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
class IsDev {}
#ConditionalOnProperty(name = "spring.profiles.active", havingValue = "stage")
class IsStage {}
#ConditionalOnProperty(name = "spring.profiles.active", havingValue = "prod")
class IsProd {}
}
I then create my class which extends the appropriate Spring NestedCondition. One that checks for ANY environment to match (Cloud) and one where NONE of the environments match (Local)
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
public class EnvironmentConditional {
public static class AnyCloud extends AnyNestedCondition implements CloudEnvironments {
public AnyCloud() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
}
public static class NotCloud extends NoneNestedConditions implements CloudEnvironments {
public NotCloud() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
}
}
and then I implement it in my custom DataSource
#Configuration
#Conditional(EnvironmentConditional.AnyCloud.class)
public class CloudDataSource {
#Bean
public DataSource stuff(){
/** CODE **/
}
}
Unfortunately when I run the App, when displaying all of the Conditional matches
- AnyNestedCondition 0 matched 0 did not (EnvironmentConditional.AnyCloud)
Meaning it didn't see any of the #ConditionalOnProperty checks. (I can make this work if I pull it up a level, I was curious if I could make it more DRY this way).
I've got a static inner class implementing another inner class...and it's seeming that I've gone too deep.
Can I get a nudge on my misfire in logic and if there's an approach that will allow me to have a single #ConditonalOnProperty for each environment while leveraging the NestedConditions from Spring.
I gave a try like this and I was able to decide to match the condition.
We can also simply make use of #Profile("cloud") to enable profile specific beans
public class EnvironmentConditional {
public static class AnyCloud extends SpringBootCondition implements CloudEnvironments {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env=context.getBeanFactory().getBean(Environment.class);
for( String profile : env.getActiveProfiles()){
switch(profile){
case "dev" : return new ConditionOutcome(true, "Matched");
case "prod" : return new ConditionOutcome(false, "UnMatched");
}
}
return new ConditionOutcome(true, "Matched");
}
}
public static class NotCloud extends NoneNestedConditions implements CloudEnvironments {
public NotCloud() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
}
}
Also have a look at this ,
#ConditionalOnCloudPlatform :
Doc link
In a java-spring web-app I would like to be able to dynamically inject beans.
For example I have an interface with 2 different implementations:
In my app I'm using some properties file to configure injections:
#Determines the interface type the app uses. Possible values: implA, implB
myinterface.type=implA
My injections actually loaded conditionally relaying on the properties values in the properties file. For example in this case myinterface.type=implA wherever I inject MyInterface the implementation that will be injected will be ImplA (I accomplished that by extending the Conditional annotation).
I would like that during runtime - once the properties are changed the following will happen (without server restart):
The right implementation will be injected. For example when setting myinterface.type=implB ImplB will be injected where-ever MyInterface is used
Spring Environment should be refreshed with the new values and re-injected as well to beans.
I thought of refreshing my context but that creates problems.
I thought maybe to use setters for injection and re-use those setters once properties are re-configured. Is there a working practice for such a requirement?
Any ideas?
UPDATE
As some suggested I can use a factory/registry that holds both implementations (ImplA and ImplB) and returns the right one by querying the relevant property.
If I do that I still have the second challenge - the environment. for example if my registry looks like this:
#Service
public class MyRegistry {
private String configurationValue;
private final MyInterface implA;
private final MyInterface implB;
#Inject
public MyRegistry(Environmant env, MyInterface implA, MyInterface ImplB) {
this.implA = implA;
this.implB = implB;
this.configurationValue = env.getProperty("myinterface.type");
}
public MyInterface getMyInterface() {
switch(configurationValue) {
case "implA":
return implA;
case "implB":
return implB;
}
}
}
Once property has changed I should re-inject my environment. any suggestions for that?
I know I can query that env inside the method instead of constructor but this is a performance reduction and also I would like to think of an ider for re-injecting environment (again, maybe using a setter injection?).
I would keep this task as simple as possible. Instead of conditionally load one implementation of the MyInterface interface at startup and then fire an event that triggers dynamic loading of another implementation of the same interface, I would tackle this problem in a different way, that is much simpler to implement and maintain.
First of all, I'd just load all possible implementations:
#Component
public class MyInterfaceImplementationsHolder {
#Autowired
private Map<String, MyInterface> implementations;
public MyInterface get(String impl) {
return this.implementations.get(impl);
}
}
This bean is just a holder for all implementations of the MyInterface interface. Nothing magic here, just common Spring autowiring behavior.
Now, wherever you need to inject a specific implementation of MyInterface, you could do it with the help of an interface:
public interface MyInterfaceReloader {
void changeImplementation(MyInterface impl);
}
Then, for every class that needs to be notified of a change of the implementation, just make it implement the MyInterfaceReloader interface. For instance:
#Component
public class SomeBean implements MyInterfaceReloader {
// Do not autowire
private MyInterface myInterface;
#Override
public void changeImplementation(MyInterface impl) {
this.myInterface = impl;
}
}
Finally, you need a bean that actually changes the implementation in every bean that has MyInterface as an attribute:
#Component
public class MyInterfaceImplementationUpdater {
#Autowired
private Map<String, MyInterfaceReloader> reloaders;
#Autowired
private MyInterfaceImplementationsHolder holder;
public void updateImplementations(String implBeanName) {
this.reloaders.forEach((k, v) ->
v.changeImplementation(this.holder.get(implBeanName)));
}
}
This simply autowires all beans that implement the MyInterfaceReloader interface and updates each one of them with the new implementation, which is retrieved from the holder and passed as an argument. Again, common Spring autowiring rules.
Whenever you want the implementation to be changed, you should just invoke the updateImplementations method with the name of the bean of the new implementation, which is the lower camel case simple name of the class, i.e. myImplA or myImplB for classes MyImplA and MyImplB.
You should also invoke this method at startup, so that an initial implementation is set on every bean that implements the MyInterfaceReloader interface.
I solved a similar issue by using org.apache.commons.configuration.PropertiesConfiguration and org.springframework.beans.factory.config.ServiceLocatorFactoryBean:
Let VehicleRepairService be an interface:
public interface VehicleRepairService {
void repair();
}
and CarRepairService and TruckRepairService two classes that implements it:
public class CarRepairService implements VehicleRepairService {
#Override
public void repair() {
System.out.println("repair a car");
}
}
public class TruckRepairService implements VehicleRepairService {
#Override
public void repair() {
System.out.println("repair a truck");
}
}
I create an interface for a service factory:
public interface VehicleRepairServiceFactory {
VehicleRepairService getRepairService(String serviceType);
}
Let use Config as configuration class:
#Configuration()
#ComponentScan(basePackages = "config.test")
public class Config {
#Bean
public PropertiesConfiguration configuration(){
try {
PropertiesConfiguration configuration = new PropertiesConfiguration("example.properties");
configuration
.setReloadingStrategy(new FileChangedReloadingStrategy());
return configuration;
} catch (ConfigurationException e) {
throw new IllegalStateException(e);
}
}
#Bean
public ServiceLocatorFactoryBean serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean serviceLocatorFactoryBean = new ServiceLocatorFactoryBean();
serviceLocatorFactoryBean
.setServiceLocatorInterface(VehicleRepairServiceFactory.class);
return serviceLocatorFactoryBean;
}
#Bean
public CarRepairService carRepairService() {
return new CarRepairService();
}
#Bean
public TruckRepairService truckRepairService() {
return new TruckRepairService();
}
#Bean
public SomeService someService(){
return new SomeService();
}
}
By using FileChangedReloadingStrategy your configuration be reload when you change the property file.
service=truckRepairService
#service=carRepairService
Having the configuration and the factory in your service, let you can get the appropriate service from the factory using the current value of the property.
#Service
public class SomeService {
#Autowired
private VehicleRepairServiceFactory factory;
#Autowired
private PropertiesConfiguration configuration;
public void doSomething() {
String service = configuration.getString("service");
VehicleRepairService vehicleRepairService = factory.getRepairService(service);
vehicleRepairService.repair();
}
}
Hope it helps.
If I understand you correctly then the goal is not to replace injected object instances but to use different implementations during interface method call depends on some condition at run time.
If it is so then you can try to look at the Sring TargetSource mechanism in combination with ProxyFactoryBean. The point is that proxy objects will be injected to beans that uses your interface, and all the interface method calls will be sent to TargetSource target.
Let's call this "Polymorphic Proxy".
Have a look at example below:
ConditionalTargetSource.java
#Component
public class ConditionalTargetSource implements TargetSource {
#Autowired
private MyRegistry registry;
#Override
public Class<?> getTargetClass() {
return MyInterface.class;
}
#Override
public boolean isStatic() {
return false;
}
#Override
public Object getTarget() throws Exception {
return registry.getMyInterface();
}
#Override
public void releaseTarget(Object target) throws Exception {
//Do some staff here if you want to release something related to interface instances that was created with MyRegistry.
}
}
applicationContext.xml
<bean id="myInterfaceFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="MyInterface"/>
<property name="targetSource" ref="conditionalTargetSource"/>
</bean>
<bean name="conditionalTargetSource" class="ConditionalTargetSource"/>
SomeService.java
#Service
public class SomeService {
#Autowired
private MyInterface myInterfaceBean;
public void foo(){
//Here we have `myInterfaceBean` proxy that will do `conditionalTargetSource.getTarget().bar()`
myInterfaceBean.bar();
}
}
Also if you want to have both MyInterface implementations to be Spring beans, and the Spring context could not contains both instances at the same time then you can try to use ServiceLocatorFactoryBean with prototype target beans scope and Conditional annotation on target implementation classes. This approach can be used instead of MyRegistry.
P.S.
Probably Application Context refresh operation also can do what you want but it can cause other problems such as performance overheads.
This may be a duplicate question or at least very similar, anyway I answered this sort of question here: Spring bean partial autowire prototype constructor
Pretty much when you want a different beans for a dependency at run-time you need to use a prototype scope. Then you can use a configuration to return different implementations of the prototype bean. You will need to handle the logic on which implementation to return yourself, (they could even be returning 2 different singleton beans it doesn't matter) But say you want new beans, and the logic for returning the implementation is in a bean called SomeBeanWithLogic.isSomeBooleanExpression(), then you can make a configuration:
#Configuration
public class SpringConfiguration
{
#Bean
#Autowired
#Scope("prototype")
public MyInterface createBean(SomeBeanWithLogic someBeanWithLogic )
{
if (someBeanWithLogic .isSomeBooleanExpression())
{
return new ImplA(); // I could be a singleton bean
}
else
{
return new ImplB(); // I could also be a singleton bean
}
}
}
There should never be a need to reload the context. If for instance, you want the implementation of a bean to change at run-time, use the above. If you really need to reload your application, because this bean was used in constructors of a singleton bean or something weird, then you need to re-think your design, and if these beans are really singleton beans. You shouldn't be reloading the context to re-create singleton beans to achieve different run-time behavior, that is not needed.
Edit The first part of this answer answered the question about dynamically injecting beans. As asked, but I think the question is more of one: 'how can I change the implementation of a singleton bean at run-time'. This could be done with a proxy design pattern.
interface MyInterface
{
public String doStuff();
}
#Component
public class Bean implements MyInterface
{
boolean todo = false; // change me as needed
// autowire implementations or create instances within this class as needed
#Qualifier("implA")
#Autowired
MyInterface implA;
#Qualifier("implB")
#Autowired
MyInterface implB;
public String doStuff()
{
if (todo)
{
return implA.doStuff();
}
else
{
return implB.doStuff();
}
}
}
You can use #Resource annotation for injection as originally answered here
e.g.
#Component("implA")
public class ImplA implements MyInterface {
...
}
#Component("implB")
public class ImplB implements MyInterface {
...
}
#Component
public class DependentClass {
#Resource(name = "\${myinterface.type}")
private MyInterface impl;
}
and then set the implementation type in properties file as -
myinterface.type=implA
Be aware that - if interesting to know about - FileChangedReloadingStrategy makes your project highly dependent on the deployment conditions: the WAR/EAR should be exploded by container and your should have direct access to the file system, conditions that are not always met in all situations and environments.
You can use Spring #Conditional on a property value. Give both Beans the same name and it should work as only one Instance will be created.
Have a look here on how to use #Conditional on Services and Components:
http://blog.codeleak.pl/2015/11/how-to-register-components-using.html
public abstract class SystemService {
}
public class FooSystemService extends FileSystemService {
}
public class GoSystemService extends FileSystemService {
}
#Configuration
public class SystemServiceConf {
#Bean
#Conditional(SystemServiceCondition.class)
public SystemService systemService(#Value("${value.key}") value) {
switch (value) {
case A:
return new FooSystemService();
case B:
return new GoSystemService();
default:
throw new RuntimeException("unknown value ");
}
}
}
public class SystemServiceCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return true;
}
}
Let's pretend that I have an interface called IValidator that looks like the following:
public interface IValidator {
/**
* Returns true if the specified strings are valid.
*/
public boolean validate(List<String> someStrings);
}
Now let's say that I have two implementations of IValidator:
public class StrictValidator implements IValidator {
public boolean validate(List<String> someStrings) {
//some strict validation code
return false;
}
}
public class LaissezFaireValidator implements IValidator {
public boolean validate(List<String> someStrings) {
//some easy-going validation code
return true;
}
}
Now let's add a servlet that uses an injected instance of IValidator:
#Service
#At("/rest")
public class MyServlet extends AbstractServlet {
private final IValidator validator;
#Inject
public MyServlet(final IValidator validator) {
this.validator = validator;
}
#Post
#At("/validate")
#LaissezFaire
public Reply<?> validate(Request request) {
//get the strings to validate out of the request object
List<String> strings = (List<String>) restUtil.parseRequest(request, List.class);
//validate the request
if (!this.validator.validate(strings)) {
return Reply.saying().status(409);
} else {
return Reply.saying().noContent();
}
}
}
Of course we'll also need to bind IValidator to StrictValidator in a module:
public class ValidatorModule implements Module {
#Override
protected void configure() {
bind(IValiator.class).to(StrictValidator.class);
}
}
But what happens if I want to conditionally bind IValidator to StrictValidator in one case, but instead bind it to LaissezFaireValidator in some other case?
Did you notice the #LaissezFaire annotation on MyServlet.validate above? That's an interceptor that looks like this:
#BindingAnnotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LaissezFaire { }
public class LaissezFaireInterceptor implements MethodInterceptor {
private boolean debug;
private IValidator validator;
#Inject
public void setDebug(#Named("debug.enabled") boolean debugEnabled) {
this.debug = debugEnabled;
}
#Inject
public void setValidator(final IValidator validator) {
this.validator = validator;
}
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (debug) {
if (!this.validator.validate(strings)) {
return Reply.saying().status(409);
} else {
return Reply.saying().noContent();
}
} else {
return invocation.proceed();
}
}
}
And once again we need some bindings to set up the interceptor:
public class InterceptorModule implements Module {
#Override
protected void configure() {
final MethodInterceptor lfInterceptor = new LaissezFaireInterceptor();
requestInjection(lfInterceptor);
bindInterceptor(Matchers.subclassesOf(AbstractServlet.class), Matchers.AnnotatedWith(LaissezFaire.class), lfInterceptor);
}
}
According to the ValidatorModule, the LaissezFaireInterceptor class will get an instance of StrictValidator when InterceptorModule calls requestInjection(lfInterceptor);.
Instead, I'd like MyServlet to get an instance of StrictValidator and LaissezFaireInterceptor to get an instance of LaissezFaireValidator.
According to the Google Guice docs, I could use a named annotation when I request injection. The constructor of MyServlet would be modified to look like the following:
#Inject
public MyServlet(#Named("strict") final IValidator validator) {
this.validator = validator;
}
and the setValidator method of LaissezFaireInterceptor would be modified to look like the following:
#Inject
public void setValidator(#Named("laissezfaire") final IValidator validator) {
this.validator = validator;
}
and finally ValidatorModule would be modified to look like the following:
public class ValidatorModule implements Module {
#Override
protected void configure() {
bind(IValiator.class).annotatedWith(Names.named("strict")).to(StrictValidator.class);
bind(IValidator.class).annotatedWith(Names.named("laissezfaire")).to(LaissezFaireValidator.class);
}
}
This is all well and good except that the docs specifically say to avoid this approach because the string names can't be checked by the compiler. In addition, it means that I have to add an #Named annotation to every place in the code that requests an IValidator by injection, or else the binding will fail.
I had really hoped that Provider Bindings could solve this problem for me, but they don't appear to know anything about the context in which the binding is being made. Since they don't know the type of the class that is requesting the binding, I can't choose which type of IValidator to return from the get() method.
Is there a better way to approach this problem?
While Condit supplied some excellent suggestions, we opted to solve this problem with a more straightforward solution.
As above, we created the IValidator interface, as well as the StrictValidator and LaissezFaireValidator classes. We used the ValidatorModule to bind IValidator to StrictValidator in the default case. As a reminder, it looks like this:
public class ValidatorModule implements Module {
#Override
protected void configure() {
//in the default case, inject an instance of StrictValidator
bind(IValiator.class).to(StrictValidator.class);
}
}
In the vast majority of cases, StrictValidator is the required implementation, as the LaissezFaireInterceptor is a cheat that is used for testing.
Wherever we wanted a StrictValidator (like we do in MyServlet), we injected an instance of IValidator:
public class MyServlet extends AbstractServlet {
private final IValidator validator;
#Inject
public MyServlet(final IValidator validator) {
this.validator = validator;
}
//... there's more code here (look above) ...
}
And wherever we wanted an instance of LaissezFaireValidator, we asked for its concrete implementation to be injected in place of IValidator:
public class LaissezFaireInterceptor implements MethodInterceptor {
private final IValidator validator;
//... a bunch of other code goes here (see above) ...
#Inject
public void setValidator(final LaissezFaireValidator validator) {
this.validator = validator;
}
//... and a bunch more code goes here (again, see above) ...
}
In this way, we were able to conditionally inject the required implementation based on the context of the injection without introducing any extra annotations or factories.
Sure, it's not as Guicy as it could be, but it works.