转载

记一次Pluggable Annotation Processing开发小demo的简要过程

背景

最近一次开发的时候,我们一般都会和数据库打交道,同时我们自己也会创建一些 Entity 对象,比如 User ,它里面有一些属性

@Entity(name = "t_user")
    @Getter
    @Setter
    @NoArgsConstructor  
    public class User {  
        private String email;  
        private Long age;  
    }

当我在做一些 CRUD 操作的时候,难免我会需要用到 User 里的 field 的名字,也就是字符串 "email""age" ,一般我们可能不会注意这种问题,可能直接就在需要用到的地方直接写上字符串 "email"

但其实这么写某种意义上算是一种硬编码了,毕竟若是这个 field 名字在代码中到处都有使用,可能我们要修改 field 名字时,就需要改很多地方

于是我们可能顺其自然的,想到建立一个常量类,类似 UserConstants ,来存放User的属性的名字的静态常量

public interface UserConstants {  
        String EMAIL = "email";  
        String AGE = "age";  
    }

这样我们貌似可以解决了,若是想要修改 field 名字,我们只用改 UserConstants 里的值即可

不过正因为有这样的想法,假如我们有20个 Entity ,我们就要再写20个 Contants 类,真的太麻烦了,而且之前在 Entity 里写的属性名,就还要在 Contants 里再写一遍。。。真的太麻烦了

于是我想,能不能当我创建了某个 Entity ,它就可以自动生成对应的 Contants 类呢

hibernate-jpamodelgen

别说,还真有,我发现了一个工具 hibernate-jpamodelgen ,这看名字也是 hibernate 出的,其实就是根据 JPA 标准的 @Entity 注解的类来自动生成了对应的元数据,有些叫元模型, 注意哈,这里不是运行时,而是编译的时候 ,编译之后,就会生成新的类,下面是生成之后的类

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")  
    @StaticMetamodel(User.class)  
    public abstract class User_ {  

        public static volatile SingularAttribute<User, String> name;  
        public static volatile SingularAttribute<User, Long> age;  

    }

而根据此类的给的 fieldSingularAttribute 类型,通过该类你可以获取这个 field 的很多信息,不仅仅是我们之前想要的 field 的名字,

记一次Pluggable Annotation Processing开发小demo的简要过程

看起来好像解决问题,但。。。。我用的不是关系型数据库啊,也就没用 JPA ,那更谈不上 hibernate 了,我用的 mongodb ,但是我也有类似 Entity 这样的数据结构, mongodb@Document ,类似这样

@Document 
    @Getter
    @Setter
    @NoArgsConstructor  
    public class User {  
        private String name;  
        private Long age;  
    }

或者更往开的想,能不能做一个类似只要我标注的类,就可以自动生成它对应的类里的 field 的属性名常量类

由于 hibernate-jpamodelgen 的实现思路跟我想要的效果一样,于是我去了解一下它的实现原理,就引申出 Pluggable Annotation Processing

Pluggable Annotation Processing

Pluggable Annotation Processing 其实是已经推出很久的 API ,从1.6开始的,不过知道的人不算特别多,但是很多人都是在用着该 API 提供的便利。 API 只能是用于生成新文件,而不是用来更改现有文件

Pluggable Annotation Processing ,翻译过来可以叫可插拔(插件)化注解处理,是根据 [JSR 269] 来做出来的

JSR 其实就是 Java Specification Requests 的缩写,意思是 Java 规范提案,如果把 Java 语言本身看成一个产品,那 JSR 就是产品经理提的需求,而产品经理其实就是 JCP 这个组织)

如果简单理解编译过程是把源文件 .java 转换为 .class 文件的话,那 Pluggable Annotation Processing 就是允许你在这个过程中可以根据注解做一些额外的事,比如生成一些新的文件

这个用面向接口编程的思想很好理解,就是定义一个接口在编译过程中调用,这样有多少实现类,就可以用多少定制的实现

记一次Pluggable Annotation Processing开发小demo的简要过程

AutoConstants

了解了上面 Pluggable Annotation Processing 的功能,那我们就可以按照它的 API 来定制自己的实现了,由于 Pluggable Annotation Processing 是注解处理,因此,我们定制其实就是要关注两个点,这两个关注点输入给 Pluggable Annotation Processing API ,那应该就可以自动被执行了

  1. 你要处理或关注什么注解
  2. 你要怎么处理被该注解标注的目标

那按照之前我自己的需求,我希望有一个注解可以标注某个类,然后可以自动生成该类的 field 名字的常量类

因此我们首先创建一个注解 @AutoConstants

@Retention(RetentionPolicy.SOURCE)  
    @Target(ElementType.TYPE)  
    public @interface AutoConstants {  
    }

当然这样就解决了我们第一个要关注的点

接着我们做一个处理类 AutoConstantsProcessor ,来处理被 AutoConstants 标注的类,这里要介绍 Pluggable Annotation Processing 的关键类 AbstractProcessor ,这其实是一个抽象类,它的实现的接口 Processor 才是上层抽象,不过 AbstractProcessor 帮我们做了一些默认实现,让我们只关注处理的定制业务本身

public class AutoConstantsProcessor extends AbstractProcessor {
        
        // 你要处理哪个注解
        @Override  
        public Set<String> getSupportedAnnotationTypes() {  
            return ImmutableSet.of(AutoConstants.class.getName());  
        }
        
        // 你处理支持的java版本
        @Override  
        public SourceVersion getSupportedSourceVersion() {  
            return SourceVersion.latestSupported();  
        }
        
        // 初始化参数
        @Override  
        public synchronized void init(ProcessingEnvironment processingEnv) {
        }
        
        // 关键方法,你怎么处理,这里面你可以做你想要做的
        @Override  
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        
        }
    }

当然你需要指定你是接口 Processor 的实现类,当然不是语法层面的告诉,而是直接告诉编译器,你是 Processor 的实现类,因此,这里要用到 ServiceLoader API 的方式,在 META-INF 下新建一个文件夹 services ,并在 services 下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名

记一次Pluggable Annotation Processing开发小demo的简要过程

记一次Pluggable Annotation Processing开发小demo的简要过程

补充一点,其实 ServiceLoader 这个大家平常也都很常用到,只是大家没有发觉而已,比如数据库驱动, JDBC 标准里定义的驱动其实是一个接口 java.sql.Driver ,大家每次使用某个数据库,需要加一个驱动包,这个驱动包的也有个类似的配置(以 Mysql 为例)

记一次Pluggable Annotation Processing开发小demo的简要过程

记一次Pluggable Annotation Processing开发小demo的简要过程

言归正传,这样的话,需要的工程直接简单依赖,然后加上 @AutoConstants 注解即可

@Document  
    @Getter  
    @Setter  
    @NoArgsConstructor  
    @AutoConstants  
    public class User {  
        private String name;  
        private Long age;  
    }

编译后自动生成常量类 User_
记一次Pluggable Annotation Processing开发小demo的简要过程

中间实现过程不再描述拉,感兴趣的可以自己尝试一下,我生成代码用到了 JavaPoet ,多半需要熟悉哈 Pluggable Annotation Processing API ,当然其实大家熟知的 Lombok 也有用到 Pluggable Annotation Processing API ,不过我们知道 Lombok 其实是修改了原文件的,所以那里用到的 API 不在 Pluggable Annotation Processing 之内

原文  https://segmentfault.com/a/1190000021359205
正文到此结束
Loading...