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 GenericDaopersonDao;
Может быть, некоторые из вас спрашивают: “Как Весна признает нашу 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 GenericDaopersonGenericDao; @DataAccess(entity=Account.class) private GenericDao accountGenericDao; @DataAccess(entity=Person.class) private GenericDao anotherPersonGenericDao; ... }
Мы вводим несколько экземпляров ДженерикДао с помощью DataAccess аннотация. Чтобы проверить правильное введение новых бобов, нам необходимо охватить:
- Если инъекция будет успешной
- Если экземпляры фасоли с одной и той же сущностью одинаковы
- Если методы в ДженерикДао на самом деле работают, как ожидалось
Точка 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, поэтому он должен быть легким для импорта и запуска как есть.