maven 如何为不同的Java版本支持不同版本的主(和测试)源代码集(6,7,8)

fzwojiic  于 12个月前  发布在  Maven
关注(0)|答案(8)|浏览(170)

我有一个Java库项目。我想实现,测试,并可能发布该项目的几个版本,旨在与不同的Java版本:6,7,8。
最简单的方法是复制粘贴项目并支持多个源代码树,但我想避免这种方法,因为它很繁琐且容易出错。
另一种可能的方法是分解“基础”项目,以及几个Java版本特定的项目。版本差异很小,但我不知道如何在类层次结构中反映这个技术开发问题。
所以我想找

  • 一种预编译器
  • 和/或标准Maven选项
  • 和/或Maven插件

这有助于从单个源代码树中支持多个Java版本特定的库版本,并对库用户透明。

agxfikkp

agxfikkp1#

您可以从一个pom.xml文件为每个Java版本(6、7、8)生成一个jar。
所有相关的工作都发生在maven-compiler-plugin:compile mojo中。
诀窍是执行mojo 3次,每次将结果文件写入不同的outputFileName。这将导致编译器多次运行,每次使用不同的版本并吐出适当命名的文件。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <executions>
        <execution>
            <id>compile-1.6</id>
            <goals>
                <id>compile</id>
            </goals>
            <phase>compile</phase>
            <configuration>
                <source>1.6</source>
                <target>1.6</target>
                <executable>${env.JAVA_HOME_6}/bin/javac</executable>
                <outputFileName>mylib-1.6.jar</outputFileName>
            </configuration>
<!-- START EDIT -->
            <dependencies>
                <dependency>
                    <groupId>com.mycompany</groupId>
                    <artifactId>ABC</artifactId>
                    <version>1.0</version>
                </dependency>
            </dependencies>
<!-- END EDIT -->
        </execution>
        <execution>
            <id>compile-1.7</id>
            <phase>compile</phase>
            <goals>
                <id>compile</id>
            </goals>
            <configuration>
                <source>1.7</source>
                <target>1.7</target>
                <executable>${env.JAVA_HOME_7}/bin/javac</executable>
                <outputFileName>mylib-1.7.jar</outputFileName>
            </configuration>
<!-- START EDIT -->
            <dependencies>
                <dependency>
                    <groupId>com.mycompany</groupId>
                    <artifactId>XYZ</artifactId>
                    <version>2.0</version>
                </dependency>
            </dependencies>
<!-- END EDIT -->
        </execution>

        <!-- one more execution for 1.8, elided to save space -->

    </executions>
</plugin>

字符串
希望能帮上忙。
编辑
RE:每个运行都针对不同源代码编译的附加要求。
参见上面对pom片段的编辑。
每个 * 执行 * 都可以定义自己的 * 依赖项 * 库列表。
因此,JDK 6构建依赖于ABC.jar,而JDK 7依赖于XYZ.jar。

8oomwypt

8oomwypt2#

你可以通过为每个java版本创建单独的配置文件来有条件地包含一些源目录。然后你可以用配置文件名运行maven,它将定义你想要使用的版本的源。在这种情况下,所有公共源都保留在src/main/java中,而java版本依赖文件放在/src/main/java-X.X directories中。

<profiles>
    <profile>
        <id>java-6</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <version>1.5</version>
                    <executions>
                        <execution>
                            <id>add-sources</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>add-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>${basedir}/src/main/java-1.6</source>
                                </sources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
    <profile>
        <id>java-7</id>
        (...)
        <source>${basedir}/src/main/java-1.7</source>
    </profile>
    <profile>
        <id>java-8</id>
        (...)
        <source>${basedir}/src/main/java-1.8</source>
    </profile>
</profiles>

字符串
您可以通过将硬编码的java-X.X替换为属性(您将与profile一起传递给maven)来更动态地执行此操作。这将类似于:

<profile>
        <id>conditional-java</id>
        (...)
        <source>${basedir}/src/main/java-${my.java.version}</source>
    </profile>
</profiles>


当你运行它的时候,你只需要传递mvn -Pconditional-java -Dmy.java.version=1.6
这需要你把java版本相关的文件放在不同的目录中。在IDE中,当你针对特定的java版本进行开发时,只需将与你的java版本相关的目录标记为源文件夹(因为默认情况下IDE只会将src/main/java识别为源目录)。
同样的方式,你可以把编译器级别传递给maven编译器插件:

<project>
  [...]
  <build>
    [...]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>${my.java.version}</source>
          <target>${my.java.version}</target>
        </configuration>
      </plugin>
    </plugins>
    [...]
  </build>
  [...]
</project>

t40tm48m

t40tm48m3#

为什么不收集每个java版本中应该不同的方法,将它们 Package 在一个“实用程序”项目中,制作您自己的主代码可以调用的API,并在发布时添加您想要的任何实用程序jar?
例如:
util-api.jar(主项目调用的方法)
util-1.6.jar(无论哪个实现都适用,如果需要,即使是“无操作”,也不需要做任何事情)
我已经成功地做了很多次类似的问题,你现在有一个。

n6lpvg4x

n6lpvg4x4#

在决定迁移到基于Gradle的构建之前,从事Hibernate工作的人已经与这个问题斗争了一段时间。
查看Gradle: why?了解更多信息。

iibxawm4

iibxawm45#

一种方法是嵌入到代码中:来自log4j 2源代码:

//  JDK 1.1 doesn't support readResolve necessary for the assertion 
   if (!System.getProperty("java.version").startsWith("1.1.")) {
        assertTrue(obj == Level.INFO);
    }

字符串
你也可以使用https://github.com/raydac/java-comment-preprocessor并根据java版本设置变量来改变代码。虽然这样做的地方很少,因为这将很难调试。或者至少在动态代码运行之前打印一个日志,这样你就知道哪个版本/真实的行有问题。

xxe27gdn

xxe27gdn6#

为了这种事,我会

  • 将依赖JVM版本的文件移动到它们自己的包中
  • 或者,使用命名约定来标识特定于JVM版本的文件
  • 使用Ant而不是Maven进行编译。Ant可以很容易地根据名称或位置排除源文件。您还可以轻松地使用Ant创建多个目标。
  • 如果库对所有JVM版本使用相同的API,我将为特定于JVM的类创建接口,然后根据JVM版本在运行时示例化适当的实现。
u2nhd7ah

u2nhd7ah7#

您需要做的是定义一个基本的API,然后将新版本的Java可用的功能作为额外的方法(可以从您原来的API方法中调用)添加进去。这使得您的API的基本完整性从旧的实现到新的实现保持不变。
举例来说:

public void forEach(Consumer<T> c);

字符串
是原始的API方法,其中Consumer是org. mylib. Consumer。在Java 8中,您可以使用它做两件事。您可以保留旧的实作,并新增一个新方法作为该方法的 * 便利方法 *,或者您可以新增新的实作,并新增一个 *defender方法 * 来呼叫新的实作。不论是哪种方式,实作API的旧版程式码都将保持有效。

  • 便捷方法 * 示例:
// No need to change the old Java 6 style implementation
public void forEach(Consumer<T> c); 

default void forEach(java.util.function.Consumer<T> c){
    // Your Consumer needs a constructor for j.u.f.C
    Consumer<T> myC = new Consumer<T>(c);
    forEach(myC);
}

  • Defender方法 * 示例:
// Consumer extends j.u.f.C so this is more specific and will override
default void forEach(Consumer<T> c){ 
    // All we need to call the other method is an explicit cast
    forEach((java.util.function.Consumer<T>) c);
}

//All new Java 8 style implementation here
public void forEach(java.util.function.Consumer<T> c);


显然,对于Java 7版本,您不能在Interface上定义默认方法,因此您必须在抽象基类中实现这些方法,但概念是基本相同的。从理论上讲,在Java 8中,您不需要defender,因为调用可以完全相同,因为Consumer的任何有效示例都是j.u.f.C的有效示例。
Java8第一次意味着它完全可以在现有接口中添加新方法,但是Java 6和Java7之间的API签名不应该有什么不同。
对于Java 6和7,只需在源代码中避免以下内容,就可以编译为这两种代码:

  • 菱形操作符<>,它很方便,但是不使用它意味着J6和J7只有一个代码库
  • switch语句(几乎在所有情况下,如果您有switch语句,则可能应该有多个类和一个工厂)。
  • 这是J7中我最喜欢的特性之一,但最终块仍然完全有效。
  • 多异常捕获器。
  • J7路径API -旧的文件API更适合于传统编码
  • 分叉和连接API

我并不知道是否有为J6编写的代码在为J8编译时不能在J8 JVM上运行,但我知道您可能希望库的较新版本利用J8的改进,如流和lambda。
您可能还想从Java7开始研究Java服务提供者api。它允许您在基本模块中定义API,并将实现安装为Java服务插件jar文件,只要它们位于应用程序的类路径中,JVM就可以检测到这些文件。然后,您可以在新特性可用时简单地定义新的服务插件jar。当然,这对您的Java 6没有帮助实现,但是您可以使用J6 API作为基础,为J7 API添加服务附加功能,并在JVM更新时不断添加和替换服务。

igetnqfo

igetnqfo8#

我在这里添加了另一个答案,以回应Leventov关于需要显式转换的最后评论,并提供此建议。这可能是也可能不是坏的实践,但我发现它在某些情况下非常有用,因为我想注入一些预处理或后处理,或者在别人的抽象层之上提供自己的抽象层(例如,我们为Hibernate的Work类定义了一个这样的类,这样如果Hibernate的API在将来发生变化,我们的实现就不必改变--只需要在我们的Interface中使用默认方法),这可能会使同一个接口的两个版本的使用变得更容易。
想想这个:函数式接口的美妙之处在于它只有一个抽象方法,所以你可以传递一个lambda作为该方法的实现。
但是,当你扩展一个接口时,你仍然希望它具有相同的功能(传递一个lambda,并在所有情况下工作),Java 8的另一个优点发挥作用:Defender方法。任何地方都没有说你必须像父类那样保留Defender方法抽象。你可以将接口扩展为一种拦截器接口。所以你可以这样定义你的MyConsumer:

public interface MyConsumer<T> extends Consumer<T> {
    default void accept(T t){ // Formerly the abstract functional method
        getConsumer().accept(t);
    }
    public Consumer<T> getConsumer(); //Our new abstract functional method
}

字符串
我们的接口,而不是将accept(T)定义为一个抽象方法,实现了accept(T),并定义了一个抽象方法getConsumer()。这使得示例化MyConsumer的lambda与示例化j.u.f.Consumer所需的lambda不同,并消除了编译器的类冲突。然后您可以定义实现Iterable的类来实现您自己的自定义接口扩展Iterable。

public interface MyIterable<T> extends Iterable<T> {    
    default void each(MyConsumer<? super T> action){
        //Iterable.super.forEach((Consumer<? super T>) action);
        //
        // Or whatever else we need to do for our special
        // class processing
    }       
    default void each(Consumer<? super T> action){
        if (action instanceof MyConsumer){
            each((MyConsumer<? super T>) action);
        } else {
            Iterable.super.forEach(action);
        }
    }
    @Override
    default void forEach(Consumer<? super T> action){
        each(action);   
    }
}


它仍然有一个forEach方法,允许它遵循可迭代的iterface,但是你的所有代码都可以在你的API的所有版本中调用each()而不是forEach()。这样你的代码也部分地面向未来-如果底层的Java API在几年后被弃用,你可以修改默认的each()方法来以新的方式做事情,但在其他任何地方 * 所有现有的实现代码在功能上仍然是正确的 *。
因此,当你调用API.each时,不需要显式的强制转换,你只需将lambda传递给另一个方法.在MyConsumer中,该方法返回一个消费者,所以你的lambda真的很简单,你只需将lambda零参数构造函数添加到你前面的语句中。消费者中的accept()方法接受一个参数并返回一个void,所以如果你定义它没有参数,Java知道它需要一个接口,它有一个不带参数的抽象方法,这个lambda示例化了MyConsumer。

api.each(()->System.out::println);


而这个示例化了j.u.f.消费者

api.each(System.out::println);


因为原始的抽象方法(accept)在那里,并且已经实现,所以它仍然是Consumer的有效示例,并且在所有情况下都可以工作,但是因为我们调用了0-arg构造函数,所以我们已经显式地使它成为我们自定义接口的示例。这样,我们仍然满足Consumer的接口契约,但是我们可以区分我们接口的签名和Consumer的签名。

相关问题