Кратко ръководство за обхват на пролетния боб

1. Общ преглед

В този бърз урок ще научите за различните видове обхват на зърна в рамките на Spring.

Обхватът на бобът определя жизнения цикъл и видимостта на този боб в контекста, в който се използва.

Последната версия на Spring framework определя 6 вида обхвати:

  • сингълтон
  • прототип
  • заявка
  • сесия
  • приложение
  • уебсайт

Последните четири обхвата, посочени заявка, сесия, приложение и уебсайт, са достъпни само в уеб-познато приложение.

2. Обхват на сингълтън

Дефинирането на бин с единичен обхват означава, че контейнерът създава единичен екземпляр на този боб и всички заявки за това име на боб ще върнат същия обект, който е кеширан. Всички модификации на обекта ще бъдат отразени във всички препратки към боб. Този обхват е стойността по подразбиране, ако не е посочен друг обхват.

Нека създадем субект Person, който да илюстрира концепцията за обхват:

public class Person { private String name; // standard constructor, getters and setters }

След това дефинираме боб с единичен обхват, като използваме анотацията @Scope :

@Bean @Scope("singleton") public Person personSingleton() { return new Person(); }

Също така можем да използваме константа вместо стойността String по следния начин:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

Сега продължаваме да пишем тест, който показва, че два обекта, отнасящи се до един и същ бин, ще имат едни и същи стойности, дори ако само един от тях променя състоянието си, тъй като и двамата се позовават на един и същ екземпляр на боб:

private static final String NAME = "John Smith"; @Test public void givenSingletonScope_whenSetName_thenEqualNames() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("scopes.xml"); Person personSingletonA = (Person) applicationContext.getBean("personSingleton"); Person personSingletonB = (Person) applicationContext.getBean("personSingleton"); personSingletonA.setName(NAME); Assert.assertEquals(NAME, personSingletonB.getName()); ((AbstractApplicationContext) applicationContext).close(); }

Файлът scopes.xml в този пример трябва да съдържа xml дефинициите на използваните зърна:

3. Обхват на прототипа

Боб с обхват на прототип ще връща различен екземпляр всеки път, когато бъде поискан от контейнера. Определя се чрез задаване на прототипа на стойността на анотацията @Scope в дефиницията на боб:

@Bean @Scope("prototype") public Person personPrototype() { return new Person(); }

Можем също така да използваме константа, както направихме за обхвата на единичното:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Сега ще напишем подобен тест, както преди, който показва, че два обекта, изискващи едно и също име на боб с прототип на обхвата, ще имат различни състояния, тъй като те вече не се отнасят до един и същ екземпляр на боб:

private static final String NAME = "John Smith"; private static final String NAME_OTHER = "Anna Jones"; @Test public void givenPrototypeScope_whenSetNames_thenDifferentNames() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("scopes.xml"); Person personPrototypeA = (Person) applicationContext.getBean("personPrototype"); Person personPrototypeB = (Person) applicationContext.getBean("personPrototype"); personPrototypeA.setName(NAME); personPrototypeB.setName(NAME_OTHER); Assert.assertEquals(NAME, personPrototypeA.getName()); Assert.assertEquals(NAME_OTHER, personPrototypeB.getName()); ((AbstractApplicationContext) applicationContext).close(); } 

Файлът scopes.xml е подобен на този, представен в предишния раздел, докато добавя xml дефиницията за боб с обхват на прототип :

4. Обхват на уеб осведомеността

Както споменахме, има четири допълнителни обхвата, които са достъпни само в контекст на уеб приложение. Те се използват по-рядко на практика.

Обхватът на заявката създава екземпляр на боб за една HTTP заявка, докато обхватът на действие създава за HTTP сесия.

Обхватът на приложението създава екземпляра на боб за жизнения цикъл на ServletContext, а обхватът на websocket го създава за определена сесия WebSocket .

Нека създадем клас, който да използваме за създаване на инстанции на зърната:

public class HelloMessageGenerator { private String message; // standard getter and setter }

4.1. Обхват на заявката

Можем да дефинираме боб с обхват на заявката, като използваме анотацията @Scope :

@Bean @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator requestScopedBean() { return new HelloMessageGenerator(); }

В ProxyMode атрибут е необходимо, тъй като в момента на инстанциирането от контекста на уеб приложение, не е активна заявка. Spring ще създаде прокси, който да се инжектира като зависимост, и ще създаде екземпляр на целевия бин, когато е необходим в заявка.

Също така можем да използваме анотация, съставена от @RequestScope, която действа като пряк път за горната дефиниция:

@Bean @RequestScope public HelloMessageGenerator requestScopedBean() { return new HelloMessageGenerator(); }

След това можем да дефинираме контролер, който има инжектирана препратка към requestScopedBean . Трябва да осъществим достъп до една и съща заявка два пъти, за да тестваме специфичните за мрежата обхвати.

Ако се покаже съобщение всеки път, когато искането се управлява, можем да видим, че стойността се връща на нула , въпреки че по-късно е променено в метода. Това е така, защото за всяка заявка се връща различен екземпляр на боб.

@Controller public class ScopesController { @Resource(name = "requestScopedBean") HelloMessageGenerator requestScopedBean; @RequestMapping("/scopes/request") public String getRequestScopeMessage(final Model model) { model.addAttribute("previousMessage", requestScopedBean.getMessage()); requestScopedBean.setMessage("Good morning!"); model.addAttribute("currentMessage", requestScopedBean.getMessage()); return "scopesExample"; } }

4.2. Обхват на сесията

Можем да определим бина с обхват на сесията по подобен начин:

@Bean @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator sessionScopedBean() { return new HelloMessageGenerator(); }

Има и специална съставена анотация, която можем да използваме за опростяване на дефиницията на боб:

@Bean @SessionScope public HelloMessageGenerator sessionScopedBean() { return new HelloMessageGenerator(); }

Next, we define a controller with a reference to the sessionScopedBean. Again, we need to run two requests in order to show that the value of the message field is the same for the session.

In this case, when the request is made for the first time, the value message is null. But once, it is changed, then that value is retained for subsequent requests as the same instance of the bean is returned for the entire session.

@Controller public class ScopesController { @Resource(name = "sessionScopedBean") HelloMessageGenerator sessionScopedBean; @RequestMapping("/scopes/session") public String getSessionScopeMessage(final Model model) { model.addAttribute("previousMessage", sessionScopedBean.getMessage()); sessionScopedBean.setMessage("Good afternoon!"); model.addAttribute("currentMessage", sessionScopedBean.getMessage()); return "scopesExample"; } }

4.3. Application Scope

The application scope creates the bean instance for the lifecycle of a ServletContext.

This is similar to the singleton scope but there is a very important difference with regards to the scope of the bean.

When beans are application scoped the same instance of the bean is shared across multiple servlet-based applications running in the same ServletContext, while singleton-scoped beans are scoped to a single application context only.

Let's create the bean with application scope:

@Bean @Scope( value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator applicationScopedBean() { return new HelloMessageGenerator(); }

Analogously as for the request and session scopes, we can use a shorter version:

@Bean @ApplicationScope public HelloMessageGenerator applicationScopedBean() { return new HelloMessageGenerator(); }

Now, let's create a controller that references this bean:

@Controller public class ScopesController { @Resource(name = "applicationScopedBean") HelloMessageGenerator applicationScopedBean; @RequestMapping("/scopes/application") public String getApplicationScopeMessage(final Model model) { model.addAttribute("previousMessage", applicationScopedBean.getMessage()); applicationScopedBean.setMessage("Good afternoon!"); model.addAttribute("currentMessage", applicationScopedBean.getMessage()); return "scopesExample"; } }

In this case, value message once set in the applicationScopedBean will be retained for all subsequent requests, sessions and even for a different servlet application that will access this bean, provided it is running in the same ServletContext.

4.4. WebSocket Scope

Finally, let's create the bean with websocket scope:

@Bean @Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator websocketScopedBean() { return new HelloMessageGenerator(); }

WebSocket-scoped beans when first accessed are stored in the WebSocket session attributes. The same instance of the bean is then returned whenever that bean is accessed during the entire WebSocket session.

We can also say that it exhibits singleton behavior but limited to a WebSocket session only.

5. Conclusion

Демонстрирахме различни обхвати на боб, предоставени от Spring, и какви са предназначенията им.

Прилагането на този урок може да бъде намерено в проекта GitHub - това е проект, базиран на Eclipse, така че трябва да е лесно да се импортира и да се изпълнява както е.