手撸两个注解

手撸两个注解

viEcho Lv5

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

TimerLog注解打印耗时

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

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

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>

自定义TimerLog注解

1
2
3
4
5
6
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimerLog {
TimerTypeEnum type() default TimerTypeEnum.MILL_SECOND;
}

TimerPrinter 打印日志切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@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;
}
}

自定义截取字段长度注解

现在有个需求,为了提高某个页面的性能,页面中的某个字段是需要我们批量去调外部接口查询的,但是导出数据量较大,实时循环查询就算异步的话也要很久;
这个时候,不考虑数据实时性的时候,我们一般采用定时任务将接口返回的数据,落地到我们自己的库中,但很多时候我们并不能准确的知道第三方接口返回的字段长度;
为了确保数据库入库是塞值不报错,我们需要在入库前对字段截取后再存入(前提是这个字段,这边不要求数据完整性);目前项目中有遇到个别数据有超长的情况,我们还是用注解加反射的方式解决

1
2
3
4
5
6
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DefFieldLength {
int length() default 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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,两个注解都能生效吗?为什么?

  • Title: 手撸两个注解
  • Author: viEcho
  • Created at : 2021-04-28 08:42:52
  • Updated at : 2024-01-18 14:52:24
  • Link: https://viecho.github.io/2021/0428/double-annotation.html
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments