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