Я пытаюсь разработать проект в Groovy, и я просматривал свой код и пытался найти области, которые я мог бы заменить чем-то более идиоматически Groovy, пока не найду решение для another issue I've been having.Есть ли способ сделать аннотацию @Builder для неизменяемых классов?
Я начал более подробно изучать аннотации трансформации АСТ - они помогли значительно сократить количество кода, который мне приходится писать в некоторых местах. Однако у меня возникла проблема с использованием аннотации groovy.transform.builder.Builder
с одним из моих неизменяемых классов значений. Источник этой аннотации размещен here.
Проблема заключается в том, что, по-видимому, аннотации заставляют строителя устанавливать значения сборщика напрямую, а не хранить копию значений и передавать их конструктору сборщика. Это приводит к ReadOnlyPropertyException
, когда вы пытаетесь использовать его с неизменяемыми классами.
Существует четыре возможные стратегии Строителя, которые вы можете выбрать с помощью этой аннотации, и из них я пробовал DefaultStrategy
, ExternalStrategy
и InitializerStrategy
. Однако все это вызвало проблемы.
ExternalStrategy
выглядит как наиболее перспективный из четырех, и вы можете найти на нем SSCCE, детализируя проблему here.
Исходный код из примера также приводится ниже:
import groovy.transform.Immutable
import groovy.transform.builder.Builder as GBuilder
import groovy.transform.builder.ExternalStrategy
/*
* Uncommenting the below causes a failure:
* 'groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: value for class: Value'
*/
//@Immutable
class Value {
@GBuilder(forClass = Value, prefix = 'set', builderStrategy = ExternalStrategy)
static class Builder { }
int value
String toString() { "Value($value)" }
}
def builder = new Value.Builder()
println builder.setValue(1).build()
Там также, как представляется, соответствующее обсуждение JIRA по этому вопросу here.
Редактировать
Я попытался с помощью ответа 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, int minor, int patch, boolean release) {
return new BaseVersion(major, minor, patch, 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, patch:3),
VersionFactory.createBaseVersion(major:1, minor:16, patch:2),
VersionFactory.createBaseVersion(major:4, minor:5, patch:8),
]
// ...
}
и другие классы, которые пытаются создавать экземпляры BaseVersion
, которые упущение, такие как TestSetup
.
'@ Immutable' является окончательным по умолчанию. не могли бы вы предоставить минимальную версию кода? это трудно понять, как сейчас. также почему '@ PackageScope'? – cfrick
@cfrick Я постараюсь собрать что-то вместе и обновить вас, когда закончите. '@ PackageScope', потому что лучше всего ограничить область как можно больше. Я хочу, чтобы интерфейс 'Version' был общедоступным, но конкретные реализации были приватными пакетами, чтобы клиенты были вынуждены проходить через« VersionFactory »для их создания (статические заводские методы часто лучше конструкторов [Effective Java Item 1]). Я не играл достаточно с этим еще, чтобы убедиться, что это лучший подход, но я попробую что-то другое, если это не сработает. – Tagc
@cfrick При создании SSCCE я обнаружил, что неправильно использовал подход Initializer. Я исправил свою ошибку, и теперь она работает нормально, поэтому я отмечаю ваш ответ как принятый. Тем не менее, мне не очень нравится подход к инициализатору (особенно потому, что он требует избавления от '@ PackageScope'), поэтому я просто придерживаюсь старомодного подхода на самом деле в исходном коде, который у меня был до. Спасибо за вашу помощь. – Tagc