【问题标题】:Contextually Binding Two Different Interface Implementations with Google Guice使用 Google Guice 上下文绑定两个不同的接口实现
【发布时间】:2013-02-21 22:59:56
【问题描述】:

假设我有一个名为IValidator 的接口,如下所示:

public interface IValidator {
    /**
     * Returns true if the specified strings are valid.
     */
    public boolean validate(List<String> someStrings);
}

现在假设我有两个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;
    }
}

现在让我们添加一个使用 IValidator 注入实例的 servlet:

@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();
        }
    }
}

当然,我们还需要在模块中将IValidator 绑定到StrictValidator

public class ValidatorModule implements Module {
    @Override
    protected void configure() {
        bind(IValiator.class).to(StrictValidator.class);
    }
}

但是,如果我想在一种情况下有条件地将IValidator 绑定到StrictValidator,但在另一种情况下将其绑定到LaissezFaireValidator,会发生什么?

您注意到上面MyServlet.validate 上的@LaissezFaire 注释了吗?这是一个看起来像这样的拦截器:

@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();
        }
    }
}

再一次,我们需要一些绑定来设置拦截器:

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);
    }
}

根据ValidatorModule,当InterceptorModule调用requestInjection(lfInterceptor);时,LaissezFaireInterceptor类会得到一个StrictValidator的实例。

相反,我希望MyServlet 获取StrictValidator 的实例,并希望LaissezFaireInterceptor 获取LaissezFaireValidator 的实例。

根据the Google Guice docs,我可以在请求注入时使用命名注释。 MyServlet 的构造函数将被修改为如下所示:

    @Inject
    public MyServlet(@Named("strict") final IValidator validator) {
        this.validator = validator;
    }

LaissezFaireInterceptorsetValidator 方法将被修改为如下所示:

    @Inject
    public void setValidator(@Named("laissezfaire") final IValidator validator) {
        this.validator = validator;
    }

最后ValidatorModule 将被修改为如下所示:

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);
    }
}

这一切都很好,除了文档特别说要避免这种方法,因为编译器无法检查字符串名称。另外,这意味着我必须在代码中通过注入请求IValidator的每个地方添加@Named注解,否则绑定会失败。

我真的希望Provider Bindings 可以为我解决这个问题,但他们似乎对进行绑定的上下文一无所知。由于他们不知道请求绑定的类的类型,所以我无法选择从get() 方法返回的IValidator 的类型。

有没有更好的方法来解决这个问题?

【问题讨论】:

  • 实际的绑定注释怎么样:@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) public @interface Strict{}?你也可以试试assisted inject
  • @condit 绑定注释比我提到的@Named 注释更干净,因为它可以被编译器检查,但它仍然需要我注释我请求注入@987654362 的每个地方@instance,所以这不是一个巨大的改进。
  • 带有类型侦听器的custom injection 怎么样?

标签: java servlets guice


【解决方案1】:

虽然Condit 提供了一些很好的建议,但我们选择使用更直接的解决方案来解决这个问题。

如上所述,我们创建了IValidator 接口,以及StrictValidatorLaissezFaireValidator 类。在默认情况下,我们使用ValidatorModuleIValidator 绑定到StrictValidator。提醒一下,它看起来像这样:

public class ValidatorModule implements Module {
    @Override
    protected void configure() {
        //in the default case, inject an instance of StrictValidator
        bind(IValiator.class).to(StrictValidator.class);
    }
}

在绝大多数情况下,StrictValidator 是必需的实现,因为LaissezFaireInterceptor 是用于测试的作弊。

无论我们想要StrictValidator(就像我们在MyServlet 中所做的那样),我们都注入了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) ...
}

无论我们想要LaissezFaireValidator 的实例,我们都要求注入它的具体实现来代替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) ...
}

通过这种方式,我们能够根据注入的上下文有条件地注入所需的实现,而无需引入任何额外的注解或工厂。

当然,它并不像它应该的那样 Guicy,但它确实有效。

【讨论】:

  • +1。直接询问你需要的依赖,而不是将其隐藏在方法调用或类中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多