Caching Aspects are not having any effects at runtime with Spring Boot and Embedded tomcat with LoadTimeWeaving enabled,but we are seeing the weaving is happening fine in the logs.
Below is the configuration, the LoadTimeWeaving is enabled along with the mode as AspectJ for Caching
#Configuration
#EnableConfigurationProperties
#EnableSpringConfigured
#EnableAspectJAutoProxy(proxyTargetClass = true)
#EnableLoadTimeWeaving
#EnableTransactionManagement
#EnableAsync
#EnableCaching(mode = AdviceMode.ASPECTJ)
public class AppConfig {
}
Spring Agent
-javaagent:../../../spring-instrument-4.3.3.RELEASE.jar
Logs
[RestartClassLoader#2aaae670] debug weaving 'uk.co.loyalty.iss.newpro.reporting.domain.refdata.gateway.RestReferenceDataGateway'
[RestartClassLoader#2aaae670] weaveinfo Join point 'method-execution(java.util.List uk.co.loyalty.iss.newpro.reporting.domain.refdata.gateway.RestReferenceDataGateway.getHierarchyLevelDefns())' in Type 'uk.co.loyalty.iss.newpro.reporting.domain.refdata.gateway.RestReferenceDataGateway' (RestReferenceDataGateway.java:118) advised by around advice from 'org.springframework.cache.aspectj.AnnotationCacheAspect' (AbstractCacheAspect.aj:64)
[RestartClassLoader#2aaae670] weaveinfo Join point 'method-execution(java.util.Map uk.co.loyalty.iss.newpro.reporting.domain.refdata.gateway.RestReferenceDataGateway.getHierarchyLevelDefinitionMap())' in Type 'uk.co.loyalty.iss.newpro.reporting.domain.refdata.gateway.RestReferenceDataGateway' (RestReferenceDataGateway.java:129) advised by around advice from '**org.springframework.cache.aspectj.AnnotationCacheAspect**' (AbstractCacheAspect.aj:64)
[RestartClassLoader#2aaae670] **debug generating class** 'uk.co.loyalty.iss.newpro.reporting.domain.refdata.gateway.RestReferenceDataGateway$AjcClosure1'
[RestartClassLoader#2aaae670] debug generating class 'uk.co.loyalty.iss.newpro.reporting.domain.refdata.gateway.RestReferenceDataGateway$AjcClosure3'
there are whole bunch of discussions around this. The weaving is happening on the RestartClassLoader, not sure is it something to do with the class loader. Have also tried adding the below,
#Bean
public LoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
Please provide your suggestions.
Edit
We need Aspectj mode as we are using caching on private methods. I removed the EnableAspectJAutoProxy but still that does not help.
On further analysis on the load time weaving, I noticed the below behaviour.
I profile the application and investigated on the class loaders to see the weaving is done correct for the caching annotation to work(classes are trasformed). I noticed for some classes in the classloader ,we have a class with suffix($AjcClosure and those are transformed classes after weaving is done). So, if cachinng annotations is part of those weaved classes, then it works fine.
Then I closely looked in to the classes why some classes are weaved correctly and some are not. Then I noticed that if classes are loaded already to the class-loader before weaving happens, then that is where the weaving is not happening.
#Bean
public IAService aService(){
return new AServiceImpl();
}
In the above case, the class is AServiceImpl is loaded to the class loader only when this instance is needed (after load time weaving and caching works perfectly).
But if the same class is being initialised using #Component then it is not getting weaved.
#Service
public class AServiceImpl{
}
I think, in the above case, the class AServiceImpl is loaded to the class loader while the spring container initialises and load time weaving is trying to weave after that).
I verified this behavior again in class loader as well. So, is it an issue if aspectj tries to weave a class that was already loaded. the same problem was raised in the Spring Jira as well in the below link.
https://jira.spring.io/browse/SPR-13786
As mentioned in the above link, if I pass the aspectj weaver as java agent like the below, then all the classes are being weaved correctly. So, is it required to have two agents or do we have any other option for this.
-javaagent:../../../aspectjweaver-1.6.1.jar
-javaagent:../../../spring-instrument-4.3.3.RELEASE.jar
Yes, the weaver needs to be active before the weaving targets are loaded by the classloader.
Now, I am an AspectJ geek but not a container geek, I mainly work with Java SE and no containers. Please try to also add
-javaagent:path/to/aspectjweaver.jar
to your command line and see if it helps.
Related
I am trying to enable AspectJ load-time weaving (not Spring AOP) in a Spring Boot application. My goal is to weave advice into annotated fields and java.lang.reflect.Field.set(Object, Object) at load-time.
Per the Spring docs, I tried:
#Configuration
#EnableLoadTimeWeaving
public class Config {}
Running the Spring Boot application with this configuration resulted in the application context failing to load with this message:
Caused by: java.lang.IllegalStateException:
ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader]
does NOT provide an 'addTransformer(ClassFileTransformer)' method.
Specify a custom LoadTimeWeaver or start your Java virtual machine
with Spring's agent: -javaagent:spring-instrument-{version}.jar
The latter suggestion in that message is not a good option as I am trying to avoid necessitating launch script modifications. The aspect I need to weave actually resides in a library, so all implementing Spring Boot projects will have to make whatever changes required to get LTW to work.
I also tried this configuration:
#Configuration
#EnableLoadTimeWeaving
public class Config implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
}
Running the Spring Boot application with this configuration resulted in the application context failing to load with this message:
Caused by: java.lang.IllegalStateException:
ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader]
does NOT provide an 'addTransformer(ClassFileTransformer)' method.
It seems I need to make the JVM use a class loader that has an addTransformer(ClassFileTransformer) method. I don't know how to do that, particularly for this situation. Any suggestions?
I am not an active Spring user, but I know that Spring supports annotation- or XML-configured agent hot-attachment and has some container-specific classes for that according to its documentation. It does not seem to work reliably in all situations, though, especially when running a Spring Boot application from an IDE or so.
Anyway, the AspectJ weaver 1.8.7 and more recent can be hot-attached. I explained how to do that in a Spring setup here. If you want a simpler solution with less boilerplate but one more dependency to a tiny helper library called byte-buddy-agent, you can use this solution as a shortcut. I have not tried it, but I know the helper library and am using it myself in other contexts when hot-attaching bytecode instrumentation agents, avoiding the fuss to cater to different JVM versions and configuration situations. But in order for that to work on JVM 9+, you might need to manually activate auto-attachment for the JVM, which would be another modification for your start-up script, and you would be back to square 1.
I am trying to synchronize declarative transactions (i.e. methods annotated with #Transactional) using AspectJ like so:
...
import org.aspectj.lang.annotation.Aspect;
...
#Component
#Aspect
public class TransactionMonitor extends TransactionSynchronizationAdapter {
#Before("execution(#org.springframework.transaction.annotation.Transactional * *.*(..))")
private void registerTransactionSynchronizationOnAnnotation(JoinPoint joinPoint) {
TransactionSynchronizationManager.registerSynchronization(this);
}
}
This currently fails with java.lang.IllegalStateException: Transaction synchronization is not active which indicates that the synchronization is not run inside the transaction execution, but before. I want to ensure that this is the other way round, of course.
I found this answer, however #Order(Ordered.LOWEST_PRECEDENCE) had no effect, and
#DeclarePrecedence(
"org.springframework.transaction.aspectj.AnnotationTransactionAspect, xxx.xxx.TransactionMonitor, *"
)
led to this during startup:
java.lang.IllegalArgumentException: DeclarePrecedence not presently supported in Spring AOP
I have the feeling this is AOP and AspectJ not being happy with each other, but I am not sure. I am thankful for any ideas.
EDIT: I have to use #EnableTransactionManagement(proxyTargetClass = true), can this be related to the issue?
For #DeclarePrecedence you need to switch to native AspectJ. Spring AOP is just "AOP lite" and technologically has little in common with AspectJ other than its syntax which is basically an AspectJ subset. The Spring manual describes how to use native AspectJ in Spring via LTW (load-time weaving). Precedence declaration for Spring components rather works using #Order, BTW.
I am not a Spring user at all, but as for declarative transaction management, it already knows proxy-based Spring AOP versus native AspectJ mode, see EnableTransactionManagement.mode and the enum constants in AdviceMode. Besides, EnableTransactionManagement also has an order property. Reading Javadoc and the Spring manual helps, I guess.
I have entity class like this:
package tr.com.example.model.porting
...//omitting imports
#Configurable
#Data
#Entity
public class PortOut{
public void handleRequest(Long portingId) {
processRequest(portingId);
...
}
}
And here is my aspect code in different class annotated with #Aspect and #Component:
#Around("execution(* tr.com.example..PortOut.handleRequest(..))")
public void handlePortingProcessAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.proceed();
log.debug(log.debug("[POM]:{} is executed with parameters:{}", joinPoint.toShortString(), joinPoint.getArgs()))
}
Normally, my aspect code works fine for other Spring managed classes such as using #Service, #Component annotation. I am using https://github.com/subes/invesdwin-instrument as a weaving agent and also using Lombok.
I tried this link(along with other so posts). But since I am using Lombok, I cannot directy compile with aspectj compiler for weaving #Configurable classes, it doesn't work with Lombok as I found out.
https://groups.google.com/forum/#!topic/project-lombok/ZkLsTZVSgD4
Shortly, what I want is using aspectj for logging some methods's arguments, but I can't use it in any Hibernate entity class, other than that it works fine. How to get it working in entity classes?
Here you find the explanation why Lombok and AspectJ do not like each other.
What is said to work though, is to delombok the Lombok-annotated source code and then normally compile the generated source code with the AspectJ compiler (e.g. via AspectJ Maven plugin, which is what I use all the time in Maven projects). Assuming you do use Maven, also the delombok step can be done with Lombok Maven plugin. If you assign the right phases to the respective plugins, it should be possible to fully automate the build process.
Disclaimer: I have never used Lombok in my whole life, but know a thing or two about AspectJ and Maven.
I am writing a library to provide some functionality that is shared between multiple different Spring Boot applications that I work with.
I would like to do something similar to the auto-configuration that is provided by the many Spring Boot starter libraries exist. That, or some other simple declarative way to integrate my library with the ApplicationContext of the apps using it.
I have found some resources explaining how auto configuration works. I can figure out the above problem.
However, I have not been able to find any good examples of how I can test as part of my library's test suite that it suitably integrates with a Spring Boot application. Ideally, I would start up a simple Spring Boot app written in the library's test directly just for the sake of testing, add the right annotation to it, and be able to assert that the correct beans are then configured.
I have tried creating a TestApplication class that does that and writing integration tests using the SpringBootTest annotation but the TestApplication was never started before my test started.
What can I do to start up a simple app like that solely for the purpose of testing my library? My tests are written with Spock and Spock-Spring in case that changes things versus other test frameworks.
I was able to make it work with the following test class:
#SpringBootTest
#ContextConfiguration(classes = TestApplication)
class DummyIntegrationSpec extends Specification {
#Autowired
DummyService dummyService
void 'dummy service should exist'() {
expect:
dummyService.getMessage() == DummyConfiguration.MESSAGE
}
}
and this TestApplication class at src/test/groovy/com/example/project/TestApplication.groovy
#SpringBootApplication(scanBasePackages = 'com.example.project.config')
#EnableAutoConfiguration
class TestApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(TestApplication)
}
static void main(String[] args) {
SpringApplication.run(TestApplication, args)
}
}
The two key changes I had to make in order for the TestApplication to start and load the correct context when I moved my TestApplication class from src/main to src/test were:
the TestApplication class needed to be added to the ContextConfiguration annotation
the package that my library's Java config files live in needed to be added to the SpringBootApplication scanBasePackages field
The library auto-configuration does follow a similar structure to the one mentioned in the link tom provided.
Your auto-configuration should be automatically picked while your main spring application/test is starting and all beans will be registered in your context. They will be available for auto-wiring and follow your conditions and init order.
As a summary, make sure you have an auto-configuration annotated by #Configuration class with an #Import that imports your #Configuration annotated configuration classes (inside of them you define beans with methods annotated with #Bean). Also make sure you created a spring.factories file that include your auto-configuration class and that you removed the spring boot maven plugin (for the packaging to be right).
Also, make sure your auto-configuration project is NOT annotated by things like #SpringBootApplication, #EnableAutoConfiguration, #ComponentScan or other spring boot annotations that need to be only in the main spring boot projects (There should be one of them in each stack).
Please also see the article below:
Spring boot is based on a lot of pre-made auto-configuration parent projects. You should already be familiar with spring boot starter projects.
You can easily create your own starter project by doing the following easy steps:
Create some #Configuration classes to define default beans. You should use external properties as much as possible to allow customization and try to use auto-configuration helper annotations like #AutoConfigureBefore, #AutoConfigureAfter, #ConditionalOnBean, #ConditionalOnMissingBean etc. You can find more detailed information on each annotation in the official documentation Condition annotations
Place an auto-configuration file/files that aggregates all of the #Configuration classes.
Create a file named spring.factories and place it in src/main/resources/META-INF.
In spring.factories, set org.springframework.boot.autoconfigure.EnableAutoConfiguration property with comma separated values of your #Configuration classes:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
Using this method you can create your own auto-configuration classes that will be picked by spring-boot. Spring-boot automatically scan all maven/gradle dependencies for a spring.factories file, if it finds one, it adds all #Configuration classes specified in it to its auto-configuration process.
Make sure your auto-configuration starter project does not contain spring boot maven plugin because it will package the project as an executable JAR and won't be loaded by the classpath as intended - spring boot will not be able to find your spring.factories and won't load your configuration
I want to use spring dependency injection for my domain classes, that are possibly not created within spring context. That's why I have annotated these classes with #Configurable annotation and I try to setup load time weaving. My spring configuration is :
#SpringBootApplication
#EnableSpringConfigured
#EnableAspectJAutoProxy
#EnableCaching(mode = AdviceMode.ASPECTJ)
#EnableLoadTimeWeaving
public class WebApplication extends WebMvcConfigurerAdapter
This works fine but only if my #Configurable classes are in same JAR as my main Spring Boot application. However I want to have my #Configurable domain classes in JAR that is added as dependency for main application.
I tried this but it looks like load time weaving is not working for external JARs. Do you have any suggestions how to solve this issue?
EDIT
I have added -verbose:class to my JVM options and I have found out that classes from external JARs are loaded by class loader before LoadTimeWeaving is configured and initialized. And classes not in external JAR are loaded as they are needed i.e. after LoadTimeWeaving init.
So basically my question is :
Is it possible to initialize LTW before external JARs loading? Or is it possible to reload (or make AspectJ enhancement) classes after LTW is configured?
EDIT 2
I found out the reason why my classes from external jar are loadded before LTW init. It's because these classes are also annotated with #Entity annotation. Therefore they are loaded during hibernate initialization which occure before LTW init.
So the final question is: :D
How to assume that LTW is initialized before hibernate (and possibly other) initializations?
Ok I have a solution. :) As I sad issue was the order of spring bean initialization. So I simple had to ensure that load time weaver was initialized before hibernate. Simply injecting LoadTimeWeaver into my configuration class was the workaround:
#Autowired
private LoadTimeWeaver loadTimeWeaver;
EDIT
Another solution is to add aspectjweaver.jar as javaagent in addition to spring-instrument.