Рубрики
Без рубрики

Весенняя пользовательская аннотация для лучшего DAO

Крутая реализация пользовательской весенней аннотации вместе с предпроцейзером фасоли, чтобы добраться до простой, общей реализации DAO.

Автор оригинала: baeldung.

1. Обзор

В этом учебнике мы будем реализовывать пользовательские весенней аннотации с фасоли пост-процессор .

Так как же это поможет? Проще говоря – мы можем повторно использовать тот же боб вместо того, чтобы создавать несколько, похожие бобы одного и того же типа.

Мы сделаем это для реализации DAO в простом проекте – заменив все из них единым, гибким ДженерикДао .

2. Мавен

Нам нужно весенне-основной , весенне-аоп , и весна-контекст-поддержка JARs, чтобы получить эту работу. Мы можем просто объявить весна-контекст-поддержка в нашем пом.xml .


    org.springframework
    spring-context-support
    5.2.2.RELEASE

Если вы хотите пойти на новую версию зависимости Весна – проверить maven репозиторий .

Большинство Весна/JPA/Hibernate реализации использовать стандартный DAO – как правило, по одному для каждой организации.

Мы заменим это решение на более ДженерикДао ; мы собираемся написать пользовательский процессор аннотации вместо этого и использовать этот ДженерикДао реализация:

3.1. Общий ДАО

public class GenericDao {

    private Class entityClass;

    public GenericDao(Class entityClass) {
        this.entityClass = entityClass;
    }

    public List findAll() {
        // ...
    }

    public Optional persist(E toPersist) {
        // ...
    }
}

В реальном мире сценарий, вы, конечно, нужно провода в НастойчивостьКонтекст и фактически обеспечить реализацию этих методов. На данный момент – мы сделаем это как можно проще.

Теперь давайте создадим аннотацию для пользовательских инъекций.

3.2. Доступ к данным

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Documented
public @interface DataAccess {
    Class entity();
}

Мы будем использовать аннотацию выше, чтобы ввести ДженерикДао следующим образом:

@DataAccess(entity=Person.class)
private GenericDao personDao;

Может быть, некоторые из вас спрашивают: “Как Весна признает нашу DataAccess аннотация?”. Это не так – не по умолчанию.

Но мы могли бы сказать Весна признать аннотацию через пользовательский BeanPostПроцессор – Давайте это будет реализовано дальше.

3.3. DataAccessAnnotationProcessor

@Component
public class DataAccessAnnotationProcessor implements BeanPostProcessor {

    private ConfigurableListableBeanFactory configurableBeanFactory;

    @Autowired
    public DataAccessAnnotationProcessor(ConfigurableListableBeanFactory beanFactory) {
        this.configurableBeanFactory = beanFactory;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) 
      throws BeansException {
        this.scanDataAccessAnnotation(bean, beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) 
      throws BeansException {
        return bean;
    }

    protected void scanDataAccessAnnotation(Object bean, String beanName) {
        this.configureFieldInjection(bean);
    }

    private void configureFieldInjection(Object bean) {
        Class managedBeanClass = bean.getClass();
        FieldCallback fieldCallback = 
          new DataAccessFieldCallback(configurableBeanFactory, bean);
        ReflectionUtils.doWithFields(managedBeanClass, fieldCallback);
    }
}

Далее – вот реализация DataAccessFieldCallback мы только что использовали:

3.4. DataAccessFieldCallback

public 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 configurableBeanFactory;
    private Object bean;

    public DataAccessFieldCallback(ConfigurableListableBeanFactory bf, Object bean) {
        configurableBeanFactory = bf;
        this.bean = bean;
    }

    @Override
    public void doWith(Field field) 
    throws IllegalArgumentException, IllegalAccessException {
        if (!field.isAnnotationPresent(DataAccess.class)) {
            return;
        }
        ReflectionUtils.makeAccessible(field);
        Type fieldGenericType = field.getGenericType();
        // In this example, get actual "GenericDAO' type.
        Class generic = field.getType(); 
        Class classValue = field.getDeclaredAnnotation(DataAccess.class).entity();

        if (genericTypeIsValid(classValue, fieldGenericType)) {
            String beanName = classValue.getSimpleName() + generic.getSimpleName();
            Object beanInstance = getBeanInstance(beanName, generic, classValue);
            field.set(bean, beanInstance);
        } else {
            throw new IllegalArgumentException(ERROR_ENTITY_VALUE_NOT_SAME);
        }
    }

    public boolean genericTypeIsValid(Class clazz, Type field) {
        if (field instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) field;
            Type type = parameterizedType.getActualTypeArguments()[0];

            return type.equals(clazz);
        } else {
            logger.warn(WARN_NON_GENERIC_VALUE);
            return true;
        }
    }

    public Object getBeanInstance(
      String beanName, Class genericClass, Class paramClass) {
        Object daoInstance = null;
        if (!configurableBeanFactory.containsBean(beanName)) {
            logger.info("Creating new DataAccess bean named '{}'.", beanName);

            Object toRegister = null;
            try {
                Constructor ctr = genericClass.getConstructor(Class.class);
                toRegister = ctr.newInstance(paramClass);
            } catch (Exception e) {
                logger.error(ERROR_CREATE_INSTANCE, genericClass.getTypeName(), e);
                throw new RuntimeException(e);
            }
            
            daoInstance = configurableBeanFactory.initializeBean(toRegister, beanName);
            configurableBeanFactory.autowireBeanProperties(daoInstance, AUTOWIRE_MODE, true);
            configurableBeanFactory.registerSingleton(beanName, daoInstance);
            logger.info("Bean named '{}' created successfully.", beanName);
        } else {
            daoInstance = configurableBeanFactory.getBean(beanName);
            logger.info(
              "Bean named '{}' already exists used as current bean reference.", beanName);
        }
        return daoInstance;
    }
}

Сейчас – это вполне реализация, но самая важная ее часть – это doWith () метод:

genericDaoInstance = configurableBeanFactory.initializeBean(beanToRegister, beanName);
configurableBeanFactory.autowireBeanProperties(genericDaoInstance, autowireMode, true);
configurableBeanFactory.registerSingleton(beanName, genericDaoInstance);

Это позволит весне инициализировать фасоль на основе объекта, введенного во время выполнения через @DataAccess аннотация.

beanName убедитесь, что мы получим уникальный экземпляр фасоли, потому что – в данном случае – мы хотим создать единый объект ДженерикДао в зависимости от объекта, вводимого через @DataAccess аннотация.

Наконец, давайте использовать этот новый процессор фасоли в конфигурации Весна дальше.

3.5. ПользовательскаяимнотацияКонфигурация

@Configuration
@ComponentScan("com.baeldung.springcustomannotation")
public class CustomAnnotationConfiguration {}

Одна вещь, которая важна здесь, заключается в том, что @ComponentScan аннотация должна указать на пакет, где наш пользовательский процессор фасоли пост находится и убедитесь, что он сканируется и автоматически проводной к весне во время выполнения.

4. Тестирование нового ДАО

Начнем с теста с поддержкой Spring и двух простых классов сущностей примеров здесь – Лицо и Учетная .

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={CustomAnnotationConfiguration.class})
public class DataAccessAnnotationTest {

    @DataAccess(entity=Person.class) 
    private GenericDao personGenericDao;
    @DataAccess(entity=Account.class) 
    private GenericDao accountGenericDao;
    @DataAccess(entity=Person.class) 
    private GenericDao anotherPersonGenericDao;

    ...
}

Мы вводим несколько экземпляров ДженерикДао с помощью DataAccess аннотация. Чтобы проверить правильное введение новых бобов, нам необходимо охватить:

  1. Если инъекция будет успешной
  2. Если экземпляры фасоли с одной и той же сущностью одинаковы
  3. Если методы в ДженерикДао на самом деле работают, как ожидалось

Точка 1 на самом деле покрыта Весна себя – как рамки бросает исключение довольно рано, если фасоль не может быть проводной дюйма

Чтобы проверить точку 2, мы должны посмотреть на 2 экземпляра ДженерикДао которые оба используют Лицо класс:

@Test
public void whenGenericDaoInjected_thenItIsSingleton() {
    assertThat(personGenericDao, not(sameInstance(accountGenericDao)));
    assertThat(personGenericDao, not(equalTo(accountGenericDao)));
    assertThat(personGenericDao, sameInstance(anotherPersonGenericDao));
}

Мы не хотим personGenericDao быть равным счетГенерикДао .

Но мы хотим personGenericDao и другойПерсон ДженерикДао быть точно таким же экземпляром.

Чтобы проверить точку 3, мы просто проверить некоторые простые сохранения связанных логики здесь:

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

5. Заключение

В этой статье мы сделали очень здорово реализации пользовательских аннотации весной – вместе с BeanPostПроцессор . Общая цель состояла в том, чтобы избавиться от многочисленных реализаций DAO, которые мы обычно используем в нашем уровне настойчивости, и использовать хорошую, простую общую реализацию, не теряя ничего в процессе.

Реализация всех этих примеров и фрагментов кода можно найти в мой проект GitHub – это проект на основе Eclipse, поэтому он должен быть легким для импорта и запуска как есть.