Ambito del bean @Scope ("prototipo") che non crea nuovo bean


133

Voglio usare un bean prototipo annotato nel mio controller. Ma la primavera sta creando un fagiolo singleton. Ecco il codice per questo:

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

  private int counter;

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

Codice controller:

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

Modello di velocità:

 LoginAction counter: ${loginAction.str}

Spring config.xmlha attivato la scansione dei componenti:

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

Ricevo un conteggio incrementale ogni volta. Non riesco a capire dove sto sbagliando!

Aggiornare

Come suggerito da @gkamal , ho creato HomeController webApplicationContext-aware e il problema è stato risolto.

codice aggiornato:

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

12
Vorrei poter raddoppiare il tuo voto per aver implementato la risposta corretta nel tuo codice affinché gli altri possano vedere la differenza effettiva
Ali Nem,

Risposte:


156

Il prototipo di ambito significa che ogni volta che chiedi a Spring (getBean o iniezione di dipendenza) un'istanza creerà una nuova istanza e fornirà un riferimento a ciò.

Nel tuo esempio, una nuova istanza di LoginAction viene creata e iniettata nel tuo HomeController. Se si dispone di un altro controller in cui si immette LoginAction, verrà visualizzata un'istanza diversa.

Se si desidera un'istanza diversa per ogni chiamata, quindi è necessario chiamare getBean ogni volta, l'iniezione in un bean singleton non lo farà.


7
Ho creato il controller ApplicationContextAware e fatto getBean e ricevo il bean fresco ogni volta. Grazie ragazzi!!!
tintin,

Come funziona se il bean avesse avuto requestscope invece di prototypescope. Avresti ancora bisogno di recuperare il bean con context.getBean(..)?
dr jerry,

2
Oppure utilizza un proxy con ambito, ad esempio @Scope (value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
svenmeier

25

Dalla primavera 2.5 esiste un modo molto semplice (ed elegante) per raggiungere questo obiettivo.

Puoi semplicemente modificare i parametri proxyModee valuel' @Scopeannotazione.

Con questo trucco puoi evitare di scrivere codice extra o iniettare ApplicationContext ogni volta che hai bisogno di un prototipo all'interno di un bean singleton.

Esempio:

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

Con la configurazione sopra LoginAction(dentro HomeController) è sempre un prototipo anche se il controller è un singleton .


2
Quindi non ce l'abbiamo adesso nella primavera 5?
Raghuveer

16

Solo perché il bean iniettato nel controller ha un ambito prototipo non significa che lo sia!


11

@controller è un oggetto singleton e se iniettare un bean prototipo in una classe singleton renderà il bean prototipo anche come singleton a meno che non si specifichi l'utilizzo della proprietà del metodo di ricerca che crea effettivamente una nuova istanza del bean prototipo per ogni chiamata effettuata.


5

Come menzionato da nicholas.hauschild, l' iniezione del contesto primaverile non è una buona idea. Nel tuo caso, @Scope ("richiesta") è sufficiente per risolverlo. Supponiamo che siano necessarie diverse istanze del LoginActionmetodo controller. In questo caso, consiglierei di creare il bean del fornitore ( soluzione Spring 4 ):

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

Quindi iniettarlo nel controller:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  

1
Vorrei suggerire l'iniezione di molle ObjectFactoryche hanno lo stesso scopo del fornitore, ma possono essere definite come normali @Beanper cui intendo dire che non è necessario restituire un lambda.
xenoterracide,

3

L'uso ti ApplicationContextAwaresta legando a Spring (che potrebbe essere o meno un problema). Consiglierei di passare a LoginActionFactory, che puoi chiedere una nuova istanza di LoginActionogni volta che ne hai bisogno.


1
Tuttavia, esistono già annotazioni specifiche per Spring; non sembra che sia molto preoccupante.
Dave Newton,

1
@Dave, buon punto. Esistono alternative per alcune cose DI (JSR 311), ma potrebbe essere più difficile liberarsi di tutto ciò che dipende dalla primavera in questo esempio. Suppongo che sto davvero sostenendo il factory-methodqui ...
nicholas.hauschild

1
+1 per l'iniezione di un singleton LoginActionFactorynel controller, ma factory-methodnon sembra che risolva il problema in quanto crea un altro bean di primavera tramite la fabbrica. L'iniezione di quel bean nel controller singleton non risolverà il problema.
Brad Cupit,

Buon punto Brad, toglierò quel suggerimento dalla mia risposta.
nicholas.hauschild,

3

usa l'ambito della richiesta @Scope("request")per ottenere il bean per ogni richiesta o @Scope("session")per ottenere il bean per ogni "utente" della sessione


1

Un bean protoype iniettato all'interno di un bean singelton si comporterà come singelton fino a quando non viene chiamato esplicitamente la creazione di una nuova istanza tramite get bean.

context.getBean("Your Bean")

0

@Componente

@Scope (value = "prototipo")

public class TennisCoach implementa Coach {

// un po 'di codice

}


0

È possibile creare una classe statica all'interno del controller in questo modo:

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

0

Per impostazione predefinita, i bean Spring sono singoli. Il problema sorge quando proviamo a cablare bean di diversi ambiti. Ad esempio, un bean prototipo in un singleton. Questo è noto come problema dell'iniezione di bean con ambito.

Un altro modo per risolvere il problema è l'iniezione di metodo con l' annotazione @Lookup .

Ecco un bell'articolo su questo problema relativo all'iniezione di bean prototipo in un'istanza singleton con più soluzioni.

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


-11

Il controller ha bisogno anche del @Scope("prototype") definito

come questo:

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

}

1
perché pensi che anche il controller debba essere un prototipo?
Jigar Parekh,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.