El alcance del bean @Scope ("prototipo") no crea un nuevo bean

133

Quiero usar un prototipo de bean anotado en mi controlador. Pero la primavera está creando un grano único en su lugar. Aquí está el código para eso:

@Component
@Scope("prototype")
public class LoginAction {

  private int counter;

  public LoginAction(){
    System.out.println(" counter is:" + counter);
  }
  public String getStr() {
    return " counter is:"+(++counter);
  }
}

Código del controlador:

@Controller
public class HomeController {
    @Autowired
    private LoginAction loginAction;

    @RequestMapping(value="/view", method=RequestMethod.GET)
    public ModelAndView display(HttpServletRequest req){
        ModelAndView mav = new ModelAndView("home");
        mav.addObject("loginAction", loginAction);
        return mav;
    }

    public void setLoginAction(LoginAction loginAction) {
        this.loginAction = loginAction;
    }

    public LoginAction getLoginAction() {
        return loginAction;
    }
    }

Plantilla de velocidad:

 LoginAction counter: ${loginAction.str}

Spring config.xmltiene habilitado el escaneo de componentes:

    <context:annotation-config />
    <context:component-scan base-package="com.springheat" />
    <mvc:annotation-driven />

Recibo un recuento incrementado cada vez. ¡No puedo entender dónde me estoy equivocando!

Actualizar

Según lo sugerido por @gkamal , hice HomeController webApplicationContext-aware y resolvió el problema.

código actualizado:

@Controller
public class HomeController {

    @Autowired
    private WebApplicationContext context;

    @RequestMapping(value="/view", method=RequestMethod.GET)
    public ModelAndView display(HttpServletRequest req){
        ModelAndView mav = new ModelAndView("home");
        mav.addObject("loginAction", getLoginAction());
        return mav;
    }

    public LoginAction getLoginAction() {
        return (LoginAction) context.getBean("loginAction");
    }
}
tintin
fuente
12
Desearía poder votarlo dos veces por implementar la respuesta correcta en su código para que otros vean la diferencia real
Ali Nem

Respuestas:

156

El prototipo de alcance significa que cada vez que solicite a spring (getBean o inyección de dependencia) una instancia, creará una nueva instancia y le dará una referencia.

En su ejemplo, se crea una nueva instancia de LoginAction y se inyecta en su HomeController. Si tiene otro controlador en el que inyecta LoginAction obtendrá una instancia diferente.

Si desea una instancia diferente para cada llamada, entonces necesita llamar a getBean cada vez, inyectar en un bean singleton no lo logrará.

gkamal
fuente
77
Hice el controlador ApplicationContextAware e hice getBean y siempre obtengo el bean fresco. ¡¡¡Gracias chicos!!!
tintin
¿Cómo funciona esto si el bean hubiera tenido requestalcance en lugar de prototypealcance? ¿Aún necesitarías recuperar el frijol context.getBean(..)?
dr jerry
2
O utilice un proxy con alcance, es decir, @Scope (value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
svenmeier
25

Desde Spring 2.5 hay una manera muy fácil (y elegante) de lograrlo.

Simplemente puede cambiar los parámetros proxyModey valuela @Scopeanotación.

Con este truco, puede evitar escribir código adicional o inyectar el ApplicationContext cada vez que necesite un prototipo dentro de un bean singleton.

Ejemplo:

@Service 
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)  
public class LoginAction {}

Con la configuración anterior LoginAction(dentro HomeController) siempre es un prototipo a pesar de que el controlador es un singleton .

db80
fuente
2
¿Entonces no lo tenemos ahora en la primavera 5?
Raghuveer
16

El hecho de que el bean inyectado en el controlador tenga un alcance prototipo no significa que el controlador lo esté.

Dave Newton
fuente
11

@controller es un objeto singleton, y si se inyecta un bean prototipo a una clase singleton, el bean prototipo también se convertirá en singleton a menos que especifique el uso de la propiedad del método de búsqueda que realmente crea una nueva instancia de bean prototipo para cada llamada que realice.

kartheek
fuente
5

Como lo mencionó nicholas.hauschild inyectando el contexto Spring no es una buena idea. En su caso, @Scope ("solicitud") es suficiente para solucionarlo. Pero supongamos que necesita varias instancias del LoginActionmétodo en el controlador. En este caso, recomendaría crear el bean de proveedor ( solución Spring 4 ):

    @Bean
    public Supplier<LoginAction> loginActionSupplier(LoginAction loginAction){
        return () -> loginAction;
    }

Luego inyectarlo en el controlador:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  
Igor Rybak
fuente
1
Sugeriría inyectar resortes ObjectFactoryque sirven para el mismo propósito que el proveedor, pero se pueden definir como normales, @Beanlo que significa que no es necesario devolver una lambda.
xenoterracide
3

Usar te ApplicationContextAwareestá atando a Spring (lo que puede o no ser un problema). Recomendaría pasar un LoginActionFactory, que puede solicitar una nueva instancia de LoginActioncada vez que lo necesite.

nicholas.hauschild
fuente
1
Sin embargo, ya hay anotaciones específicas de Spring; No parece que sea una gran preocupación.
Dave Newton
1
@Dave, buen punto. Hay alternativas para algunas de las cosas DI (JSR 311), pero puede ser más difícil deshacerse de todo lo que depende de Spring en este ejemplo. Supongo que estoy realmente sólo abogando por la factory-methodaquí ...
nicholas.hauschild
1
+1 por inyectar un singleton LoginActionFactoryen el Controlador, pero factory-methodno parece que resolvería el problema, ya que solo crea otro grano de primavera a través de la fábrica. Inyectar ese bean en el controlador singleton no resolverá el problema.
Brad Cupit
Buen punto Brad, eliminaré esa sugerencia de mi respuesta.
nicholas.hauschild
3

use el alcance de la solicitud @Scope("request")para obtener un bean para cada solicitud, o @Scope("session")para obtener un bean para cada 'usuario' de sesión

Bassem Reda Zohdy
fuente
1

Un bean prototipo inyectado dentro de un bean singelton se comportará como singelton hasta que se solicite explícitamente la creación de una nueva instancia mediante get bean.

context.getBean("Your Bean")
Ujjwal Choudhari
fuente
0

@Componente

@Scope (valor = "prototipo")

clase pública TennisCoach implementa Coach {

// algún código

}

Rakesh Singh Balhara
fuente
0

Puede crear una clase estática dentro de su controlador de esta manera:

    @Controller
    public class HomeController {
        @Autowired
        private LoginServiceConfiguration loginServiceConfiguration;

        @RequestMapping(value = "/view", method = RequestMethod.GET)
        public ModelAndView display(HttpServletRequest req) {
            ModelAndView mav = new ModelAndView("home");
            mav.addObject("loginAction", loginServiceConfiguration.loginAction());
            return mav;
        }


        @Configuration
        public static class LoginServiceConfiguration {

            @Bean(name = "loginActionBean")
            @Scope("prototype")
            public LoginAction loginAction() {
                return new LoginAction();
            }
        }
}
Jacob
fuente
0

Por defecto, los frijoles Spring son singletons. El problema surge cuando tratamos de cablear beans de diferentes ámbitos. Por ejemplo, un prototipo de bean en un singleton. Esto se conoce como el problema de inyección de frijol con alcance.

Otra forma de resolver el problema es la inyección de métodos con la anotación @Lookup .

Aquí hay un buen artículo sobre este tema de inyectar prototipos de beans en una instancia singleton con múltiples soluciones.

https://www.baeldung.com/spring-inject-prototype-bean-into-singleton

Saikat
fuente
-11

Su controlador también necesita el @Scope("prototype")definido

Me gusta esto:

@Controller
@Scope("prototype")
public class HomeController { 
 .....
 .....
 .....

}
colmillo
fuente
1
¿Por qué crees que el controlador también necesita ser un prototipo?
Jigar Parekh