Skip to content
neaudiy edited this page May 26, 2023 · 9 revisions

wsilk

wsilk是一个辅助开发人员通过java语言生成代码的一个工具框架

介绍

wsilk是一个辅助开发人员写代码的工具,通过这个工具,可以自动生成大量本应该需要人工编写的逻辑重复,或者代码重复的代码,还能够自动生成辅助工具类,把复杂问题简单化,从而提升开发人员的开发效率、统一规范、提升代码质量

特征

  • 元数据(meta)深度收集 (java代码,class,jar以及配置文件)

  • 生成代码,结构化文档(DSL)等,内容可见

  • 生成的内容可编辑、可重写、重载

  • 生成器很容易开发

  • 外挂式、零侵入对项目不产生干扰

原理

原理

环境要求

wsilk目前只支持jdk8+, 使用maven构建工具开发Java项目

更新

第一个版本,支持注解生成代码,最新版本 1.0.0

配置

插件

项目中需要引入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 void properties(JavaWriter writer, SourceEntityMeta em) throws IOException {
	}

	@Override
	public void importPackage(JavaWriter writer, SourceEntityMeta em) throws IOException {
	}
}
上面代码的意思是,当我在你的项目源码中发现  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

加入讨论

原理