备注:为了清楚表述问题,以下代码相较源码有所改动,但是并未改动代码逻辑。
先说说背景吧,项目需要用docker的java sdk做一个docker镜像的构建服务,允许用户输入构建目录,dockerfile的文件路径等等参数,构建服务根据用户输入的参数进行镜像构建。
构建的请求过程是:client将构建目录打成tar包发送至docker deamon,dockers deamon根据dockerfile参数找到dockerfile进行构建。其中实例化构建命令类 (BuildImageCmd) 的对象的示例代码如下:
BuildImageCmd buildImageCmd = new BuildImageCmd.BuildImageCmdBuilder()
.withDockerfile(dockerFile) //dockerfile路径
.withBaseDirectory(baseDir) //构建路径
.build();
由于docker build命令的参数较多,读者如果想深入了解,可以参考docker的帮助文档。
大家都知道,当一个实体类有很多的属性,这些属性有些是必选有些是可选时,我们一般会使用构建器来实例化实体类的对象。Docker build的参数这么多,有些可选有些必选,自然会选用构建器来实例化对象。具体BuildImageCmd的实现如下代码所示:
public class BuildImageCmd {
private InputStream tarInputStream; //构建目录的tar包输入流
private File dockerFile; //dockerfile路径
private File baseDirectory; //构建路径
public InputStream getInputStream() { return tarInputStream ;}
public File getDockerFile() { return dockerFile;}
public File getBaseDirectory() {return baseDirectory;}
public static class BuildImageCmdBuilder{
private InputStream tarInputStream;
private File dockerFile;
private File baseDirectory;
public BuildImageCmdBuilder withTarInputStream(InputStream tarInputStream) {
this.tarInputStream = tarInputStream;
return this;
}
public BuildImageCmdBuilder withBaseDirectory(File baseDirectory) {
this.baseDirectory = baseDirectory;
return this;
}
public BuildImageCmdBuilder withDockerfile(File dockerfile) {
if (!dockerfile. exists ()) {
throw new IllegalArgumentException("Dockerfile does not exist");
}
if (!dockerfile.isFile()) {
throw new IllegalArgumentException("Not a directory");
}
if (baseDirectory == null) {
withBaseDirectory(dockerfile.getParentFile());
}
this.dockerFile = dockerfile;
try {
withTarInputStream(new Dockerfile(dockerfile, baseDirectory).parse().buildDockerFolderTar());
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public BuildImageCmd builder() { return new BuildImageCmd(this);}
}
private BuildImageCmd(BuildImageCmdBuilder builder) {
this.tarInputStream = builder.tarInputStream;
this.baseDirectory = builder.baseDirectory;
this.dockerFile = builder.dockerFile;
}
}
我们重点看WithDockerfile(File dockerfile)方法
方法中,首先进行了参数校验,然后判断了baseDirectory这个属性是不是为空,如果为空,则设置baseDirectory为dockerfile的父目录,然后将构建目录baseDirectory目录下的所有文件打包。纳尼,如果没事先调用withBaseDirectory(File baseDirectory)方法,则会把构建目录设置为dockerfile的父目录,并且把构建内容(tarInputStream属性)也设置好???
那这不就是意味着假如我先设置了dockerfile,然后再设置baseDirectory或者tarInputStream就不起作用吗?嗯,实验证明,确实是这样的。
坑 :实体类的属性之间相互有依赖的时候,切忌不要使用构建器当作实例化的方法。如果非要这样,请想好怎么解决相互之间的依赖带来的问题或者写好注释!!!
坑已经踩了,规避办法倒也简单,只需将withDockerfile(dockerFile)的调用置于withBaseDirectory(baseDir)的调用后面。但这是在踩了坑看了源代码之后发现的。如果别人再用,这个坑难免还是会踩到,那么如何从设计上根本解决这个问题呢?
其实解决问题的根本就是得保证baseDirectory和tarInputStream的设置先于dockerfile的设置。因此我们可以将这两个属性的设置置于Builder的构造方法里面,示例代码如下所示:
public static class BuildImageCmdBuilder{
private InputStream tarInputStream;
private File dockerFile;
private File baseDirectory;
public BuildImageCmdBuilder(InputStream tarInputStream) {
this.tarInputStream = tarInputStream;
}
public BuildImageCmdBuilder(File baseDirectory) {
this.baseDirectory = baseDirectory;
}
public BuildImageCmdBuilder withTarInputStream(InputStream tarInputStream){
this.tarInputStream = tarInputStream;
return this;
}
… //其他代码省略
}
这样用户在用构建器实例化对象时,必须先设置tarInputStream或者baseDirectory。也就自然解决了上面由于属性相互依赖导致的问题了。
最后再说一遍,请写好代码注释,避免其他人不必要的踩坑!
私信来聊天!
因今日头条推荐流问题…为了避免 【一起来Java】 消失在大家的信息流中,小编这里欢迎大家多 评论 、 点赞 、 关注 哦~ 关注完之后 大家就可以每天正常收到我们的推送,愉快学习愉快聊天啦!
大家也可以将学习使用Java过程中不懂的困惑提出来,小编也会为大家解决疑惑的哦!
最近我也整理了一些Java资料,包含 面经分享、模拟试题和视频干货 ,都是免费的哦!如果感兴趣的话,欢迎 来私信~