Der Bean-Bereich @Scope („Prototyp“) erstellt keine neue Bean

133

Ich möchte eine kommentierte Prototyp-Bean in meinem Controller verwenden. Aber der Frühling schafft stattdessen eine Singleton-Bohne. Hier ist der Code dafür:

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

  private int counter;

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

Controller-Code:

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

Geschwindigkeitsvorlage:

 LoginAction counter: ${loginAction.str}

In Spring config.xmlist das Scannen von Komponenten aktiviert:

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

Ich erhalte jedes Mal eine erhöhte Anzahl. Ich kann nicht herausfinden, wo ich falsch liege!

Aktualisieren

Wie von @gkamal vorgeschlagen , habe ich darauf aufmerksam gemacht HomeController webApplicationContextund das Problem gelöst.

aktualisierter Code:

@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");
    }
}
Tim und Struppi
quelle
12
Ich wünschte, ich könnte Sie doppelt dafür stimmen, dass Sie die richtige Antwort in Ihren Code implementiert haben, damit andere den tatsächlichen Unterschied erkennen können
Ali Nem,

Antworten:

156

Der Scope-Prototyp bedeutet, dass jedes Mal, wenn Sie spring (getBean oder Abhängigkeitsinjektion) nach einer Instanz fragen, eine neue Instanz erstellt und ein Verweis darauf angegeben wird.

In Ihrem Beispiel wird eine neue Instanz von LoginAction erstellt und in Ihren HomeController eingefügt. Wenn Sie einen anderen Controller haben, in den Sie LoginAction injizieren, erhalten Sie eine andere Instanz.

Wenn Sie für jeden Aufruf eine andere Instanz wünschen - dann müssen Sie jedes Mal getBean aufrufen -, wird dies durch Injizieren in eine Singleton-Bean nicht erreicht.

gkamal
quelle
7
Ich habe den Controller ApplicationContextAware erstellt und getBean ausgeführt, und ich erhalte jedes Mal die frische Bohne. Danke Leute!!!
Tim und Struppi
Wie funktioniert das, wenn die Bean einen requestGültigkeitsbereich anstelle eines prototypeGültigkeitsbereichs gehabt hätte ? Müssen Sie die Bohne noch mit abrufen context.getBean(..)?
Dr. Jerry
2
Oder verwenden Sie einen Proxy mit Gültigkeitsbereich, dh @Scope (value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
svenmeier
24

Seit Spring 2.5 gibt es einen sehr einfachen (und eleganten) Weg, dies zu erreichen.

Sie können einfach die Parameter proxyModeund valuedie @ScopeAnmerkung ändern .

Mit diesem Trick können Sie vermeiden, jedes Mal, wenn Sie einen Prototyp in eine Singleton-Bean benötigen, zusätzlichen Code zu schreiben oder den ApplicationContext einzufügen.

Beispiel:

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

Mit der obigen Konfiguration LoginAction(innen HomeController) ist immer ein Prototyp , obwohl der Controller ein Singleton ist .

db80
quelle
2
Also haben wir es jetzt nicht im Frühjahr 5?
Raghuveer
16

Nur weil die in den Controller eingespritzte Bohne prototypisch ist, heißt das nicht, dass der Controller dies ist!

Dave Newton
quelle
11

@controller ist ein Singleton-Objekt. Wenn Sie eine Prototyp-Bean in eine Singleton-Klasse einfügen, wird die Prototyp-Bean auch als Singleton erstellt, es sei denn, Sie geben die Eigenschaft lookup-method an, mit der für jeden Aufruf eine neue Instanz der Prototyp-Bean erstellt wird.

Kartheek
quelle
5

Wie von nicholas.hauschild erwähnt, ist es keine gute Idee, den Frühlingskontext zu injizieren. In Ihrem Fall reicht @Scope ("Anfrage") aus, um das Problem zu beheben. Angenommen, Sie benötigen mehrere Instanzen der LoginActionController-Methode. In diesem Fall würde ich empfehlen, die Bean of Supplier ( Spring 4- Lösung) zu erstellen :

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

Dann injizieren Sie es in die Steuerung:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  
Igor Rybak
quelle
1
Ich würde vorschlagen, Federn einzuspritzen, ObjectFactorydie dem gleichen Zweck wie der Lieferant dienen, aber als normal definiert werden können, @Beanwomit ich meine, dass kein Lambda zurückgegeben werden muss.
Xenoterracide
3

Die Verwendung ApplicationContextAwarebindet Sie an Spring (was ein Problem sein kann oder nicht). Ich würde empfehlen, ein zu übergeben LoginActionFactory, das Sie LoginActionjedes Mal, wenn Sie eines benötigen , nach einer neuen Instanz von a fragen können .

nicholas.hauschild
quelle
1
Es gibt jedoch bereits frühlingsspezifische Anmerkungen. Das scheint kein großes Problem zu sein.
Dave Newton
1
@ Dave, guter Punkt. Es gibt Alternativen für einige DI-Inhalte (JSR 311), aber es kann schwieriger sein, sich in diesem Beispiel von allem zu befreien, was von Spring abhängt. Ich nehme an, ich befürworte wirklich nur das factory-methodhier ...
nicholas.hauschild
1
+1 für das Injizieren eines Singletons LoginActionFactoryin den Controller, factory-methodscheint aber das Problem nicht zu lösen, da nur eine weitere Spring Bean über die Fabrik erstellt wird. Durch Injizieren dieser Bean in den Singleton-Controller wird das Problem nicht behoben.
Brad Cupit
Guter Punkt Brad, ich werde diesen Vorschlag aus meiner Antwort entfernen.
nicholas.hauschild
3

Verwenden Sie den Anforderungsbereich @Scope("request"), um Bean für jede Anforderung oder @Scope("session")Bean für jeden Sitzungsbenutzer abzurufen.

Bassem Reda Zohdy
quelle
1

Eine Protoype-Bohne, die in eine Singelton-Bohne injiziert wird, verhält sich wie Singelton, bis sie ausdrücklich dazu aufgefordert wird, eine neue Instanz von get bean zu erstellen.

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

@Komponente

@Scope (Wert = "Prototyp")

TennisCoach der öffentlichen Klasse implementiert Coach {

// etwas Code

}}

Rakesh Singh Balhara
quelle
0

Sie können eine statische Klasse in Ihrem Controller wie folgt erstellen:

    @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
quelle
0

Standardmäßig sind Spring Beans Singletons. Das Problem tritt auf, wenn wir versuchen, Bohnen mit unterschiedlichen Bereichen zu verdrahten. Zum Beispiel eine Prototyp-Bean in einen Singleton. Dies ist als das Problem der Bohneninjektion mit Umfang bekannt.

Eine andere Möglichkeit, das Problem zu lösen, ist die Methodeninjektion mit der Annotation @Lookup .

Hier ist ein schöner Artikel zu diesem Problem des Injizierens von Prototyp-Beans in eine Singleton-Instanz mit mehreren Lösungen.

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

Saikat
quelle
-11

Ihr Controller benötigt auch die @Scope("prototype")definierten

so was:

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

}
Flyerfang
quelle
1
Warum denkst du, muss der Controller auch ein Prototyp sein?
Jigar Parekh