Initial commit for "Spring Custom Annotation with Dynamic Qualifier"

article.
This commit is contained in:
xsalefter 2015-10-21 00:50:44 +07:00
parent 6aec841b73
commit 32ea1e0a1e
10 changed files with 394 additions and 0 deletions

View File

@ -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 {
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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>
* &#064;DataAccess(entity=Person.class)
* private GenericDAO&lt;Account&gt; 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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"));
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}