From 502bf3c45c3eed30562b7ef09e34b4f651ba4b80 Mon Sep 17 00:00:00 2001 From: sampadawagde Date: Wed, 15 Jul 2020 17:27:50 +0530 Subject: [PATCH] JAVA-628: Moved 2 articles to spring-core-3 --- .../CustomAnnotationConfiguration.java | 9 ++ .../baeldung/customannotation/DataAccess.java | 14 +++ .../DataAccessAnnotationProcessor.java | 37 +++++++ .../DataAccessFieldCallback.java | 96 +++++++++++++++++++ .../baeldung/customannotation/GenericDAO.java | 30 ++++++ .../com/baeldung/customscope/TenantBean.java | 14 +++ .../TenantBeanFactoryPostProcessor.java | 13 +++ .../customscope/TenantBeansConfig.java | 21 ++++ .../com/baeldung/customscope/TenantScope.java | 43 +++++++++ .../customscope/TenantScopeConfig.java | 14 +++ .../baeldung/customannotation/Account.java | 40 ++++++++ .../customannotation/BeanWithGenericDAO.java | 18 ++++ .../DataAccessAnnotationIntegrationTest.java | 57 +++++++++++ ...ataAccessFieldCallbackIntegrationTest.java | 51 ++++++++++ .../com/baeldung/customannotation/Person.java | 31 ++++++ .../TenantScopeIntegrationTest.java | 72 ++++++++++++++ 16 files changed, 560 insertions(+) create mode 100644 spring-core-3/src/main/java/com/baeldung/customannotation/CustomAnnotationConfiguration.java create mode 100644 spring-core-3/src/main/java/com/baeldung/customannotation/DataAccess.java create mode 100644 spring-core-3/src/main/java/com/baeldung/customannotation/DataAccessAnnotationProcessor.java create mode 100644 spring-core-3/src/main/java/com/baeldung/customannotation/DataAccessFieldCallback.java create mode 100644 spring-core-3/src/main/java/com/baeldung/customannotation/GenericDAO.java create mode 100644 spring-core-3/src/main/java/com/baeldung/customscope/TenantBean.java create mode 100644 spring-core-3/src/main/java/com/baeldung/customscope/TenantBeanFactoryPostProcessor.java create mode 100644 spring-core-3/src/main/java/com/baeldung/customscope/TenantBeansConfig.java create mode 100644 spring-core-3/src/main/java/com/baeldung/customscope/TenantScope.java create mode 100644 spring-core-3/src/main/java/com/baeldung/customscope/TenantScopeConfig.java create mode 100644 spring-core-3/src/test/java/com/baeldung/customannotation/Account.java create mode 100644 spring-core-3/src/test/java/com/baeldung/customannotation/BeanWithGenericDAO.java create mode 100644 spring-core-3/src/test/java/com/baeldung/customannotation/DataAccessAnnotationIntegrationTest.java create mode 100644 spring-core-3/src/test/java/com/baeldung/customannotation/DataAccessFieldCallbackIntegrationTest.java create mode 100644 spring-core-3/src/test/java/com/baeldung/customannotation/Person.java create mode 100644 spring-core-3/src/test/java/com/baeldung/customscope/TenantScopeIntegrationTest.java 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(); + } + } +}