java - nfl - guice provides
Cómo usar dos módulos de Guice que instalan un módulo de dependencia común (2)
En lugar de que A
y B
instalen Common
, requireBinding()
que requireBinding()
para las clases que necesitan de Common
. Luego, los módulos que dependen de A
o B
también necesitarán instalar Common
. Esto puede parecer un poco extraño, pero en realidad es deseable, ya que A
y B
están ahora menos unidos a Common
.
Actualizar
La razón por la que estoy instalando dos
ShiroWebModule
s es porque quiero que los recursos de Jersey en el módulo deui
usuario solo se aseguren utilizando una configuración de Shiro (una que no comprenda los recursos que protegen la contraseña), mientras que todos los recursos de Jersey en el módulo deapi
se deben proteger utilizando una configuración Shiro completamente diferente (una que entiende solo los tokens de portador como un mecanismo de autenticación).
En términos generales, esto es intratable. Un Injector
Guice proporciona una forma de hacer algo (generalmente una implementación de una interfaz) a toda la aplicación; No hay diferentes mecanismos por paquete. Sus dos Module
, SwsApiServletModule
y SwsUiServletModule
proporcionan una serie de enlaces idénticos, y SwsModule
instala a la vez. En esencia, está diciendo "Guice, proporcione un mecanismo de autenticación basado en token-bearer" y, inmediatamente después de decir "Guice, proporcione un mecanismo de autenticación basado en contraseña". Solo puede hacer una o la otra, así que en lugar de elegir una arbitrariamente, falla rápidamente.
Por supuesto, hay una serie de soluciones, dependiendo de cuáles son exactamente sus necesidades. Lo más común es usar anotaciones de enlace y hacer que la IU y el código API soliciten una anotación diferente. De esa manera, puede instalar dos implementaciones diferentes (con diferentes anotaciones) de la misma interfaz o clase.
Aquí hay un ejemplo:
package api;
public class ApiResources {
@Inject
public ApiResources(@ApiAuthMechanism AuthMechanism auth) {
this.auth = auth;
}
}
---
package api;
public class ApiModule implements Module {
public void configure() {
bind(AuthMechanism.class).annotatedWith(ApiAuthMechanism.class)
.to(BearerTokenAuthMechanism.class);
}
}
---
package ui;
public class UiResources {
@Inject
public UiResources(@UiAuthMechanism AuthMechanism auth) {
this.auth = auth;
}
}
---
package ui;
public class UiModule implements Module {
public void configure() {
bind(AuthMechanism.class).annotatedWith(UiAuthMechanism.class)
.to(PasswordAuthMechanism.class);
}
}
---
package webap;
public class WebappModule implements Module {
public void configure() {
// These modules can be installed together,
// because they don''t install overlapping bindings
install(new ApiModule());
install(new UiModule());
}
}
Menciona en un comentario que no tiene control de los enlaces superpuestos que se están instalando porque vienen de un módulo de terceros. Si ese es el caso (no vi dónde estaba sucediendo en su código) es posible que el tercero no quiera que haga lo que está tratando de hacer, por razones de seguridad. Por ejemplo, simplemente vincular el mecanismo basado en contraseña podría introducir vulnerabilidades en toda la aplicación. Podría valer la pena intentar comprender mejor cómo pretende el tercero que se utilicen sus módulos.
Otra opción, que no es ideal pero puede funcionar para algunos casos de uso, es usar dos instancias de Injector
completamente separadas, una con cada enlace. Luego pasa manualmente las instancias que necesita a la IU y al código API directamente. Esto de alguna manera derrota el propósito de Guice, pero no siempre es una decisión equivocada. Usar un Injector
infantil puede hacer esto menos doloroso.
Además, su "código de muestra" es enorme, y probablemente más del 90% no está relacionado con el problema. En el futuro, tómese el tiempo para crear un SSCCE que contenga solo el código relevante para el problema en cuestión. Simplemente no hay forma de que alguien vaya a analizar más de 100 archivos Java y más de 7,300 líneas de código para comprender su problema. Esto no solo lo hará más fácil para las personas que están tratando de ayudarlo, sino que simplemente tratando de crear un SSCCE que demuestre que el problema a menudo será suficiente para ayudarlo a comprenderlo y resolverlo.
Estoy trabajando en un proyecto que consta de cuatro partes:
- El proyecto
Main
que reúne todo. Esto contiene el punto de entradapublic static void main(String... args)
. - Componente
A
- Componente
B
- Un componente
Common
terceros al que se refieren tantoA
comoB
Estoy usando Guice para la tubería entre las cuatro partes, y este es mi problema:
En los módulos principales de Guice, A
sy B
, instalo un módulo que se extiende en uno que está definido en Common
. En el tiempo de ejecución, esta configuración falla con el siguiente error:
Un enlace a
common.SomeClass
ya estaba configurado encommon.AbstractCommonModule.configure()
. [ source ]
La razón de esto es que estoy invocando common.AbstractCommonModule.configure()
dos veces; una vez instalando una instancia de subclase de common.AbstractCommonPrivateModule
desde com.a.MainModule.configure()
del Componente A
, y una segunda vez desde com.b.MainModule.configure()
del Componente B
Instalar solo una instancia de common.AbstractCommonPrivateModule
in Main
no es una opción, porque AbstractCommonPrivateModule
implementa un método de bindComplicatedStuff(ComplicatedStuff)
específico bindComplicatedStuff(ComplicatedStuff)
, por lo que solo conozco el argumento dentro de A
y B
, respectivamente.
Intenté resolver todo esto envolviendo los respectivos módulos Guice principales de A
y B
en PrivateModule
s. Sin embargo, esto falló con el siguiente error:
No se puede crear el enlace para% s. Ya estaba configurado en uno o más inyectores secundarios o módulos privados% s% n Si estaba en un módulo privado, ¿olvidó exponer el enlace? [ source ]
En mi caso, los respectivos módulos principales de Guice de A
y B
son en realidad ServletModule
s, que aparentemente puedo instalar dos veces desde Main
.
¿Cómo puedo evitar estos errores e instalar el módulo AbstractCommonPrivateModule
dos veces?
Edición: cargué un código de ejemplo (con una explicación sobre algunos detalles) en GitHub
Para instalar el mismo módulo dos veces, anule el método .equals
en su módulo para referirse a la clase en lugar de a la igualdad de objetos. Guice no instalará un módulo que sea igual a uno que ya se haya instalado. Esto no ayuda mucho la mayoría del tiempo mientras escribe:
install new AbstractCommonPrivateModule();
y así, cada objeto es una instancia diferente que no será igual a la última. Al anular el método de equals
se evita eso:
@Override
public boolean equals(Object obj) {
return obj != null && this.getClass().equals(obj.getClass());
}
// Override hashCode as well.
@Override
public int hashCode() {
return this.getClass().hashCode();
}
Sin embargo, tenga en cuenta que este método es a menudo incorrecto.
¿Por qué no hacer lo anterior ?
En este punto, realmente no está utilizando Guice o la inyección de dependencia. En su lugar, ha acoplado firmemente la implementación de AbstractCommonPrivateModule
con la implementación de B
y C
que lo instalan. Como lo menciona @ dimo414, parece que aquí el OP realmente quiere usar dos ShiroWebModule
s, que es exactamente lo que Guice hace bien al instalar estos dos módulos diferentes en un nivel superior. Del mismo modo, las instalaciones de nivel superior le permiten intercambiar mientras realiza las pruebas. Si realmente desea intercambiar uno de los módulos en algún momento, Guice se romperá de nuevo.
Esto también puede interrumpirse si anula un módulo (que es otra herramienta útil para realizar pruebas).
El OP también quiere instalar un módulo genérico dos veces. Envolver el módulo genérico de otra biblioteca agrega un riesgo adicional; Los autores originales pueden tener muy buenas razones para no implementar el truco anterior, como la seguridad.