-
Notifications
You must be signed in to change notification settings - Fork 3
Home
wsilk是一个辅助开发人员通过java语言生成代码的一个工具框架
wsilk是一个辅助开发人员写代码的工具,通过这个工具,可以自动生成大量本应该需要人工编写的逻辑重复,或者代码重复的代码,还能够自动生成辅助工具类,把复杂问题简单化,从而提升开发人员的开发效率、统一规范、提升代码质量
-
元数据(meta)深度收集 (java代码,class,jar以及配置文件)
-
生成代码,结构化文档(DSL)等,内容可见
-
生成的内容可编辑、可重写、重载
-
生成器很容易开发
-
外挂式、零侵入对项目不产生干扰
项目中需要引入maven插件
<plugin>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-maven-plugin</artifactId>
<version>${wsilk-version}</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
<configuration>
<outputDirectory>wsilk/java</outputDirectory>
<processor>com.wuba.wsilk.core.WSilkAnnotationProcessor</processor>
<incremental>true</incremental>
<override>true</override>
<options>
<logable>false</logable>
</options>
</configuration>
<dependencies>
<dependency>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-core</artifactId>
<version>${wsilk-version}</version>
</dependency>
<dependency>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-producer</artifactId>
<version>${wsilk-version}</version>
</dependency>
</dependencies>
</plugin>
功能区域说明:
配置 |
默认值 |
说明 |
outputDirectory |
wsilk/java |
指生成的代码的输出路径,如果生成的代码不在本项目,后面会说明 |
processor |
com.wuba.wsilk.core.WSilkAnnotationProcessor |
代表代码生成器 |
incremental |
false |
代表增量生成,就是只对有变化的java文件,执行生成器。默认是false,为true的时候,要求开发工具环境也是在jdk8下面 |
override |
true |
指每次都按照指令全部生成新代码,如果false,只对有变化的java重新生成代码,无变化的java会跳过 |
scanPackage |
插件扫描路径,一般情况下,插件会扫描生成器相同包下的路径 |
|
logger |
false |
是否把日志信息打印在项目目录,默认是不打印 |
options |
无 |
会传递给生成器配置信息, 在生成器中通过 getConfiguration().getOptionsMapper() 获取 <logable>false</logable> :生成器是否打印过程日志, 可以 getConfiguration().getOptionsMapper().getOption("logable")得到false 属性支持内容替换,假如我们配置了 <suffix>model</suffix> <path>web/${mode}</path> 我们通过getConfiguration().getOptionsMapper().getOption("path") 将得到 web/model |
wsilk的核心包,它主要是提供元数据收集器、组织各种生成器、解析和获得各种资源、通过生成器生成源码或者其他文件
<dependency>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-core</artifactId>
<version>${wsilk-version}</version>
</dependency>
wsilk的生成器,这个生成器是我们开发的,主要完成一些常规功能,也用于指导大家开发和感受代码生成器
<dependency>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-producer</artifactId>
<version>${wsilk-version}</version>
</dependency>
在项目中 dependencies,我们需要依赖
<dependency>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-producer</artifactId>
<version>${wsilk-version}</version>
<scope>provided</scope>
</dependency>
注意,scope 可以设置成 provided 即可,我们引入它的目的 是要使用他的注解,但是我们环境或者生成的代码不依赖它,所以provided就可以了
== 第一个项目
=== 创建一个maven 项目 现在项目中引入maven插件和依赖
如不清楚,请参考上面 配置
=== 创建一个java对象
import java.util.Date;
public class User {
private String id;
private String username;
private String password;
private Date createTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
=== 生成一个对象的装饰模式(包装模式) 我们只需要在对象中打上注解 @Wrapper
@Wrapper
public class User {
...
...
=== 生成代码 mvn generate-sources 或者右键项目 调用 generate-sources
会在指定的目录下生成如下代码
@Generated("com.wuba.wsilk.sample.UserWrapper")
public abstract class UserWrapper extends User {
private final User delegate;
public UserWrapper(User delegate) {
this.delegate = delegate;
}
public User getDelegate() {
return this.delegate;
}
public User getLastDelegate() {
User result = this.delegate;
while (result instanceof UserWrapper) {
result = ((UserWrapper) result).getDelegate();
}
return result;
}
public static User unwrap(User delegate) {
if (delegate instanceof UserWrapper) {
return ((UserWrapper) delegate).getLastDelegate();
}
return delegate;
}
public String getId() {
return delegate.getId();
}
public void setId(String id) {
delegate.setId(id);
}
public String getUsername() {
return delegate.getUsername();
}
public void setUsername(String username) {
delegate.setUsername(username);
}
public String getPassword() {
return delegate.getPassword();
}
public void setPassword(String password) {
delegate.setPassword(password);
}
public java.util.Date getCreateTime() {
return delegate.getCreateTime();
}
public void setCreateTime(java.util.Date createTime) {
delegate.setCreateTime(createTime);
}
}
=== 生成其它代码 如果我们要基于这个对象生成其他代码,比如一个对象属性拷贝程序,我可以在这个类上新增一个注解 @Mapper(User.class)
@Wrapper
@Mapper(User.class)
public class User {
...
...
代表我们需要生成一个 和自己属性影射的工具类
=== 重新生成代码 mvn generate-sources
会生成如下代码,会得到一个新的java类
@Generated("com.wuba.wsilk.sample.mapper.UserMapper")
public class UserMapper {
public static void copy(User src, User des) {
if(src != null && des != null) {
des.setId(src.getId());
des.setUsername(src.getUsername());
des.setPassword(src.getPassword());
des.setCreateTime(src.getCreateTime());
}
}
}
== 生成的代码到其他路径
默认情况下,wsilk生成的代码会存放在你项目指定的目录下 如果我们有些特殊需求,希望有些注解生成的项目放在其它目录 配置如下:
我们需要在maven plugin中配置的options中配置 <Wrapper>../wsilk-utils/src/main/java</Wrapper> 带包Wrapper注解的代码,生成到当前项目的平级目录wsilk-utils的/src/main/java 中
我们也可以这么配置: 定义一个path : <path>../wsilk-utils/src/main</path> 引入: <Wrapper>${path}/java</Wrapper> 也可以
== 编写自己的生成器 指导开发第一个生成器
=== 创建一个生成器项目
创建一个maven项目,引入依赖
<dependency>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-core</artifactId>
<version>${wsilk-version}</version>
</dependency>
<dependency>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-codegen</artifactId>
<version>${wsilk-version}</version>
</dependency>
wsilk-core 是整个生成器的核心包,里面包含了 语法收集器、代码生成器
wsilk-codegen 是代码组装和生成器,目前支持java生成,后期可能做对其它语言的生成
=== 创建一个生成器注解 假如我们想给我们对象生成 Builder模式,我们定义一个注解 @Builder
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Builder {
}
注解的@Retention(RetentionPolicy.SOURCE) 代表只需要在源码阶段起作用就行,在生成class的时候忽略
=== 创建一个代码生成器
@Support(value = Builder.class, suffix = "Builder")
public class BuilderSerializer extends JavaSerializer<SourceEntityMeta> {
public BuilderSerializer(APTConfiguration configuration, Class<? extends Annotation> annClass) {
super(configuration, annClass);
}
@Override
public SourceEntityMeta init(SourceEntityMeta em) throws NoGenericException {
return super.init(em);
}
public BiConsumer<JavaWriter, SourceEntityMeta> propertiesConsumer() {
return (writer, em) -> {
};
}
@Override
public BiConsumer<JavaWriter, SourceEntityMeta> importConsumer() {
return super.importConsumer().andThen((writer, em) -> {
});
}
}
上面代码的意思是,当我在你的项目源码中发现 Builder 注解,就调用这个生成器生成代码. 先了解一下 @Support 注解
package build;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Support {
Class<? extends Annotation> value(); // 支持的注解
int order() default 0; // 运行顺序
String suffix() default ""; // 生成类的后缀
boolean parentPkg() default false;// 生成的新类的包路径 是在原始类的包路径的上一层,否则为同一层
boolean pkgInlcudeSuffix() default true;// 是否在包路径中加上 suffix的标识
boolean override() default false; // 是否留出重写空间
}
value 支持的注解 order 注解的优先顺序,越小越靠前,因为我们在生成代码的时候,有可能后生成的代码依赖前面生成的代码,所以排序是为了解决前后依赖问题 suffix 生成代码的文件名后缀 parentPkg 生成的代码的包名,是否是以父类为基准 pkgInlcudeSuffix 是否在生成的代码中,新生成的类中包名的最后一个路径为suffix override 是否留出二次开发的重写空间
如果我们是生成java代码 ,JavaSerializer类相关方法详解
方法名 |
参数 |
作用 |
说明 |
init |
SourceEntityMeta |
通过原始数据源,初始化要生成的数据源 |
初始化自己的SourceEntityMeta,参数传递的是原始的SourceEntityMeta,原始的SourceEntityMeta包含了注解所在对象上的信息 |
importPackage |
JavaWriter,SourceEntityMeta |
在生成的java代码中引入(import)相应的包 |
支持import ,import static 及相关java |
classAnnotation |
JavaWriter,SourceEntityMeta |
在生成类上的注解 |
在java对象上添加注解信息 |
getSuperClass |
SourceEntityMeta |
指定父类 |
我们可以返回一个父类,新生成的java对象,会继承这个父类 |
getSuperInterface |
SourceEntityMeta |
指定接口 |
我们可以返回多个接口对象,新生生成的java对象,会实现这几个接口 |
constructors |
JavaWriter,SourceEntityMeta |
创建构造器 |
指定我们生成的java对象的构造器 |
properties |
JavaWriter,SourceEntityMeta |
构建java的字段 |
通过这个方法,我们可以给我们新生成的java对象,指定public,private,protected 或者static ,final 等等各种修复符的变量及静态变量字段 |
methods |
JavaWriter,SourceEntityMeta |
构建java的方法 |
通过这个方法,我们可以给我们新生成的java对象,指定指定public,private,protected 或者static ,final 等等各种修饰 |
innerClass |
JavaWriter,SourceEntityMeta |
构建内部类的方法 |
通过这个方法,可以输出一个内部类 |
我的代码生成器
package build;
@Support(value = Builder.class, suffix = "Builder", override = false)
public class BuilderSerializer extends SingleJavaSerializer<SourceEntityMeta> {
// 拿到原始类的类型
private ClassType bean;
private String name;
public BuilderSerializer(WSilkConfiguration configuration, Class<? extends Annotation> annClass) {
super(configuration, annClass);
}
@Override
public SourceEntityMeta init(SourceEntityMeta em) throws NoGenericException {
// 拿到原始类的类型
bean = new ClassType(em.getJavaClass());
// 拿到原始类的名字
name = StringUtils.uncapitalize(bean.getSimpleName());
return super.init(em);
}
@Override
public void constructors(JavaWriter writer, SourceEntityMeta em) throws IOException {
// 创建一个私有构造器
writer.beginPrivateConstructor();
writer.line(THIS, DOT, name, ASSIGN, NEW, em.getOriginal().getSimpleName(), METHOD, SEMICOLON);
writer.end();
}
@Override
public void methods(JavaWriter writer, SourceEntityMeta em) throws IOException {// 重写所有方法
// 写一个静态create的方法
writer.beginStaticMethod(em, "create");
String javaName = em.getSimpleName();
String proxName = StringUtils.uncapitalize(javaName);
writer.line(javaName, SPACE, proxName, ASSIGN, NEW, javaName, METHOD, SEMICOLON);
writer.line(RETURN, proxName, SEMICOLON);
writer.end();
// 遍历元素数据的所有属性,生成对应的方法
Set<PropertyMeta> propertyMetas = em.getOriginal().getAllProperties();
if (propertyMetas != null) {
for (PropertyMeta propertyMeta : propertyMetas) {
writer.beginPublicMethod(em, propertyMeta.getName(),
new Parameter(propertyMeta.getName(), propertyMeta.getType()));
writer.line(THIS, DOT, name, DOT, SET, StringUtils.capitalize(propertyMeta.getName()), "(",
propertyMeta.getName(), ");");
writer.line(RETURN, THIS, SEMICOLON);
writer.end();
}
}
}
public void properties(JavaWriter writer, SourceEntityMeta em) throws IOException {
// 生成一个私有遍历
writer.privateFinal(bean, name);
}
@Override
public void importPackage(JavaWriter writer, SourceEntityMeta em) throws IOException {
// 导入原始数据的对象全名 getOriginal() 是获得注解上的原数据对象
writer.importClasses(em.getOriginal().getFullName());
}
}
在生成器项目下创建文件 META-INF/wsilk 指定生成器的包路径为build 编写好了生成器后打成jar包 假如jar为
<dependency>
<groupId>builder</groupId>
<artifactId>builder</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
在你的项目中,maven配置如下
<plugin>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-maven-plugin</artifactId>
<version>${wsilk-version}</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
<configuration>
<outputDirectory>wsilk/java</outputDirectory>
</configuration>
<dependencies>
<dependency>
<groupId>io.github.wuba</groupId>
<artifactId>wsilk-core</artifactId>
<version>${wsilk-version}</version>
</dependency>
<dependency>
<groupId>builder</groupId>
<artifactId>builder</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
然后在自己的maven项目中同样引入jar包 我们编写一个java类,并添加我们的注解
@Builder
public class User {
private String id;
private String username;
private String password;
private Date createTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
mvn generate-sources
我们将得到如下结果:
@Generated("com.wuba.wsilk.sample.builder.UserBuilder")
public class UserBuilder {
private final User user;
private UserBuilder() {
this.user = new User();
}
public static UserBuilder create() {
UserBuilder userBuilder = new UserBuilder();
return userBuilder;
}
public UserBuilder id(String id) {
this.user.setId(id);
return this;
}
public UserBuilder username(String username) {
this.user.setUsername(username);
return this;
}
public UserBuilder password(String password) {
this.user.setPassword(password);
return this;
}
public UserBuilder createTime(java.util.Date createTime) {
this.user.setCreateTime(createTime);
return this;
}
}
我们的第一个简易的代码生成器就开发完成了
== 代码生成器进阶 让我们了解代码生成器其他功能
=== 用文件数据源来生成java代码 假如我们要通过本地文件,生成java对象
我们可以定义一个注解 @Config ,这个注解打package 上,比如 package-info.java 上
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({ ElementType.PACKAGE })
public @interface Config {
/** 默认的config */
String value();
String name() default "Bundle";
}
我们可以在项目中 创建一个 package-info.java,然后打上注解,如下
@com.wuba.wsilk.producer.config.Config("cn")
package com.wuba.wsilk.sample.config;
生成器的代码 请查看 wsilk-sample 项目 com.wuba.wsilk.sample.config 下 在项目目录下放置两个配置文件 cn.properties 、en.properties
mvn generate-sources
我们将得到如下结果:
@Generated(value="com.wuba.wsilk.sample.config.Bundle_cn")
public class Bundle_cn extends Bundle{
private static final String TITLE_GOOD_GG="来了";
private static final String TITLE_NAME="测试哦";
public String title_good_gg(){
return TITLE_GOOD_GG;
}
public String title_name(){
return TITLE_NAME;
}
}
@Generated(value="com.wuba.wsilk.sample.config.Bundle_en")
public class Bundle_en extends Bundle{
private static final String TITLE_GOOD_GG="gg";
private static final String TITLE_NAME="sss";
public String title_good_gg(){
return TITLE_GOOD_GG;
}
public String title_name(){
return TITLE_NAME;
}
}
需要看文件生成java,请查看 wsilk-sample 项目
=== 用代码生成器生成其它文件 我们在开发项目的时候,如果要支持 SPI协议,我们怎么让wsilk帮我们自动生成呢 我们可以定义一个注解 @SPI ,注解放在接口上
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({ ElementType.TYPE })
public @interface SPI {
Class<?> value();
}
生成器的代码 请查看 wsilk-sample 项目 com.wuba.wsilk.sample.spi 下
mvn generate-sources
我们将得到如下结果:
在 src/main/resources/META-INF/services 下,我们得到文件 com.wuba.wsilk.sample.spi.Service
内容如下
com.wuba.wsilk.sample.spi.WsilkService
=== 可二次开发的java文件 wsilk帮我们生成了java代码,但是如果生成代码不能完全满足需求,我们想在在生成的代码上做修改,我们修改的代码在下次生成的时候如何不覆盖我们的代码 首先,我们在定义生成器的时候,override=true,代表我们提供重写功能,如下
@Support(value = Factory.class, order = 1, pkgInlcudeSuffix = false, parentPkg = false, override = true, suffix = "Factory")
override=true,生成的代码结构如下
我们能看到三个区域
- 合并区域
-
在这个区域的修改,将会和代码生成器自己产生的进行合并
- 可编辑区域
-
可以在这个区域写代码,代码不能会被覆盖,每次重新生成会保留
- 生成代码区域
-
这个部分每次生成代码,都会被覆盖
开发代码生成器的时候,如果希望自己的默写方法可以被重写,请方法设置为public 或者protected .
项目源码在 wsilk-producer 中
=== 多个注解生成一个java类
我们想开发一个工厂模式,通过传递不同的参数,工厂会生产出不同的对象,如果要让wsilk 帮你生成工厂类 请查看 wsilk-producer 项目 com.wuba.wsilk.producer.factory 下
=== 一个java上多个相同注解 如果我们希望一个java对象上能够有多个相同的注解,注解的属性值不同,用来生成不同的java代码 请查看 wsilk-producer 项目 com.wuba.wsilk.producer.singleton 下代码
=== 定义自己的SourceEntityMeta 如果我们的SourceEntityMeta 需要自定义,满足一些特殊需求,可以自定义 请查看 wsilk-producer 项目 com.wuba.wsilk.producer.meta 下代码
=== 生成多个java 要生成多个java文件,可以通过JavaSerializerDecorator类来扩展出多个生成器, 请查看wsilk-producer 中的 com.wuba.wsilk.producer.config
=== 输出的多片组装的java 我们要生成一个java文件,java文件中有多个方法,方法之间有嵌套关系,涉及我们跨位置输出,我们可以通过 JavaSerializerComposite组合类,来生成多片组装的java文件 请查看wsilk-producer 中的 com.wuba.wsilk.producer.rule
=== 依赖管理
如果我们生成的代码依赖第三方jar包,我们希望生成器自动给我们导入jar包依赖 我们可以如下代码。指定Dependency,也可指定多个 Dependency
@Dependency(groupId = "com.bj58.chr", artifactId = "wj2j", version = "1.0.0-SNAPSHOT")
@Support(value = JsonToJava.class, order = 1, pkgInlcudeSuffix = false, parentPkg = false, override = false)
public class JsonToJavaSerializer extends SingleJavaSerializer<SourceEntityMeta> {
当我们生成代码的时候,系统会备份用户的pom.xml到wsilk.xml中,然后修改用户的pom.xml。
下次生成,生成器会依赖wsilk.xml作为母版,重新生成pom.xml
=== demo地址
=== 加入讨论