I am trying to log the execution time for methods annotated with custom interface.
I am using Spring AOP.
But this does not seems to work for inner methods.
I think it is the limitation in Spring AOP
#Aspect
public class BusinessProfiler {
private static Log log = LogFactory.getLog(BusinessProfiler.class);
#Around("execution(* *(..)) && #annotation(TimeLog)")
public Object profile(ProceedingJoinPoint point) throws Throwable {
long start = System.currentTimeMillis();
Object result = point.proceed();
String format =
String.format("%s#%s: took [%s msec]", point.getTarget().getClass().getSimpleName(),
MethodSignature.class.cast(point.getSignature()).getMethod().getName(),
System.currentTimeMillis() - start);
log.info(format);
return result;
}
}
Are there any alternatives than Spring AOP
If you think about the way AOP annotations are dealt with by Spring this will be clear:
Spring takes your class and wraps it in a proxy with the extra code generated on the fly by the AOP annotation added. So only code called via the proxy (i.e from outside your class will be included).
Example
#Service
public class Foo {
public void doSomething() {
doSomethingInternal();
}
public void doSomethingInternal() {
}
}
If from another Spring bean I do this:
#Service
public class Bar {
#Autowired
private Foo foo;
public void execute() {
foo.doSomething();
}
}
Only doSomething will be called via the proxy which wraps your class, not doSomethingInternal, that will be called by your class.
Related
I have two classes
public class MyTest {
#Autowired
private MyService myService;
private void test() {
myService.writeToDb();
}
}
#Service
public class MyService {
#Transactional
public void writeToDb() {
// do db related stuff
}
}
I want to know if calling a method test() (which is a private method) from MyTest class would create a transaction.
P.S
I'm using Spring Boot. And Java 17.
It will work, whether you call the method of another object from a public or private method inside yours is an implementation detail. From callee's point of view, it's the same, it is not even aware of the caller's context.
Spring AOP uses the Proxy pattern to handle those scenarios. It means you are not directly receiving a MyService bean, but a MyServiceSpringCreatedProxy (not the actual name, check in debug mode and you'll see), which is actually handling transactions around methods.
So as long as the call passes through the Spring's proxy, the #Transactional will be accounted for as expected. Bear in mind that it doesn't mean a new transaction is open, it depends if another already exists and your configuration.
However, any self call (to a public or a private method) would not pass through the proxy and then #Transactional would not be working.
#Service
public class MyService {
// can be private, public or whatever
public void callRelatedStuff() {
//self call, no transactional work done
writeToDb();
}
#Transactional
public void writeToDb() {
// do db related stuff
}
}
I’d like to apply a customizable aspect on two different services (spring bean). My problem is how/where to set/define the pointcut expression. One normally defines the pointcut expression on a ‘dummy method’ or directly on the advice method. However that means the pointcut is static (not customizable).
I’d like to define the pointcut at the bean creation level to be able to create the same kind of advice for different targets. Ideally I’d like to do something like this:
#Aspect
public class ServiceAspect {
private static final Logger LOG = LoggerFactory.getLogger(ServiceAspect.class);
private final String discriminator;
// no advice defined here!!!
public ServiceAspect(String discriminator) { this.discriminator = discriminator; }
public Object around(ProceedingJoinPoint jp) throws Throwable {
LOG.info(discriminator + " called");
return jp.proceed();
}
}
#Configuration
#EnableAspectJAutoProxy
#PropertySource("classpath:application.properties")
public class ServiceConfiguration {
#Bean
public MyService service1() { return new MyServiceImpl(); }
#Bean
#Around("bean(service1)") // define the advice when bean is created
#ConditionalOnProperty("aspect1Enbaled")
public ServiceAspect aspect() {
return new ServiceAspect("Aspect-1");
}
#Bean
public YourService service2() { return new YourServiceImpl(); }
#Bean
#Around("bean(service2)") // define a different advice when bean is created
#ConditionalOnProperty("aspect2Enbaled")
public ServiceAspect aspect() {
return new ServiceAspect("Aspect-2");
}
}
Notice that the #Around annotation is on the definition of the bean. I can thus reuse the aspect for different target. Using the #ConditionalOnProperty, this would enable me to turn on/off individual aspect base on a property.
Can anyone help me with this? I suspect I’ll need to create some kind of factory but can’t seem to see how I can REPLACE an already defined bean (the service bean) with a proxy!
I have some construct I'd really love to use an aspect on. I want to let my RestController inherit from a class which yields special logging methods which
log to the standard logback output
fires a http request to a service which does stuff with the log
message as well (done by the aspect)
I created an annotation with which I mark the method I want to aspect so the pointcut cant filter it. Special case is that this method is declared within the parent class of the RestController.
The aspect is not running even tho IntelliJ is marking the method as being used by the aspect, which tells me the pointcut has to be working?
Please see my code and check what I've might missed out to get it to work.
ApplicationClass
#SpringBootApplication
#ComponentScan("com.xetra.experimental")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopTryoutApplication {
public static void main(String[] args) {
SpringApplication.run(AopTryoutApplication.class, args);
}
}
RestController
#RestController
public class Endpoint extends SimpleLogger {
#GetMapping("/endpoint")
public void doStuff(){
log("foo");
}
}
Parent class for RestController
public class SimpleLogger implements EndpointLogger{
#AspectAnnotation
public void log(String msg) {
System.out.println(msg);
}
}
Interface for parent class (heard that aspected methods need interfaces)
public interface EndpointLogger {
void log(String msg);
}
Annotation my aspect should pointcut to
#Inherited
public #interface AspectAnnotation {
}
Spring AOP aspect
#Component
#Aspect
public class TestAspect {
#Pointcut("#annotation(com.xetra.experimental.aoptryout.AspectAnnotation)")
public void methods() {
}
#Before("methods()")
public void beforeMethodExecution(JoinPoint jp) {
System.out.println("Aspect ran!!!!");
}
}
Due to the proxy-based nature of Spring’s AOP framework, calls within the target object are by definition not intercepted.
You can find more here.
The call to log method is not intercepted since it's made from the doStuff method belonging to the same target object.
Now, any call to log method will be intercepted as long it's made externally from another object (not the same target object).
Questions
So if I use SimpleLogger as a component and not a parent class within the Endpoint it will work aye?
Yes, you are right!
Is there any way to get this to work anyway? Like using AspectJ and not Spring AOP?
You can use AspectJ's source weaving to make it work. Here, is a working example.
I have this interface:
public interface FakeTemplate {
#CustomAnnotation
void foo() {
}
}
And this implementation of the interface:
#Component
public FakeImpl implements FakeTemplate {
#Override
public void foo() {
//Do Stuff
}
}
And this aspect:
#Aspect
#Component
public class CustomAspect {
#Before(value = "#annotation(com.fake.CustomAnnotation)")
public void doStuffBefore(JoinPoint joinPoint} {
}
}
I'm using spring with AspectJ enabled using: #EnableAspectJAutoProxy(proxyTargetClass = true)
My issue is that the aspect doStuffBefore method is not being called before the execution of FakeImpl's foo() method. It Does work when I put the #CustomAnnotation on FakeImpl instead of FakeTemplate, but I'd much prefer to put the annotation on FakeTemplate as it's in a separate API package and I've kind of delegated it as the place where I put all my annotations.
I'd also like to ensure that the CustomAnnotation is called on every class that implements FakeTemplate without remembering to put the annotation on all the implementation classes themselves.
Is there any way to get the advice to be called if the annotation is only on the interface class?
Annotation inheritance doesn't work for methods in java.
But you can use other pointcut expression, something like
execution(public * FakeTemplate+.foo(..))
There is sort of a workaround. In your example, you can add an extra 'default' method e.g. named fooFacade to your FakeTemplate interface, annotate that with your #CustomAnnotation, and then delegate to the 'real' foo method:
public interface FakeTemplate {
#CustomAnnotation
default void fooFacade() {
foo();
}
void foo();
}
Now whenever you call fooFacade(), execution of the pointcut on #CustomAnnotation will be triggered.
Here is my custom annotation AnnoLogExecTime and class AOP:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface AnnoLogExecTime {
}
#Aspect
#Service
public class AOP {
Logger logger = LoggerFactory.getLogger(AOP.class);
#Around("execution(#com.judking.general.aop.AnnoLogExecTime * *(..))")
public Object calExecTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
long t1 = System.currentTimeMillis();
Object obj = proceedingJoinPoint.proceed();
long t2 = System.currentTimeMillis();
logger.info("method `"+method.getName()+"` takes "+(t2-t1)+"ms");
return obj;
}
}
And the test case is as below:
#Service
class A {
public void go() {
B b = new B() { //Anonymous class B
#Override
public void exec() {
aopMethod();
}
};
b.exec();
}
#AnnoLogExecTime
public void aopMethod() {
System.out.println("aopMethod");
}
}
#Service
class B {
public void exec() {
System.out.println("exec");
}
}
When I call a.aopMethod(), the AOP.calExecTime is hooked up to a.aopMethod().
But if I call a.go(), which is using anonymous class B instance to call a.aopMethod(), then the AOP.calExecTime is NOT hooked up to a.aopMethod().
Could anyone give me an explanation to this phenomenon? And please give me a way to resolve this problem in the case of anonymous class. Thanks a lot!
This is not exactly because it is an anonymous inner class. What you are experiencing is a limitation of AOP proxies.
When you have
A a = ...; // get proxy
The proxy itself wraps the actual instance in a wrapper instance. When you interact with this wrapper instance by calling
a.aopMethod();
the proxy interceptor intercepts the call and can execute the advice.
This would apply to you calling
a.go()
if there was a joinpoint. Instead nothing intercepts that call, and the call to go() goes through the interceptor and the method is called on the actual instance
actualA.go();
When you create the anonymous inner class and have
#Override
public void exec() {
aopMethod();
}
it's implicitly doing
#Override
public void exec() {
A.this.aopMethod();
}
which goes around the proxy because you are calling it on the actual instance, not the wrapper.
You might not be using Spring to generate your proxies, but their documentation explains this pretty well.