Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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"));
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading