Как получить bean-компонент в статическом контексте / методе (слушатель ревизии Hibernate Envers) - PullRequest
0 голосов
/ 06 июня 2018

Примечание: это не дублированный вопрос.Прочитайте весь пост;)
Чтобы предотвратить проблему XY, вот полная история:
Мне нужно написать слушатель ревизии Envers, чтобы вычислить и сохранить различие последнего изменения сущности в приложении Spring:

public class MyRevisionListener implements EntityTrackingRevisionListener {

    @Override
    public void newRevision(Object revision) {
        ...
    }

    @Override
    public void entityChanged(Class entityClass,
                              String entityName,
                              Serializable entityId,
                              RevisionType revisionType,
                              Object revisionEntity) {
        EntityManager em = EntityManagerBeanLookup.getInstance().get();
        int id = ((DefaultRevisionEntity) revisionEntity).getId();
        List<?> revisions = AuditReaderFactory.get(em)
                .createQuery()
                .forRevisionsOfEntity(entityClass, false, true)
                .add(AuditEntity.id().eq(entityId))
                .add(AuditEntity.revisionNumber().le(id + 1))
                .addOrder(AuditEntity.revisionNumber().desc())
                .setMaxResults(2)
                .getResultList();

        checkArgument(revisions.size() > 0, "Need at least one revision: %s", revisions);

        // continue with calculating and persisting the diff;
    }

}

и для получения EntityManager бина у меня есть утилита / бин EntityManagerBeanLookup:

@Component
public class EntityManagerBeanLookup implements Supplier<EntityManager> {

    @Getter
    private static EntityManagerBeanLookup instance;

    @Getter
    private EntityManager entityManager;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Autowired
    public void setInstance(EntityManagerBeanLookup instance) {
        EntityManagerBeanLookup.instance = instance;
    }

    @Override
    public EntityManager get() {
        return ofNullable(getInstance())
                .map(EntityManagerBeanLookup::getEntityManager)
                .orElseThrow(() -> new IllegalStateException("Not initialized yet"));
    }

}

Все работает просто отлично.Проблема в том, что у нас есть интеграционные тесты с использованием среды Spring Test.В зависимости от того, как Spring создает тестовые контексты и кэширует bean-компоненты, возможно, что класс поиска bean-компонентов возвращает менеджер сущностей из другого контекста, а не из текущего контекста.Это происходит в нашем случае, когда проходной тест не пройден, если вы запускаете его среди других тестов (подробнее здесь ).

Теперь вопросы:
1. Есть ли способ определить MyRevisionListener как bean-компонент Spring, чтобы я мог аккуратно внедрить контекст постоянства?
2. Если вышеприведенное является НЕТ, то как мне правильно получить контекст постоянства в статическом контексте / методе?

Спасибо.

Ответы [ 3 ]

0 голосов
/ 06 июня 2018

Полагаю, вы можете ввести EntityManager в тест и использовать его для установки в EntityManagerBeanLookup.

Но я думаю, что более чистое решение - пометить контекст как грязный, чтобы Spring создал новый.См. Как заставить новую версию контекста Spring ДО выполнения теста

0 голосов
/ 07 июня 2018

В итоге я использовал тестовый прослушиватель (так как мы уже используем spring-test с нашими тестовыми прослушивателями):

public class StaticBeanLookupResetTestListener extends AbstractTestExecutionListener {

    @Override
    public void prepareTestInstance(TestContext testContext) {
        testContext.getApplicationContext()
                .getBeansOfType(StaticBeanLookup.class) // The marker interface
                .forEach(this::resetSelfReference);
    }

    @SneakyThrows
    private void resetSelfReference(String name, StaticBeanLookup bean) {
        Method getter = bean.getClass().getDeclaredMethod("setInstance", type);
        getter.setAccessible(true);
        getter.invoke(bean, bean);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

}
0 голосов
/ 06 июня 2018

Если у вас есть два весенних контекста, вы, вероятно, захотите получить тот, в котором был вызван слушатель.Учитывая, что слушатель является одноэлементным без сохранения состояния, единственный способ установить связь между вызовом слушателя и контекстом пружины, в котором это было сделано, - это вызвать контекст потока.

Это где-то в стеке, вам нужно сохранить менеджер сущностей в локальном потоке, а затем получить его оттуда в слушателе.

Хорошее место для этого - начало весенней транзакции или создание менеджера сущностей.

...