加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

groovy – 有没有办法让@Builder注释适用于不可变类?

发布时间:2020-12-14 16:22:24 所属栏目:大数据 来源:网络整理
导读:我正在尝试在Groovy中开发一个项目,我一直在查看我的代码并尝试找到可以用更具惯用性的Groovy替换的区域,直到找到 another issue I’ve been having的解决方案. 我已经开始更深入地研究AST转换注释的使用 – 它们帮助显着减少了我在某些地方编写的代码量.但
我正在尝试在Groovy中开发一个项目,我一直在查看我的代码并尝试找到可以用更具惯用性的Groovy替换的区域,直到找到 another issue I’ve been having的解决方案.

我已经开始更深入地研究AST转换注释的使用 – 它们帮助显着减少了我在某些地方编写的代码量.但是,我在使用groovy.transform.builder.Builder注释和我的一个不可变值类时遇到了问题.此批注的来源是here.

问题是注释似乎使构建器直接设置了构建器的值,而不是存储值的副本并将它们传递给构建器的构造函数.当您尝试将其与不可变类一起使用时,这会导致ReadOnlyPropertyException.

您可以使用此注释选择四种可能的构建器策略,其中我尝试过DefaultStrategy,ExternalStrategy和InitializerStrategy.但是,所有这些都造成了问题.

ExternalStrategy看起来像四个中最有希望的,你可以找到一个基于它的SSCCE详细说明问题

关于此事here似乎也有相关的JIRA讨论.

编辑
我尝试使用下面的CFrick答案,使用InitializerStrategy而不是ExternalStrategy.

现在一切都在编译,但是当我尝试执行测试时,我在运行时遇到以下错误:

java.lang.IllegalAccessError: tried to access class com.github.tagc.semver.version.BaseVersion from class com.github.tagc.semver.version.BaseVersion$com.github.tagc.semver.version.BaseVersionInitializer
    at java.lang.Class.getDeclaringClass(Class.java:1227)
    at java.beans.MethodRef.set(MethodRef.java:46)
    at java.beans.MethodDescriptor.setMethod(MethodDescriptor.java:117)
    at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:72)
    at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:56)
    at java.beans.Introspector.getTargetMethodInfo(Introspector.java:1163)
    at java.beans.Introspector.getBeanInfo(Introspector.java:426)
    at java.beans.Introspector.getBeanInfo(Introspector.java:173)
    at com.github.tagc.semver.version.VersionFactory.createBaseVersion(VersionFactory.groovy:34)
    at com.github.tagc.semver.test.util.TestSetup.<clinit>(TestSetup.groovy:77)
    at java.lang.Class.forName(Class.java:344)
    at com.github.tagc.semver.version.SnapshotDecoratorSpec.#decoratedVersion should be considered equal to patch-bumped #releaseVersion snapshot(SnapshotDecoratorSpec.groovy:24)

之后是一系列例外情况,如下所示:

java.lang.NoClassDefFoundError: Could not initialize class com.github.tagc.semver.test.util.TestSetup
    at java.lang.Class.forName(Class.java:344)
    at com.github.tagc.semver.version.SnapshotDecoratorSpec.#decoratedVersion should be considered equal to minor-bumped #releaseVersion snapshot(SnapshotDecoratorSpec.groovy:36)

我现在拥有的BaseVersion类如下:

/**
 * A concrete,base implementation of {@link com.github.tagc.semver.version.Version Version}.
 *
 * @author davidfallah
 * @since v0.1.0
 */
@Immutable
@Builder(prefix = 'set',builderStrategy = InitializerStrategy)
@PackageScope
final class BaseVersion implements Version {
    // ...

    /**
     * The major category of this version.
     */
    int major = 0

    /**
     * The minor category of this version.
     */
    int minor = 0

    /**
     * The patch category of this version.
     */
    int patch = 0

    /**
     * Whether this version is a release or snapshot version.
     */
    boolean release = false

    // ...
}

生产这些实例的工厂:

/**
 * A factory for producing base and decorated {@code Version} objects.
 *
 * @author davidfallah
 * @since v0.5.0
 */
class VersionFactory {

    // ...

    /**
     * Returns an instance of {@link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
     * with the given parameters.
     *
     * @param major the major category value of the version instance
     * @param minor the minor category value of the version instance
     * @param patch the patch category value of the version instance
     * @param release the release setting of the version instance
     * @return an instance of {@code BaseVersion}
     */
    static BaseVersion createBaseVersion(int major,release)
    }

    /**
     * Returns an instance of {@link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
     * with the given parameters.
     *
     * @param m a map of parameter names and their corresponding values corresponding to the
     *        construction parameters of {@code BaseVersion}.
     *
     * @return an instance of {@code BaseVersion}
     */
    static BaseVersion createBaseVersion(Map m) {
        return new BaseVersion(m)
    }

    /**
     * Returns an instance of {@link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
     * with the given parameters.
     *
     * @param l a list of parameter values corresponding to the construction parameters of {@code BaseVersion}.
     *
     * @return an instance of {@code BaseVersion}
     */
    static BaseVersion createBaseVersion(List l) {
        return new BaseVersion(l)
    }

    /**
     * Returns a builder for {@link com.github.tagc.semver.version.BaseVersion BaseVersion} to specify
     * the construction parameters for the {@code BaseVersion} incrementally.
     *
     * @return an instance of {@code BaseVersion.Builder}
     */
    static Object createBaseVersionBuilder() {
        return BaseVersion.builder()
    }

    // ...
}

Version对象的测试规范类:

/**
 * Test specification for {@link com.github.tagc.semver.version.Version Version}.
 *
 * @author davidfallah
 * @since 0.1.0
 */
@Unroll
class VersionSpec extends Specification {

    static exampleVersions = [
        VersionFactory.createBaseVersion(major:1,minor:2,patch:3),VersionFactory.createBaseVersion(major:0,minor:0,patch:0),VersionFactory.createBaseVersion(major:5,minor:4,VersionFactory.createBaseVersion(major:1,minor:16,patch:2),VersionFactory.createBaseVersion(major:4,minor:5,patch:8),]

    // ...
}

尝试创建失败的BaseVersion实例的其他类,例如TestSetup.

解决方法

你的代码失败了,因为那里选择的策略基本上做了:

def v = new Value().with{ setValue(1); return it }

这不能在@Immutable对象上完成.

根据docs,只有InitializerStrategy,它可以明确地处理@Immutable.

You can use the InitializerStrategy in conjunction with @Canonical and @Immutable. If your @Builder annotation doesn’t have explicit includes or excludes annotation attributes but your @Canonical annotation does,the ones from @Canonical will be re-used for @Builder.

例如.

import groovy.transform.*
import groovy.transform.builder.*

@Immutable
@ToString
@Builder(prefix='set',builderStrategy=InitializerStrategy)
class Value {
    int value
}

def builder = Value.createInitializer().setValue(1)
assert new Value(builder).toString()=='Value(1)'

根据你的目标,这是一个更糟糕的语法,你可能最好只使用基于地图的c’tors.即使没有例如@TypeChecked一个新值(vlaue:666)将生成一个错误,并且让params(对于具有多个属性的类)将使它们保持为空.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读