最近一次开发的时候,我们一般都会和数据库打交道,同时我们自己也会创建一些 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
出的,其实就是根据 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; }
而根据此类的给的 field
是 SingularAttribute
类型,通过该类你可以获取这个 field
的很多信息,不仅仅是我们之前想要的 field
的名字,
看起来好像解决问题,但。。。。我用的不是关系型数据库啊,也就没用 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
其实是已经推出很久的 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
的功能,那我们就可以按照它的 API
来定制自己的实现了,由于 Pluggable Annotation Processing
是注解处理,因此,我们定制其实就是要关注两个点,这两个关注点输入给 Pluggable Annotation Processing API
,那应该就可以自动被执行了
那按照之前我自己的需求,我希望有一个注解可以标注某个类,然后可以自动生成该类的 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
下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名
补充一点,其实 ServiceLoader
这个大家平常也都很常用到,只是大家没有发觉而已,比如数据库驱动, JDBC
标准里定义的驱动其实是一个接口 java.sql.Driver
,大家每次使用某个数据库,需要加一个驱动包,这个驱动包的也有个类似的配置(以 Mysql
为例)
言归正传,这样的话,需要的工程直接简单依赖,然后加上 @AutoConstants
注解即可
@Document @Getter @Setter @NoArgsConstructor @AutoConstants public class User { private String name; private Long age; }
编译后自动生成常量类 User_
中间实现过程不再描述拉,感兴趣的可以自己尝试一下,我生成代码用到了 JavaPoet
,多半需要熟悉哈 Pluggable Annotation Processing API
,当然其实大家熟知的 Lombok
也有用到 Pluggable Annotation Processing API
,不过我们知道 Lombok
其实是修改了原文件的,所以那里用到的 API
不在 Pluggable Annotation Processing
之内