You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Groovy Builder转换是否生效?私有字段使用遇方法缺失异常

解决Groovy @Builder注解与私有final字段兼容问题

问题场景

你在用Groovy的@Builder注解生成建造者模式时遇到了麻烦:实体类里的字段都是private final的,调用建造者的url()方法时抛出了MissingMethodException,但去掉private修饰符后代码就能正常运行。你希望保留字段的私有性,同时让建造者正常工作。

先来看下你的相关代码和报错:

项目配置

plugins { 
    id 'groovy' 
    id 'java' 
}
group 'org.example' 
version '1.0-SNAPSHOT'
sourceCompatibility = 11
repositories { mavenCentral() }
dependencies {
    implementation 'org.codehaus.groovy:groovy-all:2.4.15'
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testImplementation ('org.spockframework:spock-core:1.1-groovy-2.4') { 
        exclude group: 'org.codehaus.groovy' 
    }
}

实体类代码

package pattern
import groovy.transform.builder.Builder
import groovy.transform.builder.DefaultStrategy

@Builder
class HttpRequestBuilderAnnotation {
    //Mandatory
    private final String url
    private final String httpMethod
    private final String requestContentType
    private final String acceptContentType
    //Optional
    private final String requestBody
    private final List<Integer> validResponseCodes

    String getUrl() { return url }
    String getHttpMethod() { return httpMethod }
    String getRequestContentType() { return requestContentType }
    String getAcceptContentType() { return acceptContentType }
    String getRequestBody() { return requestBody }
    List<Integer> getValidResponseCodes() { return validResponseCodes }
}

测试代码

package pattern
import spock.lang.Specification

class HttpRequestBuilderAnnotationSpec extends Specification {
    private String url = 'https://dsd.com'

    void "Instantiate HttpRequest"() {
        when:
        HttpRequestBuilderAnnotation httpRequestBuilderAnnotation = HttpRequestBuilderAnnotation.builder()
                .url(url).build()
        then:
        assert httpRequestBuilderAnnotation
        assert httpRequestBuilderAnnotation.getUrl() == url
    }
}

报错信息

groovy.lang.MissingMethodException: No signature of method: static pattern.HttpRequestBuilderAnnotation.url() is applicable for argument types: (java.lang.String) values: [https://dsd.com]
Possible solutions: use([Ljava.lang.Object;), grep(), dump(), any(), getUrl(), use(java.lang.Class, groovy.lang.Closure)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at pattern.HttpRequestBuilderAnnotationSpec.Instantiate HttpRequest(HttpRequestBuilderAnnotationSpec.groovy:12)

问题原因

核心问题在于@Builder默认使用的DefaultStrategy策略:它在编译时生成的建造者代码会尝试直接访问类的私有字段,但Java的访问权限规则不允许外部类(包括生成的内部建造者类)直接访问私有字段。虽然Groovy平时允许通过属性语法“绕过”私有访问,但@Builder是编译时生成代码,遵循严格的Java访问规则,所以就出现了找不到方法的错误。

解决方案

这里有两种靠谱的方案,都能保留字段的private final修饰符:

方案1:切换到ExternalStrategy策略

这个策略会生成一个独立的外部建造者类,通过调用类的构造方法来初始化实例,而不是直接访问私有字段。修改实体类的注解即可:

package pattern
import groovy.transform.builder.Builder
import groovy.transform.builder.ExternalStrategy

@Builder(strategy = ExternalStrategy, prefix = "")
class HttpRequestBuilderAnnotation {
    // 字段和getter方法完全保持不变
    private final String url
    private final String httpMethod
    private final String requestContentType
    private final String acceptContentType
    private final String requestBody
    private final List<Integer> validResponseCodes

    String getUrl() { return url }
    String getHttpMethod() { return httpMethod }
    String getRequestContentType() { return requestContentType }
    String getAcceptContentType() { return acceptContentType }
    String getRequestBody() { return requestBody }
    List<Integer> getValidResponseCodes() { return validResponseCodes }
}

这样修改后,你的测试代码不需要做任何改动,就能正常运行。

方案2:添加全参构造方法

如果不想切换策略,也可以给实体类添加一个包含所有字段的全参构造方法。@Builder会自动检测到这个构造方法,用它来生成建造者逻辑,避免直接访问私有字段:

package pattern
import groovy.transform.builder.Builder
import groovy.transform.builder.DefaultStrategy

@Builder
class HttpRequestBuilderAnnotation {
    private final String url
    private final String httpMethod
    private final String requestContentType
    private final String acceptContentType
    private final String requestBody
    private final List<Integer> validResponseCodes

    // 添加全参构造方法
    HttpRequestBuilderAnnotation(String url, String httpMethod, String requestContentType, String acceptContentType, String requestBody, List<Integer> validResponseCodes) {
        this.url = url
        this.httpMethod = httpMethod
        this.requestContentType = requestContentType
        this.acceptContentType = acceptContentType
        this.requestBody = requestBody
        this.validResponseCodes = validResponseCodes
    }

    // getter方法保持不变
    String getUrl() { return url }
    String getHttpMethod() { return httpMethod }
    String getRequestContentType() { return requestContentType }
    String getAcceptContentType() { return acceptContentType }
    String getRequestBody() { return requestBody }
    List<Integer> getValidResponseCodes() { return validResponseCodes }
}

不过这个方案需要注意:因为你的字段都是必填的final字段,测试代码必须给所有必填字段赋值,否则会报错。比如修改测试代码:

void "Instantiate HttpRequest"() {
    when:
    HttpRequestBuilderAnnotation httpRequestBuilderAnnotation = HttpRequestBuilderAnnotation.builder()
            .url(url)
            .httpMethod("GET")
            .requestContentType("application/json")
            .acceptContentType("application/json")
            .build()
    then:
    assert httpRequestBuilderAnnotation
    assert httpRequestBuilderAnnotation.getUrl() == url
}

推荐方案

优先选择方案1,因为它不需要修改构造方法,也不需要调整测试代码,完全符合建造者模式“分步构建”的初衷,同时完美保留了字段的私有性和不可变性。

内容的提问来源于stack exchange,提问作者Kaliyug Antagonist

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 14:27:40