Spring - Как кешировать при самовызове с помощью aspectJ?

Спасибо, что нажали на мой вопрос. Я хочу вызвать метод кэширования при самовызове, поэтому мне нужно использовать AspectJ. (конфигурация кеша в порядке)

  1. добавить зависимости AspectJ
implementation 'org.springframework.boot:spring-boot-starter-aop'
  1. добавить @EnableCaching(mode = AdviceMode.ASPECTJ) в мой application.java
@EnableJpaAuditing
@EnableCaching(mode = AdviceMode.ASPECTJ) // <-- here 
@SpringBootApplication
public class DoctorAnswerApplication {

    public static void main(String[] args) {
        SpringApplication.run(DoctorAnswerApplication.class, args);
    }

}
  1. мой сервис.java
@Service
public class PredictionService {

    @Cacheable(value = "findCompletedRecordCache")
    public HealthCheckupRecord getRecordComplete(Long memberId, String checkupDate) {
        Optional<HealthCheckupRecord> recordCheckupData;
        recordCheckupData = healthCheckupRecordRepository.findByMemberIdAndCheckupDateAndStep(memberId, checkupDate, RecordStep.COMPLETE);

        return recordCheckupData.orElseThrow(NoSuchElementException::new);
    }
}
  1. мой тестовый код
    @Test
    public void getRecordCompleteCacheCreate() {
        // given
        Long memberId = (long)this.testUserId;
        List<HealthCheckupRecord> recordDesc = healthCheckupRecordRepository.findByMemberIdAndStepOrderByCheckupDateDesc(testUserId, RecordStep.COMPLETE);
        String checkupDate = recordDesc.get(0).getCheckupDate();
        String checkupDate2 = recordDesc.get(1).getCheckupDate();

        // when
        HealthCheckupRecord first = predictionService.getRecordComplete(memberId,checkupDate);
        HealthCheckupRecord second = predictionService.getRecordComplete(memberId,checkupDate);
        HealthCheckupRecord third = predictionService.getRecordComplete(memberId,checkupDate2);

        // then
        assertThat(first).isEqualTo(second);
        assertThat(first).isNotEqualTo(third);
    }

Что я не так...? Я не делал никакого класса, связанного с аспектом J. Я думаю, что @EnableCaching(mode = AdviceMode.ASPECTJ) заставить @Cacheable работать с AspectJ вместо Spring AOP (прокси).


person hynuah_iia    schedule 16.07.2020    source источник


Ответы (3)


Вы читали Javadoc для EnableCaching?

Обратите внимание, что если для режима() установлено значение AdviceMode.ASPECTJ, то значение атрибута proxyTargetClass() будет игнорироваться. Также обратите внимание, что в этом случае файл JAR модуля spring-aspects должен присутствовать в пути к классам, а преобразование во время компиляции или во время загрузки применяет аспект к затронутым классам. В таком сценарии нет прокси-сервера; Местные звонки также будут перехвачены.

Поэтому, пожалуйста, проверьте, если вы

  1. иметь spring-aspects на пути к классу и
  2. запустил ваше приложение с параметром java -javaagent:/path/to/aspectjweaver.jar.

Существует альтернатива № 2, но проще всего использовать агент Java. Я не пользователь Spring, поэтому я не эксперт в настройке Spring, но даже такой новичок в Spring, как я, добился успеха с агентом Java, поэтому, пожалуйста, попробуйте сначала.

person kriegaex    schedule 16.07.2020
comment
Спасибо за комментарий. Я видел документы, в которых говорится, что реализация «org.springframework.boot:spring-boot-starter-aop» включает org.springframework:spring-aop и org.aspectj:aspectjweaver. Я пытаюсь начать с ~aspectjweaver.jar. Я думаю, что я не установил какую-то конфигурацию. - person hynuah_iia; 21.07.2020
comment
Пожалуйста, сообщите после попытки, чтобы мы могли закрыть этот вопрос вместе. - person kriegaex; 21.07.2020
comment
Хорошо! Я вернусь. - person hynuah_iia; 21.07.2020
comment
Ты Терминатор? ???? - person kriegaex; 21.07.2020

Благодаря @kriegaex, он исправил меня, указав на зависимость spring-aspects и требование javaagent для загрузки во время загрузки. Для удобства других ниже приведены фрагменты конфигурации для Spring Boot и Maven.

(Примечание. В конце концов, я не почувствовал, что все это того стоит для моего проекта. См. Мой другой ответ для более простого, хотя и несколько уродливого, обходного пути.)

ПОМ:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

Конфигурация приложения:

@Configuration
@EnableCaching(mode = AdviceMode.ASPECTJ)
public class ApplicationConfig { ... }

Целевой метод:

@Cacheable(cacheNames = { "cache-name" })
public Thingy fetchThingy(String identifier) { ... }

Механизм плетения:

Вариант 1: Плетение во время загрузки (весна по умолчанию)

Используйте аргумент JVM javaagent или добавьте в свои библиотеки контейнеров сервлетов

-javaagent:<path-to-jar>/aspectjweaver-<version>.jar

Вариант 2: плетение во время компиляции

(Предположительно, это работает, но я обнаружил отсутствие связных примеров для использования с Spring Caching — см. дальнейшее чтение ниже)

Используйте аспект-maven-plugin: https://www.mojohaus.org/aspectj-maven-plugin/index.html

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.11</version>
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
    </dependencies>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <outxml>true</outxml>
        <showWeaveInfo>false</showWeaveInfo>
        <Xlint>warning</Xlint>
        <verbose>false</verbose>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
        <complianceLevel>${java.version}</complianceLevel>
        <source>${java.version}</source>
        <target>${java.version}</target>
    </configuration>
</plugin>

Для справки/поиска вот ошибка, из-за которой все это началось:

Caused by: java.io.FileNotFoundException: class path resource [org/springframework/cache/aspectj/AspectJCachingConfiguration.class] cannot be opened because it does not exist

Больше чтения:

person Marquee    schedule 24.11.2020
comment
Еще раз спасибо. Прочитав ваш ответ, я создал новый проект для тестирования. Если вам интересно и у вас есть время помочь, не могли бы вы проверить мой мерзавец? ссылка — ветка 'aspectj': я пытаюсь применить AspjectJ. но @Cacheable не работает - ветвь «кэш»: до применения AspectJ. @Cacheable работает хорошо - person hynuah_iia; 06.12.2020

TL;DR: если AspectJ доставляет вам головную боль, и вам это действительно не нужно, кроме как обойти самовызов Spring Caching, на самом деле может быть чище/легче/проще добавить простой компонент делегата кеша, который могут использовать ваши сервисные слои. повторное использование.

Код:

@Component
public class CacheDelegateImpl implements CacheDelegate {
    @Override @Cacheable(cacheNames = { "things" })
    public Object getTheThing(String id) { ... }
}

@Service
public class ThingServiceImpl implements ThingService {
    @Resource
    private CacheDelegate cacheDelegate;

    public Object getTheThing(String id) {
        return cacheDelegate.getTheThing(id);
    }

    public Collection<Object> getAllTheThings() {
        return CollectionUtils.emptyIfNull(findAllTheIds())
                .parallelStream()
                .map(this::getTheThing)
                .collect(Collectors.toSet());
    }
}

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

Для моего проекта добавление AspectJ только для того, чтобы разрешить кэшируемые ссылки на тот же компонент, было катастрофой, которая вызвала 10 новых головных болей вместо одной простой (но раздражающей).

Краткое неисчерпывающее изложение:

  • Введение нескольких новых зависимостей
  • Внедрение сложных плагинов POM либо для переплетения во время компиляции (что никогда не работало правильно для меня), либо для маршалирования jar-переплетения байтов во время выполнения в правильное место.
  • Добавление аргумента Javaagent JVM во время выполнения во все наши развертывания
  • Гораздо более низкая производительность как во время сборки, так и во время запуска (для плетения)
  • AspectJ подбирает и терпит неудачу в аннотациях Spring Transactional в других областях кодовой базы (где в остальном я был счастлив использовать прокси-серверы Spring)
  • Java versioning issues
    • Somehow a dependency on the ancient Sun Microsystems tools.jar (which is not present in later OpenJDK versions)
  • Как правило, плохой и разрозненный документ о том, как реализовать вариант использования кэширования отдельно от Spring Transactions и/или без полноценного AOP с AspectJ (который мне не нужен/не нужен)

В конце концов, я просто вернулся к Spring Caching через прокси и представил делегата кеша, на который вместо этого могли ссылаться оба моих метода службы. Этот обходной путь является грубым, но для меня он был предпочтительнее всех обручей AspectJ, через которые я прыгал, когда мне действительно не нужен/не нужен AspectJ. Мне просто нужно бесшовное кэширование и СУХОЙ сервисный код, которого достигает этот обходной путь.

person Marquee    schedule 30.11.2020
comment
Спасибо, что поделились своим опытом и хорошими статьями. Я применил Spring Cache (EhCache) через прокси с аннотацией @EnableAspectJAutoProxy(exposeProxy = true) вместо AspectJ. Это позволяет получить доступ непосредственно к классу АОП класса (самовызов). Я новичок в Spring, поэтому я просто хотел изучить и применить лучшие практики в каждой ситуации. Я не представлял себе, что AspectJ вызывает 10 головных болей????, поэтому я просто снова проведу исследование и прочитаю статьи, на которые вы ссылаетесь, для подготовки в следующий раз. - person hynuah_iia; 06.12.2020