Рубрики
Без рубрики

Настраиваемая область весной

Краткое практическое руководство по реализации настраиваемой области весной.

Автор оригинала: baeldung.

1. Обзор

Из коробки Spring предоставляет две стандартные области bean ( “singleton” и “prototype” ), которые могут использоваться в любом приложении Spring, а также три дополнительные области bean ( “request” , “session” и “globalSession” ) для использования только в веб-приложениях.

Стандартные области бобов не могут быть переопределены, и обычно считается плохой практикой переопределять области с поддержкой Интернета. Однако у вас может быть приложение, требующее других или дополнительных возможностей, отличных от тех, которые содержатся в предоставленных областях.

Например, если вы разрабатываете мультитенантную систему, вам может потребоваться предоставить отдельный экземпляр определенного компонента или набор компонентов для каждого клиента. Spring предоставляет механизм для создания пользовательских областей для сценариев, подобных этому.

В этом кратком руководстве мы продемонстрируем , как создать, зарегистрировать и использовать пользовательскую область в приложении Spring .

2. Создание пользовательского класса области видимости

Чтобы создать пользовательскую область, мы должны реализовать Область интерфейс . При этом мы также должны убедиться, что реализация потокобезопасна , поскольку области могут использоваться несколькими фабриками компонентов одновременно.

2.1. Управление объектами области видимости и обратными вызовами

Одна из первых вещей, которую следует учитывать при реализации пользовательского класса Scope , – это то, как вы будете хранить и управлять объектами с областью действия и обратными вызовами. Это можно сделать, например, с помощью карты или выделенного класса.

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

Давайте начнем определять наш пользовательский класс области видимости:

public class TenantScope implements Scope {
    private Map scopedObjects
      = Collections.synchronizedMap(new HashMap());
    private Map destructionCallbacks
      = Collections.synchronizedMap(new HashMap());
...
}

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. Удаление объекта из области видимости

Далее, давайте реализуем метод remove , который удаляет именованный объект из области видимости, а также удаляет его зарегистрированный обратный вызов уничтожения, возвращая удаленный объект:

@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 . Если ваша область поддерживает несколько контекстных объектов, вы свяжете каждый из них со значением ключа и вернете объект, соответствующий предоставленному параметру key . В противном случае соглашение должно возвращать null :

@Override
public Object resolveContextualObject(String key) {
    return null;
}

3. Регистрация пользовательской области

Чтобы контейнер Spring знал о вашей новой области, вам необходимо зарегистрировать его с помощью метода register Scope в экземпляре ConfigurableBeanFactory . Давайте взглянем на определение этого метода:

void registerScope(String scopeName, Scope scope);

Первый параметр, Имя области , используется для идентификации/указания области по ее уникальному имени. Второй параметр, 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. Использование пользовательской области

Теперь, когда мы зарегистрировали нашу пользовательскую область, мы можем применить ее к любому из наших компонентов точно так же, как и к любому другому компоненту, использующему область, отличную от singleton (область по умолчанию) — используя аннотацию @Scope и указав нашу пользовательскую область по имени.

Давайте создадим простой класс TenantBean — через мгновение мы объявим бобы этого типа с областью действия арендатора:

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 для этого класса.

Теперь давайте определим некоторые компоненты с областью действия клиента в классе конфигурации:

@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 , зарегистрировав наши Configuration классы и получив наши компоненты с областью действия клиента:

@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 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.baeldung.customscope.TenantBean
Hello from bar of type org.baeldung.customscope.TenantBean

6. Заключение

В этом кратком руководстве мы показали, как определить, зарегистрировать и использовать пользовательскую область весной.

Вы можете прочитать больше о пользовательских областях в справочнике Spring Framework . Вы также можете взглянуть на реализации Spring различных классов Scope в репозитории Spring Framework на GitHub .

Как обычно, вы можете найти примеры кода, используемые в этой статье, в проекте GitHub .