Merge pull request #269 from xsalefter/master
Initial commit for "Spring Custom Annotation with Dynamic Qualifier"
This commit is contained in:
		
						commit
						1ef53e2f68
					
				| @ -0,0 +1,9 @@ | ||||
| package org.baeldung.customannotation; | ||||
| 
 | ||||
| import org.springframework.context.annotation.ComponentScan; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 
 | ||||
| @Configuration | ||||
| @ComponentScan("org.baeldung.customannotation") | ||||
| public class CustomAnnotationConfiguration { | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| package org.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(); | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| package org.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); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,104 @@ | ||||
| package org.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: | ||||
|      * <pre> | ||||
|      * @DataAccess(entity=Person.class)  | ||||
|      * private GenericDAO<Account> personGenericDAO; | ||||
|      * </pre> | ||||
|      * 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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package org.baeldung.customannotation; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| public class GenericDAO<E> { | ||||
| 
 | ||||
|     private Class<E> entityClass; | ||||
|     private String message; | ||||
| 
 | ||||
|     public GenericDAO(Class<E> entityClass) { | ||||
|         this.entityClass = entityClass; | ||||
|     } | ||||
| 
 | ||||
|     public List<E> findAll() { | ||||
|         message = "Would create findAll query from " + entityClass.getSimpleName(); | ||||
|         return Collections.emptyList(); | ||||
|     } | ||||
| 
 | ||||
|     public Optional<E> 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; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| package org.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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| package org.baeldung.customannotation; | ||||
| 
 | ||||
| import org.springframework.stereotype.Repository; | ||||
| 
 | ||||
| @Repository | ||||
| public class BeanWithGenericDAO { | ||||
| 
 | ||||
|     @DataAccess(entity=Person.class) | ||||
|     private GenericDAO<Person> personGenericDAO; | ||||
| 
 | ||||
|     public BeanWithGenericDAO() {} | ||||
| 
 | ||||
|     public GenericDAO<Person> getPersonGenericDAO() { | ||||
|         return personGenericDAO; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,57 @@ | ||||
| package org.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 DataAccessAnnotationTest { | ||||
| 
 | ||||
|     @DataAccess(entity = Person.class) | ||||
|     private GenericDAO<Person> personGenericDAO; | ||||
|     @DataAccess(entity = Account.class) | ||||
|     private GenericDAO<Account> accountGenericDAO; | ||||
|     @DataAccess(entity = Person.class) | ||||
|     private GenericDAO<Person> 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")); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,53 @@ | ||||
| package org.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 DataAccessFieldCallbackTest { | ||||
| 
 | ||||
|     @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)); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package org.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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user