自定义注解实践

370次阅读
没有评论

项目中很多地方的重复轮子都可以抽出来用AOP切面的形式搞定,这次我们就来撸两个注解玩下

TimerLog注解打印耗时

项目中,我们经常打印日志看下逻辑的耗时,大家都是各写各的搞的很不规范,那么我们为何不弄个注解,用的时候方法上加上不就可以了;

自定义切面需要引入Aspect的jar包:

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>

自定义TimerLog注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimerLog {
    TimerTypeEnum type() default TimerTypeEnum.MILL_SECOND;
}

TimerPrinter 打印日志切面类

@Aspect
@Component
@Slf4j
public class TimerPrinter {
    private static final ThreadLocal<Long> TIMER_LONG_LOCAL=new ThreadLocal<Long>(){
        protected Long initValue(){
            return System.currentTimeMillis();
        }
    };

    @Pointcut("@annotation(com.example.demo.aop.TimerLog)")
    public void pointCut(){

    }
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        TimerPrinter.start(signature.getDeclaringTypeName(),signature.getName());
    }

    @Around("pointCut()")
    public void around(){

    }
    @AfterReturning(pointcut = "pointCut()")
    public void after(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        TimerLog annotation = ((MethodSignature)signature).getMethod().getAnnotation(TimerLog.class);
        TimerTypeEnum typeEnum = annotation.type();
        TimerPrinter.end(signature.getDeclaringTypeName(),signature.getName(),typeEnum);
    }

    public static final void start(String className,String methodName) {
        TIMER_LONG_LOCAL.set(System.currentTimeMillis());
        log.info("[{}]-[{}] start...",className,methodName);
    }
    public static final void end(String className,String methodName){
        log.info("[]-[] total use time: [{}]{}",className,methodName,buildTime(null),TimerTypeEnum.MILL_SECOND.value);
        TIMER_LONG_LOCAL.get().intValue();
    }
    public static void end(String className,String methodName,TimerTypeEnum timerTypeEnum){
        log.info("[]-[] total use time: [{}]{}",className,methodName,buildTime(timerTypeEnum),TimerTypeEnum.MILL_SECOND.value);
        TIMER_LONG_LOCAL.get().intValue();
    }
    public static Long buildTime(TimerTypeEnum typeEnum){
        Long useTime = 0L;
        Long totalTime = System.currentTimeMillis()-TIMER_LONG_LOCAL.get();
        if(null == typeEnum){
            useTime = totalTime;
        }else if(Objects.equals(typeEnum,TimerTypeEnum.SECOND)){
            useTime = totalTime/1000;
        }else if(Objects.equals(typeEnum,TimerTypeEnum.MINUTE)){
            useTime = totalTime/6000;
        }
        return  useTime;
    }
}

自定义截取字段长度注解

现在有个需求,为了提高某个页面的性能,页面中的某个字段是需要我们批量去调外部接口查询的,但是导出数据量较大,实时循环查询就算异步的话也要很久;

这个时候,不考虑数据实时性的时候,我们一般采用定时任务将接口返回的数据,落地到我们自己的库中,但很多时候我们并不能准确的知道第三方接口返回的字段长度;

为了确保数据库入库是塞值不报错,我们需要在入库前对字段截取后再存入(前提是这个字段,这边不要求数据完整性);目前项目中有遇到个别数据有超长的情况,我们还是用注解加反射的方式解决。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DefFieldLength {
    int length() default 0;
}
public class DefinedUtils {

    /**
     * 自定义序列化时按注解截取 字段的长度
     * 注:只对string Integer Long类型字段生效
     * 其他例如Double BigDecimal float等有精度的字段 需要自行确认精度
     * @param needSerializeList
     * @param <T>
     */
    public static <T> void defSerialize(List<T> needSerializeList){
        if(CollectionUtils.isEmpty(needSerializeList)){
            return;
        }
        List<Field> allFields = new ArrayList<>(100);
        T firstT = needSerializeList.get(0);

        Class<?> aClass = firstT.getClass();
        allFields.addAll(Arrays.stream(aClass.getDeclaredFields()).collect(Collectors.toList()));
        Class<?> superClass = aClass.getSuperclass();
        // 当其父类不为Object时 停止获取属性
        while (superClass != Object.class){
            Field [] declaredFields = superClass.getDeclaredFields();
            if(declaredFields.length>0){
                List<Field> collect= Arrays.stream(declaredFields).collect(Collectors.toList());
                allFields.addAll(collect);
            }
            superClass =superClass.getSuperclass();
        }
        if(CollectionUtils.isEmpty(allFields)){
            return;
        }
        List<Field> distinctFieldList = allFields.stream().distinct().collect(Collectors.toList());

        needSerializeList.stream().forEach(t->{
            for(Field field:distinctFieldList){
                try {
                    field.setAccessible(true);
                    if(!field.isAnnotationPresent(DefFieldLength.class)){
                        continue;
                    }
                    Object fieldVal = field.get(t);
                    if(null == fieldVal){
                        continue;
                    }
                    DefFieldLength annotation = field.getAnnotation(DefFieldLength.class);
                    int annotationLen = annotation.length();
                    if(field.toString().length()<= annotationLen){
                        continue;
                    }
                    String finalStr = fieldVal.toString().substring(0,annotationLen);
                    setFieldVal(t,field,fieldVal,finalStr);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (Exception e){
                    e.printStackTrace();
                }

            }
        });
    }
    public static <T> void setFieldVal(T t,Field field,Object fieldVal,String value) throws Exception{
        if(fieldVal instanceof String){
            field.set(t,value);
        }else if(fieldVal instanceof Integer){
            field.set(t,Integer.valueOf(value));
        }else if(fieldVal instanceof Long){
            field.set(t,Long.valueOf(value));
        }
    }
}

小结:如上我们定义好了两个注解,实现了一个简单耗时打印的功能和一个自定义截取字段长度的功能,也展示了泛型和反射在我们开发中的应用; 思考:如果一个类中,方法A 方法B 都加了TimerLog注解,A中调用了B,两个注解都能生效吗?为什么?

viEcho
版权声明:本站原创文章,由viEcho2021-04-30发表,共计4503字。
转载提示:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
载入中...