1. Обзор
По умолчанию Spring предоставляет две стандартные области действия компонента ( «singleton»
и «prototype»
), которые можно использовать в любом приложении Spring, а также три дополнительных области действия компонента ( «request»
, «session»
и «globalSession»
) для использования . только в веб-приложениях.
Стандартные области действия bean-компонентов не могут быть переопределены, и обычно считается плохой практикой переопределять области видимости веб-компонентов. Однако у вас может быть приложение, требующее отличных или дополнительных возможностей от тех, которые находятся в предоставленных областях.
Например, если вы разрабатываете систему с несколькими арендаторами, вы можете захотеть предоставить отдельный экземпляр определенного компонента или набора компонентов для каждого арендатора. Spring предоставляет механизм для создания настраиваемых областей действия для таких сценариев, как этот.
В этом кратком руководстве мы покажем, как создавать, регистрировать и использовать пользовательскую область видимости в приложении Spring .
2. Создание пользовательского класса области видимости
Чтобы создать пользовательскую область видимости, мы должны реализовать интерфейс Scope
. При этом мы также должны гарантировать, что реализация является потокобезопасной, поскольку области могут использоваться несколькими фабриками компонентов одновременно.
2.1. Управление объектами с ограниченной областью действия и обратными вызовами
Одна из первых вещей, которую следует учитывать при реализации пользовательского класса Scope
, — это то, как вы будете хранить и управлять объектами с областью действия и обратными вызовами уничтожения. Это можно сделать, например, с помощью карты или специального класса.
В этой статье мы сделаем это потокобезопасным способом, используя синхронизированные карты.
Давайте начнем определять наш пользовательский класс области видимости:
public class TenantScope implements Scope {
private Map<String, Object> scopedObjects
= Collections.synchronizedMap(new HashMap<String, Object>());
private Map<String, Runnable> destructionCallbacks
= Collections.synchronizedMap(new HashMap<String, Runnable>());
...
}
2.2. Получение объекта из области видимости
Чтобы получить объект по имени из нашей области видимости, давайте реализуем метод getObject
. Как указано в JavaDoc, если именованный объект не существует в области видимости, этот метод должен создать и вернуть новый объект .
В нашей реализации мы проверяем, есть ли именованный объект на нашей карте. Если это так, мы возвращаем его, а если нет, мы используем ObjectFactory
для создания нового объекта, добавляем его на нашу карту и возвращаем:
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if(!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
Из пяти методов, определенных интерфейсом Scope
, для полной реализации описанного поведения требуется только метод get .
Остальные четыре метода являются необязательными и могут вызывать исключение UnsupportedOperationException
, если они не нужны или не могут поддерживать какую-либо функциональность.
2.3. Регистрация обратного вызова уничтожения
Мы также должны реализовать метод registerDestructionCallback
. Этот метод обеспечивает обратный вызов, который должен выполняться, когда именованный объект уничтожается или если сама область видимости уничтожается приложением:
@Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
2.4. Удаление объекта из области видимости
Далее давайте реализуем метод удаления
, который удаляет именованный объект из области видимости, а также удаляет его зарегистрированный обратный вызов уничтожения, возвращая удаленный объект:
@Override
public Object remove(String name) {
destructionCallbacks.remove(name);
return scopedObjects.remove(name);
}
Обратите внимание, что вызывающая сторона несет ответственность за фактическое выполнение обратного вызова и уничтожение удаленного объекта .
2.5. Получение идентификатора беседы
Теперь давайте реализуем метод getConversationId
. Если ваша область поддерживает концепцию идентификатора беседы, вы должны вернуть его здесь. В противном случае соглашение должно возвращать null
:
@Override
public String getConversationId() {
return "tenant";
}
2.6. Разрешение контекстных объектов
Наконец, давайте реализуем метод resolveContextualObject
. Если ваша область поддерживает несколько контекстных объектов, вы должны связать каждый со значением ключа и вернуть объект, соответствующий предоставленному ключевому
параметру. В противном случае соглашение должно возвращать null
:
@Override
public Object resolveContextualObject(String key) {
return null;
}
3. Регистрация пользовательской области
Чтобы контейнер Spring знал о вашей новой области видимости, вам необходимо зарегистрировать его с помощью метода registerScope
в экземпляре ConfigurableBeanFactory
. Давайте посмотрим на определение этого метода:
void registerScope(String scopeName, Scope scope);
Первый параметр, scopeName
, используется для идентификации/указания области по ее уникальному имени. Второй параметр, scope
, является фактическим экземпляром пользовательской реализации Scope
, которую вы хотите зарегистрировать и использовать.
Давайте создадим собственный BeanFactoryPostProcessor
и зарегистрируем нашу пользовательскую область с помощью ConfigurableListableBeanFactory
:
public class TenantBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope("tenant", new TenantScope());
}
}
Теперь давайте напишем класс конфигурации Spring, который загружает нашу реализацию BeanFactoryPostProcessor
:
@Configuration
public class TenantScopeConfig {
@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
return new TenantBeanFactoryPostProcessor();
}
}
4. Использование пользовательской области
Теперь, когда мы зарегистрировали нашу настраиваемую область, мы можем применить ее к любому из наших bean-компонентов так же, как и к любому другому bean-компоненту, который использует область, отличную от singleton
(область по умолчанию) — используя аннотацию @Scope
и указав нашу пользовательскую область . по имени.
Давайте создадим простой класс TenantBean
— через мгновение мы объявим bean-компоненты этого типа с областью действия арендатора:
public class TenantBean {
private final String name;
public TenantBean(String name) {
this.name = name;
}
public void sayHello() {
System.out.println(
String.format("Hello from %s of type %s",
this.name,
this.getClass().getName()));
}
}
Обратите внимание, что мы не использовали аннотации @Component
и @Scope
на уровне класса для этого класса.
Теперь давайте определим некоторые bean-компоненты с областью действия арендатора в классе конфигурации:
@Configuration
public class TenantBeansConfig {
@Scope(scopeName = "tenant")
@Bean
public TenantBean foo() {
return new TenantBean("foo");
}
@Scope(scopeName = "tenant")
@Bean
public TenantBean bar() {
return new TenantBean("bar");
}
}
5. Тестирование пользовательской области
Давайте напишем тест для проверки нашей пользовательской конфигурации области, загрузив ApplicationContext
, зарегистрировав наши классы конфигурации
и получив наши bean-компоненты в области клиента:
@Test
public final void whenRegisterScopeAndBeans_thenContextContainsFooAndBar() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
try{
ctx.register(TenantScopeConfig.class);
ctx.register(TenantBeansConfig.class);
ctx.refresh();
TenantBean foo = (TenantBean) ctx.getBean("foo", TenantBean.class);
foo.sayHello();
TenantBean bar = (TenantBean) ctx.getBean("bar", TenantBean.class);
bar.sayHello();
Map<String, TenantBean> foos = ctx.getBeansOfType(TenantBean.class);
assertThat(foo, not(equalTo(bar)));
assertThat(foos.size(), equalTo(2));
assertTrue(foos.containsValue(foo));
assertTrue(foos.containsValue(bar));
BeanDefinition fooDefinition = ctx.getBeanDefinition("foo");
BeanDefinition barDefinition = ctx.getBeanDefinition("bar");
assertThat(fooDefinition.getScope(), equalTo("tenant"));
assertThat(barDefinition.getScope(), equalTo("tenant"));
}
finally {
ctx.close();
}
}
И результат нашего теста:
Hello from foo of type org.foreach.customscope.TenantBean
Hello from bar of type org.foreach.customscope.TenantBean
6. Заключение
В этом кратком руководстве мы показали, как определить, зарегистрировать и использовать пользовательскую область в Spring.
Подробнее о настраиваемых областях можно прочитать в Справочнике по Spring Framework . Вы также можете взглянуть на реализации Spring различных классов Scope в
репозитории Spring Framework на GitHub .
Как обычно, вы можете найти примеры кода, использованные в этой статье, в проекте GitHub .