前言

最近一些缘故软构实验,需要开发一些Java的命令行程序。然而习惯了使用MobileSuit的我觉得这些重复的输入输出、解析操作,实在是过于繁琐。

MobileSuit是什么呢?是一套把用户输入的命令直接映射到某个类的方法的框架。具体参见MobileSuitDocs。它可以极大的简化命令解析这种繁琐、高度重复,但是又毫无意义的工作。

经过思考与权衡,我不难得出,我直接写实验所需要的时间\ge我移植MobileSuit的时间+使用MobileSuit完成实验的时间。

成品文档

因此,我花了昨天接近一天的时间,完成了移植和Maven打包发布的配置(现在在等审核)。以下是具体过程:

.NET到JAVA移植

经过这么长时间的发展,.NET Core无疑是比Java更有效率、更强大的平台,再加上我学习Java时间不过两个月,理解不太深入,因此.NET的很多特性,(凭借我的能力)难以在JVM上实现。因此,在进行移植时,我决定只移植最核心的一部分功能,而去除了一些高级功能(参数类型转换,并行和异步流支持,成员递归成员的函数调用等等)。

尽管是轻量级版本,JMobileSuitLite仍然是强大的指够用了

考虑到C#和Java代码的高度相似性,移植就是一个映射过程;实际上在移植时,我做了如下映射:

namespacepackageclass/structclassenumenuminterfaceinterface(T1,T2)Tuple<T1,T2>propertyfield,methodobjectswitchswitchcaseIEnumberableIterableIenumeratorIteratorattributeannotationoutargumentTuplereturnvaluenullableasynchronouselementsnamespace\rightarrow package\\ class/struct\rightarrow class\\ enum\rightarrow enum\\ interface\rightarrow interface\\ (T1,T2)\rightarrow Tuple<T1,T2>\\ property\rightarrow field,method\\ object-switch{}\rightarrow switch-case\\ IEnumberable\rightarrow Iterable\\ Ienumerator\rightarrow Iterator\\ attribute\rightarrow annotation\\ out-argument\rightarrow Tuple-return-value\\ nullable\rightarrow \emptyset\\ asynchronous-elements\rightarrow\emptyset

不难看出,前9个都是相当直接的映射。而最后两者则由于JVM的局限性并没有被移植。

那么移植的重点在于对attribute和out-argument的处理。

Attribute的处理

Java中没有Attribute,但是有与之非常相似的Annotation.

其语法如

1
2
3
4
5
6
7
8
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SuitIgnore
{

}

其中Retention(RetentionPolicy.RUNTIME)表示这个Annotation会被保留到运行时。Inherited表示允许继承。

然而这样只能解决单重Attribute的问题,对于多重Attribute(即一个目标上加的多个Attribute)

如SuitAlias标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public sealed class SuitAliasAttribute : Attribute
{
/// <summary>
/// Initialize a SuitAlias with its text.
/// </summary>
/// <param name="text">The alias.</param>
public SuitAliasAttribute(string text)
{
Text = text;
}
/// <summary>
/// The alias.
/// </summary>
public string Text { get; }
}

在Java中转换为

1
2
3
4
5
6
7
8
9
10
11
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SuitAlias
{
/**
* @return The alias.
*/
String value();

}

会出现问题。为了解决这一问题,需要额外增加一个Annotation

1
2
3
4
5
6
7
8
9
10
11
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SuitAliases
{
/**
* Container.
* @return SuitAliases
*/
SuitAlias[] value();
}

并修改SuitAlias

1
2
3
4
5
6
7
8
9
10
11
12
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(SuitAliases.class)
public @interface SuitAlias
{
/**
* @return The alias.
*/
String value();

}

这样,一个目标就可以添加多个SuitAlias了。在解析时,一个SuitAlias会被解释为SuitAlias注解,而SuitAliases会get到null;多个SuitAlias会被解释为一个SuitAliases,而SuitAlias会get到null。

out-argument的处理

C#中out关键字用于方法参数的声明,如:

1
bool TryParse(string expression, out int result){}

这个函数尝试解析expression,返回解析是否成功,然后把解析结果输出在result变量。

很遗憾,Java并没有这样的功能。因此我们只能在函数返回值上进行通融:把返回值改成一个元组。

Java也没有Tuple,不过这个很容易就能写一个(

那么映射过程就是:

1
public TraceBack Execute(string[] args, out object? returnValue);

映射为

1
Tuple<TraceBack,Object>  Execute(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException;

小结

C#代码移植到Java,除了上面提到的两点以外,基本都是机械性的东西。因为C#的很多语法糖在Java(尤其是被迫使用的Java8)里都是没有的。不过只要有耐心,移植绝对不是难事。

使用Maven打包和发布

创建Maven的JIRA仓库

和C#统一使用nuget不同,java的包管理(貌似)很混乱,所以我就选择了一个maven(因为之前一直在用)。

要把jar包发布到Maven,供所有人进行下载,实际上是发布到maven的中央仓库里。在search.maven.org里,你可以搜到所有maven的jar包。

然而这个站点和nuget.org不同,完全找不到上传!(吐血

经过一番搜索与学习,最终我找到了发布maven jar包的正路。

首先,你得有一个待发布的maven项目。

然后,到jira创建账户(密码要求复杂,建议用Chrome生成的),并new一个issue。这个网站打开很慢,不过后面还有一个网站打开比它还慢,审核、同步也需要一段时间,所以,可能你传一个nuget包,上传+审核只需要几分钟,发布一个maven包却可能需要几个小时。
问题配置如图:
issue创建配置

项目选Community Support - Open Source Project Repository Hosting (OSSRH),问题类型New Project,概要写项目名,描述就是项目描述~
Group ID一般写io.github.用户名就行
Project URL是项目地址,github链接贴上
SCM url是仓库地址,就是git clone时用的地址
usernames写自己和合作伙伴的JIRA用户名即可。
Already Synced to Central表示是否已经上传到中央仓库,当然是否啦~
创建完Issue,你会被导航到这个页面
issue页面

等待一会,管理员在comment中会给提示,然后issue状态变为waiting for response。

issue页面的comment

这时,只要按照人家的要求做,然后评论就行,然后issue状态变回开放。

一般而言,再过几分钟,issue状态会变为已解决,那么JIRA的一切都已经准备妥当了!车备好了,团长!

创建并上传GPG密钥

之后,我们需要生成一个gpg密钥用来给包签名,具体操作参考github给的教程

gpg要添加到path。

生成的gpg密钥要上传到OpenGPG服务器,才有效。
执行命令:

1
gpg --keyserver hkp://pool.sks-keyservers.net --send-keys <key的ID>

来上传。key的ID就是gpg --list-secret-keys --keyid-format LONG得到那个.
然后运行命令

1
gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys <key的ID>

来进行检查,显示
GPG key检查结果
就备好了。由于GPG服务器之间的同步需要时间,后面mvn deploy的时候可能会出现认证失败的问题,等一段时间就好了。

配置maven设置

之后我们要配置maven的setting.xml,可以在maven home/conf里配置全局的,也可以在用户文件夹/.m2/里配置用户的。
一共有两处需要增加;一个是在servers标签下,增加:

1
2
3
4
5
<server>
<id>随便起一个名</id>
<username>jira用户名</username>
<password>jira密码</password>
</server>

记刚才随便起的名为"server名"
还有profiles下增加:

1
2
3
4
5
6
7
8
9
10
11
12
<profile>
<id>server名</id>

<activation>
<activeByDefault>true</activeByDefault>
</activation>

<properties>
<gpg.executable>gpg</gpg.executable>
<gpg.passphrase>gpg密钥的密码</gpg.passphrase>
</properties>
</profile>

另外,由于国内下载maven包速度感人,还可以换maven源,在mirrors下面添加

1
2
3
4
5
6
 <mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>

这是阿里云的源,速度很不错。

配置项目

最后,我们配置Maven项目的POM.xml,格式如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>项目名</name>
<groupId>就是JIRA上提issue用的那个</groupId>
<artifactId>项目识别id,需要是唯一的</artifactId>
<version>版本</version>
<packaging>jar</packaging>
<organization>
<!-- 如果有组织,可填写此字段 -->
<name>组织名</name>
<url>组织url</url>
</organization>
<url>项目url</url>
<inceptionYear>项目创建年份,用于copyright</inceptionYear>
<developers>
<developer>
<!-- 开发者字段,写自己就行咯 -->
<name>名字</name>
<email>邮箱</email>
<url>地址</url>
</developer>
</developers>
<description>项目描述</description>

<licenses>
<license>
<!-- 项目的license,比如MIT -->
<name>MIT</name>
<url>https://www.mit.edu/~amini/LICENSE.md</url>
</license>
</licenses>
<build>
<finalName>${project.organization.name}.${project.name}.${project.version}</finalName>
<defaultGoal>Package</defaultGoal>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- 打包Javadoc的插件,建议加上 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<encoding>UTF-8</encoding>
<charset>UTF-8</charset>
<docencoding>UTF-8</docencoding>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 打包源代码的插件,建议加上 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>




</plugins>
</pluginManagement>

</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.3</version>
<extensions>true</extensions>
<configuration>
<serverId>server名</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<failOnError>false</failOnError>
<doclint>none</doclint>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.0.0-M1</version>
<configuration>
<tagNameFormat>[email protected]{project.version}</tagNameFormat>
<autoVersionSubmodules>true</autoVersionSubmodules>
</configuration>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>server名</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>server名</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
</profile>
</profiles>
<scm>
<!-- git相关配置 -->
<url>项目github地址</url>
<connection>scm:git:+仓库地址(git clone时用的那个)</connection>
<developerConnection>scm:git:+仓库地址(git clone时用的那个)</developerConnection>
<tag>${project.version}</tag>
</scm>

</project>

上传,发布

之后,执行maven命令

1
mvn clean deploy -P release 

就可以进行发布了。

在所有本地操作运行完后(一分钟),你可以到JIRA仓库里找到你上传的包,搜索groupID或者包名就行。然后,再过2小时左右,你可以在maven中心仓库搜索到你的包,此时,发布就算是完全成功了。

总结

移植花了一整天,发布花了大半天,总的来说,移植就是简单粗暴体力活,发布则需要更多耐心和细心,这一路上坑很多,网站打开速度也很慢。

但是,当你准备发布一个项目时,就应该为这些做好心理准备了吧。
所以说啊,不要停下来啊!

加油!奥利给!