Skip to main content

JMobileSuitLite:.NET->Java移植,以及Maven打包和发布

· 13 min read
Ferdinand Su

前言

最近一些缘故软构实验,需要开发一些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.

其语法如

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SuitIgnore
{

}

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

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

如SuitAlias标签:

    [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中转换为

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SuitAlias
{
/**
* @return The alias.
*/
String value();

}

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

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SuitAliases
{
/**
* Container.
* @return SuitAliases
*/
SuitAlias[] value();
}

并修改SuitAlias

@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关键字用于方法参数的声明,如:

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

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

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

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

那么映射过程就是:

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

映射为

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服务器,才有效。 执行命令:

gpg --send-keys <key的ID>

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

gpg --recv-keys <key的ID>

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

配置maven设置

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

    <server>
<id>随便起一个名</id>
<username>jira用户名</username>
<password>jira密码</password>
</server>

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

    <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下面添加

     <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,格式如下,注意repository那里可能需要修改,具体看Jira那边给的评论

<?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>v@{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命令

mvn clean deploy -P release 

就可以进行发布了。

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

总结

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

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

加油!奥利给!