O escopo do bean @Scope (“prototype”) não está criando um novo bean

133

Eu quero usar um protótipo de bean anotado no meu controlador. Mas a primavera está criando um feijão singleton. Aqui está o código para isso:

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

  private int counter;

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

Código do controlador:

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

Modelo de velocidade:

 LoginAction counter: ${loginAction.str}

O Spring config.xmltem a verificação de componentes ativada:

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

Estou recebendo uma contagem incrementada a cada vez. Não consigo descobrir onde estou errado!

Atualizar

Como sugerido por @gkamal , eu tomei HomeController webApplicationContextconhecimento e resolvi o problema.

código atualizado:

@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");
    }
}
tintim
fonte
12
Eu gostaria de poder dobrar upvote você para implementar a resposta correta em seu código para que outros possam ver a diferença real
Ali Nem

Respostas:

156

O protótipo de escopo significa que toda vez que você solicitar uma mola (injeção de dependência ou getBean), ela criará uma nova instância e fornecerá uma referência a ela.

No seu exemplo, uma nova instância do LoginAction é criada e injetada no seu HomeController. Se você tiver outro controlador no qual você injeta o LoginAction, receberá uma instância diferente.

Se você deseja uma instância diferente para cada chamada - é necessário chamar getBean a cada vez - a injeção em um bean singleton não conseguirá isso.

Gkamal
fonte
7
Eu criei o controlador ApplicationContextAware e o getBean e estou obtendo o bean fresco toda vez. Obrigado rapazes!!!
tintin
Como isso funciona se o bean teria requestescopo em vez de prototypeescopo. Você ainda precisaria recuperar o bean context.getBean(..)?
dr Jerry
2
Ou usar um proxy escopo, ou seja @Scope (value = "protótipo", ProxyMode = ScopedProxyMode.TARGET_CLASS)
svenmeier
25

Desde a Primavera 2.5, existe uma maneira muito fácil (e elegante) de conseguir isso.

Você pode apenas alterar os parâmetros proxyModee valuea @Scopeanotação.

Com esse truque, você pode evitar escrever código extra ou injetar o ApplicationContext toda vez que precisar de um protótipo dentro de um bean singleton.

Exemplo:

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

Com a configuração acima LoginAction(dentro HomeController), sempre é um protótipo, mesmo que o controlador seja um singleton .

db80
fonte
2
Então, não temos agora na primavera 5?
Raghuveer
16

Só porque o feijão injetado no controlador é do escopo do protótipo não significa que o controlador é!

Dave Newton
fonte
11

@controller é um objeto singleton, e se injetar um protótipo de bean em uma classe singleton fará com que o protótipo também seja singleton, a menos que você especifique usando a propriedade lookup-method, que na verdade cria uma nova instância de prototype bean para cada chamada que você fizer.

kartheek
fonte
5

Como mencionado por nicholas.hauschild, injetar o contexto da Primavera não é uma boa ideia. No seu caso, o @Scope ("request") é suficiente para corrigi-lo. Mas digamos que você precise de várias instâncias do LoginActionmétodo no controlador. Nesse caso, eu recomendaria criar o bean do fornecedor ( solução da Primavera 4 ):

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

Em seguida, injete-o no controlador:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  
Igor Rybak
fonte
1
Eu sugeriria injetar molas ObjectFactoryque servem ao mesmo propósito do fornecedor, mas podem ser definidas como normais, @Beanpelo que quero dizer que não há necessidade de devolver um lambda.
Xenoterracide
3

O uso ApplicationContextAwareestá vinculando você ao Spring (o que pode ou não ser um problema). Eu recomendaria passar um LoginActionFactory, que você pode solicitar uma nova instância de LoginActioncada vez que precisar.

nicholas.hauschild
fonte
1
Já existem anotações específicas do Spring; não parece ser uma grande preocupação.
Dave Newton
1
@ Dave, bom ponto. Existem alternativas para algumas coisas de DI (JSR 311), mas pode ser mais difícil se livrar de tudo o que o Spring depende neste exemplo. Suponho que estou realmente defendendo o factory-methodaqui ...
nicholas.hauschild
1
+1 por injetar um singleton LoginActionFactoryno Controller, mas factory-methodparece que isso não resolveria o problema, pois apenas cria outro bean de mola na fábrica. Injetar esse bean no controlador singleton não resolverá o problema.
precisa saber é o seguinte
Bom ponto Brad, vou remover essa sugestão da minha resposta.
Nicholas.hauschild
3

use o escopo da solicitação @Scope("request")para obter bean para cada solicitação ou @Scope("session")para obter bean para cada sessão 'user'

Bassem Reda Zohdy
fonte
1

Um protoype bean injetado dentro de um singelton se comportará como singelton, até que seja exigido, de maneira exilada, a criação de uma nova instância por get bean.

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

@Componente

@Scope (valor = "protótipo")

a classe pública TennisCoach implementa o Coach {

// algum código

}

Rakesh Singh Balhara
fonte
0

Você pode criar uma classe estática dentro do seu controlador assim:

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

Por padrão, os beans Spring são singletons. O problema surge quando tentamos conectar feijões de escopos diferentes. Por exemplo, um protótipo de bean em um singleton. Isso é conhecido como problema de injeção de feijão com escopo definido.

Outra maneira de resolver o problema é a injeção de método com a anotação @Lookup .

Aqui está um bom artigo sobre esta questão de injeção de protótipo de beans em uma instância singleton com várias soluções.

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

Saikat
fonte
-11

Seu controlador também precisa do @Scope("prototype")definido

como isso:

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

}
flyerfang
fonte
1
por que você acha que o controlador também precisa ser um protótipo?
Jigar Parekh