从不同类型的方法参数中提取特定字段的值
Extract the value of a particular field from different types of method arguments
我正在为我的 spring 引导应用程序中的服务方法编写一个拦截器,如下所示:
@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) {
...
}
...
}
从上面的代码片段可以看出,可以有以下几种情况:
- 方法参数可以是对象,也可以是原始类型。
- 可以有零个或多个方法参数。
此外,我也无法更改 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) {
...
}
}
那么切面的解析逻辑会变成这样:
// pseudocode
foreach p in 'parameters' {
if(isAnnotatedWith(p, DTO.class)) {
// it matches
}
}
我正在为我的 spring 引导应用程序中的服务方法编写一个拦截器,如下所示:
@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) {
...
}
...
}
从上面的代码片段可以看出,可以有以下几种情况:
- 方法参数可以是对象,也可以是原始类型。
- 可以有零个或多个方法参数。
此外,我也无法更改 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) {
...
}
}
那么切面的解析逻辑会变成这样:
// pseudocode
foreach p in 'parameters' {
if(isAnnotatedWith(p, DTO.class)) {
// it matches
}
}