背景

最近一次开发的时候,我们一般都会和数据库打交道,同时我们自己也会创建一些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的名字,

image.png

看起来好像解决问题,但。。。。我用的不是关系型数据库啊,也就没用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就是允许你在这个过程中可以根据注解做一些额外的事,比如生成一些新的文件

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

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下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名

image.png

image.png

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

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

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

编译后自动生成常量类User_
image.png

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


imango
3k 声望113 粉丝