java - funciona - ¿Por qué se considera que ApplicationContext.getBean de Spring es malo?
como funciona spring (13)
Es cierto que incluir la clase en application-context.xml evita la necesidad de usar getBean. Sin embargo, incluso eso es realmente innecesario. Si está escribiendo una aplicación independiente y NO desea incluir su clase de controlador en application-context.xml, puede usar el siguiente código para hacer que Spring conduzca automáticamente las dependencias del controlador:
public class AutowireThisDriver {
private MySpringBean mySpringBean;
public static void main(String[] args) {
AutowireThisDriver atd = new AutowireThisDriver(); //get instance
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
"/WEB-INF/applicationContext.xml"); //get Spring context
//the magic: auto-wire the instance with all its dependencies:
ctx.getAutowireCapableBeanFactory().autowireBeanProperties(atd,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
// code that uses mySpringBean ...
mySpringBean.doStuff() // no need to instantiate - thanks to Spring
}
public void setMySpringBean(MySpringBean bean) {
this.mySpringBean = bean;
}
}
He necesitado hacer esto un par de veces cuando tengo algún tipo de clase independiente que necesita usar algún aspecto de mi aplicación (por ejemplo, para pruebas) pero no quiero incluirla en el contexto de la aplicación porque no está En realidad parte de la aplicación. Tenga en cuenta también que esto evita la necesidad de buscar el bean con un nombre de cadena, que siempre he pensado que era feo.
Hice una pregunta general de Spring: Auto-cast Spring Beans e hice que varias personas respondieran que la ApplicationContext.getBean()
de Spring''s ApplicationContext.getBean()
debería evitarse tanto como sea posible. ¿Porqué es eso?
¿De qué otra manera debería obtener acceso a los beans que configuré Spring para crear?
Estoy usando Spring en una aplicación no web y había planeado acceder a un objeto ApplicationContext
compartido como lo describe LiorH .
Enmienda
Acepto la respuesta a continuación, pero aquí hay una toma alternativa de Martin Fowler que discute los méritos de la inyección de dependencia en lugar de usar un Localizador de servicio (que es esencialmente lo mismo que llamar a un ApplicationContext.getBean()
envuelto).
En parte, Fowler declara: " Con el localizador de servicios, la clase de aplicación lo solicita [el servicio] explícitamente mediante un mensaje al localizador. Con la inyección no hay una solicitud explícita, el servicio aparece en la clase de aplicación, de ahí la inversión del control. La inversión de control es una característica común de los marcos, pero es algo que tiene un precio. Tiende a ser difícil de entender y da lugar a problemas cuando intenta depurar. Por lo general, prefiero evitarlo [Inversión de control ] a menos que lo necesite. Esto no quiere decir que sea algo malo, solo que creo que debe justificarse por la alternativa más sencilla " .
Hay otro momento en el que usar getBean tiene sentido. Si está reconfigurando un sistema que ya existe, las dependencias no se mencionan explícitamente en los archivos de contexto de Spring. Puede iniciar el proceso realizando llamadas a getBean, para que no tenga que conectarlo todo de una vez. De esta manera, puede construir lentamente su configuración de resorte colocando cada pieza en su lugar a lo largo del tiempo y alineando correctamente las brocas. Las llamadas a getBean eventualmente serán reemplazadas, pero a medida que entienda la estructura del código, o la falta de ellas, puede iniciar el proceso de cablear más y más beans y usar cada vez menos llamadas para obtenerBean.
La idea es que dependa de la inyección de dependencia ( inversión de control o IoC). Es decir, sus componentes están configurados con los componentes que necesitan. Estas dependencias se inyectan (a través del constructor o los definidores), no se obtiene entonces.
ApplicationContext.getBean()
requiere que nombre un bean explícitamente dentro de su componente. En cambio, al usar IoC, su configuración puede determinar qué componente se utilizará.
Esto le permite volver a cablear su aplicación con diferentes implementaciones de componentes fácilmente, o configurar objetos para probarlos de manera directa al proporcionar variantes simuladas (por ejemplo, un DAO simulado para que no llegue a una base de datos durante la prueba)
La motivación es escribir código que no dependa explícitamente de Spring. De esa forma, si elige cambiar de contenedor, no tiene que volver a escribir ningún código.
Piense en el contenedor como si algo fuera invisible para su código, satisfaciendo mágicamente sus necesidades, sin que se lo pidan.
La inyección de dependencia es un contrapunto al patrón del "localizador de servicios". Si va a buscar dependencias por nombre, también puede deshacerse del contenedor DI y usar algo como JNDI.
Las razones para preferir el Localizador de Servicios sobre la Inversión de Control (IoC) son:
El Localizador de servicios es mucho más fácil para que otras personas lo sigan en su código. IoC es "mágico", pero los programadores de mantenimiento deben comprender sus complicadas configuraciones Spring y todas las innumerables ubicaciones para descubrir cómo conectó sus objetos.
IoC es terrible para depurar problemas de configuración. En ciertas clases de aplicaciones, la aplicación no se iniciará cuando esté mal configurada y es posible que no tenga la oportunidad de pasar por lo que está pasando con un depurador.
IoC se basa principalmente en XML (las anotaciones mejoran las cosas pero todavía hay mucho XML por ahí). Eso significa que los desarrolladores no pueden trabajar en su programa a menos que conozcan todas las etiquetas mágicas definidas por Spring. Ya no es suficiente saber Java. Esto dificulta menos experiencia a los programadores (es decir, en realidad es un diseño deficiente usar una solución más complicada cuando una solución más simple, como el Localizador de servicios, cumplirá con los mismos requisitos). Además, el soporte para diagnosticar problemas XML es mucho más débil que el soporte para problemas de Java.
La inyección de dependencia es más adecuada para programas más grandes. La mayoría de las veces la complejidad adicional no vale la pena.
A menudo, Spring se utiliza en caso de que "desee cambiar la implementación más adelante". Hay otras formas de lograr esto sin la complejidad de Spring IoC.
Para las aplicaciones web (Java EE WARs), el contexto Spring está efectivamente vinculado en tiempo de compilación (a menos que desee que los operadores busquen el contexto en la guerra explosiva). Puede hacer que Spring use los archivos de propiedades, pero con los servlets los archivos de propiedades deberán estar en una ubicación predeterminada, lo que significa que no puede implementar varios servlets de la misma hora en el mismo cuadro. Puede usar Spring con JNDI para cambiar las propiedades en el momento de inicio del servlet, pero si está usando JNDI para parámetros modificables por el administrador, la necesidad de Spring sí disminuye (ya que JNDI es efectivamente un Localizador de Servicios).
Con Spring puede perder el control del programa si Spring está enviando a sus métodos. Esto es conveniente y funciona para muchos tipos de aplicaciones, pero no para todas. Es posible que deba controlar el flujo del programa cuando necesite crear tareas (subprocesos, etc.) durante la inicialización o necesite recursos modificables que Spring no conocía cuando el contenido estaba vinculado a su WAR.
La primavera es muy buena para la gestión de transacciones y tiene algunas ventajas. Es solo que IoC puede ser una ingeniería excesiva en muchas situaciones e introducir una complejidad injustificada para los mantenedores. No utilice IoC automáticamente sin pensar en formas de no usarlo primero.
Mencioné esto en un comentario sobre la otra pregunta, pero toda la idea de Inversión de control es que ninguna de sus clases sepa o cuide cómo obtienen los objetos de los que dependen . Esto facilita el cambio del tipo de implementación de una dependencia determinada que utiliza en cualquier momento. También hace que las clases sean fáciles de probar, ya que puede proporcionar implementaciones simuladas de dependencias. Finalmente, hace que las clases sean más simples y más centradas en su responsabilidad principal.
¡Llamar a ApplicationContext.getBean()
no es Inversión de Control! Si bien aún es fácil cambiar la implementación que se configura para el nombre de bean dado, la clase ahora depende directamente de Spring para proporcionar esa dependencia y no puede obtenerla de ninguna otra manera. No puedes simplemente hacer tu propia implementación simulada en una clase de prueba y pasarla a ti mismo. Esto básicamente derrota el propósito de Spring como un contenedor de inyección de dependencia.
En cualquier lugar que quieras decir:
MyClass myClass = applicationContext.getBean("myClass");
en su lugar debería, por ejemplo, declarar un método:
public void setMyClass(MyClass myClass) {
this.myClass = myClass;
}
Y luego en su configuración:
<bean id="myClass" class="MyClass">...</bean>
<bean id="myOtherClass" class="MyOtherClass">
<property name="myClass" ref="myClass"/>
</bean>
Spring entonces automáticamente inyectará myClass
en myOtherClass
.
Declare todo de esta manera, y en la raíz de todo esto tiene algo como:
<bean id="myApplication" class="MyApplication">
<property name="myCentralClass" ref="myCentralClass"/>
<property name="myOtherCentralClass" ref="myOtherCentralClass"/>
</bean>
MyApplication
es la clase más central y depende, al menos indirectamente, de cualquier otro servicio en su programa. Cuando arranque, en su método main
, puede llamar a applicationContext.getBean("myApplication")
pero no debería necesitar llamar a getBean()
ningún otro lugar.
Otros han señalado el problema general (y son respuestas válidas), pero solo ofreceré un comentario adicional: no es que NUNCA debas hacerlo, sino que lo hagas lo menos posible.
Por lo general, esto significa que se hace exactamente una vez: durante el arranque. Y luego es solo para acceder al bean "raíz", a través del cual se pueden resolver otras dependencias. Este puede ser un código reutilizable, como servlet base (si se están desarrollando aplicaciones web).
Solo encontré dos situaciones donde se requería getBean ():
Otros han mencionado el uso de getBean () en main () para obtener el bean "main" para un programa independiente.
Otro uso que he hecho de getBean () está en situaciones en las que una configuración de usuario interactiva determina la configuración del bean para una situación particular. De modo que, por ejemplo, parte del sistema de arranque se desplaza a través de una tabla de base de datos usando getBean () con una definición de bean scope = ''prototype'' y luego configura propiedades adicionales. Presumiblemente, hay una IU que ajusta la tabla de la base de datos que sería más amigable que intentar (re) escribir el XML de contexto de la aplicación.
Una de las instalaciones de Spring es evitar el coupling . Defina y use Interfaces, DI, AOP y evite usar ApplicationContext.getBean () :-)
Una de las razones es la comprobabilidad. Digamos que tienes esta clase:
interface HttpLoader {
String load(String url);
}
interface StringOutput {
void print(String txt);
}
@Component
class MyBean {
@Autowired
MyBean(HttpLoader loader, StringOutput out) {
out.print(loader.load("http://.com"));
}
}
¿Cómo puedes probar este frijol? Por ejemplo, así:
class MyBeanTest {
public void creatingMyBean_writesPageToOutput() {
// setup
String Html = "dummy";
StringBuilder result = new StringBuilder();
// execution
new MyBean(Collections.singletonMap("https://.com", Html)::get, result::append);
// evaluation
assertEquals(result.toString(), Html);
}
}
Fácil, ¿verdad?
Si bien aún depende de Spring (debido a las anotaciones), puede eliminar su dependencia de spring sin cambiar ningún código (solo las definiciones de anotación) y el desarrollador de pruebas no necesita saber nada sobre cómo funciona spring (tal vez debería hacerlo, pero permite revisar y probar el código por separado de lo que hace Spring).
Todavía es posible hacer lo mismo cuando se utiliza ApplicationContext. Sin embargo, entonces debes simular ApplicationContext
que es una gran interfaz. O bien necesitas una implementación ficticia o puedes usar un marco de burla como Mockito:
@Component
class MyBean {
@Autowired
MyBean(ApplicationContext context) {
HttpLoader loader = context.getBean(HttpLoader.class);
StringOutput out = context.getBean(StringOutput.class);
out.print(loader.load("http://.com"));
}
}
class MyBeanTest {
public void creatingMyBean_writesPageToOutput() {
// setup
String Html = "dummy";
StringBuilder result = new StringBuilder();
ApplicationContext context = Mockito.mock(ApplicationContext.class);
Mockito.when(context.getBean(HttpLoader.class))
.thenReturn(Collections.singletonMap("https://.com", Html)::get);
Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append);
// execution
new MyBean(context);
// evaluation
assertEquals(result.toString(), Html);
}
}
Esta es una posibilidad, pero creo que la mayoría de la gente estaría de acuerdo en que la primera opción es más elegante y simplifica la prueba.
La única opción que realmente es un problema es esta:
@Component
class MyBean {
@Autowired
MyBean(StringOutput out) {
out.print(new HttpLoader().load("http://.com"));
}
}
Esto requiere grandes esfuerzos o su bean intentará conectarse a en cada prueba. Y tan pronto como tenga una falla en la red (o los administradores en lo bloqueen debido a una tasa de acceso excesiva), tendrá pruebas que fallan al azar.
Entonces, como conclusión, no diría que usar ApplicationContext
directamente es automáticamente incorrecto y debe evitarse a toda costa. Sin embargo, si hay mejores opciones (y las hay en la mayoría de los casos), entonces use las mejores opciones.
Uno de los mejores beneficios de usar algo como Spring es que no tiene que conectar sus objetos. La cabeza de Zeus se abre y sus clases aparecen, completamente formadas con todas sus dependencias creadas y conectadas, según sea necesario. Es mágico y fantástico.
Cuanto más diga ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed");
, menos magia estás obteniendo. Menos código es casi siempre mejor. Si su clase realmente necesitaba un bean ClassINeed, ¿por qué no lo conectó?
Dicho esto, algo obviamente necesita crear el primer objeto. No hay nada de malo en que tu método principal adquiera un bean o dos a través de getBean (), pero debes evitarlo porque siempre que lo estés utilizando no estás usando toda la magia de Spring.
Usar @Autowired
o ApplicationContext.getBean()
es realmente lo mismo. De ambas formas, obtiene el bean que está configurado en su contexto y de ambas maneras su código depende de Spring. Lo único que debe evitar es crear una instancia de ApplicationContext. ¡Haz esto solo una vez! En otras palabras, una línea como
ApplicationContext context = new ClassPathXmlApplicationContext("AppContext.xml");
Solo debe usarse una vez en su aplicación.
sin embargo, todavía hay casos en los que necesita el patrón de localización de servicios. por ejemplo, tengo un bean controlador, este controlador puede tener algunos beans de servicio predeterminados, que pueden ser dependientes de la configuración. mientras que también puede haber muchos servicios nuevos o adicionales, este controlador puede invocar ahora o más adelante, que luego necesitan el localizador de servicios para recuperar los beans de servicio.