Загрузка объекта Java Builder из файла Yaml

Я создал класс Bean, используя шаблон Builder, и у меня возникли проблемы с созданием объекта из файла 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();
    }
}

Я использую змею для загрузки файла, но подойдет любой API-интерфейс yaml. Поскольку displayName является обязательным параметром, я хочу передать это значение при создании экземпляра. Другие параметры могут быть переданы при создании объекта, но я хотел бы иметь возможность загружать их через файл yaml.

Я могу загрузить файл yaml, если использую java bean. Есть ли способ создать экземпляры объектов строителя?

Я попытался:

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"

person jaypal singh    schedule 22.03.2015    source источник


Ответы (2)


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 можно посмотреть здесь. Так что ваш код все еще в силе. Возможно, вам потребуется изменить содержимое yaml в правильном формате. После этого все готово.

person Sairam Krish    schedule 31.03.2015

Исключением является отсутствие конструктора без аргументов в Builder, как вы, вероятно, поняли.

Что вы можете сделать, так это разрешить конструктор без аргументов для Builder и добавить к нему соответствующий сеттер и геттер для displayName. Затем просто создайте исключение в build(), если displayName не установлено (или укажите для него значение по умолчанию). Исключение может быть во время выполнения, или вы можете уточнить его и добавить явное throws.

Хотя это не самое красивое решение, оно должно работать нормально. Тот факт, что Builder создается без обязательного аргумента, не должен иметь значения, поскольку именно ClientBuilder необходимо построить правильно (поскольку фабрика/построитель используется для обеспечения правильности каждого экземпляра того, что он создает).

В настоящее время у меня нет доступа к каким-либо инструментам синтаксического анализа yaml для Java, но если есть способ улучшить свой ответ, дайте мне знать - я буду рад это сделать.

person Miki    schedule 24.03.2015
comment
Спасибо Сорроу за ответ. Да, добавление конструктора без аргументов работает, но оно только работает, если я делаю все setters моего класса Builder как возвращаемый тип void вместо Builder. Если я потеряю возможность иметь свободный интерфейс, я думаю, мне следует просто использовать шаблон bean. Что ты думаешь? - person jaypal singh; 26.03.2015
comment
Вам, вероятно, не следует слишком зацикливаться на шаблонах, они должны быть скорее рекомендациями, чем строгим кодом ;) Ничто не мешает вам оставить сеттеры такими, какие они есть, по крайней мере, я этого не вижу. . Да, существует риск того, что Builder находится в неправильном состоянии (не установлено displayName), но он существует, даже если ваши сеттеры возвращают void. Хитрость здесь заключается в том, чтобы отсрочить момент, когда неправильное состояние действительно имеет значение. Имеет ли значение при построении Builder, точнее ClientBuilder? Последнее кажется более подходящим в данном случае, по крайней мере мне. - person Miki; 26.03.2015
comment
Несколько мудрых слов должен сказать! :). Сеттеры не соответствуют стандарту bean-компонентов, и, похоже, змея хочет этого. Если я верну его к своим старым сеттерам (которые возвращают это вместо void), он выдаст ошибку. Это будет частью набора тестов, и, недавно прочитав «Эффективную Java», я подумал, что шаблон Builder будет более подходящим. Я тоже нормально отношусь к фасоли. Их легко создать (благодаря автоматической генерации кода eclipse :P)! - person jaypal singh; 26.03.2015