【问题标题】:Error casting object MethodSignature. Spring AOP转换对象 MethodSignature 时出错。春季AOP
【发布时间】:2019-09-10 11:45:21
【问题描述】:

提前感谢您的支持。 目前我陷入了下一个问题。我开发了一个 Aspect 类来验证来自 RestController 的 pkg 的输入 JSON。 符合一定的特点。 我的控制器的每个方法都返回一个不同的 DTO 对象。 当我的逻辑未实现时,我创建了一个新的通用对象以从我的方面返回它。当我进行测试时,我收到了一个错误,即 CannotCastClass "xxxxDTO" 到 newErrorResponseDTO。 目前我已经可以获取方法签名或对象类型。我的想法是将返回类型(来自 methodSignature)转换为我的新 DTOResponse。对象响应总是不同的。 我提到整个项目的架构和设计已经开发完毕。我只做了方面 目前,我还没有成功。 我附上证据。谢谢

我尝试了 ResponseAdvice,以及多种投射对象的方法。 我更喜欢留在方面。我得到的解决方案将控制器中的所有响应 DTO 更改为 Object generic。假设这样做是不好的做法,我更喜欢真正的解决方案

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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Arrays;

// Other imports missing...

@Aspect
@Component("validateParameterAspect")
public class ValidatorParameterAspect {

  public static final Logger logger = Logger.getLogger(ValidatorParameterAspect.class);

  @Autowired
  ServiciosRest servicio;

  @Pointcut("execution(* com.actinver.rest.*.* (..))")
  public void executeController() {}

  @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
  public void logRequestMapping() {}

  @Around("logRequestMapping() && executeController() && args(..,@RequestBody requestBody) ")
  public Object logRequestBody(ProceedingJoinPoint joinPoint, Object requestBody) throws Throwable {
    String vlDataDecrypt = "";

    try {
      // output = joinPoint.proceed();

      System.out.println("--------------123------------");
      logger.warn("Entering in Method :  " + joinPoint.getSignature().getName());
      logger.warn("Class Name :  " + joinPoint.getSignature().getDeclaringTypeName());
      logger.warn("Arguments :  " + Arrays.toString(joinPoint.getArgs()));
      logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());

      SimpleJSONDataContainer args = (SimpleJSONDataContainer) joinPoint.getArgs()[0];

      MethodSignature sign = (MethodSignature) joinPoint.getSignature();
      Class<?> ret = sign.getReturnType();
      String returnString = sign.getReturnType().getName();

      logger.warn("Signature : " + ret);

      vlDataDecrypt = AESHelper.decrypt(servicio.getSeedWord(), args.getData());

      logger.info(" Decrypt -> " + vlDataDecrypt);
      logger.info("args " + args.getData());

      ErrorDataResponse res = validDataEmpty(args.getData());

      if (res.getResult() == "2") {
        return res; // or cast Class<?>

        //return ret.cast(res);
      }

    } catch (Exception e) {
      logger.error("Stack trace -> ", e);
    }
    return joinPoint.proceed();
  }

  public ErrorDataResponse validDataEmpty(String vlDataDecrypt) {
    ErrorDataResponse errorDto = new ErrorDataResponse();

    if (vlDataDecrypt == null || vlDataDecrypt.hashCode() == "77631826690E45839D7B49B932CBC81B".hashCode()
      && vlDataDecrypt.equalsIgnoreCase("77631826690E45839D7B49B932CBC81B")) {
      errorDto.setResult("2");
      errorDto.setMensaje(RestValidatorUtil.EnumErrors.ERROR_INPUT.getMsg());
      logger.info("JSON null" + errorDto.getResult());
      return errorDto;

    }
    return errorDto;
  }
}
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

// Other imports missing...

@RestController
@RequestMapping("inicio")
public class Bursanet {
  public final static Logger logger = Logger.getLogger(Bursanet.class);

  @RequestMapping(
    value = "cashByDate",
    method = { RequestMethod.GET, RequestMethod.POST },
    consumes = "application/json",
    produces = "application/json"
  )
  public CashByDateDTO cashByDate(
    @RequestBody SimpleJSONDataContainer simpleJSONDataContainer,
    Authentication authentication
  ) {
    String vlDataDecrypt = "";
    CashByDateDTO outJson = new CashByDateDTO();
    CashByDateRequest request = null;
    try {
      UsernamePasswordAuthenticationToken userPasswordAuthenticationToken =
        (UsernamePasswordAuthenticationToken)
          ((OAuth2Authentication) authentication).getUserAuthentication();
      //////example
      return outJson;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

【问题讨论】:

  • 欢迎来到 SO。我修复了缩进并稍微调整了代码格式,将代码拆分为每个类的一个块,还添加了一些导入。在您切断try 块之后,我还添加了一个虚拟catch 块。至少现在代码更具可读性。你问的对我来说仍然是个谜,你可以编辑问题中的散文吗?如果我发现我会尝试回答,必须再次阅读。文字太乱了,抱歉,无意冒犯。

标签: spring aspectj spring-aop


【解决方案1】:

分析您的代码非常困难,因为您没有提供MCVE

  • 您的类中没有包名。
  • 也没有导入。
  • 您使用了几个特定于项目的类(不是 Spring 框架的一部分),您也没有在这里分享它们的代码。
  • 也没有 Spring 配置。

所以我必须在这里做一些有根据的猜测。据我所知,我可以告诉你:

  • 如果您希望ValidatorParameterAspect.logRequestBody(..) 拦截Bursanet.cashByDate(..) 的执行,它应该不起作用,因为

    • args(.., @RequestBody requestBody) 中,您希望该参数是目标方法签名中的最后一个,但实际上在Bursanet.cashByDate(..) 中它是第一个。所以切入点永远不应该匹配。
    • 同样在args(.., @RequestBody requestBody) 中,您应该使用完全限定的类名,即args(.., @org.springframework.web.bind.annotation.RequestBody requestBody)
  • 还请注意,execution(* com.actinver.rest.*.* (..)) 仅匹配直接位于 com.actinver.rest 包中的类中的方法,而不匹配任何子包中的方法。如果您也想包含这些,则需要将切入点更改为execution(* com.actinver.rest..* (..))

  • 在您的问题中,您提到您只想拦截 REST 控制器,但您不会将切入点匹配限制为带有 @RestController 注释的类。你可以通过@within(org.springframework.web.bind.annotation.RestController) 做到这一点。现在,您仅通过使用 @annotation(org.springframework.web.bind.annotation.RequestMapping) 的方法间接地做到这一点,只要这些方法仅出现在 @RequestController 类中,它也将起作用。您的应用程序中可能就是这种情况,我只是作为细节提及。

  • 为什么不通过args() 将第一个参数绑定到SimpleJSONDataContainer 参数而不是SimpleJSONDataContainer args = (SimpleJSONDataContainer) joinPoint.getArgs()[0];,然后在代码中使用当前未使用的requestBody 建议方法参数?像这样:

  @Around("logRequestMapping() && executeController() && args(@org.springframework.web.bind.annotation.RequestBody requestBody, ..)")
  public Object logRequestBody(ProceedingJoinPoint joinPoint, SimpleJSONDataContainer requestBody) throws Throwable {
    // (...)

      vlDataDecrypt = AESHelper.decrypt(servicio.getSeedWord(), requestBody.getData());

      logger.info(" Decrypt -> " + vlDataDecrypt);
      logger.info("args " + requestBody.getData());

      ErrorDataResponse res = validDataEmpty(requestBody.getData());

    // (...)
  }
  • 您定义了MethodSignature sign = (MethodSignature) joinPoint.getSignature();,但不要在多次调用joinPoint.getSignature() 的情况下多次使用它。相反,您可以像这样重新组织代码:
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

      System.out.println("--------------123------------");
      logger.warn("Entering in Method :  " + methodSignature.getName());
      logger.warn("Class Name :  " + methodSignature.getDeclaringTypeName());
      logger.warn("Arguments :  " + Arrays.toString(joinPoint.getArgs()));
      logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());

      Class<?> ret = methodSignature.getReturnType();
      String returnString = methodSignature.getReturnType().getName();
  • 我不明白为什么这么多人调用许多JoinPoint 方法来提取日志记录的详细信息,如果他们可以简单地记录连接点实例。这将显示切入点的类型(例如execution())以及目标方法签名。好吧,如果你想列出所有方法参数,你可以额外这样做,但是这样还不够吗?
      logger.warn(joinPoint);
//      logger.warn("Entering in Method :  " + methodSignature.getName());
//      logger.warn("Class Name :  " + methodSignature.getDeclaringTypeName());
      logger.warn("Arguments :  " + Arrays.toString(joinPoint.getArgs()));
//      logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
  • 我猜你也可以删除整个代码块。它甚至会打印错误信息并调用返回类型“签名”:
      Class<?> ret = methodSignature.getReturnType();
      String returnString = methodSignature.getReturnType().getName();
      logger.warn("Signature : " + ret);

现在对于可能是您的问题的部分:

      ErrorDataResponse res = validDataEmpty(requestBody.getData());
      if (res.getResult() == "2") {
        return res; // or cast Class<?>
        //return ret.cast(res);
      }

在这里,您正在使方面建议跳过joinPoint.proceed() 调用并返回另一个对象。您拦截的方法具有签名public CashByDateDTO cashByDate(..),即它返回特定的 DTO 类型。如果您想返回 ErrorDataResponse,则仅当 ErrorDataResponseCashByDateDTO 的子类型时才有效,可能不是。从类名我什至会说*Response*DTO 是完全不同的对象类型。您的建议不能只是更改或忽略方法签名。无论如何,您都必须返回一个 CashByDateDTO 对象。如果你在这里做不到,可能是你拦截了错误的方法或试图在你的方面做错事。

抱歉回复冗长,但你的代码太混乱了,我不得不指出一些细节。

【讨论】:

  • @kriagaex 感谢您的回复。我在这里,我要求学习 :) 正如我在这段代码之前所说的那样,它已经完全完成了。我在这个咨询中的主要职责是维护。确实你解决了很多疑虑
  • 此外,通过== 进行字符串比较也是一个坏主意。而不是res.getResult() == "2",你应该写res.getResult().equals("2"),或者更好的是,你应该防御性地编码并假设方法结果可能是null(在调用equals(..)时产生NullPointerException)。然后你会写"2".equals(res.getResult()),它在任何情况下都避免了NPE。
  • @kriagaex 非常感谢您的帮助。我已经在逐点进行更改。问候
猜你喜欢
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-19
  • 2015-08-09
  • 2012-02-17
相关资源
最近更新 更多