@Scope («прототип») область видимости bean-компонента не создает новый bean-компонент


133

Я хочу использовать аннотированный компонент-прототип в своем контроллере. Но вместо этого spring создает одноэлементный компонент. Вот код для этого:

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

Шаблон скорости:

 LoginAction counter: ${loginAction.str}

В Spring config.xmlвключено сканирование компонентов:

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

Каждый раз я получаю увеличивающийся счет. Не могу понять, где я ошибаюсь!

Обновить

Как было предложено @gkamal , я сделал HomeController webApplicationContext-aware и решил проблему.

обновленный код:

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

12
Хотел бы я дважды проголосовать за то, чтобы вы реализовали правильный ответ в вашем коде, чтобы другие увидели реальную разницу
Али Нем

Ответы:


156

Прототип области действия означает, что каждый раз, когда вы запрашиваете у spring (getBean или внедрение зависимости) экземпляр, он создает новый экземпляр и дает ссылку на него.

В вашем примере новый экземпляр LoginAction создается и вводится в ваш HomeController. Если у вас есть другой контроллер, в который вы вводите LoginAction, вы получите другой экземпляр.

Если вам нужен другой экземпляр для каждого вызова - тогда вам нужно каждый раз вызывать getBean - внедрение в одноэлементный компонент этого не приведет.


7
Я сделал контроллер ApplicationContextAware, сделал getBean и каждый раз получаю свежий bean-компонент. Спасибо, парни!!!
tintin 01

Как это работает, если бы у bean-компонента requestвместо prototypeобласти была бы область видимости. Вам все еще нужно получить bean-компонент с помощью context.getBean(..)?
доктор Джерри

2
Или используйте прокси с ограниченной областью видимости, например @Scope (value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
svenmeier

25

Начиная с Spring 2.5, есть очень простой (и элегантный) способ добиться этого.

Вы можете просто изменения параметров proxyModeи valueв @Scopeаннотации.

С помощью этой уловки вы можете избежать написания лишнего кода или внедрения ApplicationContext каждый раз, когда вам нужен прототип внутри одноэлементного bean-компонента.

Пример:

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

Конфигурация выше LoginAction(внутри HomeController) всегда является прототипом, даже если контроллер синглтон .


2
То есть у нас его сейчас весной 5?
Raghuveer 08

16

Просто потому, что bean-компонент, введенный в контроллер, имеет область видимости прототипа, это не означает, что контроллер таковым!


11

@controller - это одноэлементный объект, и если внедрить компонент-прототип в одноэлементный класс, он сделает его также одноэлементным, если только вы не укажете свойство lookup-method, которое фактически создает новый экземпляр компонента-прототипа для каждого вашего вызова.


5

Как упоминал nicholas.hauschild, внедрение контекста Spring - не лучшая идея. В вашем случае достаточно @Scope ("request"), чтобы это исправить. Но допустим, вам нужно несколько экземпляров LoginActionметода контроллера. В этом случае я бы рекомендовал создать bean-компонент поставщика ( решение Spring 4 ):

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

Затем введите его в контроллер:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  

1
Я бы предложил впрыскивать пружины, ObjectFactoryкоторые служат той же цели, что и поставщик, но могут быть определены как нормальные, @Beanпод которыми я подразумеваю отсутствие необходимости возвращать лямбду.
xenoterracide

3

Использование ApplicationContextAwareпривязывает вас к Spring (что может быть, а может и не быть проблемой). Я бы порекомендовал передать a LoginActionFactory, который вы можете запрашивать LoginActionкаждый раз, когда он вам нужен.


1
Однако уже есть аннотации, специфичные для Spring; не похоже, что это сильно беспокоит.
Дэйв Ньютон

1
@ Дэйв, Хорошее замечание. Есть альтернативы для некоторых вещей DI (JSR 311), но в этом примере может быть сложнее избавиться от всего, что зависит от Spring. Я полагаю, я действительно просто защищаю factory-methodздесь ...
nicholas.hauschild

1
+1 для ввода синглтона LoginActionFactoryв контроллер, но factory-methodпохоже, что это не решит проблему, поскольку он просто создает другой компонент Spring через фабрику. Внедрение этого bean-компонента в одноэлементный контроллер не решит проблему.
Брэд Купит,

Хорошее замечание, Брэд, я уберу это предложение из своего ответа.
nicholas.hauschild

3

использовать область запроса @Scope("request")для получения bean-компонента для каждого запроса или @Scope("session")для получения bean-компонента для каждого сеанса 'user'


1

Бин-прототип, внедренный внутри бина-одиночки, будет вести себя как бин-одиночка до тех пор, пока не будет явно вызван для создания нового экземпляра с помощью bean-компонента.

context.getBean("Your Bean")


0

Вы можете создать статический класс внутри вашего контроллера следующим образом:

    @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

По умолчанию bean-компоненты Spring являются одиночными. Проблема возникает, когда мы пытаемся связать компоненты разного объема. Например, прототип bean-компонента в синглтон. Это известно как проблема инъекции фасоли в ограниченную область видимости.

Еще один способ решения проблемы - внедрение метода с аннотацией @Lookup .

Вот хорошая статья по этой проблеме внедрения компонентов-прототипов в одноэлементный экземпляр с несколькими решениями.

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


-11

Вашему контроллеру также нужен @Scope("prototype")определенный

как это:

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

}

1
почему вы думаете, что контроллер тоже должен быть прототипом?
Джигар Парех
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.