Why can't Spring AOP determine if proxy should be made for #target? [duplicate] - spring

When using spring AOP with class level annotations, spring context.getBean seems to always create and return a proxy or interceptor for every class, wether they have the annotation or not.
This behavior is only for class level annotation. For method level annotations, or execution pointcuts, if there is no need for interception, getBean returns a POJO.
Is this a bug? As designed? Or am I doing something wrong?
#Component
#Aspect
public class AspectA {
#Around("#target(myAnnotation)")
public Object process(ProceedingJoinPoint jointPoint, MyAnnotation myAnnotation) throws Throwable {
System.out.println(
"AspectA: myAnnotation target:" + jointPoint.getTarget().getClass().getSimpleName());
System.out.println(" condition:" + myAnnotation.condition());
System.out.println(" key:" + myAnnotation.key());
System.out.println(" value:" + myAnnotation.value());
return jointPoint.proceed();
}
}
#Component("myBean2")
//#MyAnnotation(value="valtest-classLevel2", key="keytest-classLevel2", condition="contest-classLevel2")
public class MyBean2 {
public Integer testAspectCallInt(int i) {
System.out.println("MyBean2.testAspectCallInt(i=" + i + ")");
return i + 1000;
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
public #interface MyAnnotation {
String value() default "";
String key() default "";
String condition() default "";
}
#ComponentScan()
#EnableAspectJAutoProxy
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(Test.class);
MyBean2 bean = (MyBean2) ctx.getBean("myBean2");
System.out.println(bean.getClass()); // prints CGLIB proxy, even when annotation is commented out on class
bean.testAspectCallInt(12); // calling method
}
}

Andy Brown is right, it is by design. The reason is that according to the AspectJ manual pointcut designators such as #args, #this, #target, #within, #withincode, and #annotation (or the subset of those available in Spring AOP) are used to match based on the presence of an annotation at runtime. This is why in the Spring debug log you see that proxies are created for all components which might need aspect functionality.
If you want to avoid that, you can refactor your aspect into something like this at the cost of an uglier pointcut and even uglier reflection in the advice code:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
#Component
#Aspect
public class AspectA {
#Around("execution(* (#MyAnnotation *).*(..)) || execution(#MyAnnotation * *(..))")
public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
MyAnnotation myAnnotation = null;
for (Annotation annotation : ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaredAnnotations()) {
if (annotation instanceof MyAnnotation) {
myAnnotation = (MyAnnotation) annotation;
break;
}
}
if (myAnnotation == null) {
myAnnotation = joinPoint.getTarget().getClass().getAnnotationsByType(MyAnnotation.class)[0];
}
System.out.println("AspectA: myAnnotation target:" + joinPoint.getTarget().getClass().getSimpleName());
System.out.println(" condition:" + myAnnotation.condition());
System.out.println(" key:" + myAnnotation.key());
System.out.println(" value:" + myAnnotation.value());
return joinPoint.proceed();
}
}
If neither the bean's class nor any of its methods bears the annotation, no proxy will be created. The advice detects both types of annotation, but prefers a method annotation if both are present.
Update: Instead of this workaround you could of course use full AspectJ from within Spring and avoid proxies altogether.

Related

advice is not executing if the annotations contains parameters

I'm trying to get some advice to execute.
When I use annotation without parameters its do execute but when the annotation includes parameters it's not.
#Aspect
class a{
#Pointcut("execution(#com.annotations.AnnotationName* *(..))")
void someMethod() {}
#Around("someMethod()")
public Object aroundSomeMethod(ProceedingJoinPoint pjp) throws Throwable
{
// some code
}
}
Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface AnnotationName
{
public String someString();
public boolean someBoolean();
}
The use of the annotation:
#AnnotationName(
someString= "string",
someBoolean = false
)
private void mycode()
{//code }
Following aspect code would advice a target method annotated with #AnnotationName
#Component
#Aspect
public class SomeMethodAspect {
#Pointcut("#annotation(annotationName) && within(so.qn69016852..*)")
private void someMethod(AnnotationName annotationName) {}
#Around("someMethod(annotationName)")
public Object aroundSomeMethod(ProceedingJoinPoint pjp,AnnotationName annotationName) throws Throwable
{
System.out.println(annotationName.someString());
System.out.println(annotationName.someBoolean());
return pjp.proceed();
}
}
Couple of corrections/observations .
Spring AOP cannot advice a private method of a Spring bean. The mycode() method should be in a bean and ideally public. ( Refer )
The Aspect should also be a spring bean. This can be achieved by annotating the aspect with #Component
Remember to limit the scope : https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#writing-good-pointcuts
You may also go through this answer from #kriegaex to understand why an #annotation has a global scope.
Update :
The code shared by OP also works with modifying a typo ( a space between the AnnotationName and * in the pointcut expression ) . The observations shared earlier holds good here as well.
#Component
#Aspect
public class SomeMethodAspect {
#Pointcut("execution(#so.qn69016852.anno.AnnotationName * so.qn69016852..*.*(..))")
private void someMethod() {}
#Around("someMethod() && #annotation(annotationName)")
public Object aroundSomeMethod(ProceedingJoinPoint pjp,AnnotationName annotationName) throws Throwable
{
System.out.println(annotationName.someBoolean());
System.out.println(annotationName.someString());
return pjp.proceed();
}
}

Spring AOP not working for Feign Client

I having an aop-setup
#Target({ElementType.METHOD})
#Retention(value = RetentionPolicy.RUNTIME)
public #interface IgnoreHttpClientErrorExceptions { }
#Aspect
#Component
public class IgnoreHttpWebExceptionsAspect {
#Around(value = "#annotation(annotation)", argNames = "joinPoint, annotation")
public Object ignoreHttpClientErrorExceptions(ProceedingJoinPoint joinPoint, IgnoreHttpClientErrorExceptions annotation)
throws Throwable {
try {
//do something
} catch (HttpClientErrorException ex) {
//do something
}
}
If I add this annotation(#IgnoreHttpClientErrorExceptions) in service layer,
#Service
public class SentenceServiceImpl implements SentenceService {
#Autowired
VerbClient verbClient;
#HystrixCommand(ignoreExceptions = {HttpClientErrorException.class})
#IgnoreHttpClientErrorExceptions
public ResponseEntity<String> patch(String accountId, String patch) {
return verbClient.patchPreferences(accountId, patch);
}
}
My AOP is invoked.
But when I add this annotation(#IgnoreHttpClientErrorExceptions) in my feign layer.
#FeignClient(value = "account")
#RequestMapping(value = "/url")
public interface VerbClient {
#RequestMapping(value = "/{id}/preferences", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE)
#IgnoreHttpClientErrorExceptions
ResponseEntity<String> patchPreferences(#PathVariable("id") String accountId, String patchJson);
}
AOP is not invoked.
Any idea why aop is not get invoked, when I add the annotation in feign-layer?
Dependency added:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Annotation on method is not supposed to inherited.
Hence spring AOP cannot intercept your methods.
Event #Inherited only support inheritance from superclass to subclasses.
So in this case, you should try another pointcut, depend on your need:
// Match all method in interface VerbClient and subclasses implementation
#Around(value = "execution(* com.xxx.VerbClient+.*(..))")
// Match all method in interface VerbClient and subclasses implementation
#Around(value = "execution(* com.xxx.VerbClient+.*(..))")
// Match all method `patchPreferences` in interface VerbClient and subclasses implementation
#Around(value = "execution(* com.xxx.VerbClient+.patchPreferences(..))")
// Or make IgnoreHttpClientErrorExceptions work for Type,
// and match all method with in annotated interface and subclass implementation
// (#Inherited must be used)
// By this way, you can mark your VerbClient feign interface with this annotation
#Around(value = "execution(* (com.yyy.IgnoreHttpClientErrorExceptions *+).*(..))")

Regarding Struts2 Spring AOP Logger

In Strtus2 Action class I am having methods with name like getData(), getDetails(). Along with these I am having several getter/setters in my action class.
I want to use Spring's AOP functionality for logging purpose. I need to maintain Log for getData() and getDetails() method which in turn are calling the service class but I don't want to maintain Log for the getter/setters which are present in the action class.
I don't want to hard code method name in my Aspect. Don't have any clue of how to implement this. Please help me out.
My AOP Logger class is like :
#Before("myPointCut() && allService()")
public void logEntryService(JoinPoint joinPoint) {
System.out.println("Start of " + joinPoint.getSignature().getName() + "() of "+joinPoint.getThis());
start = System.currentTimeMillis();
}
#After("myPointCut() && allService()")
public void logExitService(JoinPoint joinPoint) {
System.out.println("End of " + joinPoint.getSignature().getName() + "() of "+joinPoint.getThis());
start = System.currentTimeMillis();
}
#Pointcut("#annotation(com.ing.trialbal.log.LogThis)")
public void myPointCut() {
System.out.println("*************My PointCut");
}
#Pointcut("execution(* com.ing.trialbal.*.*.*(..))")
public void allService() {
System.out.println("************All service");
}
LogThis.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LogThis {
}
But this has created very tight coupling in my application as I always have to write #Logthis on every method in service and dao for having their logs.
Pointcut bound with #annotation().
Something like
Annotation class
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface Loggable {}
AOP Logger
#Pointcut("#annotation(com.Loggable)") // && execution( ... )
Action class
#Loggable
Object getData() {}
#Loggable
Object getDetails() {}

How do I pass arguments to Spring AOP advice with annotated parameters?

I am using Spring 3.1.2.RELEASE with cglib load-time weaving and I am trying to get advice to work with a method that has custom annotations and annotated parameters.
Advice:
#Aspect
public class MyAdvice
{
#Around("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal(*), ..)) " +
"&& args(batch) && #args(propertyToLock)"
public Object lockAndProceed(ProceedingJoinPoint pjp, Object batch, LockVal propertyToLock) throws Throwable {
//Do stuff....
pjp.proceed();
}
}
Here is the class that I am testing:
public interface UpdateManager
{
public void processUpdate(MyBatchObject batch);
}
public class UpdateManagerImpl implements UpdateManager
{
#Lock
public void processUpdate(#LockVal("lockValue") MyBatchObject batch)
{
//Do stuff...
}
}
The problem is that I can't get the advice to execute. If I remove the #args and args conditions in the pointcut, the advice fires, but then I have to dig through the ProceedingJoinPoint to get the parameter that I need.
Why isn't the advice firing? Did I do something wrong?
Edit: The following pointcut DOES WORK as a standalone program with Spring:
#Aspect
public class MyAdvice
{
#Around("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal(*), ..)) " +
"&& args(batch)"
public Object lockAndProceed(ProceedingJoinPoint pjp, Object batch) throws Throwable {
//Do stuff....
pjp.proceed();
}
}
However, it does NOT work under JBoss 6 using load-time weaving. I suppose my question should be, then, why does it work as a standalone program but not under JBoss 6?
Update: I forgot to mention that #args() is not meant to match a parameter's annotation, but a parameter type's annotation, which is not what you want and which thus I do not use here.
You cannot bind a parameter's annotation via args(), only the parameter itself. This means that you can only access the parameter's annotation via reflection. You need to determine the method signature, create a Method object from it and then iterate over the method parameters' annotations. Here is a full code sample:
package com.mycompany.locking;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Lock {}
package com.mycompany.locking;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface LockVal {
String value() default "";
}
package com.mycompany;
public class MyBatchObject {}
package com.mycompany;
public interface UpdateManager {
public void processUpdate(MyBatchObject batch);
}
package com.mycompany;
import com.mycompany.locking.Lock;
import com.mycompany.locking.LockVal;
public class UpdateManagerImpl implements UpdateManager {
#Lock
#Override
public void processUpdate(#LockVal("lockValue") MyBatchObject batch) {
System.out.println("Processing update");
}
public static void main(String[] args) {
UpdateManager updateManager = new UpdateManagerImpl();
updateManager.processUpdate(new MyBatchObject());
}
}
package com.mycompany.aop;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import com.mycompany.MyBatchObject;
import com.mycompany.locking.LockVal;
#Aspect
public class MyAspect {
#Pointcut("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal (*), ..)) && args(batch)")
public void lockedMethod(MyBatchObject batch) {}
#Around("lockedMethod(batch)")
public Object lockAndProceed(ProceedingJoinPoint pjp, MyBatchObject batch) throws Throwable {
System.out.println(pjp);
System.out.println(batch);
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Class<?> clazz = methodSignature.getDeclaringType();
Method method = clazz.getDeclaredMethod(methodSignature.getName(), methodSignature.getParameterTypes());
LockVal propertyToLock;
for (Annotation ann : method.getParameterAnnotations()[0]) {
if(LockVal.class.isInstance(ann)) {
propertyToLock = (LockVal) ann;
System.out.println(propertyToLock.value());
}
}
return pjp.proceed();
}
}
When I run UpdateManagerImpl.main, I see the following output, just as expected:
execution(void com.mycompany.UpdateManagerImpl.processUpdate(MyBatchObject))
com.mycompany.MyBatchObject#86f241
lockValue
Processing update
Disclaimer: I am not a Spring guy, I just tested this with plain AspectJ, not Spring AOP.
This is not a solution, but should take you a step further:
I am assuming you made a typo in your annotations, you probably meant #Aspect and not #Advice?
The suggestion that I have would be to try out these:
a. Separate out into named point cuts and the advice that you want to apply on the pointcut:
#PointCut("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal(*), ..)) && args(batch) && #args(propertyToLock)")
public void mypointcut(Object batch, LockVal propertyToLock){}
#Around("mypointcut(batch, propertyToLock)"
public Object lockAndProceed(ProceedingJoinPoint pjp, Object batch, LockVal propertyToLock) throws Throwable {
//Do stuff....
pjp.proceed();
}
b. It could be that either args expression or #args expression is causing the issue - try keeping one and removing other and seeing which combination works.
c. If this does not narrow things down, one more option could be to explicitly add an argNames expression also, it could be that the argument names are being cleaned out and not being matched up by name at runtime:
#PointCut("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal(*), ..)) && args(batch) && #args(propertyToLock) && argNames="batch,test1,test2")
public void mypointcut(Object batch, LockVal propertyToLock){}

AOP Spring Before Advice not working

The method DefaultProduitGeneriqueService.valider is not catched by the method traceWhenReturnedValueDoesntExistOrNotNecessary and I don't understand why?
package fr.generali.nova.atp.service.metier.impl;
public class DefaultProduitGeneriqueService extends DefaultService implements IProduitGeneriqueService, IBacAware {
...
#Override
#Traceable(value = ETraceableMessages.VALIDATION_PRODUIT_GENERIQUE, hasReturnedValue=Traceable.HAS_NOT_RETURNS_VALUE)
public void valider(ElementNiveauUn element) {
...
}
...
}
package fr.generali.nova.atp.logging.advisor;
#Aspect
public class TraceableAdvisor {
#Before(value = "execution(* fr.generali.nova.atp.service.metier.impl.*.*(..)) && #annotation(traceable) && args(element)", argNames = "element,traceable")
public void traceWhenReturnedValueDoesntExistOrNotNecessary(ElementNiveauUn element, Traceable traceable) {
...
}
}
Assuming that the service interfaces are in package fr.generali.nova.atp.service.metier.api:
package fr.generali.nova.atp.service.metier.api;
public interface IProduitGeneriqueService {
void valider(ElementNiveauUn element);
}
And the service implementations are in package fr.generali.nova.atp.service.metier.impl:
package fr.generali.nova.atp.service.metier.impl;
public class DefaultProduitGeneriqueServiceImpl implements IProduitGeneriqueService {
#Override
#Traceable(value = ETraceableMessages.VALIDATION_PRODUIT_GENERIQUE, hasReturnedValue=Traceable.HAS_NOT_RETURNS_VALUE)
public void valider(ElementNiveauUn element) {
// TODO: implement
}
}
Your aspect should look like this:
package fr.generali.nova.atp.logging.advisor;
#Aspect
public class TraceableAdvisor {
#Before(value = "execution(* fr.generali.nova.atp.service.metier.api.*.*(..)) && #annotation(traceable) && args(element)", argNames = "element,traceable")
public void traceWhenReturnedValueDoesntExistOrNotNecessary(ElementNiveauUn element, Traceable traceable) {
// TODO: implement
System.err.println("traced...");
}
}
The default proxy strategy for Spring AOP is JDK interface-based proxies, so Your pointcut expression should match the interface method execution, not the implementation method execution, and Your poincut expression may match either interface mothod execution or implementation method execution.
And remember to include an AspectJAutoProxyCreator in your configuration using for example <aspectj-autoproxy /> tag.
And here is a simple test to prove everyting is working:
public class TraceableAdvisorTest {
#Configuration
public static class TestConfiguration {
#Bean
public IProduitGeneriqueService produitGeneriqueService() {
return new DefaultProduitGeneriqueServiceImpl();
}
#Bean
public TraceableAdvisor traceableAdvisor() {
return new TraceableAdvisor();
}
#Bean
public AnnotationAwareAspectJAutoProxyCreator autoProxyCreator() {
return new AnnotationAwareAspectJAutoProxyCreator();
}
}
private AnnotationConfigApplicationContext testApplicationContext;
#Test
public void testTraceWhenReturnedValueDoesntExistOrNotNecessary() {
this.testApplicationContext = new AnnotationConfigApplicationContext();
this.testApplicationContext.register(TestConfiguration.class);
this.testApplicationContext.refresh();
IProduitGeneriqueService service = BeanFactoryUtils.beanOfType(this.testApplicationContext, IProduitGeneriqueService.class);
System.err.println("BEFORE");
service.valider(null);
System.err.println("AFTER");
}
}
The err output is:
BEFORE
traced...
AFTER
For all combinations:
fr.generali.nova.atp.service.metier.api.*.*(..)
fr.generali.nova.atp.service.metier.impl.*.*(..)
fr.generali.nova.atp.service.metier..*.*(..)
Make sure both beans are properly configured, either through annotations or in your appCtx.
It looks like your Aspect is definitely right, but how about the other class? Is it Spring enabled?
Also, if both classes are indeed configured correctly, are you sure that the instance being passed is a Spring bean and not a "new" instance from a constructor?

Resources