Spring - How to cache in self-invocation with aspectJ? - spring

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

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

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

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.

Related

When should I use mapstruct or converters with java 8 to avoid error-prone?

At work, we use MapStruct in many SpringBoot projects with Java 8 REST Full applications and when we need to map Entity to DTO or DTO to Response or in similar cases. But today my friend showed me a great advantage of using a simple Converter instead of MapStruct.
This is a simple example using MapStrurct:
#Mapper(componentModel="spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface AccountMapper {
#Mapping(source = "customerBank.customerId", target = "customerId")
AccountResponse toResponse(AccountBank accountBank);
}
It works perfectly but actually in the case of someone change the customerId attribute by another name and forgets to change this mapper we will have a Runtime error.
The pros for Converter is we will have a Compile time error and avoid Runtime error.
Please, let me know if someone managed to share how to avoid Runtime error, like my presented scenario, using MapStruct, due to Converter does not bring the same advantage.
My question is: Is it possible to use MapStruct with efficiency, I mean without Runtime error prone?
If I understand well, you would like to have a compile-time error for wrong property naming, using MapStruct custom mapping.
If so, you should add a necessary build plugin in your pom.xml (if you use maven).
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
And of course declare a property for MapStruct version:
<properties>
<mapstruct.version>1.4.1.Final</mapstruct.version>
</properties>
After compiling the project, with the added plugin, the annotation processor will generate full implementation:
public class AccountMapperImpl implements AccountMapper
In target\generated-sources\annotations folder.
You can check generated source of the implementation class, all is set and carefully checked.
In case of unexisting property names in #Mapping annotation, the compiler will throw an error.

Transaction synchronization in Spring Boot

I have a small Spring Boot application with spring-boot-starter-web, spring-boot-starter-data-jpa, and postgresql as dependencies.
I'm able to use the #Transactional annotation and use JPA to fetch and save entities to the database. However, if I were to add afterCommit/afterCompletion hooks via registering a synchronization, it gives an IllegalStateException saying that Transaction synchronization is not active.
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
//this doesn't get called
log.info("do something here");
}
});
Doing TransactionSynchronizationManager.initSynchronization(); gets rid of the error, but the hooks don't get called (eg: the afterCommit hook doesn't get called even though the transaction has committed.)
Any clues on how to debug this?
It turned out that I had forgotten to include the build plugin that is used to create the AoP-proxies for beans having #Transactional annotations.
In the absence of this plugin, no proxies would get generated, and the code would run non-transactionally; except for when it enters the JpaRepository methods where it would create a short-lived transaction for the duration of the call (such as save/findAll/delete).
This is the plugin that I missed including in my pom.xml (this got generated in the pom output by the spring initializr (https://start.spring.io/) but I didn't notice it at first and didn't copy it over into my pom)
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
I think you need #TransactionalEventListener annotation. It supports hooks BEFORE_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION, AFTER_COMMIT and AFTER_ROLLBACK.
More info in this post: Better application events in Spring Framework 4.2.

WildFly 10, JCache - method caching

i have simple application using Spring Boot. I wanted allow method caching with JSR107 - JCache. So with help of tutorial i put together this code :
#CacheResult(cacheName = "testpoc")
public Country getCountry(Integer id){
System.out.println("---> Loading country with code '" + id + "'");
return new Country(id, "X", "Title");
}
with this POM file
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
...
(dependency 'spring-boot-starter-web' is there for simple REST service which call getCountry method)
Everything works like documentations says - method is invoked only once.
Now i wanted to try it on WildFly 10 application server
I have modified pom file :
excluded tomcat
exluded spring-boot-starter-cache
added infinispan-jcache (because i want to use cache configured / managed by wildfly in standalone/domain.xml)
Check pom file here on pastebin.
Problem is, that i am receiving following error :
Cannot find cache named 'java:jboss/infinispan/app-cache'
(i have tried to use both JNDI assigned and name to infinispan cache configured in wildfly).
Following code created Cache object (so i can used it) :
CacheManager cacheManager = Caching.getCachingProvider().getCacheManager();
Cache<String, String> cache = cacheManager.createCache("testpoc", new MutableConfiguration<String, String>());
Question :
It is possible to use JCache method caching on WildFly 10 using Infinispan managed by WildFly ?
Or Infinispan should be used for method caching like JCache, hence JCache has "more functionality" than Infinispan.
Thank you very much
PS :It is not problem for me to put whole code on github and post link - it is few lines of code ...
There are a couple of problems with your approach so let me go through them in steps.
At first you need to use proper Infinispan setup. Infinispan bits shipped with WF should be considered as internal or private. In order to use Infinispan in your app properly - either add org.infinispan:infinispan-embedded to your deployment or install Infinispan Wildfly modules. You can find installation guide here (it's a bit outdated but still, the procedure is exactly the same - unpack modules into WF and use Dependencies MANIFEST.MF entry).
Once you have successfully installed Infinispan (or added it to your app), you need to consider whether you want to use Spring Cache or JCache. If you're only interested in using annotations - I would recommend the former since it's much easier to setup (all you need to do is to add #EnableCaching to one of your configurations). Finally with Spring Cache you will create an Infinispan CacheManager bean. An example can be found here.
Final note - if you still need to use JCache - use this manual to setup Caching Provider.

Injecting spring dependencies into Domain objects best practices?

I've scraped all over many resources, and have made this work and it's kinda complex, which turns me into asking for review and other ideas on how to properly inject spring dependencies into DomainObjects ..
My solution so far includes ..
Defining the dependencies needed for loadweaving
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
And then .. configure it in the spring context file :
<context:spring-configured />
<context:load-time-weaver/>
Using #Configurable for my domain classes :
#Configurable
public class MyDomainClass {
....
}
And of course, using these VM arguments :
-XX:-UseSplitVerifier -javaagent:C:/Users/albert/.m2/repository/org/springframework/spring-instrument/3.0.6.RELEASE/spring-instrument-3.0.6.RELEASE.jar
For this current solution, i have the feeling that this seems too much, like the lots of dependencies needed, and also the VM args, which i would dislike when deploying in production server where i have to use specific options, which i fear could be not supported in the future or perhaps have different behaviours between version.
Im thinking of doing domainObjects with prototype scope, but i fear the dependencies issues when fetching the domain objects from the database (not from applicationContext).
Please share your experiences, thank you !
1: When you start injection stuff dynamically into domain objects, they're really not domain objects any more in the sense that the domain should reflect your information model, independent of any business rules and functional logic.
2: Remember KISS (keep it simple...). At some point, someone else might have to take ownership and maintain your code so have some mercy on that person :)
I would call this an anti-pattern, which in my opinion should be avoided.
If you use compile time waving, then you will not need the VM argument.

using javax.cache.CacheManager with EhCache

I am trying to use javax.cache.CacheManager JSR107 API using EhCache as caching solutioarin provider. But I am unable to find any such resources.
As per the link at http://ehcache.org/documentation/integrations/jsr107 , it says that ehcache jsr107 is still in draft phase. Can any one please confirm if it's still the case?
Any sample code to use net sf cacheManager using JSR107 javax.cache.* classes?
Thanks,
Harish
As specified in this page:
Because JCACHE has not yet been released the JCACHE API that Ehcache
implements has been released as net.sf.jsr107cache.
This effort can be found in the Github repository. If you see JCacheManager implements javax.cache.CacheManager
Here's a better answer now the API is finalised. Stick this in your pom:
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>jcache</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.jsr107.ri</groupId>
<artifactId>cache-annotations-ri-guice</artifactId>
<version>1.0.0</version>
</dependency>
And you're off. Annotate with any of the annotations like this: (good luck finding the javadocs!)
#CacheResult(cacheName = "monthly")
public List<QueryResult> monthly(String prefix) {
//...
}
I agree though, the documentation sucks.
Here's more javax.cache info if you're interested.

Resources