问题
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)
- add AspectJ dependencies
implementation 'org.springframework.boot:spring-boot-starter-aop'
- 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);
}
}
- 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);
}
}
- 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).
回答1:
Did you read the Javadoc for EnableCaching?
Note that if the mode() is set to
AdviceMode.ASPECTJ
, then the value of theproxyTargetClass()
attribute will be ignored. Note also that in this case thespring-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
- have
spring-aspects
on the class path and - 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.
回答2:
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 was worth it for my project. See my other answer for a simpler, 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:
- AOP and Spring: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop
- AspectJ tutorial (Baeldung): https://www.baeldung.com/aspectj
- Complie-Time Weaving vs Load-Time Weaving: https://stackoverflow.com/a/23042793/631272
- CTW vs LTW in spring brief: https://stackoverflow.com/a/41370471/631272
- CTW vs LTW Tutorial: https://satenblog.wordpress.com/2017/09/22/spring-aspectj-compile-time-weaving/
- Getting CTW to work in Eclipse M2e: https://stackoverflow.com/a/19616845/631272
- CTW and Java 11 issues (may have been part of my struggles with it): https://www.geekyhacker.com/2020/03/28/how-to-configure-aspectj-in-spring-boot/
回答3:
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.
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 gross, but for me was preferable to all the AspectJ hoops I was jumping through when I really didn't need/want AspectJ. I just want seamless caching and DRY service code, which this workaround achieves.
来源:https://stackoverflow.com/questions/62932146/spring-how-to-cache-in-self-invocation-with-aspectj