4

Thank you to click my question. I want to call a caching method in self-invocation, so I need to use AspectJ. (cache's config is okay)

  1. add AspectJ dependencies
implementation 'org.springframework.boot:spring-boot-starter-aop'
  1. add @EnableCaching(mode = AdviceMode.ASPECTJ) to my application.java
@EnableJpaAuditing
@EnableCaching(mode = AdviceMode.ASPECTJ) // <-- here 
@SpringBootApplication
public class DoctorAnswerApplication {

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

}
  1. my service.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. my test code
    @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);
    }

What did I don't...? I didn't make any class related with aspectJ. I think @EnableCaching(mode = AdviceMode.ASPECTJ) make @Cacheable work by AspectJ instead Spring AOP(proxy).

hynuah_iia
  • 429
  • 7
  • 14

4 Answers4

4

With thanks to @kriegaex, he fixed me by pointing out the spring-aspects dependency and the load-time-weaving javaagent requirement. For the convenience of others, the configuration snippets for Spring Boot and Maven follow.

(Note: In the end, I didn't feel all this (and the side-effects) were worth it for my project. See my other answer for a simple, if somewhat ugly, workaround.)

POM:

    <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>

Application Config:

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

Target Method:

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

Weaving mechanism:

Option 1: Load Time Weaving (Spring default)

Use JVM javaagent argument or add to your servlet container libs

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

Option 2: Compile Time Weaving

(This supposedly works, but I found a lack of coherent examples for use with Spring Caching - see further reading below)

Use aspectj-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>

For reference/search purposes, here is the error that started all this:

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

More reading:

Marquee
  • 1,776
  • 20
  • 21
  • Thank you again. After reading your answer, I created new project to test. If you're interesting and have time to help, could you check my git? [link](https://github.com/HyunAh-iia/aspectj/tree/aspectj) - 'aspectj' branch : I'm trying to apply AspjectJ. but `@Cacheable` is not working - 'cache' branch : Before applied AspectJ. `@Cacheable` works good – hynuah_iia Dec 06 '20 at 15:45
3

Did you read the Javadoc for EnableCaching?

Note that if the mode() is set to AdviceMode.ASPECTJ, then the value of the proxyTargetClass() attribute will be ignored. Note also that in this case the spring-aspects module JAR must be present on the classpath, with compile-time weaving or load-time weaving applying the aspect to the affected classes. There is no proxy involved in such a scenario; local calls will be intercepted as well.

So please check if you

  1. have spring-aspects on the class path and
  2. started your application with the parameter java -javaagent:/path/to/aspectjweaver.jar.

There is an alternative to #2, but using the Java agent is the easiest. I am not a Spring user, so I am not an expert in Spring configuration, but even a Spring noob like me succeeded with the Java agent, so please give that a shot first.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
1

TL;DR: If AspectJ is giving you headaches and you don't really need it other than to work around Spring Caching self-invocation, it might actually be cleaner/lighter/easier to add a simple "cache delegate" bean that your service layers can re-use. No extra dependencies, no performance impacts, no unintended side-effects to changing the way spring proxies work by default.

Code:

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

Adding another answer, because to solve this same issue for myself I ended up changing direction. The more direct solutions are noted by @kriegaex and myself earlier, but this is a different option for people that have issues getting AspectJ to work when you don't fundamentally need it.

For my project, adding AspectJ only to allow cacheable same-bean references was a disaster that caused 10 new headaches instead of one simple (but annoying) one.

A brief non-exhaustive rundown is:

  • Introduction of multiple new dependencies
  • Introduction of complex POM plugins to either compile-time weave (which never worked quite right for me) OR marshal the run-time byte-weaving jar into the correct place
  • Adding a runtime javaagent JVM argument to all our deployments
  • Much poorer performance at either build-time or start-time (to do the weaving)
  • AspectJ picking up and failing on Spring Transactional annotations in other areas of the codebase (where I was otherwise happy to use Spring proxies)
  • Java versioning issues
    • Somehow a dependency on the ancient Sun Microsystems tools.jar (which is not present in later OpenJDK versions)
  • Generally poor and scattershot doc on how to implement the caching use case in isolation from Spring Transactions and/or without full-blown AOP with AspectJ (which I don't need/want)

In the end, I just reverted to Spring Caching by proxy and introduced a "cache delegate" that both my service methods could refer to instead. This workaround is not the prettiest, but for me was preferable to all the AspectJ hoops I was jumping through when I really didn't need AspectJ. I just want seamless caching and DRY service code, which this workaround achieves.

Marquee
  • 1,776
  • 20
  • 21
  • 1
    Thank you for sharing your experience and good articles. I applied Spring Cache(EhCache) by proxy with `@EnableAspectJAutoProxy(exposeProxy = true)` annotation instead of AspectJ. It makes possible to access directly to the AOP class of the class(self-invocation). I'm a Spring beginner so I just wanted to learn and apply best practices of each situations. I didn't imagined AspectJ occurs 10 headaches so I'll just research again and read articles you linked for preparing next time. – hynuah_iia Dec 06 '20 at 11:21
  • Sorry for the late comment, but please allow me to say that I think the problem in your case was not AspectJ or that AspectJ use in Spring is so difficult (actually with LTW it is pretty straightforward), but that probably you just ran into problems because you did it for the first time. For example, the tools.jar issue was probably due to the fact that you used an outdated version of AspectJ Maven Plugin which still depended on Java 8, referring to tools.jar, but you used a more recent JDK for compilation. With a more up-to-date Maven plugin, this would not have been an issue. – kriegaex Oct 21 '21 at 06:48
  • Actually, AspectJ Maven Plugin is not necessary at all for load-time weaving (LTW), only for compile-time weaving (CTW). Maybe you mixed both accidentally, or you tried both in different iterations of your problem-solving process. – kriegaex Oct 21 '21 at 06:50
  • @kriegaex Thanks for your comments. I did mention that there were two approaches, CTW or LTW and the configurations/plug-ins for both were different. I didn't do them both at the same time, but I addressed both in my answers. – Marquee Oct 22 '21 at 16:41
  • Also, I agree that it is hard doing it for the first time and you can get it to work with enough effort. I am saying that the trade-offs even when it did work were not worth it (to me). I did not need it to do anything functional in my code OTHER than make caching a little more seamless. The configuration was a pain and the performance ding was not trivial. The tools dependency came from a dep in the spring-boot BOM. Deviating much from it removes a large benefit to using spring-boot in the first place. I would have been using the latest plugins. They have likely moved on since then. – Marquee Oct 22 '21 at 16:49
1

After a lot of researching, I get caching method in self-invocation working using compile time weaving, spring boot 3.0.2 and jdk 17.

The only anotation I need in spring configuration was:

@EnableCaching(mode = AdviceMode.ASPECTJ)

Neither @EnableLoadTimeWeaving nor @EnableAspectJAutoProxy were needed.

And these are the relevant parts of the the pom.xml (dev.aspectj:aspectj-maven-plugin seams to be the only one that works with last java versions):

<properties>
    <java.version>17</java.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <maven.compiler.release>17</maven.compiler.release>
    <spring-boot.version>3.0.2</spring-boot.version>
    <spring.version>6.0.4</spring.version>
    <aspectj.version>1.9.19</aspectj.version>
</properties>
<build>
    <plugins>
        <plugin>
            <groupId>dev.aspectj</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.13.1</version>
            <configuration>
                <complianceLevel>${maven.compiler.target}</complianceLevel>
                <proc>none</proc>
                <showWeaveInfo>true</showWeaveInfo>
                <forceAjcCompile>true</forceAjcCompile>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-aspects</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
            </configuration>
            <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>
                    <?m2e execute onConfiguration,onIncremental?>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
        <version>${spring-boot.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
</dependencies>
cfillol
  • 540
  • 4
  • 8
  • Thanks for sharing this. I am eager to try it out at some point. Sounds like you've figured out most of the issues that gave me pain. – Marquee Jun 14 '23 at 20:42