【问题标题】:Loading Java Builder Object from Yaml file从 Yaml 文件加载 Java Builder 对象
【发布时间】:2015-03-22 06:46:32
【问题描述】:

我使用构建器模式创建了一个 Bean 类,但在从 yaml 文件创建对象时遇到问题。

这是一个示例类(实际类很大,如果您想用示例回答,这只是一个摘录):

public class ClientBuilder {

    private final String firstName;
    private final String lastName;
    private final String displayName;

    private ClientBuilder(Builder builder) { 
        firstName   = builder.firstName; 
        lastName    = builder.lastName; 
        displayName = builder.displayName;
    }

    public static class Builder {

        private final String displayName; // Mandatory Attribute

        public Builder( String displayName ) {
            this.displayName = displayName;
        }

        private String firstName;
        private String lastName;

        public Builder setFirstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder setLastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public ClientBuilder build() {
            return new ClientBuilder(this);
        }
    }

    @Override
    public String toString() {
        StringBuffer sbf = new StringBuffer();
        sbf.append("New Company Object: \n");
        sbf.append("firstName   : " +  this.firstName   + "\n");
        sbf.append("lastName    : " +  this.lastName    + "\n");
        sbf.append("displayName : " +  this.displayName + "\n");
        return sbf.toString();
    }
}

我正在使用snakeyaml 来加载文件,但任何yaml api 都可以。由于displayName 是强制参数,我想在创建实例时传递该值。其他参数可以在创建对象时传递,但我希望选择通过 yaml 文件加载它们。

如果我使用 java bean,我可以加载 yaml 文件。有没有办法实例化构建器对象?

我试过了:

InputStream input = new FileInputStream(new File("src/main/resources/client.yaml"));
Yaml yaml = new Yaml();
Builder builder = new Builder("Display Name");
builder = (Builder) yaml.loadAs(input, ClientBuilder.Builder.class);
ClientBuilder client = builder.build();
System.out.println(client.toString());

但我收到以下错误:

Exception in thread "main" Can't construct a java object for tag:yaml.org,2002:com.xxx.xxx.xxx.ClientBuilder$Builder; exception=java.lang.NoSuchMethodException: com.xxx.xxx.xxx.ClientBuilder$Builder.<init>()
 in 'reader', line 2, column 1:
    firstName: "Jaypal"

【问题讨论】:

    标签: java yaml builder-pattern


    【解决方案1】:

    SnakeYaml 是一个非常强大的库,它支持基于构造函数注入创建实例。

    /**
     * create JavaBean
     */
    public void testGetBeanAssumeClass() {
        String data = "--- !!org.yaml.snakeyaml.constructor.Person\nfirstName: Andrey\nage: 99";
        Object obj = construct(data);
        assertNotNull(obj);
        assertTrue("Unexpected: " + obj.getClass().toString(), obj instanceof Person);
        Person person = (Person) obj;
        assertEquals("Andrey", person.getFirstName());
        assertNull(person.getLastName());
        assertEquals(99, person.getAge().intValue());
    }
    
    /**
     * create instance using constructor arguments
     */
    public void testGetConstructorBean() {
        String data = "--- !!org.yaml.snakeyaml.constructor.Person [ Andrey, Somov, 99 ]";
        Object obj = construct(data);
        assertNotNull(obj);
        assertTrue(obj.getClass().toString(), obj instanceof Person);
        Person person = (Person) obj;
        assertEquals("Andrey", person.getFirstName());
        assertEquals("Somov", person.getLastName());
        assertEquals(99, person.getAge().intValue());
    }
    

    Junit code sample 可以在这里查看。 所以你的代码仍然很好。您可能需要以正确的格式更改 yaml 内容。完成后,一切就绪。

    【讨论】:

      【解决方案2】:

      例外是Builder 中没有无参数构造函数,您可能已经猜到了。

      您可以做的是允许Builder 的无参数构造函数,并为displayName 添加相应的setter 和getter。如果未设置displayName(或为其提供默认值),则只需在build() 中抛出异常。异常可以是运行时异常,或者您可以明确说明并添加明确的throws

      虽然它不是最漂亮的解决方案,但它应该可以正常工作。 Builder 是在没有强制参数的情况下创建的这一事实并不重要,因为需要正确构造的是 ClientBuilder(因为工厂/构建器用于确保它正在构建的每个实例都是正确的)。

      我目前无法访问任何用于 Java 的 yaml 解析工具,但如果有任何方法可以改进我的答案,请告诉我 - 我很乐意这样做。

      【讨论】:

      • 感谢 Sorrow 留下答案。是的,添加一个无 arg 构造函数是有效的,但它只有在我将我的 Builder 类的所有 setters 设为 void 而不是 Builder 返回类型时才有效。如果我失去了拥有流畅界面的能力,那么我想我应该只使用 bean 模式。你有什么想法?
      • 您可能不应该过多地使用模式,它们应该是更多指南而不是严格的代码 ;) 没有什么可以阻止您保持设置器的原样,至少我没有看到它。是的,Builder 存在处于错误状态的风险(没有设置displayName),但即使您的设置器返回void,它也存在。这里的诀窍是延迟错误状态真正重要的时刻。构造Builder 还是ClientBuilder 时是否重要?在这种情况下,后者似乎更合适,至少对我而言。
      • 我必须说一些明智的话! :)。设置器不符合 bean 标准,看起来snakeyaml 想要那样。如果我将它改回我的旧设置器(返回这个,而不是 void)它会出错。这将成为测试套件的一部分,并且最近阅读了 Effective Java,我认为 Builder 模式会更合适。我也可以吃豆子。它们很容易创建(感谢 eclipse 自动代码生成:P)!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-15
      • 1970-01-01
      • 2019-05-16
      • 1970-01-01
      • 2016-04-06
      • 2014-08-31
      相关资源
      最近更新 更多