【问题标题】:Get method arguments using Spring AOP?使用 Spring AOP 获取方法参数?
【发布时间】:2013-03-27 13:50:52
【问题描述】:

我正在使用 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 方法的输入。
有可能吗?

【问题讨论】:

  • addCustomer(..)的方法签名是什么?

标签: java spring


【解决方案1】:

你有几个选择:

首先,您可以使用JoinPoint#getArgs() 方法,该方法返回一个Object[],其中包含建议方法的所有参数。根据您想对它们做什么,您可能需要进行一些强制转换。

其次,你可以像这样使用args切入点表达式:

// 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,..)")

那么您的方法可以改为定义为

public void logBefore(JoinPoint joinPoint, String yourString) 

【讨论】:

  • 如果我没记错的话,这两个选项之间的行为有所不同。第二个只会在 arg 存在时触发,而第一个会在参数不存在时触发。
  • @SamuelEUSTACHI 没有为第一个示例指定切入点表达式。如果我们假设执行addCustomer(..),当然可以。可以是无参数或多个。
  • 第二个选项非常方便。我需要在向某个 xyz url 发送请求之前和之后记录请求和响应以及其他一些参数,如果没有第二个选项,Pointcut 表达式将变得非常复杂且不可读。
【解决方案2】:

是的,任何参数的值都可以使用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);
      ...
   }
}

【讨论】:

    【解决方案3】:

    如果您为许多建议定义一个切入点,还有另一种方法会很有帮助:

    @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());
    }
    

    【讨论】:

      【解决方案4】:

      如果您必须记录所有 args 或您的方法只有一个参数,您可以简单地使用 getArgs,如之前的答案中所述。

      如果你必须记录一个特定的参数,你可以注释它,然后像这样恢复它的值:

      @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]);
              }
          }
       }
      }
      

      使用示例:

      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
      }
      

      【讨论】:

      • @Around("...") 这个模式不是对@Around无效吗?
      • "..." 表示您的模式已完成。
      • 不错,我在找什么
      • 非常好,正是我正在寻找的基于属性的访问控制评估!
      【解决方案5】:

      您可以获取方法参数及其值,如果使用以下代码进行注释:

      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;
          }
      

      【讨论】:

        【解决方案6】:

        如果您使用 @Aspect 选项是在您的 Aspect 中添加此方法并发送 JoinPoint 和您需要的参数名称。

        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;
        }
        

        以及调用示例:

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

        只需要知道转换对象的类型

        【讨论】:

          【解决方案7】:

          您可以使用以下任一方法。

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

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

          joinpoint.getArgs() 返回对象数组。由于输入是单个字符串,因此只返回一个对象。

          在第二种方法中,通知方法中的表达式和输入参数名称应相同,即args(inputString)public void logBefore2(JoinPoint joinPoint, String inputString)

          这里addCustomer(String)表示方法只有一个String输入参数。

          【讨论】:

            【解决方案8】:

            如果是单个字符串参数,请执行以下操作: joinPoint.getArgs()[0];

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-11-19
              • 2019-05-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多