diff --git a/spring-core-3/src/main/java/com/baeldung/customannotation/CustomAnnotationConfiguration.java b/spring-core-3/src/main/java/com/baeldung/customannotation/CustomAnnotationConfiguration.java
new file mode 100644
index 0000000000..2e42a3f744
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customannotation/CustomAnnotationConfiguration.java
@@ -0,0 +1,9 @@
+package com.baeldung.customannotation;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan("com.baeldung.customannotation")
+public class CustomAnnotationConfiguration {
+}
diff --git a/spring-core-3/src/main/java/com/baeldung/customannotation/DataAccess.java b/spring-core-3/src/main/java/com/baeldung/customannotation/DataAccess.java
new file mode 100644
index 0000000000..4160bad16d
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customannotation/DataAccess.java
@@ -0,0 +1,14 @@
+package com.baeldung.customannotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+@Documented
+public @interface DataAccess {
+ Class> entity();
+}
diff --git a/spring-core-3/src/main/java/com/baeldung/customannotation/DataAccessAnnotationProcessor.java b/spring-core-3/src/main/java/com/baeldung/customannotation/DataAccessAnnotationProcessor.java
new file mode 100644
index 0000000000..27008176a8
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customannotation/DataAccessAnnotationProcessor.java
@@ -0,0 +1,37 @@
+package com.baeldung.customannotation;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.ReflectionUtils.FieldCallback;
+
+@Component
+public class DataAccessAnnotationProcessor implements BeanPostProcessor {
+
+ private ConfigurableListableBeanFactory configurableListableBeanFactory;
+
+ @Autowired
+ public DataAccessAnnotationProcessor(ConfigurableListableBeanFactory bf) {
+ configurableListableBeanFactory = bf;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+ scanDataAccessAnnotation(bean, beanName);
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ return bean;
+ }
+
+ protected void scanDataAccessAnnotation(Object bean, String beanName) {
+ Class> managedBeanClass = bean.getClass();
+ FieldCallback fcb = new DataAccessFieldCallback(configurableListableBeanFactory, bean);
+ ReflectionUtils.doWithFields(managedBeanClass, fcb);
+ }
+}
diff --git a/spring-core-3/src/main/java/com/baeldung/customannotation/DataAccessFieldCallback.java b/spring-core-3/src/main/java/com/baeldung/customannotation/DataAccessFieldCallback.java
new file mode 100644
index 0000000000..07b5298ea9
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customannotation/DataAccessFieldCallback.java
@@ -0,0 +1,96 @@
+package com.baeldung.customannotation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.ReflectionUtils.FieldCallback;
+
+public final class DataAccessFieldCallback implements FieldCallback {
+
+ private static Logger logger = LoggerFactory.getLogger(DataAccessFieldCallback.class);
+ private static int AUTOWIRE_MODE = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
+
+ private static String ERROR_ENTITY_VALUE_NOT_SAME = "@DataAccess(entity) " + "value should have same type with injected generic type.";
+ private static String WARN_NON_GENERIC_VALUE = "@DataAccess annotation assigned " + "to raw (non-generic) declaration. This will make your code less type-safe.";
+ private static String ERROR_CREATE_INSTANCE = "Cannot create instance of " + "type '{}' or instance creation is failed because: {}";
+
+ private ConfigurableListableBeanFactory configurableListableBeanFactory;
+ private Object bean;
+
+ public DataAccessFieldCallback(final ConfigurableListableBeanFactory bf, final Object bean) {
+ configurableListableBeanFactory = bf;
+ this.bean = bean;
+ }
+
+ @Override
+ public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException {
+ if (!field.isAnnotationPresent(DataAccess.class)) {
+ return;
+ }
+ ReflectionUtils.makeAccessible(field);
+ final Type fieldGenericType = field.getGenericType();
+ // In this example, get actual "GenericDAO' type.
+ final Class> generic = field.getType();
+ final Class> classValue = field.getDeclaredAnnotation(DataAccess.class).entity();
+
+ if (genericTypeIsValid(classValue, fieldGenericType)) {
+ final String beanName = classValue.getSimpleName() + generic.getSimpleName();
+ final Object beanInstance = getBeanInstance(beanName, generic, classValue);
+ field.set(bean, beanInstance);
+ } else {
+ throw new IllegalArgumentException(ERROR_ENTITY_VALUE_NOT_SAME);
+ }
+ }
+
+ /**
+ * For example, if user write:
+ *
+ * @DataAccess(entity=Person.class)
+ * private GenericDAO<Account> personGenericDAO;
+ *
+ * then this is should be failed.
+ */
+ public boolean genericTypeIsValid(final Class> clazz, final Type field) {
+ if (field instanceof ParameterizedType) {
+ final ParameterizedType parameterizedType = (ParameterizedType) field;
+ final Type type = parameterizedType.getActualTypeArguments()[0];
+
+ return type.equals(clazz);
+ } else {
+ logger.warn(WARN_NON_GENERIC_VALUE);
+ return true;
+ }
+ }
+
+ public final Object getBeanInstance(final String beanName, final Class> genericClass, final Class> paramClass) {
+ Object daoInstance = null;
+ if (!configurableListableBeanFactory.containsBean(beanName)) {
+ logger.info("Creating new DataAccess bean named '{}'.", beanName);
+
+ Object toRegister = null;
+ try {
+ final Constructor> ctr = genericClass.getConstructor(Class.class);
+ toRegister = ctr.newInstance(paramClass);
+ } catch (final Exception e) {
+ logger.error(ERROR_CREATE_INSTANCE, genericClass.getTypeName(), e);
+ throw new RuntimeException(e);
+ }
+
+ daoInstance = configurableListableBeanFactory.initializeBean(toRegister, beanName);
+ configurableListableBeanFactory.autowireBeanProperties(daoInstance, AUTOWIRE_MODE, true);
+ configurableListableBeanFactory.registerSingleton(beanName, daoInstance);
+ logger.info("Bean named '{}' created successfully.", beanName);
+ } else {
+ daoInstance = configurableListableBeanFactory.getBean(beanName);
+ logger.info("Bean named '{}' already exist used as current bean reference.", beanName);
+ }
+ return daoInstance;
+ }
+}
diff --git a/spring-core-3/src/main/java/com/baeldung/customannotation/GenericDAO.java b/spring-core-3/src/main/java/com/baeldung/customannotation/GenericDAO.java
new file mode 100644
index 0000000000..0edd33b049
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customannotation/GenericDAO.java
@@ -0,0 +1,30 @@
+package com.baeldung.customannotation;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+public class GenericDAO {
+
+ private Class entityClass;
+ private String message;
+
+ public GenericDAO(Class entityClass) {
+ this.entityClass = entityClass;
+ }
+
+ public List findAll() {
+ message = "Would create findAll query from " + entityClass.getSimpleName();
+ return Collections.emptyList();
+ }
+
+ public Optional persist(E toPersist) {
+ message = "Would create persist query from " + toPersist.getClass().getSimpleName();
+ return Optional.empty();
+ }
+
+ /** Only used for unit-testing. */
+ public final String getMessage() {
+ return message;
+ }
+}
diff --git a/spring-core-3/src/main/java/com/baeldung/customscope/TenantBean.java b/spring-core-3/src/main/java/com/baeldung/customscope/TenantBean.java
new file mode 100644
index 0000000000..e892ae9d9b
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customscope/TenantBean.java
@@ -0,0 +1,14 @@
+package com.baeldung.customscope;
+
+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()));
+ }
+}
diff --git a/spring-core-3/src/main/java/com/baeldung/customscope/TenantBeanFactoryPostProcessor.java b/spring-core-3/src/main/java/com/baeldung/customscope/TenantBeanFactoryPostProcessor.java
new file mode 100644
index 0000000000..84ed0b46d7
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customscope/TenantBeanFactoryPostProcessor.java
@@ -0,0 +1,13 @@
+package com.baeldung.customscope;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+
+public class TenantBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
+ factory.registerScope("tenant", new TenantScope());
+ }
+}
diff --git a/spring-core-3/src/main/java/com/baeldung/customscope/TenantBeansConfig.java b/spring-core-3/src/main/java/com/baeldung/customscope/TenantBeansConfig.java
new file mode 100644
index 0000000000..c219000fe6
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customscope/TenantBeansConfig.java
@@ -0,0 +1,21 @@
+package com.baeldung.customscope;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.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");
+ }
+}
diff --git a/spring-core-3/src/main/java/com/baeldung/customscope/TenantScope.java b/spring-core-3/src/main/java/com/baeldung/customscope/TenantScope.java
new file mode 100644
index 0000000000..f3077bc4c2
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customscope/TenantScope.java
@@ -0,0 +1,43 @@
+package com.baeldung.customscope;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.config.Scope;
+
+public class TenantScope implements Scope {
+
+ private Map scopedObjects = Collections.synchronizedMap(new HashMap());
+ private Map destructionCallbacks = Collections.synchronizedMap(new HashMap());
+
+ @Override
+ public Object get(String name, ObjectFactory> objectFactory) {
+ if (!scopedObjects.containsKey(name)) {
+ scopedObjects.put(name, objectFactory.getObject());
+ }
+ return scopedObjects.get(name);
+ }
+
+ @Override
+ public Object remove(String name) {
+ destructionCallbacks.remove(name);
+ return scopedObjects.remove(name);
+ }
+
+ @Override
+ public void registerDestructionCallback(String name, Runnable callback) {
+ destructionCallbacks.put(name, callback);
+ }
+
+ @Override
+ public Object resolveContextualObject(String key) {
+ return null;
+ }
+
+ @Override
+ public String getConversationId() {
+ return "tenant";
+ }
+}
diff --git a/spring-core-3/src/main/java/com/baeldung/customscope/TenantScopeConfig.java b/spring-core-3/src/main/java/com/baeldung/customscope/TenantScopeConfig.java
new file mode 100644
index 0000000000..1829e1e8c4
--- /dev/null
+++ b/spring-core-3/src/main/java/com/baeldung/customscope/TenantScopeConfig.java
@@ -0,0 +1,14 @@
+package com.baeldung.customscope;
+
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class TenantScopeConfig {
+
+ @Bean
+ public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
+ return new TenantBeanFactoryPostProcessor();
+ }
+}
diff --git a/spring-core-3/src/test/java/com/baeldung/customannotation/Account.java b/spring-core-3/src/test/java/com/baeldung/customannotation/Account.java
new file mode 100644
index 0000000000..cfdd8815e4
--- /dev/null
+++ b/spring-core-3/src/test/java/com/baeldung/customannotation/Account.java
@@ -0,0 +1,40 @@
+package com.baeldung.customannotation;
+
+import java.io.Serializable;
+
+public class Account implements Serializable {
+
+ private static final long serialVersionUID = 7857541629844398625L;
+
+ private Long id;
+ private String email;
+ private Person person;
+
+ public Account() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public Person getPerson() {
+ return person;
+ }
+
+ public void setPerson(Person person) {
+ this.person = person;
+ }
+
+}
diff --git a/spring-core-3/src/test/java/com/baeldung/customannotation/BeanWithGenericDAO.java b/spring-core-3/src/test/java/com/baeldung/customannotation/BeanWithGenericDAO.java
new file mode 100644
index 0000000000..a0707f263b
--- /dev/null
+++ b/spring-core-3/src/test/java/com/baeldung/customannotation/BeanWithGenericDAO.java
@@ -0,0 +1,18 @@
+package com.baeldung.customannotation;
+
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class BeanWithGenericDAO {
+
+ @DataAccess(entity = Person.class)
+ private GenericDAO personGenericDAO;
+
+ public BeanWithGenericDAO() {
+ }
+
+ public GenericDAO getPersonGenericDAO() {
+ return personGenericDAO;
+ }
+
+}
diff --git a/spring-core-3/src/test/java/com/baeldung/customannotation/DataAccessAnnotationIntegrationTest.java b/spring-core-3/src/test/java/com/baeldung/customannotation/DataAccessAnnotationIntegrationTest.java
new file mode 100644
index 0000000000..1baea4505b
--- /dev/null
+++ b/spring-core-3/src/test/java/com/baeldung/customannotation/DataAccessAnnotationIntegrationTest.java
@@ -0,0 +1,57 @@
+package com.baeldung.customannotation;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { CustomAnnotationConfiguration.class })
+public class DataAccessAnnotationIntegrationTest {
+
+ @DataAccess(entity = Person.class)
+ private GenericDAO personGenericDAO;
+ @DataAccess(entity = Account.class)
+ private GenericDAO accountGenericDAO;
+ @DataAccess(entity = Person.class)
+ private GenericDAO anotherPersonGenericDAO;
+
+ @Test
+ public void whenGenericDAOInitialized_thenNotNull() {
+ assertThat(personGenericDAO, is(notNullValue()));
+ assertThat(accountGenericDAO, is(notNullValue()));
+ }
+
+ @Test
+ public void whenGenericDAOInjected_thenItIsSingleton() {
+ assertThat(personGenericDAO, not(sameInstance(accountGenericDAO)));
+ assertThat(personGenericDAO, not(equalTo(accountGenericDAO)));
+
+ assertThat(personGenericDAO, sameInstance(anotherPersonGenericDAO));
+ }
+
+ @Test
+ public void whenFindAll_thenMessagesIsCorrect() {
+ personGenericDAO.findAll();
+ assertThat(personGenericDAO.getMessage(), is("Would create findAll query from Person"));
+
+ accountGenericDAO.findAll();
+ assertThat(accountGenericDAO.getMessage(), is("Would create findAll query from Account"));
+ }
+
+ @Test
+ public void whenPersist_thenMakeSureThatMessagesIsCorrect() {
+ personGenericDAO.persist(new Person());
+ assertThat(personGenericDAO.getMessage(), is("Would create persist query from Person"));
+
+ accountGenericDAO.persist(new Account());
+ assertThat(accountGenericDAO.getMessage(), is("Would create persist query from Account"));
+ }
+}
diff --git a/spring-core-3/src/test/java/com/baeldung/customannotation/DataAccessFieldCallbackIntegrationTest.java b/spring-core-3/src/test/java/com/baeldung/customannotation/DataAccessFieldCallbackIntegrationTest.java
new file mode 100644
index 0000000000..bc7a5f7246
--- /dev/null
+++ b/spring-core-3/src/test/java/com/baeldung/customannotation/DataAccessFieldCallbackIntegrationTest.java
@@ -0,0 +1,51 @@
+package com.baeldung.customannotation;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+import java.lang.reflect.Type;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { CustomAnnotationConfiguration.class })
+public class DataAccessFieldCallbackIntegrationTest {
+
+ @Autowired
+ private ConfigurableListableBeanFactory configurableListableBeanFactory;
+
+ @Autowired
+ private BeanWithGenericDAO beanWithGenericDAO;
+
+ @Rule
+ public ExpectedException ex = ExpectedException.none();
+
+ @Test
+ public void whenObjectCreated_thenObjectCreationIsSuccessful() {
+ final DataAccessFieldCallback dataAccessFieldCallback = new DataAccessFieldCallback(configurableListableBeanFactory, beanWithGenericDAO);
+ assertThat(dataAccessFieldCallback, is(notNullValue()));
+ }
+
+ @Test
+ public void whenMethodGenericTypeIsValidCalled_thenReturnCorrectValue() throws NoSuchFieldException, SecurityException {
+ final DataAccessFieldCallback callback = new DataAccessFieldCallback(configurableListableBeanFactory, beanWithGenericDAO);
+ final Type fieldType = BeanWithGenericDAO.class.getDeclaredField("personGenericDAO").getGenericType();
+ final boolean result = callback.genericTypeIsValid(Person.class, fieldType);
+ assertThat(result, is(true));
+ }
+
+ @Test
+ public void whenMethodGetBeanInstanceCalled_thenReturnCorrectInstance() {
+ final DataAccessFieldCallback callback = new DataAccessFieldCallback(configurableListableBeanFactory, beanWithGenericDAO);
+ final Object result = callback.getBeanInstance("personGenericDAO", GenericDAO.class, Person.class);
+ assertThat((result instanceof GenericDAO), is(true));
+ }
+}
diff --git a/spring-core-3/src/test/java/com/baeldung/customannotation/Person.java b/spring-core-3/src/test/java/com/baeldung/customannotation/Person.java
new file mode 100644
index 0000000000..4fa70e51af
--- /dev/null
+++ b/spring-core-3/src/test/java/com/baeldung/customannotation/Person.java
@@ -0,0 +1,31 @@
+package com.baeldung.customannotation;
+
+import java.io.Serializable;
+
+public class Person implements Serializable {
+
+ private static final long serialVersionUID = 9005331414216374586L;
+
+ private Long id;
+ private String name;
+
+ public Person() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/spring-core-3/src/test/java/com/baeldung/customscope/TenantScopeIntegrationTest.java b/spring-core-3/src/test/java/com/baeldung/customscope/TenantScopeIntegrationTest.java
new file mode 100644
index 0000000000..1cd7357a09
--- /dev/null
+++ b/spring-core-3/src/test/java/com/baeldung/customscope/TenantScopeIntegrationTest.java
@@ -0,0 +1,72 @@
+package com.baeldung.customscope;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.junit.Test;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+public class TenantScopeIntegrationTest {
+
+ @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();
+ }
+ }
+
+ @Test
+ public final void whenComponentScan_thenContextContainsFooAndBar() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ try {
+ ctx.scan("com.baeldung.customscope");
+ 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();
+ }
+ }
+}