微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

从不同类型的方法参数中提取特定字段的值

如何解决从不同类型的方法参数中提取特定字段的值

我正在为 Spring Boot 应用程序中的服务方法编写一个拦截器,如下所示:

@Aspect
public class MyAspect {

  public MyAspect() {
   
  }

  @pointcut("@within(org.springframework.stereotype.Service)")
  public void applicationservicepointcut() {
  }

  @Before(value = ("applicationservicepointcut()"))
  public void process(JoinPoint joinPoint) throws Exception {
   ...
    
  }
}

一种这样的服务如下:

@Service
@Transactional
public class AService {
   
  public ADTO create(ADTO aDTO) {
    ...
  }

  public ADTO update(ADTO aDTO) {
    ...
  }
}

一个服务可以如下:

@Service
@Transactional
public class BService {
   
  public BDTO create(BDTO bDTO) {
    ...
  }

  public BDTO update(BDTO bDTO) {
    ...
  }
  
  public void doSomething(String a,int b) {
    ...
  }
}

这里我的目标是从关联的方法参数中提取特定字段的值。为了做到这一点,我可以在方面编写一个函数,其中我可以有多个 if-else 块,如下所示:

 String extractMyField(JoinPoint joinPoint) {
  
     //extract the arguments of the associated method from joinpoint
     //depending on the type of arguments,extract the value of myfield as follows

    if(arg instance of ADTO) {
       ...
     } else if (arg instance of BDTO) {
       ...
     }
     ...
    }

从上面的代码片段可以看出,可能有以下几种情况:

  1. 方法参数可以是对象也可以是原始类型。
  2. 可以有零个或多个方法参数。

此外,我也无法更改 DTO 对象。

我想知道是否有更好的方法来做到这一点。基本上,我正在寻找一种使其易于扩展的方法

有人可以帮忙吗?谢谢。

编辑

截至目前,为了处理方法参数,我首先收集方法参数如下:

      Map<String,Object> getmethodArguments(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    
        Map<String,Object> argumentNameValueMap = new HashMap<>();
    
        if (methodSignature.getParameterNames() == null) {
          return argumentNameValueMap;
        }
    
        for (int i = 0; i < methodSignature.getParameterNames().length; i++) {
          String argumentName = methodSignature.getParameterNames()[i];
          Object argumentValue = joinPoint.getArgs()[i];
          Class argumentType = joinPoint.getArgs()[i].getClass();
          //check if i th parameter is json serializable
          if (Objects.isNull(methodSignature.getParameterTypes()) ||
              isJsonSerializable(methodSignature.getParameterTypes()[i])) {
            argumentNameValueMap.put(argumentName,mapper.convertValue(argumentValue,Map.class));
          }
        }
        return argumentNameValueMap;
       }

        private boolean isJsonSerializable(Class parameterType) {
    return
        !Objects.isNull(parameterType) &&
            !parameterType.getName().endsWith("HttpServletRequest") &&
            !parameterType.getName().endsWith("HttpServletResponse") &&
            !parameterType.getName().endsWith("multipartfile");
  }

实际上,这里的问题是我要查找的字段在 DTO 中的命名不同。例如,在 ADTO 中,该字段被命名为 id,而在 BDTO 中,它被命名为 aId

解决方法

如何让您的 ADTO/BDTO 实现自定义界面

interface AspectDataProvider {
  String aspectSpecificData()
}

然后在您的参数处理器中,您可以轻松做到

if (arg instanceof AspectDataProvider) {
  return ((AspectDataProvider) arg).aspectSpecificData();
}

或者您可以深入挖掘并为您的实现添加访问者模式

  interface AspectImplementor {
     void executeAspect(String value);
  }

  interface AspectAware {
     void accept(AspectImplementor aspect);
  }

然后在您的 BDTO ADTO 中添加 AspectAware

的自定义实现
  void accept(AspectImplementor aspect) {
    String property = // object-specific property extraction
    aspect.executeAspect(property);
  }

在你的方面,你可以写类似的

  if (arg instanceof AspectAware ) {
    ((AspectAware ) arg).accept(aspectExecutorInstance);
  }

但我不建议从一开始就从设计模式开始——一旦你确定你有一个访问者模式,它应该总是下一个改进。

,

好的,那么让我提出以下建议:

我想,你不能改变 DTO 对象 我假设,有时您的方法甚至没有 DTO 参数,但是您有一个逻辑可以“找到”它们(如果它们存在)

如果你知道截获的DTO的类型是什么,你可以创建接口:

interface DTOHandler {
   Class<?> getDTOClass();
   Object getMyField(Object dtoObject); 
}

为每个DTO实现这个接口

例如,如果您计划拦截 2 种不同类型的 DTO:

class ADTO {
  Integer myField;
}
class BDTO {
  String myField;
}

然后执行:

class DTOAHandler implements DTOHandler {
   Class<?> getDtoClass() {return ADTO.class;}
   Object getMyField(Object obj) {return ((ADTO)obj).getMyField();}
}

// and do a similar implementation same for DTOB

现在将所有这些处理程序定义为 spring bean,并将它们的列表注入方面。 Spring boot 会将它们全部注入为列表,顺序无关紧要

在方面的构造函数中创建一个 Class> -> DTOHandler 的映射。 迭代抛出列表并创建映射的键作为“getDTOClass”的调用和作为处理程序本身的值:

public class MyAspect {

   private Map<Class<?>,DTOHandler> handlersRegistry;

   public MyAspect(List<DTOHandler> allHandlers) {
      handlersRegistry = new HashMap<>();
      for(DTOHandler h : allHandlers) {
        handlersRegistry.put(h.getDTOClass(),h);
      }
   }
}

通过此设置,extractMyField 将如下所示:

public void extractMyField(JoinPoint jp) {
    
     Object myDTO = findDTOParameter(jp);
     
     DTOHandler handler = this.handlersRegistry.get(myDTO.getClass());
     if(handler != null) {
       // there is an actual handler for this type of DTOs
        Object myField = handler.getMyField(myDTO);
     }
     else {
       // the aspect can't handle the DTO of the specified type
     }
}

更新(基于 Op 的评论):

为了实际找到具有“myfield”值的参数,您使用了一个非常复杂的逻辑(在问题的编辑部分)。

相反,您可以创建可应用于参数的注释(运行时保留)。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface @DTO {
}

然后你可以用这个注解对服务中的参数进行注解,以“帮助”你的方面了解哪些参数实际上是 DTO:

@Service
@Transactional
public class AService {
   
  public ADTO create(@DTO ADTO aDTO) {
    ...
  }

  public ADTO update(@DTO ADTO aDTO) {
    ...
  }
}

那么,aspect中的解析逻辑就会变成这样:

// pseudocode
foreach p in 'parameters' {
  if(isAnnotatedWith(p,DTO.class))  {
     // it matches
  }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。