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

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.


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 .


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!


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.


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;  

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.


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”


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")


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

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


-11

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

lubię to:

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

}

1
dlaczego uważasz, że kontroler również musi być prototypem?
— Jigar Parekh
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.