I am new to Spring-boot and AOP. I am trying to log the exceptions raised in my spring-boot application. What exactly i am trying to do is whenever any method in my application classes raises the runtime exception, i am logging it to console.
So i created an aspect with #AfterThrowing annotation. To check whether it is working i have intentionally written a line of code which will raise / by zero exception. I tested with it but this advice method is not working.
Following is my test code:
package com.sware.SpringBoot_JPA_MySQL_Gradle_Project.aspects;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import com.sware.SpringBoot_JPA_MySQL_Gradle_Project.domains.User;
#Aspect
#Component
public class UserAspect {
#Before("execution(* com.sware.SpringBoot_JPA_MySQL_Gradle_Project.services.UserService.saveUserToDb(com.sware.SpringBoot_JPA_MySQL_Gradle_Project.domains.User)) && args(user)")
public void beforeUserSave(User user) {
System.out.println("++++++++++++++++++++++++++++++Creating UserBefore Pointcut: \n"+user.toString());
}
#After("execution(* com.sware.SpringBoot_JPA_MySQL_Gradle_Project.services.UserService.saveUserToDb(com.sware.SpringBoot_JPA_MySQL_Gradle_Project.domains.User)) && args(user)")
public void aftereUserSave(User user) {
System.out.println("++++++++++++++++++++++++++++++Creating User After pointcut: \n"+user.toString());
}
#AfterThrowing(pointcut = "execution(* com.sware.SpringBoot_JPA_MySQL_Gradle_Project.services.*.*(..))", throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("Okay - we're in the handler...");
/*Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
String stuff = signature.toString();
String arguments = Arrays.toString(joinPoint.getArgs());
System.out.println("**************************EXCEPTION: "
+ methodName + " with arguments "
+ arguments + "\nand the full toString: " + stuff + "\nthe exception is: "
+ e.getMessage());*/
}
}
i have tried multiple permutations of pointcut expression like bellow,but no one works:
#AfterThrowing(pointcut = "execution(* com.sware.SpringBoot_JPA_MySQL_Gradle_Project.services.*.*(..))", throwing = "e")
#AfterThrowing(pointcut = "execution(* *.*(..))", throwing = "e") // Application fails to start throws some internal null pointer exception
#AfterThrowing(pointcut = "execution(* com.sware.*.*.*.*(..))", throwing = "e")
My Class Code:
package com.sware.SpringBoot_JPA_MySQL_Gradle_Project.services;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sware.SpringBoot_JPA_MySQL_Gradle_Project.domains.User;
import com.sware.SpringBoot_JPA_MySQL_Gradle_Project.projections.UserWithFnameAndLname;
import com.sware.SpringBoot_JPA_MySQL_Gradle_Project.repositories.UserRepository;
#Service
public class UserService {
#Autowired
UserRepository userRepository;
public List<User> getAllUsers(){
List<User> userList=new ArrayList<>();
try {
Integer n=10/0;
userList=userRepository.findAll();
} catch (Exception e) {
System.out.println("Exception in UserRepostory:"+e.getMessage());
}
return userList;
}
}
Here, myAfterThrowing method is not getting call though /by zero is thrown at run time. Can anyone tell me where i am going wrong?
Method does not not throw any exception. The "try-catch" block basically absorbs the exception. Move "Integer n=10/0;" outside of the try-catch
Related
This is my Globalexceptionhandlerclass.java. I am trying to write JUnit 5 test cases, but getting stuck. Can anyone help me on this please?
Globalexceptionhandlerclass.java
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
protected final Log loger = LogFactory.getLog(ResponseEntityExceptionHandler.class);
#ExceptionHandler(Exception.class)
ResponseEntity<?> handleAllExceptions(Exception ex, WebRequest request ) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("date", new Date());
result.put("message", ex.getMessage());
result.put("details", request.getDescription(true));
loger.error(ex);
ResponseEntity<?> responseEntity = ResponseEntity.badRequest()
.header("exception-erro", "error")
.body(result);
return responseEntity;
}
}
This is my GlobalExceptionHandlerTest.java. I got stuck on this, it is failing. I tried other things but it is not working. The last two lines are failing, I don't know why. Anyone please help me to corect this cases. It will be very helpful to me.
import javax.servlet.http.HttpServletRequest;
#ExtendWith(MockitoExtension.class)
class ExceptionHandlerControllerAdviceTest {
/**
* Given a handle invalid exception when controller advice then return a bad request exception.
*/
#Test
void handleInvalidFormatException() {
GlobalExceptionHandler controllerAdvice = new GlobalExceptionHandler();
ResponseEntity<?> response = controllerAdvice.handleAllExceptions(null, null);
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode().value());
}
}
handleAllExceptions is not null-safe because of this line result.put("message", ex.getMessage()); and you passing ex with null value in your test controllerAdvice.handleAllExceptions(null, null).
This test has no reason to be because Spring always provides you Exception and WebRequest. So calling handleAllExceptions(null, null) is not possible in Spring environment.
in my spring boot application, I have been using one external commons library for handling exceptions. The external library has an aspect defined for the same something like below:
#Aspect
#Order(0)
public class InternalExceptionAspect {
public InternalExceptionAspect() {
}
#Pointcut("#within(org.springframework.stereotype.Service)")
public void applicationServicePointcut() {
}
#AfterThrowing(
pointcut = "applicationServicePointcut()",
throwing = "e"
)
public void translate(JoinPoint joinPoint, Throwable e) {
String resourceId = this.getResourceId();
if (e instanceof BadInputException) {
BadInputException inputException = (BadInputException)e;
throw new BadRequestAlertException(inputException.getErrorCode().getDefaultMessage(), inputException.getMessage(), inputException.getErrorCode().getHttpStatusCode(), resourceId, inputException.getErrorCode().getCode());
} else if (!(e instanceof BadServerStateException) && !(e instanceof InternalException)) {
String message;
if (e instanceof JDBCException) {
...
throw new BadServerStateException(message, resourceId, "20");
} else {
...
throw new BadServerStateException(message, resourceId, "10");
}
} else {
InternalException serverStateException = (InternalException)e;
throw new BadServerStateException(serverStateException.getErrorCode().getDefaultMessage(), serverStateException.getMessage(), resourceId, serverStateException.getErrorCode().getHttpStatusCode(), serverStateException.getErrorCode().getCode(), serverStateException.getErrorCode().getErrorType().name());
}
}
String getResourceId() {
RequestHeaders requestHeaders = RequestResponseContext.getRequestHeaders();
return requestHeaders.getResourceId();
}
}
Here I would like to introduce another else if block in order to handle DuplicateKeyException for my application.
The problem is, the above code, being part of the commons library is being used by multiple other applications. But, I would like to do the change to be applied only in my application.
I have been thinking to inherit the Aspect class something like below, inside my application:
#Aspect
#Order(0)
public class MyInternalExceptionAspect extends InternalExceptionAspect {
public MyInternalExceptionAspect() {
}
#Pointcut("#within(org.springframework.stereotype.Service)")
public void applicationServicePointcut() {
}
#AfterThrowing(
pointcut = "applicationServicePointcut()",
throwing = "e"
)
public void translate(JoinPoint joinPoint, Throwable e) {
if(e instanceof DuplicateKeyException) {
...
}
super.translate(joinpoint, e);
}
}
But, I am not sure, if this is the correct approach to do this. Could anyone please help here regarding what would be the best approach to achieve this? Thanks.
You cannot extend a concrete aspect using class MyInternalExceptionAspect extends InternalExceptionAspect. It will cause an exception in Spring AOP:
...AopConfigException:
[...MyInternalExceptionAspect] cannot extend concrete aspect
[...InternalExceptionAspect]
Only abstract aspects are meant to be extended.
But you can simply create a new aspect without inheritance and make sure that it has a lower priority than the original aspect.
Why lower priority?
Acording to the #Order javadoc, "lower values have higher priority".
You want your own aspect's #AfterReturning advice to kick in before the original aspect possibly transforms the exception of interest into something else, before you have a chance to handle it. But according to the Spring manual: "The highest precedence advice runs first "on the way in" (so, given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so, given two pieces of after advice, the one with the highest precedence will run second).".
Therefore, your own aspect should have #Order(1), giving it lower priority than the original aspect, but making the #AfterThrowing advide run before the original one. Sorry for the reverse logic, even though it makes sense. You just need to be aware of it.
Here is an MCVE, simulating your situation in a simplified way:
package de.scrum_master.spring.q69862121;
import org.springframework.stereotype.Service;
#Service
public class MyService {
public void doSomething(Throwable throwable) throws Throwable {
if (throwable != null)
throw throwable;
}
}
package de.scrum_master.spring.q69862121;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
#SpringBootApplication
#Configuration
public class DemoApplication {
private static final Logger log = LoggerFactory.getLogger(DemoApplication.class.getName());
public static void main(String[] args) throws Throwable {
try (ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args)) {
doStuff(appContext);
}
}
private static void doStuff(ConfigurableApplicationContext appContext) {
MyService myService = appContext.getBean(MyService.class);
List<Throwable> throwables = Arrays.asList(
null, // No exception -> no aspect should kick in
new Exception("oops"), // Not covered by any aspects -> no translation
new IllegalArgumentException("uh-oh"), // Original aspect translates to RuntimeException
new NullPointerException("null"), // Custom aspect translates to RuntimeException
new ArithmeticException("argh") // Custom aspect translates to IllegalArgumentException,
// then original aspect translates to RuntimeException
);
for (Throwable originalThrowable : throwables) {
try {
myService.doSomething(originalThrowable);
}
catch (Throwable translatedThrowable) {
log.info(translatedThrowable.toString());
}
}
}
}
As you can see, the application calls the service, the first time with null, not causing any exception, then with several types of exceptions the aspects are meant to either ignore or translate.
package de.scrum_master.spring.q69862121;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
#Aspect
#Component
#Order(0)
public class InternalExceptionAspect {
private static final Logger log = LoggerFactory.getLogger(InternalExceptionAspect.class.getName());
#AfterThrowing(pointcut = "#within(org.springframework.stereotype.Service)", throwing = "e")
public void translate(JoinPoint joinPoint, Throwable e) {
log.info(joinPoint + " -> " + e);
if (e instanceof IllegalArgumentException)
throw new RuntimeException("Transformed by InternalExceptionAspect", e);
}
}
package de.scrum_master.spring.q69862121;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
#Aspect
#Component
#Order(1)
public class MyInternalExceptionAspect {
private static final Logger log = LoggerFactory.getLogger(MyInternalExceptionAspect.class.getName());
#AfterThrowing(pointcut = "#within(org.springframework.stereotype.Service)", throwing = "e")
public void translate(JoinPoint joinPoint, Throwable e) {
log.info(joinPoint + " -> " + e);
if (e instanceof NullPointerException)
throw new RuntimeException("Transformed by MyInternalExceptionAspect", e);
if (e instanceof ArithmeticException)
throw new IllegalArgumentException("Transformed by MyInternalExceptionAspect", e);
}
}
The console log proves that everything works as expected, also with regard to invocation order:
d.s.s.q.MyInternalExceptionAspect : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable)) -> java.lang.Exception: oops
d.s.s.q69862121.InternalExceptionAspect : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable)) -> java.lang.Exception: oops
d.s.spring.q69862121.DemoApplication : java.lang.Exception: oops
d.s.s.q.MyInternalExceptionAspect : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable)) -> java.lang.IllegalArgumentException: uh-oh
d.s.s.q69862121.InternalExceptionAspect : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable)) -> java.lang.IllegalArgumentException: uh-oh
d.s.spring.q69862121.DemoApplication : java.lang.RuntimeException: Transformed by InternalExceptionAspect
d.s.s.q.MyInternalExceptionAspect : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable)) -> java.lang.NullPointerException: null
d.s.s.q69862121.InternalExceptionAspect : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable)) -> java.lang.RuntimeException: Transformed by MyInternalExceptionAspect
d.s.spring.q69862121.DemoApplication : java.lang.RuntimeException: Transformed by MyInternalExceptionAspect
d.s.s.q.MyInternalExceptionAspect : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable)) -> java.lang.ArithmeticException: argh
d.s.s.q69862121.InternalExceptionAspect : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable)) -> java.lang.IllegalArgumentException: Transformed by MyInternalExceptionAspect
d.s.spring.q69862121.DemoApplication : java.lang.RuntimeException: Transformed by InternalExceptionAspect
I am new to AOP.I made a generic logging API with the help of AOP in which advices are defined.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
#Component
#Aspect
public class LogExecutionTime {
private static final String LOG_MESSAGE_FORMAT = "%s.%s execution time: %dms";
private static final Log LOG = LogFactory.getLog(LogExecutionTime.class);
#Around("myPointCut()")
public Object logTimeMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Inside aAdvice LogExecutionTime");
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object retVal = joinPoint.proceed();
stopWatch.stop();
logExecutionTime(joinPoint, stopWatch);
return retVal;
}
private void logExecutionTime(ProceedingJoinPoint joinPoint, StopWatch stopWatch) {
String logMessage = String.format(LOG_MESSAGE_FORMAT, joinPoint.getTarget().getClass().getName(), joinPoint.getSignature().getName());
LOG.info(logMessage.toString());
}
}
Now I have included this jar in my application code and written pointcuts.
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class ButterflyPointCut {
#Pointcut("execution(* com.*.Abc.methodName(..))")
public void myPointCut(){
System.out.println("Executed");
}
}
It is giving following error.I want to know whether it is because of different classes.I have put the package of jar in component scan.
Caused by: java.lang.IllegalArgumentException: error at ::0 can't find referenced pointcut myPointCut
at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:317)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.buildPointcutExpression(AspectJExpressionPointcut.java:217)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.checkReadyToMatch(AspectJExpressionPointcut.java:190)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.getClassFilter(AspectJExpressionPointcut.java:169)
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:220)
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:279)
at org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply(AopUtils.java:311)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply(AbstractAdvisorAutoProxyCreator.java:119)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:89)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:70)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:346)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:298)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1588)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
... 57 common frames omitted
You can also use the full qualified name of the pointcut method (which you have defined in a separate class) with around annotation.
for example :
here you need to mention the full qualified name of the pointcut method with around annotation
for eg. #Around("domain.package.class.myPointCutMethodName()")
#Around("domain.package.className.myPointCut()")
public Object logTimeMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Inside aAdvice LogExecutionTime");
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object retVal = joinPoint.proceed();
stopWatch.stop();
logExecutionTime(joinPoint, stopWatch);
return retVal;
}
Yes the exception is due to the annotated methods in different
classes.
Keep #Pointcut annotated method and #Around annotated method in a same class, Other wise use point cut expression inside #Around annotation itself like.
#Around("execution(* com.*.Abc.methodName(..))")
I have a spring rest api which gets json data and binds to a pojo GetData.
Whenever i recieve unknown fields it doesnt fail or throw any exception. My requirement here is it should throw a error when it receives unknown fields in json data.
public ResponseEntity<Error> saveLocation(#Valid #RequestBody GetData getdata,BindingResult bindingResults) {
Below is my Pojo GetData
public class GetData{
#JsonProperty("deviceID")
#Pattern(regexp="^[\\p{Alnum}][-\\p{Alnum}\\p{L}]+[\\p{Alnum}]$",message = "Not a valid Device Id")
private String deviceID;
#JsonProperty("Coordinates")
#Pattern(regexp="^[\\p{Alnum}\\-][\\.\\,\\-\\_\\p{Alnum}\\p{L}\\s]+|",message = "Coordinates are not valid")
private String coordinates;}
Below is my json request.
{
"deviceID" : "01dbd619-843b-4197-b954",
"Coordinates" : "12.984012,80.246712",
}
Now if i send a request with an extra field say country. It doesn't throw any error.
{
"deviceID" : "01dbd619-843b-4197-b954",
"Coordinates" : "12.984012,80.246712",
"country" : "dsasa"
}
Please suggest how can i have an error for unknown properties being sent in a json request
You can try out any one of the below implementations, it works for me. You will have to override one more method from ResponseEntityExceptionHandler or by using ExceptionHandler.
1. By Overriding Method of ResponseEntityExceptionHandler
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(CustomExceptionHandler.class);
//Other Handlers
// Handle 400 Bad Request Exceptions
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.info(ex.getLocalizedMessage() + " ",ex);
final CustomErrorMessage errorMessage = new CustomErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, ex.fillInStackTrace().toString());
return handleExceptionInternal(ex, errorMessage, headers, errorMessage.getStatus(), request);
}
//Other Handlers
}
Apart from above implementation you can try out the below one also, if you want to throw error only if unrecognised properties are present in request payload or empty property and empty value is present like below JSON
{
"":""
}
2. Using ExceptionHandler
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class GenericExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GenericExceptionHandler.class);
#ExceptionHandler(value = {UnrecognizedPropertyException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleUnrecognizedPropertyException(UnrecognizedPropertyException ex) {
log.info(ex.getLocalizedMessage() + " ",ex);
final String error = "JSON parse error: Unrecognized field " + "[ " + ex.getPropertyName() + " ]";
final CustomErrorMessage errorMessage = new CustomErrorMessage(error, InfoType.ERROR, HttpStatus.BAD_REQUEST);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMessage);
}
}
Note : For above both implementations to work properly, you need to add the below line in your application.properties file.
spring.jackson.deserialization.fail-on-unknown-properties=true
Hope this will help you :)
You need to configure your ObjectMapper to handle such cases:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
Alternatively you can use:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = false)
public class GetData {
}
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){}