Java编译期代码检查实用功能介绍

81 阅读1分钟

一、java生命周期

image.png

二、在编译期的时候检查代码中某些配置是否正常

2.1 引入maven依赖

        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
        </dependency>

AutoService是Google开发一个自动生成SPI文件的框架。

2.2 实现一个注解检查功能

我的代码里面有用到一个自己写的注解@Cache 里面有一个属性叫 allowOptionalEmptyValues,这个属性的作用是当被@Cache 修饰的方法返回类型如果是 Optional<T>的时候不要进行拆箱操作。但是方法的返回类型不是Optional<T>就没必要用这个属性,我想提醒用这个属性的人,就用java自带的编译期检查来实现这个提醒功能。

@SupportedAnnotationTypes("com.my.framework.cache.annotation.Cacheable") // 你的注解全路径
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 你的Java版本,支持的最大java版本限制
@AutoService(Processor.class) // javax.annotation.processing.Processor 这个是Processor是Java内置的,Javac编译前默认的注解处理器接口
public class CacheableCompilerProcessor extends AbstractProcessor {


    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement annotation : annotations) {
            // processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "annotation: " + annotation.toString());

            Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
            // 遍历被@Cacheable表示的属性或方法
            for (Element annotatedMethodElement : annotatedElements) {
                // processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "annotatedElements: " + annotatedMethodElement.toString());
                if (annotatedMethodElement.getKind() != ElementKind.METHOD && annotatedMethodElement.getKind() != ElementKind.FIELD) {
                    continue;
                }
                // 获取方法上@Cacheable注解属性
                Cacheable methodCacheable = annotatedMethodElement.getAnnotation(Cacheable.class);
                if (Objects.isNull(methodCacheable)) {
                    continue;
                }
                // processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "methodCacheable: " + methodCacheable);
                ExecutableElement roundMethodExecutableElement = ((ExecutableElement) annotatedMethodElement);
                // 遍历出方法的修饰符如: public static void 等
                String methodModifiersStr = roundMethodExecutableElement.getModifiers().stream().map(Modifier::toString).collect(Collectors.joining(" "));
                TypeMirror returnType = roundMethodExecutableElement.getReturnType();
                // 组装方法完整签名
                String methodDescription = methodModifiersStr + " " + returnType + " " + annotatedMethodElement;
                boolean hasOptionalReturnType = returnType.toString().startsWith("java.util.Optional");
                if(methodCacheable.allowOptionalEmptyValues() && !hasOptionalReturnType) {
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,methodDescription +
                            " @Cacheable 注解的方法当属性 allowOptionalEmptyValues = true 时,返回类型必须为 java.util.Optional 类型! " +
                            "当前@cacheable allowOptionalEmptyValues = " + methodCacheable.allowOptionalEmptyValues(), annotatedMethodElement);
                }

                if (hasOptionalReturnType && methodCacheable.allowNullValues() && methodCacheable.allowOptionalEmptyValues()) {
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, methodDescription +
                            "注意:@Cacheable 注解的方法由于会拆包装包Optional返回值,当 allowNullValues=true 且 allowOptionalEmptyValues=true 时," +
                            "不论是null还是Optional包装的null对象都会被缓存!" +
                            " 当前@cacheable allowNullValues = " + methodCacheable.allowNullValues() +
                            ", allowOptionalEmptyValues = " + methodCacheable.allowOptionalEmptyValues());
                }
            }
        }
        return true; // 表示注解已被处理,后续处理器不会再处理它们
    }
}

参考资料:Java注解编译期处理AbstractProcessor详解