Zakres bean @Scope („prototyp”) nie tworzy nowego komponentu bean

136

Chcę użyć w kontrolerze prototypowego komponentu bean z adnotacjami. Ale zamiast tego wiosna tworzy singleton. Oto kod:

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

  private int counter;

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

Kod kontrolera:

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

Szablon prędkości:

 LoginAction counter: ${loginAction.str}

Spring config.xmlma włączone skanowanie komponentów:

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

Za każdym razem otrzymuję zwiększoną liczbę. Nie mogę dowiedzieć się, dokąd zmierzam źle!

Aktualizacja

Zgodnie z sugestią @gkamal zrobiłem HomeController webApplicationContext-świadomość i rozwiązałem problem.

zaktualizowany kod:

@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
źródło
14
Chciałbym móc dwukrotnie zagłosować za zaimplementowaniem poprawnej odpowiedzi w Twoim kodzie, aby inni mogli zobaczyć rzeczywistą różnicę
Ali Nem

Odpowiedzi:

159

Prototyp zakresu oznacza, że ​​za każdym razem, gdy zapytasz spring (getBean lub dependency injection) o instancję, utworzy on nową instancję i poda do niej odniesienie.

W twoim przykładzie nowe wystąpienie LoginAction jest tworzone i wstrzykiwane do HomeController. Jeśli masz inny kontroler, do którego wstrzykniesz LoginAction, otrzymasz inną instancję.

Jeśli chcesz mieć inną instancję dla każdego wywołania - wtedy musisz wywoływać getBean za każdym razem - wstrzyknięcie do pojedynczego ziarna nie zapewni tego.

gkamal
źródło
7
Zrobiłem kontroler ApplicationContextAware i zrobiłem getBean i za każdym razem dostaję świeży bean. Dzięki chłopaki!!!
tintin
Jak to działa, jeśli fasola miałaby requestzasięg zamiast prototypezakresu. Czy nadal będziesz musiał pobrać fasolę context.getBean(..)?
dr jerry
2
Lub użyj proxy o określonym zakresie, np. @Scope (value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
svenmeier
26

Od wiosny 2.5 można to osiągnąć w bardzo łatwy (i elegancki) sposób.

Można po prostu zmienić params proxyModeoraz valuez @Scopeadnotacją.

Dzięki tej sztuczce możesz uniknąć pisania dodatkowego kodu lub wstrzykiwania ApplicationContext za każdym razem, gdy potrzebujesz prototypu wewnątrz pojedynczego komponentu bean.

Przykład:

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

Z powyższą konfiguracją LoginAction(wewnątrz HomeController) jest zawsze prototypem, mimo że kontroler jest singletonem .

db80
źródło
2
Więc nie mamy tego teraz wiosną 5?
Raghuveer
16

Tylko dlatego, że fasola wprowadzona do kontrolera ma zakres prototypowy, nie oznacza, że ​​kontroler jest!

Dave Newton
źródło
11

@controller jest obiektem singleton i jeśli wstrzykniesz fasolę prototypu do klasy singleton, ten komponent bean będzie również singletonem, chyba że określisz właściwość lookup-method, która w rzeczywistości tworzy nową instancję prototypowego komponentu bean dla każdego wywołania.

kartheek
źródło
5

Jak wspomniano przez nicholas.hauschild wstrzykiwanie kontekstu wiosny nie jest dobrym pomysłem. W Twoim przypadku @Scope („request”) wystarczy, aby to naprawić. Ale powiedzmy, że potrzebujesz kilku instancji LoginActionmetody kontrolera. W tym przypadku poleciłbym stworzyć fasolę Dostawcy ( rozwiązanie Spring 4 ):

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

Następnie wstrzyknij go do kontrolera:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  
Igor Rybak
źródło
1
Sugerowałbym wtryskiwanie sprężyn, ObjectFactoryktóre służą temu samemu celowi co dostawca, ale można je zdefiniować jako normalne, @Beanprzez co mam na myśli brak konieczności zwracania lambda.
xenoterracide
3

Używanie ApplicationContextAwarewiąże cię ze Springiem (co może, ale nie musi, stanowić problem). Poleciłbym przejść przez a LoginActionFactory, o który możesz poprosić o nową instancję za LoginActionkażdym razem, gdy jej potrzebujesz.

nicholas.hauschild
źródło
1
Jednak są już adnotacje specyficzne dla wiosny; nie wydaje się, żeby to był duży problem.
Dave Newton
1
@Dave, słuszna uwaga. Istnieją alternatywy dla niektórych elementów DI (JSR 311), ale w tym przykładzie może być trudniej pozbyć się wszystkiego, co jest zależne od Springa. Przypuszczam, że naprawdę popieram factory-methodtutaj ...
nicholas.hauschild
1
+1 za wstrzyknięcie singletona LoginActionFactorydo kontrolera, ale factory-methodnie wydaje się, aby to rozwiązało problem, ponieważ po prostu tworzy kolejną wiosenną fasolkę za pośrednictwem fabryki. Wstrzyknięcie tego ziarna do pojedynczego kontrolera nie rozwiąże problemu.
Brad Cupit
Słuszna uwaga Brad, usunę tę sugestię z mojej odpowiedzi.
nicholas.hauschild
3

użyj zakresu żądania, @Scope("request")aby uzyskać komponent bean dla każdego żądania lub @Scope("session")uzyskać bean dla każdej sesji „user”

Bassem Reda Zohdy
źródło
1

Fasola-protoype wstrzyknięta do fasoli singelton będzie zachowywać się jak singelton, dopóki nie zostanie natychmiast wezwana do utworzenia nowej instancji przez get bean.

context.getBean("Your Bean")
Ujjwal Choudhari
źródło
0

@Składnik

@Scope (wartość = "prototyp")

Klasa publiczna TennisCoach wdraża Coach {

// jakiś kod

}

Rakesh Singh Balhara
źródło
0

Możesz stworzyć statyczną klasę wewnątrz swojego kontrolera w następujący sposób:

    @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();
            }
        }
}
Jakub
źródło
0

Domyślnie ziarna wiosenne są singletonami. Problem pojawia się, gdy próbujemy drutować fasole o różnych zakresach. Na przykład prototypowy komponent bean w singleton. Jest to znane jako problem z wtryskiem fasoli w określonym zakresie.

Innym sposobem rozwiązania problemu jest wstrzyknięcie metody z adnotacją @Lookup .

Oto fajny artykuł na temat wstrzykiwania prototypowych ziaren do pojedynczej instancji z wieloma rozwiązaniami.

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

Saikat
źródło
-11

Twój kontroler również potrzebuje @Scope("prototype")zdefiniowanego pliku

lubię to:

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

}
flyerfang
źródło
1
dlaczego uważasz, że kontroler również musi być prototypem?
Jigar Parekh