java自定义注解实现字段格式化
实际需求中,有这一个场景,从上游接数据,数据长度和精度五花八门,但输出的时候要统一修改为保留两位小数
一、自定义格式化注解
我自定义了一个注解,来完成这个功能
这个注解定义在对象属性之上,有两个字段,一个代表目标格式,默认是保留两位小数,一个是如果遇到异常,比如字段为空的时候,这个时候默认值是多少
@Target({ ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface DecimalFormat { String format() default "%.2f"; String exception() default "0"; }
二、注解处理器
定义好之后,就可以把注解加上,但想要注解生效,还需要一个注解处理器
public class DecimalFormatProcessor { public static void doFormat(Object obj) throws IllegalAccessException { Class<?> aClass = obj.getClass(); Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { if(declaredField.isAnnotationPresent(DecimalFormat.class)){ DecimalFormat format = declaredField.getDeclaredAnnotation(DecimalFormat.class); String format1 = format.format(); String exceptionResult = format.exception(); declaredField.setAccessible(true); Object o = declaredField.get(obj); if (o!=null) { try { double v = Double.parseDouble(o.toString()); String result = new Formatter().format(format1, v).toString(); declaredField.set(obj,result); } catch (NumberFormatException e) { declaredField.set(obj,exceptionResult); } }else{ declaredField.set(obj,exceptionResult); } } } } }
这样,生成对象后,手动调用注解处理器,就可以把对象的属性格式化一遍,类似下面
public EnergyRealTime realTime() { EnergyRealTime lastRecord = realTimeRepository.findLastRecord(); try { DecimalFormatProcessor.process(lastRecord); } catch (IllegalAccessException e) { throw new RuntimeException(e); } return lastRecord; }
三、注解处理自动化
但是当使用的地方比较多,每次都手动调,显得很不优雅,我想到了spring的切面AOP
先改造下上面的注解处理器,让它成为spring的一个bean
@Service public class DecimalFormatProcessor{ public void process(Object obj) throws IllegalAccessException { Class<?> aClass = obj.getClass(); Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { if(declaredField.isAnnotationPresent(DecimalFormat.class)){ DecimalFormat format = declaredField.getDeclaredAnnotation(DecimalFormat.class); String format1 = format.format(); String exceptionResult = format.exception(); declaredField.setAccessible(true); Object o = declaredField.get(obj); if (o!=null) { try { double v = Double.parseDouble(o.toString()); String result = new Formatter().format(format1, v).toString(); declaredField.set(obj,result); } catch (NumberFormatException e) { declaredField.set(obj,exceptionResult); } }else{ declaredField.set(obj,exceptionResult); } } } } }
然后定义一个切点注解,用于标记注解处理器在哪里执行,这个注解只能用在方法上
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FormatPoint { }
最后我们实现一个AOP,告诉AOP在有FormatPoint注解的方法上,对这个方法的返回值对象进行一次数据格式化
@Slf4j @Component @Aspect public class FormatAop { @Pointcut("@annotation(cn.jinka.gcdp.infrastructure.utils.FormatPoint)") public void ServiceAspect() { } @Autowired private DecimalFormatProcessor processor; @Around("ServiceAspect()") public Object around(ProceedingJoinPoint proceedingJoinPoint) { Object[] args = proceedingJoinPoint.getArgs(); Object proceed = null; try { proceed = proceedingJoinPoint.proceed(args); processor.process(proceed); } catch (Throwable e) { log.error(e.getMessage()); } return proceed; } }
现在想调用注解,只需要把@FormatPoint加到对应的方法上,就可以生效
@FormatPoint public EnergyRealTime realTime() { return realTimeRepository.findLastRecord(); }
这样就大功告成!