AOP 全局trim关键属性

本文介绍了一种在业务开发中对关键属性进行全局Trim处理的方法,通过AOP技术对providerId和parkingId等属性进行自动去除前后空格的操作,避免因空格问题导致的后端逻辑错误。

再业务开发中经常以为一些关键属性的前后空格问题,导致后端业务逻辑不能正常处理,如果每个接口单独trim,代码繁琐太容易遗漏,因此需求想要全局对关键属性trim, 比如在我们的对外接口中, providerId , 和 parkingId 是对用户身份的关键验证,所以需要对这两个属性做全局trim 。

1:申明切入点。

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorLog {
    /**
     * 日志级别
     * @return
     */
    LogLevelEnum level() default LogLevelEnum.INFO ;
}

2:定义切面:

@Aspect
@Component
@Slf4j
public class MonitorLog1Aspect {

    /**
     * 切点
     */
    @Pointcut("@annotation(com.dsx.MonitorLog)")
    public void monitorLog() {
    }

    @Around("monitorLog()")
    public Object interceptMonitorLogMethod(ProceedingJoinPoint point) throws Throwable {
        Object[] args = point.getArgs();
        for (int i = 0; i < args.length; i++) {
            Field[] fields = args[i].getClass().getDeclaredFields();
            for (int j = 0; j < fields.length; j++) {
                Field field = fields[j];
                String type = field.getGenericType().toString();
                String name = field.getName();
                if (("providerId".equals(name) || "parkingId".equals(name) || "service_name".equals(name) || "parking_id".equals(name)) && "class java.lang.String".equals(type)) {
                    field.setAccessible(true);
//                        // 拼装get set方法  例如 setProviderId getProviderId
//                        String setMethodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
//                        String getMethodName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
//                        //通过方法名称获取对应的方法,这里是获取字段的get方法
//                        Method getMethod = args[i].getClass().getMethod(getMethodName);
//                        // 获取set方法
//                        Method setMethod = args[i].getClass().getMethod(setMethodName, String.class);
//                        // 通过 invoke 获取到get方法的Value值  PS: invoke()方法就是用来执行指定对象的方法
//                        String value = (String) getMethod.invoke(args[i]);
//                        // 再invoke 执行 set方法  StringUtils.trim 对空格进行处理
//                        setMethod.invoke(args[i], StringUtils.trim(value));
                    PropertyDescriptor pd = new PropertyDescriptor(field.getName(), args[i].getClass());
                    Method getMethod = pd.getReadMethod();
                    Method setMethod = pd.getWriteMethod();
                    String value = (String) getMethod.invoke(args[i]);
                    setMethod.invoke(args[i], StringUtils.trim(value));
                }
            }
        }
        Object result = point.proceed();
        return result;
    }

我这里是直接嵌入到 链路日志aop里面统一处理,删减了日志代码整理的文档,使用过程中根据自己的需求自定义名称即可。 切面中的处理逻辑,注释部分的代码和 下面的代码可以达到同样的效果,看个人喜欢选择。

<think>好的,我现在需要帮用户解决如何在Java中定义一个自定义注解,并对加了该注解的请求参数做trim处理的问题。首先,我应该回忆一下Java自定义注解的基本知识。自定义注解需要使用@interface关键字来定义,然后通过AOP或者拦截器来实现处理逻辑。 用户的需求是对请求参数做trim处理,也就是去除字符串两端的空格。这通常用在Web应用中,比如Spring MVC的控制器方法参数上。所以,我需要考虑如何在Spring框架中实现这个功能。可能的实现方式有:使用Spring的AOP,或者在参数解析器(HandlerMethodArgumentResolver)中处理,或者使用@ControllerAdvice配合@InitBinder。 首先,定义注解。注解需要能够标注在方法参数上,可能还需要指定是否处理空字符串,或者是否允许null值。但用户的需求比较基础,可能只需要一个标记注解,所以先定义一个简单的@Trim注解,可以应用在字段或参数上。 接下来是处理逻辑。如果使用Spring,AOP可能是一个选择,但更常见的是使用拦截器或者参数解析器。不过,Spring的@ControllerAdvice和@InitBinder可以用来全局处理参数的绑定,可能更适合。或者,使用Servlet Filter或Spring的Interceptor来预处理请求参数,但这样可能不够灵活,因为需要针对特定的注解进行处理。 另一个思路是使用Jackson的序列化/反反序列化机制,通过自定义JsonDeserializer来处理字符串的trim。如果请求参数是通过@RequestBody接收的JSON,这种方法可行。例如,定义一个自定义的反序列化器,并在需要的字段上使用@JsonDeserialize注解指定。但这样可能无法覆盖所有情况,比如表单提交的参数。 或者,结合Spring MVC的@RequestParam和自定义注解,通过修改参数解析器来实现。例如,实现一个HandlerMethodArgumentResolver,检查参数是否有@Trim注解,然后对字符串进行trim处理。 这里可能需要分步骤来实现: 1. 创建自定义注解@Trim。 2. 实现处理逻辑,比如通过AOP拦截带有@Trim注解的参数,或者在参数解析时处理。 3. 注册处理逻辑到Spring容器,确保生效。 考虑到Spring MVC的参数解析机制,使用HandlerMethodArgumentResolver可能更直接。但需要确定该解析器能处理被@Trim注解标注的参数。或者,更简单的方式是使用@ControllerAdvice中的@InitBinder,对所有String类型的参数进行trim,但这样不够精确,无法只处理特定注解的参数。 或许更好的方法是结合@ControllerAdvice和@InitBinder,定制WebDataBinder,在绑定参数时检查字段是否有@Trim注解,然后进行trim处理。这需要对数据绑定过程有更深入的了解。 例如,可以注册一个自定义的编辑器(PropertyEditor或Formatter),在绑定过程中处理字符串。然后,在@InitBinder方法中注册这个编辑器。但如何只处理带有@Trim注解的字段呢? 或者,在实体类的字段上使用@Trim注解,然后在绑定的时候,通过反射检查字段是否有该注解,如果有的话,进行trim处理。这种方法可能更灵活,适用于表单对象(比如@ModelAttribute注解的参数)。 总结步骤: 1. 定义@Trim注解,可以应用在字段上。 2. 创建@ControllerAdvice类,在@InitBinder方法中注册自定义的绑定逻辑。 3. 在绑定过程中,检查字段是否有@Trim注解,如果有,对输入值进行trim处理。 这样,当Spring MVC绑定表单数据到对象时,会自动处理带有@Trim注解的字段。 另一种情况是处理@RequestParam注解的参数,这时可能需要自定义参数解析器。例如,实现HandlerMethodArgumentResolver,判断参数是否有@Trim注解,然后对值进行trim处理,并注册该解析器。 现在需要具体实现这些步骤。首先,定义@Trim注解: ```java @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface Trim { } ``` 然后,处理表单对象的字段,可以通过@InitBinder和自定义编辑器: 在@ControllerAdvice类中: ```java @ControllerAdvice public class TrimAdvice { @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); // 但这样会处理所有String类型的字段,不管是否有注解 } } ``` 但这里的问题是,StringTrimmerEditor会处理所有String类型的字段,而不仅仅是带有@Trim注解的。所以需要更精确的控制。 可能需要自定义PropertyEditorSupport,覆盖setAsText方法,并在其中检查字段是否有@Trim注解,然后进行处理。但如何获取字段的注解呢?这可能需要通过反射,获取目标对象的字段,并检查是否有注解。 这可能比较复杂,因为在WebDataBinder中,当前正在绑定的字段可能不容易直接获取。或者,可以结合Spring的BeanWrapper来访问字段的元数据。 另一种思路是使用Hibernate Validator的约束注解,在验证时处理,但可能不符合用户的需求,因为用户希望在绑定时就处理,而不是验证时。 或者,在实体类的setter方法中处理,但这需要手动为每个字段添加trim逻辑,不够通用。 回到之前的思路,自定义一个PropertyEditor,在设置值时检查字段是否有@Trim注解: 可能需要创建一个自定义的Editor,例如: ```java public class TrimmingEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { if (text == null) { setValue(null); } else { // 如何判断当前字段是否有@Trim注解? // 这里可能需要访问目标字段的信息,但PropertyEditor中可能无法直接获取 // 因此这种方法可能不可行 setValue(text.trim()); } } } ``` 这样的话,无法根据字段是否有注解来决定是否trim,只能全部trim。所以,这种方法无法满足需求。 那可能需要另一个方法:使用AOP来拦截控制器方法,检查方法参数上的@Trim注解,然后处理参数值。 例如,定义一个切面,拦截带有@Trim注解的参数: ```java @Aspect @Component public class TrimAspect { @Around("execution(* com.example.controller.*.*(.., @Trim (*), ..))") public Object trimParameters(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { if (args[i] instanceof String) { args[i] = ((String) args[i]).trim(); } } return joinPoint.proceed(args); } } ``` 但这种方法只能处理参数类型为String的情况,并且需要确保切点表达式正确匹配参数位置上的@Trim注解。此外,如果参数是一个对象,比如DTO,其内部字段需要被处理,这种方法就无法处理。 因此,可能需要结合不同的方法。例如,对于直接使用@RequestParam的String参数,可以使用AOP;对于对象中的字段,使用@Trim注解并结合@InitBinder的处理。 或者,针对对象中的字段,使用自定义的BeanPostProcessor或者在反序列化时处理,比如使用Jackson的@JsonDeserialize。 例如,定义一个自定义的JsonDeserializer: ```java public class TrimmingDeserializer extends JsonDeserializer<String> { @Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { String value = p.getValueAsString(); return value != null ? value.trim() : null; } } ``` 然后在需要trim的字段上使用注解: ```java @JsonDeserialize(using = TrimmingDeserializer.class) private String username; ``` 但这种方法需要为每个字段添加@JsonDeserialize,不太方便。如果用户希望使用自定义的@Trim注解,可以结合两者: 创建@Trim注解,并在其元注解中添加@JsonDeserialize,指定使用TrimmingDeserializer: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @JsonDeserialize(using = TrimmingDeserializer.class) public @interface Trim { } ``` 这样,任何带有@Trim注解的字段在通过Jackson反序列化时会自动trim。但这种方法仅适用于JSON请求体(即@RequestBody),对于表单数据(application/x-www-form-urlencoded)可能无效。 因此,可能需要同时处理多种情况。例如,对于@RequestBody中的DTO字段,使用Jackson的反序列化器;对于@RequestParam参数,使用AOP或参数解析器。 综合考虑,用户可能需要一个通用的解决方案,涵盖不同场景的参数trim处理。这可能比较复杂,但可以分步骤实现: 1. 对于@RequestParam注解的参数,使用自定义的HandlerMethodArgumentResolver。 2. 对于@ModelAttribute对象中的字段,使用@InitBinder结合自定义逻辑。 3. 对于@RequestBody中的JSON字段,使用Jackson的@JsonDeserialize。 不过,这样实现起来可能比较繁琐。是否有更统一的方式? 另一种思路是使用Spring的Converter或Formatter,将String类型的值自动trim,但同样需要根据注解来判断是否处理。 或者,在参数解析时,通过反射检查参数是否有@Trim注解,然后进行处理。例如,自定义一个AnnotationAwareHandlerMethodArgumentResolver,但这需要替换Spring默认的参数解析器,可能比较复杂。 现在回到用户的问题,用户可能希望一个比较直接的实现方式。因此,可以提供一个基于@ControllerAdvice和@InitBinder的解决方案,处理@ModelAttribute对象的字段,并结合自定义注解。 具体步骤: 1. 定义@Trim注解,应用于字段。 2. 创建@ControllerAdvice类,在@InitBinder中注册一个自定义的编辑器。 3. 在编辑器中,检查字段是否有@Trim注解,如果有,进行trim处理。 实现代码如下: 首先,定义@Trim注解: ```java @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Trim { } ``` 然后,创建ControllerAdvice: ```java @ControllerAdvice public class TrimStringAdvice { @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(String.class, new StringTrimmerEditor(true) { @Override public void setValue(Object value) { if (value instanceof String) { // 检查当前字段是否有@Trim注解 // 这里需要获取到目标字段,但WebDataBinder中如何获取? // 可能需要通过反射获取目标对象的字段信息 // 这可能比较复杂,因为WebDataBinder的getTarget()可能返回null在初始化阶段 // 所以这种方法可能不可行 super.setValue(((String) value).trim()); } else { super.setValue(value); } } }); } } ``` 但这种方法的问题是无法确定当前正在绑定的字段是否有@Trim注解,因此无法条件判断。因此,可能需要寻找其他方法。 或许,可以通过反射遍历目标对象的所有字段,找到带有@Trim注解的字段,并在绑定后进行处理。例如,在@ModelAttribute方法中,或者在控制器方法中手动处理,但这样不够自动化。 另一种方式,使用BeanPostProcessor,在对象创建后处理带有@Trim注解的字段: ```java @Component public class TrimAnnotationBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Trim.class) && field.getType() == String.class) { field.setAccessible(true); try { String value = (String) field.get(bean); if (value != null) { field.set(bean, value.trim()); } } catch (IllegalAccessException e) { // 异常处理 } } } return bean; } } ``` 但这种方法的问题在于,BeanPostProcessor通常用于Spring管理的Bean,而控制器中的参数对象可能不是由Spring容器管理的,因此可能不会触发BeanPostProcessor的处理。 因此,这可能不适用于处理请求参数对象。 回到最初的思路,使用HandlerMethodArgumentResolver来处理带有@Trim注解的参数。例如,对于使用@RequestParam的参数,如果带有@Trim注解,则进行trim处理。 实现步骤: 1. 创建自定义的HandlerMethodArgumentResolver。 2. 注册该解析器到Spring MVC。 3. 在解析参数时,检查是否有@Trim注解,如果有,trim字符串。 代码示例: 自定义解析器: ```java public class TrimArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(Trim.class) && parameter.getParameterType() == String.class; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String paramName = parameter.getParameterName(); String value = webRequest.getParameter(paramName); return value != null ? value.trim() : null; } } ``` 然后,注册该解析器: ```java @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new TrimArgumentResolver()); } } ``` 这样,当控制器方法的参数被@Trim注解标记时,解析器会处理: ```java @GetMapping("/test") public String test(@Trim @RequestParam String name) { return name; } ``` 但这种方法只适用于@RequestParam的参数,并且参数类型是String。对于对象中的字段,比如@ModelAttribute User user,其中User类的某个字段有@Trim注解,这种方法无法处理。 因此,需要针对对象中的字段进行处理。这时,可能需要结合@InitBinder和反射。 例如,在@InitBinder中注册一个自定义的绑定器,处理所有String类型的字段,但仅当字段有@Trim注解时才进行trim: ```java @ControllerAdvice public class TrimAdvice { @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(String.class, new PropertyEditorSupport() { @Override public void setAsText(String text) throws IllegalArgumentException { if (text == null) { setValue(null); } else { // 检查当前属性是否有@Trim注解 // 这里的binder.getTarget()可能获取到目标对象,但属性名如何获取? // 需要知道当前正在绑定的属性名 String fieldName = binder.getObjectName(); // 这可能不准确 Object target = binder.getTarget(); if (target != null) { try { Field field = target.getClass().getDeclaredField(fieldName); if (field.isAnnotationPresent(Trim.class)) { setValue(text.trim()); } else { setValue(text); } } catch (NoSuchFieldException e) { // 没有找到字段,可能不是直接绑定到字段 setValue(text); } } else { setValue(text.trim()); // 或者根据需求处理 } } } }); } } ``` 但这里的问题是如何获取当前正在绑定的字段名。在WebDataBinder中,可能无法直接获取到当前正在处理的字段名,因为绑定过程是基于属性路径的,例如,绑定到user.name时,可能需要递归处理。 因此,这种方法可能不可行,因为无法在PropertyEditor中获取当前绑定的字段信息。 另一个思路是使用BeanWrapper,在绑定完成后,遍历所有带有@Trim注解的字段并进行trim处理: ```java @ControllerAdvice public class TrimAdvice { @ModelAttribute public void trimStringFields(@RequestBody Object target, WebDataBinder binder) { BeanWrapper beanWrapper = new BeanWrapperImpl(target); for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) { try { Field field = target.getClass().getDeclaredField(pd.getName()); if (field.isAnnotationPresent(Trim.class) && pd.getPropertyType() == String.class) { String value = (String) beanWrapper.getPropertyValue(pd.getName()); if (value != null) { beanWrapper.setPropertyValue(pd.getName(), value.trim()); } } } catch (NoSuchFieldException e) { // 忽略没有对应字段的情况 } } } } ``` 但这种方法的问题在于,@ModelAttribute方法适用于所有被@ModelAttribute注解的参数,而@RequestBody的参数可能不会被处理。而且,使用@RequestBody的参数会被Jackson处理,可能需要结合之前的Jackson反序列化方法。 综上所述,可能需要结合多种方法来实现全面的trim处理: 1. 对于@RequestParam的参数,使用自定义的HandlerMethodArgumentResolver。 2. 对于@RequestBody的JSON参数,使用自定义的Jackson反序列化器结合@Trim注解。 3. 对于@ModelAttribute的表单对象,使用反射在绑定后处理带有@Trim注解的字段。 这样,用户可以在不同的场景下使用@Trim注解,确保参数被正确处理。 例如,针对@RequestBody的JSON处理: 定义@Trim注解: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @JacksonAnnotationsInside @JsonDeserialize(using = TrimmingDeserializer.class) public @interface Trim { } ``` 然后定义TrimmingDeserializer: ```java public class TrimmingDeserializer extends JsonDeserializer<String> { @Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return p.getValueAsString().trim(); } } ``` 这样,任何带有@Trim注解的字段在反序列化时会自动trim。 对于表单提交的@ModelAttribute对象,可以在@ControllerAdvice中使用@InitBinder和反射处理: ```java @ControllerAdvice public class TrimAdvice { @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(String.class, new StringTrimmerEditor(true) { @Override public void setAsText(String text) { // 这里无法直接知道当前字段是否有@Trim注解,因此可能无法条件处理 super.setAsText(text != null ? text.trim() : null); } }); } @ModelAttribute public void handleTrimAnnotations(Object commandObject) throws Exception { Class<?> clazz = commandObject.getClass(); for (Field field : clazz.getDeclaredFields()) { if (field.getType() == String.class && field.isAnnotationPresent(Trim.class)) { field.setAccessible(true); String value = (String) field.get(commandObject); if (value != null) { field.set(commandObject, value.trim()); } } } } } ``` 但这种方法可能在数据绑定之后再次处理,确保字段被trim。不过,@ModelAttribute方法会在每次请求时对所有被@ModelAttribute注解的参数执行,可能会影响性能,但通常可以接受。 总结以上思路,最终的解决方案可能需要结合多种方式,覆盖不同的参数绑定场景。但对于用户的问题,可能希望一个较为简单的示例,所以给出一个基于HandlerMethodArgumentResolver处理@RequestParam参数,以及基于@ControllerAdvice处理@ModelAttribute对象的示例。 最后,给出完整的代码示例,并说明不同情况下的处理方式,确保用户能够根据需求选择合适的实现方法。</think>在Java中实现自定义注解对请求参数进行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值