Why AspectJ weaving on package scoped methods not working? - java

I am trying to print logs for private and package scoped methods (from package scoped classes) in my application which is based on Spring. Since Spring's proxy based aspects don't work on private and package scoped methods(?), I tried to use AspectJ load time weaving as per the documentation. Below are the details:
LoggingAspect
#Component(Constants.LOGGING_ASPECT_BEAN)
#Aspect
public class LoggingAspect {
#Around("#annotation(my.pkg.Loggable)")
public Object doLogging(final ProceedingJoinPoint joinPoint) throws Throwable {
// ... logging code.
}
}
Spring Configuration
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass = true)
#Import(AnotherBaseConfig.class)
#ComponentScan("my.pkg")
#EnableWebMvc
#EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.AUTODETECT)
public class AppConfiguration extends WebMvcConfigurerAdapter {
// ... bean configs
}
src/main/webapp/META-INF/aop.xml
<aspectj>
<weaver>
<include within="my.pkg.*">
</weaver>
<aspects>
<aspect name="my.logger.LoggingAspect"/>
</aspects>
</aspectj>
JVM Config
-javaagent:"/path/to/spring-instrument-4.0.6.jar" -javaagent:"/path/to/aspectjweaver-1.8.1.jar"
With above configurations, I have my package scoped classes as:
#Component("someClass")
class SomeClass {
#Loggable
void doSomething(#Loggable final String s, #Loggable final Integer i) {
// ... do something here.
}
}
The weaving is not working for such package scoped classes. Am I doing anything wrong?

You are mixing up two AOP styles here:
Spring AOP ist a proxy-based "AOP lite" approach which is activated via #EnableAspectJAutoProxy. It only works for public, non-static methods of Spring components.
AspectJ, a full-blown AOP framework which also works on package-local or private methods, no matter if they are Spring components or not, is enabled from within Spring via #EnableLoadTimeWeaving and the weaving agent via -javaagent:"/path/to/aspectjweaver-1.8.1.jar". In this case your aspect does not need to be a Spring component either. P.S.: I recommend to use the latest AspectJ version 1.8.5, not 1.8.1.
As you want to work with package-locals, you need AspectJ LTW. Please configure Spring clearly in preference of one AOP type. More information can be found in the Spring Manual, chapter 9. Specific information about AspectJ LTW configuration is in chapter 9.8.

Related

Intercept nested methods using native AspectJ in Spring Boot

I am trying to intercept any method in my application which is annotated with my custom developed annotation.
Initially I used the Spring AOP which works fine. But, it is not intercepting if the method call is in the same target class.
Going through the official docs, I got to know that the Spring AOP uses proxy beans for the same.
One workaround I found was to self inject the target class. But, this seems like too much fuss. Like every time I am adding my custom annotation to a method, I need to make sure that I add the #Scope annotation, set the proxyMode & self inject target class as shown here
Later I moved on to configuring and using native AspectJ.
This is my Custom annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface TrackTime {
String description() default "";
}
Here is the Aspect for TrackTime annotation:
#Configuration
#Slf4j
#Aspect
public class TrackTimeServiceImpl {
#Pointcut("execution(public * *(..))")
public void methodsToBeProfiled(){
}
#Around("methodsToBeProfiled() && #annotation(x.y.z.TrackTime)")
public Object audit(ProceedingJoinPoint joinPoint) throws Exception {
//Business logic
}
}
I would like to mention here that my application is running on Jetty server.
The configuration file:
#Configuration
#EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
#EnableSpringConfigured
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class TrackTimeConfig implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new InstrumentationLoadTimeWeaver();
}
}
The aop.xml file:
Path to file: /resources/META-INF/aop.xml
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="in.xxx.*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="in.xxx.yyy.zzz.TrackTimeServiceImpl"/>
</aspects>
</aspectj>
Relative dependencies added in parent pom.xml have been mentioned here:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.21</version>
</dependency>
My service class:
#Component
public class SomeService {
public void a(){
b();
}
#TrackTime
public void b(){
//business logic
}
}
Now when the method a() is called from the controller, even though the b() has the #TrackTime annotation, it is not intercepted.
Also, I would like to mention that I have set the following program arguments while running the application
-javaagent:/Users/xxx/.m2/repository/org/springframework/spring-instrument/5.3.6/spring-instrument-5.3.6.jar
-javaagent:/Users/xxx/.m2/repository/org/aspectj/aspectjweaver/1.9.6/aspectjweaver-1.9.6.jar
I have gone through docs, articles, followed solutions on stackoverflow. But, for the above mentioned configuration/code, it is not working as I want.
Requesting help from the community.
Thanks in advance.
I did not try to run your example locally yet, but noticed two details at first glance in your code:
You are configuring both #EnableLoadTimeWeaving (for native AspectJ) and #EnableAspectJAutoProxy (for Spring AOP), which might be conflicting with each other. Try to remove the latter and see if it fixes the problem.
In aop.xml, you are using <include within="in.xxx.*"/>. Please note that this will only include classes directly in the in.xxx package, but not classes in sub-packages. In order to include them as well, please use the double-dot notation in.xxx..*.
Feel free to report back if it is still not working with the suggested changes. I can take a closer look then.
First of all, I would like to thank #hfontanez, #void void & #kriegaex for responding and helping me out in moving forward in solving the problem statement.
So if anyone is looking out on how to intercept nested & private methods, let's have this as a one stop in configuring native AspectJ.
Please check my POC here on github for a working example.
In my case, I added the aspectjweaver JAR in my project structure and passed the arguments through VM Options in IDE.
That all !!
Nested/Private methods will now be intercepted.
Thank you,
Karthik

Is it posible to create a log.info pointcut?

I never tried AOP before and want to know if I could make a pointcut that captures the execution of a log.info(...) in Spring.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class AfterFinallyExample {
#After("execution(* org.slf4j.*.*(..))")
public void sendLogParameterToServer() {
// ...
}
}
If it is posible, is it consider a good practice?
Could you use AOP to do this, yes you could. Can you do it with, the default, Spring AOP no you cannot.
Spring AOP, by default, is based on proxies and it will only create proxies for Spring-managed beans. As the loggers aren't managed by Spring (unless you inject all loggers) this won't work.
You will need to resort to fullblown AspectJ with either compile or loadtime weaving. Which will change the actual bytecode of the classes based on the pointcut and aspect.

Override/disable Aspect on runtime

In my spring-boot application I have a dependency on external jar, which contains a class marked with annotation, on which #Aspect from this jar is being triggered.
I have dao method annotated with mu custom annotation:
#MyAnnotation
public void save(MyEntity entity)
{
super.save(entity);
}
I have an aspect, which has an advice, sending message after save() method is called:
#Aspect
public class MySuperAspect
{
#Autowired
MessageSender messageSender;
#Around("#annotation(MyAnnotation) && args(entity)")
public void sendMessage(MyEntity entity)
{
messageSender.send();
}
}
I do need Dao method from the jar, but I want to disable aspect for it.
Aspect is being created via Spring XML configuration, which is inside the jar I use as well.
I could modify the aspect itself, but it's undesirable as it's being used not only by my spring-boot app.
I tried:
Disabling xml configuration from scanning in my spring-boot app;
Changing xml config to annotations and filter it in scanning;
Adding #ConditionalOnExpression and #ConditionalOnProperty on aspect to be disabled by property;
As of now the only way which works is adding #Value annotation with property by which I may control logic inside the advice, but I'm curious is this the only way to do that or probably I'm missing something?
You need to stop spring boot from scanning this class... Because it must be configured to scan this aspect otherwise it wouldn't be picking up and applying the aspect.

Rest + Spring AOP + interface doesn't inject

I have a Spring AOP aspect used for logging, where a method can be included for logging by adding an annotation to it, like this:
#AspectLogging("do something")
public void doSomething() {
...
}
I've been using this on Spring beans and it's been working just fine. Now, I wanted to use it on a REST-service, but I ran into some problems. So, I have:
#Path("/path")
#Service
public class MyRestService {
#Inject
private Something something;
#GET
#AspectLogging("get some stuff")
public Response getSomeStuff() {
...
}
}
and this setup works just fine. The Rest-service that I'm trying to add the logging to now has an interface, and somehow that messes stuff up. As soon as I add the #AspectLogging annotation to one of the methods, no dependencies are injected in the bean, and also, the aspect is newer called!
I've tried adding an interface to the REST-service that works, and it gets the same error.
How can having an interface lead to this type of problems? The aspect-logger works on classes with interfaces elsewhere, seems it's only a problem when it's a REST-service..
Ref the below Spring documentation (para 2) -
To enable AspectJ annotation support in the Spring IoC container, you
only have to define an empty XML element aop:aspectj-autoproxy in your
bean configuration file. Then, Spring will automatically create
proxies for any of your beans that are matched by your AspectJ
aspects.
For cases in which interfaces are not available or not used in an
application’s design, it’s possible to create proxies by relying on
CGLIB. To enable CGLIB, you need to set the attribute
proxy-targetclass= true in aop:aspectj-autoproxy.
In case your class implements an interface, a JDK dynamic proxy will be used. However if your class does not implement any interfaces then a CGLIB proxy will be created. You can achieve this #EnableAspectJAutoProxy. Here is the sample
#Configuration
#EnableAspectJAutoProxy
public class AppConfig {
#Bean
public LoggingAspect logingAspect(){
return new LoggingAspect();
}
}
#Component
#Aspect
public class LoggingAspect {
...
...
}
In my opinion what you are actually trying to do is to add spring annotations to a class maintained by jersey. In the result you are receiving a proxy of proxy of proxy of somethng. I do not think so this is a good idea and this will work without any problems. I had a similar issue when I tried to implement bean based validation. For some reasons when there were #PahtParam and #Valid annotations in the same place validation annotations were not visible. My advice is to move your logging to a #Service layer instead of #Controller.

JUnit weaving wrong Spring AOP Bean

I've run into a strange problem that I am having difficulty tracking down. I have an class (ServiceErrorInterceptor) defined as an #Aspect which is instantiated via XML configuration as a singleton bean. The XML configuration allows me to inject its dependent beans.
In my normal workflow, everything works fine. The aspect class is properly instantiated, and whenever the advice is called, the injected beans are as I would expect.
However, when I run my JUnit test, all my injected beans are null. This leads me to the conclusion that the advice is called from a different bean - not the same singleton bean that was instantiated by Spring. To further validate my hypothesis, I put a breakpoint on a setter which is called during the instantiation, and see that the bean id is not the same as the bean id if I put a breakpoint in my advice.
Is there some special configuration I must enable in my JUnit class to rectify this? My test class is already annotate with:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {
"classpath:spring/applicationContext-base.xml",
"classpath:spring/applicationContext-calculateServices.xml",
"classpath:spring/applicationContext-dom.xml"})
public class LendingSimulationServiceImplTest {
...
...
}
I've looked through the logs (I enabled spring trace logs), but don't see anything that stands out. And posting the entire log here would likely be overkill. If there is value in a specific part of the log please let me know and I will post it.
I'm able to post my code for the aspect, my junit and my config if that is helpful.
application-context.xml snippet:
<!-- SPRING ASPECT BEAN. POINTCUT DEFINED IN BEAN WITH ANNOTATION -->
<bean id="serviceErrorInterceptor" class="com.cws.cs.lendingsimulationservice.error.ServiceErrorInterceptor" scope="singleton">
<property name="errorMessageProvider" ref="resourceBundleProviderImpl"/>
<property name="defaultLocale">
<util:constant static-field="java.util.Locale.ENGLISH" />
</property>
</bean>
Any suggestions would be appreciated.
EDIT
My bean is implemented as:
#Aspect
public class ServiceErrorInterceptor {
/**
* Logger
*/
private static final Logger logger = LoggerFactory.getLogger(ServiceErrorInterceptor.class);
/**
* SOAP Header data
*/
#Autowired
private SOAPHeaderData soapHeaderData;
public ServiceErrorInterceptor(){
int x = 0;
x=x+1;
}
/**
* Exception Interceptor.
* #param ex
*/
#AfterThrowing(pointcut = "execution(* com.cws.cs.lendingsimulationservice.process.CalculatorProcess.calculate (..))", throwing = "ex")
public void errorInterceptor(Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error Message Interceptor started");
}
}
The relevant portions of my pom:
<!-- Aspect Oriented Programming (AOP) Framework (depends on spring-core,
spring-beans) Define this if you use Spring AOP APIs (org.springframework.aop.*) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- Support for AspectJ Annotations -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj}</version>
</dependency>
I've done further debugging and putting a breakpoint in the dummy constructor, I get the following results:
with #Aspect and XML configuration, the constructor is called twice (different bean ids)
if I remove the #Aspect annotation then it is only called once.
If leave the #Aspect but remove the XML configuration, then the constructor isn't even called.
If I use an #Component annotation in combination with #Aspect (but without any XML configuration), then the bean is constructed twice.
Oddly enough, however, with both the #Component and #Aspect annotations AND the XML configuration, the constructor is still only called twice.
So then why would having both the XML configuration and the #Aspect annotation cause the constructor to be called twice with two different bean ids?
I have further validated that if I move the entire AOP definition into the XML configuration (removing the #Aspect and #Pointcut annotations) then the bean is only constructed once.
END EDIT
Thanks,
Eric
Does your aspect by any chance have any of the autodetect annotations (#Component, #Service) etc, apart from #Aspect - if it does, that could be one reason why a different bean seems to be present along with the one defined in your configuration.
Also, just scan through all your configuration to make sure that you are not declaring this bean elsewhere.
There is nothing special that needs to be done at the Junit level that I am aware of.
After a lot of discussion with the folks over at the SpringSource STS forum (see this thread), it turns out that the issue is related to the AJDT configuration. At the moment, AJ is weaving in the aspect, and Spring is locating the aspect on the Classpath, so they are both being executed.
Unfortunately, the AJ maven plugin is missing a configuration parameter to allow for exclusion of weaving; the current configuration excludes both LTW and CTW.
So, the workaround at the moment is to add -xmlConfigured to the AJ compiler flags and then specify an aop.xml file in aop.xml management which only lists the AJ aspects that you want to include in the project.
To get this to work, add '-xmlConfigured' to the Project Properties
'Non-standard compiler options' and then in AspectJBuild>'aop.xml
management' point it at a simple aop.xml file:
<aspectj>
<aspects>
<aspect name="com.fooMyNewNoneSpringAspect"/>
</aspects>
</aspectj>
Thanks to Andy Clement at the STS forum for this discovery and workaround. He will be raising JIRA issues to further address this shortcoming in the maven plugin.
A possible way you might find yourself in the same situation: you used the new operator rather than letting Spring inject your service.
Remember, we're not in AspectJ compile time weaving here. Nope, we're using Spring AOP proxies, so Spring must instantiate the object and dress it with proxies. If you were silly, like myself, and created a new service inside your tests, you won't get any AOP.
All the more reason to do compile time weaving with AspectJ and skip over all of the drawbacks of Spring AOP such as runtime weaving startup delay, inability to weave non-public and non-final classes.

Categories

Resources