My project is based on spring framework 2.5.4. And I try to add aspects for some controllers (I use aspectj 1.5.3).
I've enabled auto-proxy in application-servlet.xml, just pasted these lines to the end of the xml file:
<aop:aspectj-autoproxy />
<bean id="auditLogProcessor" class="com.example.bg.web.utils.AuditLogProcessor" />
Created aspect:
package com.example.bg.web.utils;
import org.apache.log4j.Logger;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class AuditLogProcessor
{
private final static Logger log = Logger.getLogger(AuditLogProcessor.class);
#After("execution(* com.example.bg.web.controllers.assets.AssetThumbnailRebuildController.rebuildThumbnail(..))")
public void afterHandleRequest() {
log.info("test111");
}
#After("execution(* com.example.bg.web.controllers.assets.AssetThumbnailRebuildController.rebuildThumbnail(..))")
public void afterRebuildThumbnail() {
log.info("test222");
}
}
My controllers:
class AssetAddController implements Controller
class AssetThumbnailRebuildController extends MultiActionController
When I set brake points in aspect advisors and invoke controllers I catch only afterHandleRequest() but not afterRebildThumbnail()
What did I do wrong?
NOTE
I'm asking this question on behalf of my friend who doesn't have access to SO beta, and I don't have a clue what it's all about.
EDIT
There were indeed some misspellings, thanks Cheekysoft. But the problem still persists.
Your breakpoints aren't being hit because you are using Spring's AOP Proxies. See understanding-aop-proxies for a description of how AOP Proxies are special.
Basically, the MVC framework is going to call the handleRequest method on your controller's proxy (which for example the MultiActionController you're using as a base class implements), this method will then make an "internal" call to its rebuildThumbnail method, but this won't go through the proxy and thus won't pick up any aspects. (This has nothing to do with the methods being final.)
To achieve what you want, investigate using "real" AOP via load time weaving (which Spring supports very nicely).
AspectJ doesn't work well with classes in the Spring Web MVC framework. Read the bottom of the "Open for extension..." box on the right side of the page
Instead, take a look at the HandlerInterceptor interface.
The new Spring MVC Annotations may work as well since then the Controller classes are all POJOs, but I haven't tried it myself.
The basic setup looks ok.
The syntax can be simplified slightly by not defining an in-place pointcut and just specifying the method to which the after-advice should be applied. (The named pointcuts for methods are automatically created for you.)
e.g.
#After( "com.example.bg.web.controllers.assets.AssetAddController.handleRequest()" )
public void afterHandleRequest() {
log.info( "test111" );
}
#After( "com.example.bg.web.controllers.assets.AssetThumbnailRebuildController.rebuildThumbnail()" )
public void afterRebuildThumbnail() {
log.info( "test222" );
}
As long as the rebuildThumbnail method is not final, and the method name and class are correct. I don't see why this won't work.
see http://static.springframework.org/spring/docs/2.0.x/reference/aop.html
Is this as simple as spelling? or are there just typos in the question?
Sometimes you write rebuildThumbnail and sometimes you write rebildThumbnail
The methods you are trying to override with advice are not final methods in the MVC framework, so whilst bpapas answer is useful, my understanding is that this is not the problem in this case. However, do make sure that the rebuildThumbnail controller action is not final
#bpapas: please correct me if I'm wrong. The programmer's own controller action is what he is trying to override. Looking at the MultiActionController source (and its parents') the only finalized method potentially in the stack is MultiActionController.invokeNamedMethod, although I'm not 100% sure if this would be in the stack at that time or not. Would having a finalized method higher up the stack cause a problem adding AOP advice to a method further down?
Related
Working with Springboot 2.7.0. I had a a working application and I made these changes on top of it
Aspect Configuration
#Configuration
#EnableAspectJAutoProxy
#ComponentScan
public class AspectConfig {}
Aspect Interface
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Timed { }
Aspect Class to Measure method execution time
#Around("#annotation(Timed)")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
LOG.info("Time taken for {} is {} ms, joinPoint.getSignature(), System.currentTimeMillis() - start,);
return proceed;
}
Added the new #Timed annotation to an existing method in a bean (omitting non relevant code)
#Component
#ConditionalOnExpression("${oauth.enabled}")
public class JwtAuthFilter extends OncePerRequestFilter {
#Timed
public boolean verifySignatureAndExpiry(String bearerToken){
// method logic
}
}
This causes the Springboot application to fail startup.
I can get it to start if I add #Aspect to the JwtAuthFilter class.
but why would I need to do that? It makes the #Timed annotation limited use if I have to annotate every class that needs to use it with #Aspect. Not to mention, though there are no errors, the functionality won't work because an Aspect cannot work on another Aspect.
#Timed works on my controller method though.
#RestController
#RequestMapping(value = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {
#GetMapping("/health")
#Timed
public Map<String, String> health(){
return Map.of("status", "up");
}
}
This causes the Spring Boot application to fail startup.
You should always post error messages and relevant stack traces, not just say "fails to start up". You are lucky that in this case, I remember the situation, so I can answer your question. Normally, I would be unable to do so without further information.
I can get it to start if I add #Aspect to the JwtAuthFilter class.
That does not make any sense. Why would you add #Aspect to something which is not an aspect? Of course, it makes the start-up error go away, but it also makes your real aspect not fire, because one Spring AOP aspect cannot advise another one, as you already mentioned. Therefore, this approach is - with all due respect - complete nonsense.
The reason for the exception is: You cannot advise your filter by Spring AOP, because it is derived from GenericFilterBean, which has some final methods. Final methods cannot be overriden, therefore not be proxied either. This has the effect of those methods being called upon the proxy instance directly instead of being delegated to the target object, i.e. if such a method accesses an instance field, it shall find it uninitialised, because the proxy's fields are not meant to be initialised, only the target object's ones. See also my answer here for more info.
In this case, final method org.springframework.web.filter.GenericFilterBean#init is trying to access this.logger, which leads to the NPE which makes Spring Boot's Tomcat fail to start up. This has been reported and briefly explained in this comment in Spring issue #27963, which has been closed as invalid.
#Timed works on my controller method though.
Yes, because your controller does not have a problem with accessing an instance field from a final method.
If you absolutely think that you need to measure your filter method's execution time from an aspect, you can switch from Spring AOP to native AspectJ, either for the whole project via load-time weaving or selectively for some target classes via compile-time weaving. I have tried locally, it works with the right pointcut. Then you can also advise your filter. FYI, the pointcut would be something like:
// Annotated class
#Around("execution(* *(..)) && !within(MyAspect) && #target(Timed)")
// Annotated method
#Around("execution(* *(..)) && !within(MyAspect) && #annotation(Timed)")
AspectJ is more powerful than Spring AOP, so you explicitly need to limit matching to method executions, otherwise other joinpoints such as method calls, constructor calls and others would be affected, too. You also need to make sure that the aspect does not advise itself or other aspects, which is perfectly possible in AspectJ.
I'm trying to use Spring AOP to add logging to methods coming from a third party library. So there is a class, ProxyServlet, that is being used by my Spring Boot application, and I just want to apply logging to it.
#Pointcut("within(com.common.httpproxy.ProxyServlet)")
private void proxyServlet() {}
#Before("proxyServlet()")
public void testLog() {
log.info("THIS IS WORKING");
}
This is just some test AOP code right here. I know that my Spring AOP is set up, because I can get log output for any classes included in my codebase. However, I can't get log output for any classes from a third party library, like the ProxyServlet.
Is there anything I can do to get this AOP advice to work?
You should annotate the class containing this code with:
#Aspect
#Component
Assuming that the ProxyServer instance is a bean, you can achieve it by defining an aspect for the logging something like below.
#Aspect
public class LoggingAspect {
#Before(execution(* the.package.ProxyServlet.*(..)))
public void loggingAdvice(JoinPoint joinPoint){
System.out.println("Started loggingAdvice on method="+joinPoint.toString());
System.out.println("The aruguments are =" + Arrays.toString(joinPoint.getArgs()));
}
}
Please note that it is not necessary to have a custom point-cut like #LoggingAdvice here and annotate the target method using it (I am pointing it because you mentioned that the class is in third party library by which means you are expressing the concern that you may not be able to annotate meothod(s) with a pointcut) . Custom point-cuts are useful when you define widely applicable Aspects and limit the application of it to certain joint pints (thru the custom pointcut)
As your target is a single class and is third party library, you are good without a custom point-cut but with an aspect specifically targeting the required class.
The above define Aspect will be executed for every method defined in the ProxyBean class.
I excluded part of my project for easier reproduce problem: GitHub repo.
When I compile it by Javac everything works as expected. I see logging in console when I open URLs /user/ and /user/2/:
Access: execution(List ru.krivochenko.demo.user.UserController.getAll())
Access: execution(User ru.krivochenko.demo.user.UserController.getOne(Integer))
But I wanna use AspectJ compiler. When I switch to it, error occurs:
java.lang.NoSuchMethodError: ru.krivochenko.demo.logging.LoggingAspect: method <init>()V not found
As I understood it happens because there is not no-args constructor in LoggingAspect. If I add it, I get another error, because logger is not injected:
java.lang.NullPointerException: null
at ru.krivochenko.demo.logging.LoggingAspect.beforeGettingUsers(LoggingAspect.java:28) ~[classes/:na]
So, how we can see, AspectJ ignores Autowired constructor with args.
In branch via-setter of my repo I implemented another solution. I removed #Component annotation of LoggingAspect and replaced constructor injection to setter injection. In DemoApplication.java I added #Bean configuration of LoggingAspect. It works fine, but in some situations it requires getting dependencies from application context. What is the best practice to resolve it?
Thanks for help.
Spring Aspects and compile time weaving don't automatically integrate. This is primary because aspectj and spring are fairly separate and I suspect Spring's recommended approach is not to use compile time weaving.
So thus by default Aspects are not spring magic and we need to add a little bit of plumbing to ensure they are.
In this regard, it is important to note that Aspects are not spring managed (they are managed by aspectj so we need to add something to ensure they are).
Thus the reason why you need a parameterless constructor on your aspect (so must use field injection).
Traditionally I have had to add the following piece of xml to my xml config files:
<bean id="securityAspect" class="com.<skip>.security.AuthorizationAspect"
factory-method="aspectOf" autowire="byType" />
So this works because the AspectJ compiler adds the static method aspectOf to the aspects and this method is available for acquiring the instance of the Aspect that aspectj creates (and uses).
This method is obviously not available in the source so we can't just add to our application class (DemoApplication):
#Bean
public LoggingAspect loggingAspect() {
return LoggingAspect.aspectOf();
}
Then what to do? My next option was to write some reflective code to call this method then having looked at this very helpful example that demonstrates exactly what you need - The Aspects class from AspectJ has a utilty method that does this work for us, so adding the following to our DemoApplication we have success:
#Bean
public LoggingAspect loggingAspect() {
return Aspects.aspectOf(LoggingAspect.class);
}
Btw, remove the #Component from the LoggingAspect as that will mean both Aspectj and Spring create an instance of the class...
Btw, I'd also suggest you add the following to your test class to demonstrate the problem in a test:
#Autowired
private UserController controller;
#Test
public void contextLoads() {
controller.getAll();
controller.getOne(1);
}
Btw, other suggestions to address this problem used #Configurable. I suspect this might work but you'll need to make sure you include the spring aspects java in your aspectj compile time config and I suspect it may still not work as I'm not sure the Spring context will be ready in time. i.e. if the Aspect is created before the spring context then #Configurable won't work as the beans to be injected will not yet be created.
Your approach to configure the aspect via setter injection looks valid to me. For more information about how to use AspectJ in combination with Spring check out the corresponding chapter in the Spring manual, specifically the description about how to configure AspectJ aspects by Spring IoC. It is mostly explained in the context of LTW, but it should work pretty much the same for CTW.
I have a class that is using Spring AOP framework in my web app just like the code shown below. I was wondering why the Spring AOP was able to trace add() but not able trace multiple() if I implement the following code.
public interface calculator {
public void add();
public void multiple();
}
public class calculatorImpl implements calculator {
public void add() {
multiple();
}
public void multiple() {}
}
I've did an experiment and found out that the following code is working fine. Meaning that the Spring AOP able to trace both add and multiple function.
ICalculator calcProxy = (ICalculator) context.getBean("calculatorProxy");
calcProxy.add();
calcProxy.multiple();
I think that must be cause by the multiple() was inject by the proxy bean whereas the multiple() in calculatorImpl class wasn't, thus Spring AOP wasn't able to trace it. Correct me if I am wrong.
My next question. Is there a work around on this issue where the add() really need to execute multiple() and get trace by the Spring AOP?
Spring AOP doesn't change the actual method, but adds a proxy around the Object. Once you are inside the inner Object (inside the add() method), there is no proxy, you are underneath it.
I have explained this mechanism in more detail in this past answer:
https://stackoverflow.com/a/4171649/342852
There are three ways to deal with that situation:
Use AspectJ, not Spring AOP. AspectJ compiles the behavior into your code, whereas Spring AOP works with proxies that surround your code.
Access the Proxy from inside the Bean. This is reeeeaaally ugly, but it can be done.
Example code (here you make the class aware of it's being proxied, which is contrary to everything AOP stands for):
public void add() {
((Calculator)AopContext.currentProxy()).multiple();
}
Slightly less horrible (here you make the class aware that it's being managed by Spring):
#Autowired
private ApplicationContext context;
public void add() {
context.getBean(Calculator.class).multiple();
}
Change your design. Example: move the two methods to separate beans and inject each into the other bean.
I've made an application with Spring Roo (I'm still a newbie) and I'd like to do some processing after the entity is persisted. I've set up the application with Service and DAO layer. In the service I created a custom method called triggerChange(MyEntity myEntity). I'd like that this method would be invoked after saving the entity but I don't know how could I call that method without modifying the *ServiceImpl_Roo_Service managed by Roo (which shouldn't be editted).
So I have a code like this one:
The service:
public class MyEntityServiceImpl implements MyEntityService {
//this is the method I want to invoke inside or after invoking save()
public void triggerChange(MyEntity myEntity) {
...
}
}
The Aspect for the service:
privileged aspect MyEntityServiceImpl_Roo_Service {
...
public void MyEntityServiceImpl.saveMyEntity(MyEntity myEntity) {
myEntityRepository.save(myEntity);
}
}
How could I customize save method?
Thanks
You have AspectJ enabled in your Spring application thanks to Roo. Just create an Aspect (after or around the method call)
You also can to move the methods from the Roo aspects (.aj)
In STS select the desired methods (and even attributes) from the aspect class, right click, refactor->push in... and press Review-OK or OK directly (I recommend the former in order to review the changes)
Another way: with Roo running, just create a method/attribute with the same signature in the class. Roo will remove the equivalent from the aspect.
One way to do this is, to use the JPA or Hibernate events.
Have a look a this blog (the author is a very active SO User), it explains how Spring Beans can be use for such events.
I've just had to perform some business logic after a save method, just like you wanted.
Let's say I have to log that a save operation has been performed. For doing so, I've created this aspect:
package com.malsolo.aspects;
import org.apache.log4j.Logger;
import com.malsolo.myproject.domain.MyEntity;
aspect MyEntityAspect {
private final Logger logger = Logger.getLogger(MyEntityAspect.class);
pointcut persistEntity() : execution(* MyEntity.persist(..));
public Logger getLogger() {
return logger;
}
after() : persistEntity() {
logger().info("Entity persisted "+thisJoinPoint);
}
}
It writes:
2011-11-30 11:47:27,056 [main] INFO
com.malsolo.aspects.ModifyRolAspect - Entity persisted execution(void
com.malsolo.myproject.domain.MyEntity.persist())
I hope it helps you.
Notes:
I'm using Roo 1.1.5 with the #Entity approach, I have no service layer
Pay attention to the import of the Entity. It wouldn't work
without it.
If you use STS with AJDT, you'll see the advises applied.