I use spring-aop to make some treatments on my services methods. The methods on wich the treatment must occur are annotated with #History. Moreover, the annotation can have some params like "comment". Here is on exemple :
#Service
public class MyServiceImpl implements IMyService {
#Override
#History(comment = "my comment")
public void myMethod() {...}
}
public interface IMyService {
void create();
}
And, I have a aspect defined like this :
#Aspect
#Component
public class MyHistoryAspect {
#AfterReturning(pointcut = "execution(* my.service.package.*(..)) && #annotation(history)", returning = "result")
public void myTreatment(JoinPoint joinPoint, History history, Object result) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
...
}
}
Now, my problem : when I use reflection to find out the value of "comment" in my aspect, I can't find it. The reason : the method is the method signature of IMyService, not the method signature of MyServiceImpl. And if I put my annotation on the interface instead of the service, my Aspect is never reached.
Am I missing something or is it the normal behavior of spring aop ?
Thank you
Related
I have my custom annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface CutomAnnotation{
String value() default "";
}
My aspect class looks like this:
#Aspect
#Component
public class MyCustomAspect{
#Around("#annotation(com.forceframework.web.handlers.monitoring.MeterRegTimer)")
public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("Timer started: "+joinPoint.getSignature());
Object objToReturn=joinPoint.proceed();
System.out.println("Timer ended: "+joinPoint.getSignature());
return objToReturn;
}
}
The place I use the annotation in a controller class:
#CustomAnnotation(value="timer")
#GetMapping(value="/test")
public ResponseEntity test() {}
I would like to know can I access the value passed from my CustomAnnotation in the around advice method aroundJoinPoint in MyCustomAspect class.
Your advice should be declared as shown below:
#Around("#annotation(customAnnotationArgumentName)")
public Object aroundJoinPoint(ProceedingJoinPoint joinPoint, CustomAnnotation customAnnotationArgumentName) throws Throwable {
// ...
}
See documentation for more info.
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
CutomAnnotation methodAnnotation = method.getDeclaredAnnotation(CutomAnnotation.class);
I just tried an #Around for a method defined with my repository interface.
#Repository
public interface MyRepository {
Page<Some> getAllSome();
}
With my Aspect I can't specify the getAllSome method.
#Aspect
#Component
public class MyRepositoryAspect {
#Around("execution(* my.package.MyRepository.*(..))") // works
//#Around("execution(* my.package.MyRepository.getAllSome(..))") // does't work
public Object aroundGetAllSome(ProceedingJoinPoint joinPoint) throws Throwable {
}
}
Why the version with .getAllSome(..) doesn't work? How can I match a specific method?
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();
}
}
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 *+).*(..))")
I'm trying to get the value of an annotation via Spring Aop AspectJ-style, where the annotation can be on the class OR the method. I tried a lot of different things, but I can only get it to work when the annotation is on the method. I'd really like to annotate ONCE on the class - but advice all the methods of the class - and access the value of the class annotation in the advice. Here's where I've ended up:
Annotation:
#Inherited
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {
String value() default "";
}
Aspect:
#Aspect
public class MyAspect {
#Pointcut("execution(#com.myco.MyAnnotation * com.myco.somepackage..*.*(..))")
public void atExecution() { }
#Before("atExecution() && #annotation(myAnnotation)")
public void myAdvice(JoinPoint joinPoint, MyAnnotation myAnnotation) {
...
}
}
Any thoughts? Thanks.
Short answer
While you can formulate a pointcut that will match both directly annotated methods and methods of annotated types at the same time, you cannot make a pointcut and/or advice where you bind the value of the annotation (i.e. use the annotation value in the advice code).
#Aspect
public class MyAspect {
#Pointcut("execution(#com.myco.MyAnnotation * com.myco.somepackage..*.*(..))")
public void atExecutionOfAnnotatedMethod() {}
#Pointcut("execution(* (#com.myco.MyAnnotation com.myco.somepackage..*).*(..))")
public void atExecutionOfMethodsOfAnnotatedClass() {}
#Before("atExecutionOfAnnotatedMethod() && #annotation(myAnnotation)")
public void myAdviceForMethodAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
System.out.println("myAdviceForMethodAnnotation: " + myAnnotation.value());
}
#Before("atExecutionOfMethodsOfAnnotatedClass() && #this(myAnnotation)")
public void myAdviceForTypeAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
System.out.println("myAdviceForTypeAnnotation: " + myAnnotation.value());
}
// /* the following pointcut will result in "inconsistent binding" errors */
// #Pointcut("(atExecutionOfAnnotatedMethod() && #annotation(myMethodAnnotation)) || (atExecutionOfMethodsOfAnnotatedClass() && #this(myTypeAnnotation))")
// public void combinedPointcut(MyAnnotation myMethodAnnotation, MyAnnotation myTypeAnnotation) {}
}
Some detail
To combine the two separate pointcuts (atExecutionOfAnnotatedMethod and atExecutionOfMethodsOfAnnotatedClass) we would have to use the OR (||) construct. Since the OR construct doesn't guarantee that either of the two annotation bindings will be present at advice execution, they will both result in a compile error (inconsistent binding).
You can still handle both cases in separate advices, you may also delegate the actual advice code to a common method to avoid duplication. In that case you'll need to take care of the case where both the type and the method is annotated with #MyAnnotation because that would match both pointcuts and would result in your method doubly advised by both advices, hence your common advice handling code will execute twice.
Combining the two
If you need to combine the two cases while defending against doubly advising the target code, you need to set up a precedence between the method level annotation and the class level annotation. Based on the principle of specificity, I'd suggest to go on the route where the method level annotation takes precedence over the class level one. Your aspect would look like this:
#Aspect
public class MyCombinedAspect {
#Pointcut("execution(#com.myco.MyAnnotation * com.myco.somepackage..*.*(..))")
public void atExecutionOfAnnotatedMethod() {}
#Pointcut("execution(* (#com.myco.MyAnnotation com.myco.somepackage..*).*(..))")
public void atExecutionOfMethodsOfAnnotatedClass() {}
#Before("atExecutionOfAnnotatedMethod() && !atExecutionOfMethodsOfAnnotatedClass() && #annotation(myAnnotation)")
public void myAdviceForMethodAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
handleBeforeExecution(joinPoint, myAnnotation);
}
#Before("atExecutionOfMethodsOfAnnotatedClass() && !atExecutionOfAnnotatedMethod() && #this(myAnnotation)")
public void myAdviceForTypeAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
handleBeforeExecution(joinPoint, myAnnotation);
}
#Before("atExecutionOfMethodsOfAnnotatedClass() && atExecutionOfAnnotatedMethod() && #annotation(myMethodAnnotation)")
public void myAdviceForDoublyAnnotated(JoinPoint joinPoint, MyAnnotation myMethodAnnotation) {
handleBeforeExecution(joinPoint, myMethodAnnotation);
}
protected void handleBeforeExecution(JoinPoint joinPoint, MyAnnotation myAnnotation) {
System.out.println(myAnnotation.value());
}