I have a spring book application, which works fine. I would like to send email notifications to me if there are exceptions thrown from my application. I think aspectj is a good fit.
First, I tried to use Spring AOP runtime weaving, which works OK for public methods. However, I would like to get notified on private methods as well, and I have scheduled tasks. According to Spring AOP doc, runtime weaving won't work for scheduled tasks and private methods. Thus I have decided to use aspectj compile time weaving in my boot application.
I found the official aspectj gradle plugin here:
https://plugins.gradle.org/plugin/at.jku.isse.gradient-gradle
My boot application has this dependency:
compile group: 'org.springframework.boot', name: 'spring-boot-configuration-processor', version:'1.5.14.RELEASE'
My project compiles fine if I use default compiler. But if I use AspectJ compiler, it always complains:
[ant:iajc] warning Field value processing of #ConfigurationProperty meta-data is not supported
[ant:iajc] warning Hibernate JPA 2 Static-Metamodel Generator 4.3.11.Final
[ant:iajc] error at (no source information available)
D:\work\proj\build\classes\java\main\com\abc\dao\entity\Channel_.java:0::0 Internal compiler error: java.lang.Exception: java.lang.NoClassDefFoundError: org/springframework/boot/configurationprocessor/metadata/JsonMarshaller at org.aspectj.org.eclipse.jdt.internal.compiler.apt.dispatch.RoundDispatcher.handleProcessor(RoundDispatcher.java:169)
If I remove that dependency, aspectj compiles ok, I get the jar file. But then when I run my code, I got this:
Caused by: org.springframework.aop.framework.AopConfigException: Advice must be declared inside an aspect type: Offending method 'public void com.proj.aop.AspectConfiguration.afterThrowing(org.aspectj.lang.JoinPoint,java.lang.Throwable)' in class [com.proj.aop.AspectConfiguration]
And this is my Advice class:
package com.proj.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
#Aspect
#Configuration
public class AspectConfiguration {
#AfterThrowing (pointcut="execution(* org.hibernate..*.*(..)) || execution(* com.proj..*.*(..))", throwing="excep")
public void afterThrowing(JoinPoint joinPoint, Throwable excep){
System.out.println("inafterthrowing");
}
}
and my appconfig has annotation #EnableAspectJAutoProxy added.
If I replace #Configuration with #Component in my Advice class, I can run my application but the afterthrowing method is not called. Same if I remove #Configuration.
So my questions are:
Why I get the java.lang.NoClassDefFoundError: org/springframework/boot/configurationprocessor/metadata/JsonMarshaller if I have spring-boot-configuration-processor as dependency when compiled by AspectJ?
What is the correct way to use AspectJ in spring boot application and build with gradle? (use #Configuration or #Component? and why my afterthrowing is not called if I use #Component or, why I get exception if I use #Configuration?)
Thanks
Related
This question already has an answer here:
Bean 'x' of type [TYPE] is not eligible for getting processed by all BeanPostProcessors
(1 answer)
Closed 2 years ago.
upon configuring actuator to start up on a distinct port, the app fails with the following stacktrace:
java.lang.NullPointerException: null
at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:241) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:270) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:106) ~[tomcat-embed-core-9.0.37.jar:9.0.37]
at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4528) [tomcat-embed-core-9.0.37.jar:9.0.37]
aop issues the following info warning:
2020-08-16 10:01:11.240 INFO 83848 --- [ main] o.s.aop.framework.CglibAopProxy : Unable to proxy interface-implementing method [public final void org.springframework.web.filter.GenericFilterBean.init(javax.servlet.FilterConfig) throws javax.servlet.ServletException] because it is marked as final: Consider using interface-based JDK proxies instead!
here are my gradle dependencies:
plugins {
id 'org.springframework.boot' version '2.3.3.RELEASE'
...
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.1.5.RELEASE'
implementation 'org.springframework.security:spring-security-jwt:1.0.10.RELEASE'
we do have one #Aspect annotated class:
...
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.stereotype.Component;
...
#Aspect
#Component
#Slf4j
public class MyFilter {
#Around("execution(public void org.springframework.security.web.FilterChainProxy.doFilter(..))")
public void handleRequestRejectedException (ProceedingJoinPoint pjp) throws Throwable {
try {
pjp.proceed();
} catch (RequestRejectedException exception) {
...
}
}
}
i have found a few issues discussing this problem, having something to do with aop, but i have as of yet failed to find a solution.
any/all help appreciated.
james
After the OP has updated the question with some sample code, I see that the problem is not a pointcut which is too generic, capturing too many classes like in the other question I linked to in my comment but something else:
James, you are targeting method FilterChainProxy.doFilter(..), i.e. directly a Spring framework class. The problem is, as you can also see in the error message you get in your log:
o.s.aop.framework.CglibAopProxy:
Unable to proxy interface-implementing method [
public final void org.springframework.web.filter.GenericFilterBean.init(javax.servlet.FilterConfig)
throws javax.servlet.ServletException
]
because it is marked as final:
Consider using interface-based JDK proxies instead!
The problem is that the target class is derived from GenericFilterBean where the init(..) method is marked as final. CGLIB works via subclassing and overriding non-private methods, but a final method cannot be overridden. Thus, Spring AOP complains.
The error message also hints you towards a solution for the problem: Instead of using a CGLIB proxy for the target, you could use a JDK proxy which works by implementing interfaces. Luckily, doFilter is an interface method, more precisely an implementation of JavaEE method Filter.doFilter(..).
Now the next problem is that you are not using plain vanilla Spring but Spring Boot, which is known for having a combination of presets which seem to make it impossible to switch to JDK proxies, even though in plain Spring that is even the default. But Boot wants to be too user-friendly and smart, leaving you in this trap. I do not remember the Spring Boot ticket dealing with this problem, but last time I checked it was unresolved.
One way to avoid the whole situation is to use full AspectJ instead of just Spring AOP for your aspect, thus freeing you of the proxy-based "AOP lite" approach and enabling you to directly modify the target class or to alternatively weave your aspect not into the target class but into all calling classes instead via call() pointcut (not supported by Spring AOP).
Another solution would be to hook into another, less problematic target class/method with your aspect, not directly into a Spring framework class with final methods.
P.S.: I am not a Spring user, I just happen to know a few details from answering AOP-related questions here. Actually, I am surprised that CGLIB proxying as implemented by Spring is not more lenient with regard to final methods, just ignoring them and logging a warning instead of trying to override them and producing an error. Maybe there is some configuration option for that, but I leave it up to the Spring folks to answer that question, I have no clue here.
I cannot answer more precisely because I would need to see a full MCVE, ideally a Maven or Gradle project on GitHub, in order to further analyse your situation. I just wanted to explain some basics here.
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.
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.
My goal is to get a bare bones aspectj+spring aop setup such that I can use #Configurable on one class. One additional restriction is that it needs to use load-time weaving as Lombok doesn't work with CTW.
The good news: I have it working!
The bad news: The console is flooded with [Xlint:cantFindType] errors. See results section below.
The Environment
I have a single class annotated with #Configurable. It is a class used by and instantiated by Jackson, hence the need for AOP. It's not very interesting so I won't show it here. It's just a normal class with the single annotation of Configurable, and an #Autowired bean inside.
SpringBootApplication
My Application class has the usual annotations:
#SpringBootApplication
#EnableSpringConfigured
#EnableLoadTimeWeaving
public class MyApplication {
build.gradle
My build.gradle has all the usual suspects. Sample:
configurations {
springinstrument
}
dependencies {
compile('org.projectlombok:lombok')
compile('org.springframework.boot:spring-boot-starter-aop')
compile("org.springframework.boot:spring-boot-starter-data-rest")
compile('org.springframework.data:spring-data-rest-hal-browser')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-devtools')
compile('org.springframework.plugin:spring-plugin:1.2.0.RELEASE')
..snip..
runtime('org.springframework:spring-instrument:4.+')
springinstrument "org.springframework:spring-instrument:4.+"
runtime configurations.springinstrument.dependencies
}
test.doFirst {
jvmArgs "-javaagent:${configurations.springinstrument.asPath}"
}
jvm args
I am running a JUnit test with the following args (via Intellij's run config)
-ea
-javaagent:/Users/me/.gradle/caches/modules-2/files-2.1/org.springframework/spring-instrument/4.3.3.RELEASE/5db399fa5546172b9c107817b4abaae6b379bb8c/spring-instrument-4.3.3.RELEASE.jar
aop.xml
I have a src/main/resources/META-INF/aop.xml containing:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-Xreweavable -showWeaveInfo">
<!-- only weave classes with #Configurable interface -->
<include within="#org.springframework.beans.factory.annotation.Configurable */>
</weaver>
</aspectj>
However, my suspicion is that this file is not being picked up. It doesn't matter what I put in the file, the results are always the same. Even if I put random non-valid XML.
The Test
The junit test is a simple Jackson serialize-deserialize test using the #Configurable annotated class.
The test class has the annotations:
#SpringBootTest
#RunWith(SpringRunner.class)
The Results
When running a junit test via Intellij -> It Works, but..
The LTW is actual working and my test is passing when run via Intellij with the jvm args above.
However the boot time of the app is significantly longer and the log is flooded with Xlint:cantFindType errors
Example:
2016-11-07 19:28:21.944 INFO 45213 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
[AppClassLoader#18b4aac2] error can't determine implemented interfaces of missing type org.springframework.security.ldap.authentication.LdapAuthenticationProvider
when weaving type org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer
when weaving classes
when weaving
[Xlint:cantFindType]
[AppClassLoader#18b4aac2] error can't determine implemented interfaces of missing type io.undertow.server.handlers.accesslog.AccessLogHandler
when weaving type org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory
when weaving classes
when weaving
[Xlint:cantFindType]
[AppClassLoader#18b4aac2] error can't determine implemented interfaces of missing type io.undertow.server.handlers.accesslog.AccessLogHandler
when weaving type org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory
when weaving classes
when weaving
[Xlint:cantFindType]
[AppClassLoader#18b4aac2] error can't determine implemented interfaces of missing type io.undertow.server.handlers.accesslog.AccessLogHandler
when weaving type org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory
when weaving classes
when weaving
[Xlint:cantFindType]
.. many more.. all in the org.springframework.boot package
When running a test via gradle command line -> works, sort of.
When I run the test with gradle test --tests *MyTestClass, the test fails because of a NullPointerException on.. guess what, the #Autowired bean I'm expecting to be injected automatically.
I got it working with gradle test config, huzzah! I've updated the configuration above. But.. it suffers from the same problem as running it in my IDE: the Xlint:cantFindType
So the issues:
The gradle configuration is wrong, so running the app via gradle fails. How do I fix the gradle config?
The Xlint:cantFindType errors. Is the aop.xml not being picked up? How to resolve this?
I have yet to find a simple spring-boot+gradle+ #Configurable sample that uses aop.xml
I solved the problem, and it's rather embarrassing.
It was due to the fact that the aop.xml wasn't being picked up.
The file wasn't being detected because it was in a directory named META_INF/ on the class path, not META-INF/. That's a pretty important difference.
Anyways, I've updated the question with the minimal configuration needed to get #Configurable working with a modern spring-boot and gradle setup with load time weaving for Lombok support. Hopefully it will be useful for someone.
In my project I use some aspects as part of my project and some as part of a common library. The first request takes about 10 seconds with the default weaving concept of Spring Boot. Thus, we try to switch to CTW (compile time weaving) using aspectj-maven-plugin 1.7.
Start-up time has been reduced now, an aspect with pointcut
#Around("execution(public com.XXXX.XXXXX.common.XXXX.APIErrorResponse *(..))")
is working, but the aspect with pointcut
#Around("#annotation(com.xxxxxx.XXX.common.xxx.Loggable)"))
is not working (I have created a #Loggable annotation to apply entry/exit logging). I get the following compiler message:
[WARNING] advice defined in com.xx.xx.xx.xx.LoggerAspect has not been applied [Xlint:adviceDidNotMatch]
I use the following configuration in my pom.xml:
Editor's remark: missing information about pom.xml should go here.
I use ajc 1.8.1 and compliance level 1.8.
My Loggable annotation:
#Retention(RetentionPolicy.CLASS)
#Target({ ElementType.METHOD, ElementType.TYPE })
public #interface Loggable {}
Any help will be appreciated.
The warning means that the fully qualified package & class name in your pointcut does not exactly match the real one in your annotation.