Персонализиран обхват през пролетта

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

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

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

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

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

2. Създаване на персонализиран клас на обхват

За да създадем персонализиран обхват, трябва да внедрим интерфейса Scope . По този начин трябва също така да гарантираме, че внедряването е безопасно за нишки, тъй като обхватът може да се използва от множество фабрики за боб едновременно.

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. Разрешаване на контекстни обекти

И накрая, нека приложим метода resolContextualObject . Ако вашият обхват поддържа множество контекстуални обекти, бихте свързали всеки с ключова стойност и бихте върнали обекта, съответстващ на предоставения параметър на ключа . В противен случай конвенцията е да върне null :

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

3. Регистриране на персонализиран обхват

За да освежите контейнера Spring за новия ви обхват, трябва да го регистрирате чрез метода registerScope в екземпляр ConfigurableBeanFactory . Нека да разгледаме дефиницията на този метод:

void registerScope(String scopeName, Scope scope);

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

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

Сега, след като регистрирахме потребителския си обхват, можем да го приложим към всеки от нашите зърна, точно както бихме направили с всеки друг боб, който използва обхват, различен от единичен (обхватът по подразбиране) - като използваме анотацията @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 , регистрираме нашите класове за конфигуриране и извлечем нашите зърна с обхват от наематели:

@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 Reference. Можете също така да разгледате реализациите на Spring на различни класове Scope в хранилището Spring Framework на GitHub.

Както обикновено, можете да намерите примерните кодове, използвани в тази статия, в проекта GitHub.