La portée du bean @Scope («prototype») ne crée pas de nouveau bean

133

Je souhaite utiliser un prototype de bean annoté dans mon contrôleur. Mais le printemps crée plutôt un haricot singleton. Voici le code pour cela:

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

  private int counter;

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

Code contrôleur:

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

Modèle de vitesse:

 LoginAction counter: ${loginAction.str}

Spring config.xmla activé l'analyse des composants:

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

J'obtiens un décompte incrémenté à chaque fois. Je ne sais pas où je me trompe!

Mettre à jour

Comme suggéré par @gkamal , j'ai créé HomeController webApplicationContext-aware et cela a résolu le problème.

code mis à jour:

@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
la source
12
J'aimerais pouvoir doubler votre vote pour avoir implémenté la bonne réponse dans votre code pour que les autres puissent voir la différence réelle
Ali Nem

Réponses:

156

Le prototype de portée signifie que chaque fois que vous demanderez à spring (getBean ou injection de dépendances) une instance, il créera une nouvelle instance et y donnera une référence.

Dans votre exemple, une nouvelle instance de LoginAction est créée et injectée dans votre HomeController. Si vous avez un autre contrôleur dans lequel vous injectez LoginAction, vous obtiendrez une instance différente.

Si vous voulez une instance différente pour chaque appel - alors vous devez appeler getBean à chaque fois - l'injection dans un bean singleton n'y parviendra pas.

gkamal
la source
7
J'ai créé le contrôleur ApplicationContextAware et j'ai fait getBean et je reçois le haricot frais à chaque fois. Merci les gars!!!
tintin
Comment cela fonctionne-t-il si le bean aurait eu une requestportée au lieu d'une prototypeétendue? Auriez-vous encore besoin de récupérer le haricot avec context.getBean(..)?
dr jerry
2
Ou utilisez un proxy à portée, c'est-à-dire @Scope (value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
svenmeier
25

Depuis Spring 2.5, il existe un moyen très simple (et élégant) d'y parvenir.

Vous pouvez simplement changer les paramètres proxyModeet valuede l' @Scopeannotation.

Avec cette astuce, vous pouvez éviter d'écrire du code supplémentaire ou d'injecter le ApplicationContext chaque fois que vous avez besoin d'un prototype à l'intérieur d'un bean singleton.

Exemple:

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

Avec la configuration ci-dessus LoginAction(à l'intérieur HomeController), il y a toujours un prototype même si le contrôleur est un singleton .

db80
la source
2
Donc nous ne l'avons pas maintenant au printemps 5?
Raghuveer
16

Ce n'est pas parce que le bean injecté dans le contrôleur est à portée prototype que le contrôleur l'est!

Dave Newton
la source
11

@controller est un objet singleton, et si vous injectez un bean prototype dans une classe singleton, le bean prototype sera également un singleton à moins que vous ne spécifiez en utilisant la propriété lookup-method qui crée en fait une nouvelle instance de bean prototype pour chaque appel que vous effectuez.

kartheek
la source
5

Comme mentionné par nicholas.hauschild, l' injection de contexte Spring n'est pas une bonne idée. Dans votre cas, @Scope ("request") suffit à le corriger. Mais disons que vous avez besoin de plusieurs instances de la LoginActionméthode du contrôleur. Dans ce cas, je recommanderais de créer le bean de Supplier ( solution Spring 4 ):

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

Puis injectez-le dans le contrôleur:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  
Igor Rybak
la source
1
Je suggérerais d'injecter des ressorts ObjectFactoryqui servent le même but que le fournisseur, mais peuvent être définis comme une norme @Beanpar laquelle je veux dire pas besoin de retourner un lambda.
xenoterracide
3

L'utilisation ApplicationContextAwarevous lie à Spring (ce qui peut ou non être un problème). Je recommanderais de passer un LoginActionFactory, que vous pouvez demander une nouvelle instance de a LoginActionchaque fois que vous en avez besoin.

nicholas.hauschild
la source
1
Cependant, il existe déjà des annotations spécifiques à Spring; cela ne semble pas très préoccupant.
Dave Newton
1
@Dave, bon point. Il existe des alternatives pour certains éléments de la DI (JSR 311), mais il peut être plus difficile de se débarrasser de tout ce qui dépend de Spring dans cet exemple. Je suppose que je ne fais que préconiser factory-methodici ...
nicholas.hauschild
1
+1 pour injecter un singleton LoginActionFactorydans le contrôleur, mais factory-methodne semble pas résoudre le problème car il crée simplement un autre haricot de printemps via l'usine. L'injection de ce bean dans le contrôleur singleton ne résoudra pas le problème.
Brad Cupit
Bon point Brad, je vais supprimer cette suggestion de ma réponse.
nicholas.hauschild
3

utiliser la portée de la requête @Scope("request")pour obtenir le bean pour chaque requête, ou @Scope("session")pour obtenir le bean pour chaque session 'utilisateur'

Bassem Reda Zohdy
la source
1

Un bean protoype injecté à l'intérieur d'un bean singelton se comportera comme singelton jusqu'à ce qu'il soit appelé expilitivement pour créer une nouvelle instance par get bean.

context.getBean("Your Bean")
Ujjwal Choudhari
la source
0

@Composant

@Scope (valeur = "prototype")

public class TennisCoach implémente Coach {

// du code

}

Rakesh Singh Balhara
la source
0

Vous pouvez créer une classe statique dans votre contrôleur comme ceci:

    @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
la source
0

Par défaut, les beans Spring sont des singletons. Le problème se pose lorsque nous essayons de câbler des haricots de différentes portées. Par exemple, un bean prototype en un singleton. Ceci est connu sous le nom de problème d'injection de haricots étendus.

Une autre façon de résoudre le problème est l'injection de méthode avec l' annotation @Lookup .

Voici un bel article sur cette question de l' injection de haricots prototypes dans une instance singleton avec plusieurs solutions.

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

Saikat
la source
-11

Votre contrôleur besoin également @Scope("prototype")défini

comme ça:

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

}
flyerfang
la source
1
pourquoi pensez-vous que le contrôleur doit également être un prototype?
Jigar Parekh