使用 SpringAOP 获取方法参数?

我使用的是 Spring AOP,有以下方面:

@Aspect
public class LoggingAspect {


@Before("execution(* com.mkyong.customer.bo.CustomerBo.addCustomer(..))")
public void logBefore(JoinPoint joinPoint) {


System.out.println("logBefore() is running!");
System.out.println("hijacked : " + joinPoint.getSignature().getName());
System.out.println("******");
}


}

以上方面拦截 addCustomer方法的执行。 addCustomer方法采用字符串作为输入。 但是我需要在 logBefore方法中记录传递给 addCustomer方法的输入。
有可能这样做吗?

140069 次浏览

Yes, the value of any argument can be found using getArgs

@Before("execution(* com.mkyong.customer.bo.CustomerBo.addCustomer(..))")
public void logBefore(JoinPoint joinPoint) {


Object[] signatureArgs = thisJoinPoint.getArgs();
for (Object signatureArg: signatureArgs) {
System.out.println("Arg: " + signatureArg);
...
}
}

If it's a single String argument, do: joinPoint.getArgs()[0];

You have a few options:

First, you can use the JoinPoint#getArgs() method which returns an Object[] containing all the arguments of the advised method. You might have to do some casting depending on what you want to do with them.

Second, you can use the args pointcut expression like so:

// use '..' in the args expression if you have zero or more parameters at that point
@Before("execution(* com.mkyong.customer.bo.CustomerBo.addCustomer(..)) && args(yourString,..)")

then your method can instead be defined as

public void logBefore(JoinPoint joinPoint, String yourString)

Your can use either of the following methods.

@Before("execution(* ong.customer.bo.CustomerBo.addCustomer(String))")
public void logBefore1(JoinPoint joinPoint) {
System.out.println(joinPoint.getArgs()[0]);
}

or

@Before("execution(* ong.customer.bo.CustomerBo.addCustomer(String)), && args(inputString)")
public void logBefore2(JoinPoint joinPoint, String inputString) {
System.out.println(inputString);
}

joinpoint.getArgs() returns object array. Since, input is single string, only one object is returned.

In the second approach, the name should be same in expression and input parameter in the advice method i.e. args(inputString) and public void logBefore2(JoinPoint joinPoint, String inputString)

Here, addCustomer(String) indicates the method with one String input parameter.

There is also another way if you define one pointcut for many advices it can be helpful:

@Pointcut("execution(@com.stackoverflow.MyAnnotation * *(..))")
protected void myPointcut() {
}


@AfterThrowing(pointcut = "myPointcut() && args(someId,..)", throwing = "e")
public void afterThrowingException(JoinPoint joinPoint, Exception e, Integer someId) {
System.out.println(someId.toString());
}


@AfterReturning(pointcut = "myPointcut() && args(someId,..)")
public void afterSuccessfulReturn(JoinPoint joinPoint, Integer someId) {
System.out.println(someId.toString());
}

you can get method parameter and its value and if annotated with a annotation with following code:

Map<String, Object> annotatedParameterValue = getAnnotatedParameterValue(MethodSignature.class.cast(jp.getSignature()).getMethod(), jp.getArgs()); ....

private Map<String, Object> getAnnotatedParameterValue(Method method, Object[] args) {
Map<String, Object> annotatedParameters = new HashMap<>();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Parameter[] parameters = method.getParameters();


int i = 0;
for (Annotation[] annotations : parameterAnnotations) {
Object arg = args[i];
String name = parameters[i++].getDeclaringExecutable().getName();
for (Annotation annotation : annotations) {
if (annotation instanceof AuditExpose) {
annotatedParameters.put(name, arg);
}
}
}
return annotatedParameters;
}

If you have to log all args or your method have one argument, you can simply use getArgs like described in previous answers.

If you have to log a specific arg, you can annoted it and then recover its value like this :

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Data {
String methodName() default "";
}


@Aspect
public class YourAspect {


@Around("...")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method method = MethodSignature.class.cast(point.getSignature()).getMethod();
Object[] args = point.getArgs();
StringBuilder data = new StringBuilder();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int argIndex = 0; argIndex < args.length; argIndex++) {
for (Annotation paramAnnotation : parameterAnnotations[argIndex]) {
if (!(paramAnnotation instanceof Data)) {
continue;
}
Data dataAnnotation = (Data) paramAnnotation;
if (dataAnnotation.methodName().length() > 0) {
Object obj = args[argIndex];
Method dataMethod = obj.getClass().getMethod(dataAnnotation.methodName());
data.append(dataMethod.invoke(obj));
continue;
}
data.append(args[argIndex]);
}
}
}
}

Examples of use :

public void doSomething(String someValue, @Data String someData, String otherValue) {
// Apsect will log value of someData param
}


public void doSomething(String someValue, @Data(methodName = "id") SomeObject someData, String otherValue) {
// Apsect will log returned value of someData.id() method
}

if your using @Aspect an option is add this method inside your Aspect and send the JoinPoint and the name of parameter you need.

private Object getParameter(ProceedingJoinPoint joinPoint, String parameterName) {
Object valueParameter = null;
if (Objects.nonNull(joinPoint) && joinPoint.getSignature() instanceof MethodSignature
&& Objects.nonNull(parameterName) ) {
MethodSignature method = (MethodSignature)joinPoint.getSignature();
String[] parameters = method.getParameterNames();
for (int t = 0; t< parameters.length; t++) {
if( Objects.nonNull(parameters[t]) && parameters[t].equals(parameterName)) {
Object[] obj = joinPoint.getArgs();
valueParameter = obj[t];
}
}
}
return valueParameter;
}

and the call example:

Object parameterObject = getParameter(joinPoint, "nameClient");
if ( Objects.nonNull(parameterObject) ) {
String parametro = String.valueOf(parameterObject);
}

Only need know the type of object for convert