Статьи

Apache Wicket с лямбда-выражениями

Ниже приведен пост из личного блога Родриго Учоа. Здесь вы можете найти оригинальный пост.

Что происходит? ?

Я работал над некоторыми проектами, которые, к счастью, используют Apache Wicket для уровня презентации. Естественно, мне пришло в голову, как 8 лямбда-выражений Java идеально подходят для Wicket. И не только я, команда Wicket, похоже, уже работает над изменением API, чтобы обеспечить поддержку лямбд из коробки.

Эта статья будет более приятной, если вы уже знаете, как работают лямбды в Java. Если вы этого не сделаете, это хорошая отправная точка . Некоторые знания в Apache Wicket также рекомендуются, но если вы когда-либо играли с любым GUI-ориентированным API, таким как Swing или GWT, этого должно быть достаточно, чтобы понять его.

Прежде чем мы начнем, я просто хочу сказать, почему мне нравится Wichet лучше, чем JSF. Если вам все равно, просто пропустите эту часть ?

Мой Rant с JSF

Подводя итог, если вы планируете использовать для проекта инфраструктуру на основе компонентов на стороне сервера , я не вижу причин выбирать JSF вместо Wicket. Вот некоторые из моих аргументов:

1- Код калитки легче читать и поддерживать

JSF заставляет вас разбрасывать логику представления между вашими файлами xHTML и java-классами (управляемыми bean-компонентами) из-за всего, что рендерится , рендерится и так далее. Калитка с другой стороны позволяет нам просто написать код Java. Вся логика содержится в классе контроллера, что облегчает чтение и поддержку, по моему скромному мнению.

Некоторые могут утверждать, что, как и все другие API, ориентированные на GUI, код Wicket более многословен, особенно из-за того, что все анонимные внутренние классы заканчиваются написанием. Для меня это только частично верно. Код действительно более многословен, чем JSF Managed Beans, но, тем не менее, его легче читать. Это просто код Java в одном месте. Не Java смешивается с EL внутри страниц Facelets.

Что касается всех анонимных внутренних классов , они могут и могут быть предотвращены более чем когда-либо, используя лямбда-выражения . Это то, что я показываю вам в этой статье.

2- У Wicket есть четкое разделение ролей

Wicket построен на предпосылке, что мы можем использовать чистый HTML для создания наших страниц. Есть несколько тегов, которые нужно использовать, но конечный результат по-прежнему на 95% состоит из HTML . Это позволяет веб-дизайнерам, которые ничего не знают о Java или Wicket, работать вместе с разработчиками. Дизайнеры делают то, что у них получается лучше всего, а разработчики практически полностью используют то, что они создали, беспокоясь только об обычном Java-коде.

JSF — совершенно другой зверь. У вас есть два варианта: 1) Заставить ваших дизайнеров изучать JSF, который они будут ненавидеть 2) Если они делают чистые HTML-прототипы, кто-то должен будет «перекодировать» их в JSF.

Я знаю, что есть ряд альтернатив для решения этого вопроса, и они даже представили функцию «дружественной разметки» в JSF 2.2. Тем не менее, ни один из известных мне подходов не так прост, как у Уикета.

3- В Wicket расширение / создание компонентов намного проще

Создание компонента с нуля в JSF — это кошмар. Даже когда речь идет о композициях Facelets, это не так просто, как у Wicket.

В общем, это только мое мнение. Конечно, JSF привлекает к себе внимание, потому что является частью спецификации Java EE, и каждый новый разработчик в городе хочет изучать JSF, а не Wicket. И хотя, безусловно, возможно создавать превосходные веб-приложения с помощью JSF, Wicket также способен справиться с половиной головной боли.

Также важно отметить, что я ничего не имею против команды JSF. Наоборот, экспертная группа JSF заполнена замечательными людьми. Это не вызывает сомнений. Я могу только удивляться тому, чего они могут достичь, если настроятся на работу в совершенно ином решении.

Наконец, Wicket & Lambdas

Лямбды заполнят место, заполненное анонимными внутренними классами. Обычно, как и в любом API GUI, обработка событий GUI заставит вас писать анонимные классы. Вот простой пример с Swing:

JButton button = new JButton("Save");
button.addActionListener(new ActionListener() { //anonymous class
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button clicked");
        //Our button was clicked. Here we perform
        //everything needed to make the action
        //of clicking a button work.
    }
});

Вот и все. В итоге мы передали состояние, объект, где мы должны передавать поведение, метод. Код просто более подробный, чем должен.

С Wicket проблема практически идентична:

AjaxFallbackLink link = new AjaxFallbackLink("linkId") {
    @Override
    public void onClick(AjaxRequestTarget target) {
        System.out.println("Link clicked!");
    }
};

Чтобы облегчить обработку событий GUI, используя лямбда-выражения и пару вспомогательных классов, мы могли бы написать один и тот же код, описанный выше, в одной строке кода:

AjaxFallbackLink link = ComponentFactory.newAjaxLink("linkId", (target) -> System.out.println("Link clicked!"));

Конечно, большая часть обработки событий потребует гораздо большего, чем простой «System.out». Поэтому по этой причине будет лучше скрыть эти детали внутри частного метода в том же классе. Вот более полный пример кода, как это будет выглядеть:

public class MyPage extends WebPage {
 
    public MyPage(final PageParameters parameters) {
        super(parameters);
 
        AjaxFallbackLink link = ComponentFactory.newAjaxLink("linkId", (target) -> linkClick(target));
    }
     
    //this method is called in line 6
    private void linkClick(AjaxRequestTarget target) {
        //do a bunch of stuff here
    }
}

Гвоздь Это

Как всегда, я создал небольшой проект на GitHub с полным кодом. Вы можете получить его здесь. Там не так много кода, но я думаю, что этого достаточно, чтобы у вас была идея создать собственные API с поддержкой лямбды.

Проект состоит из небольшого варианта использования для вставки пользователей в виртуальную базу данных (на самом деле база данных представляет собой ArrayList, как вы можете видеть в коде). Похоже на это. Некрасиво, но функционально

использование регистра

Там нет никакой тайны. Вновь добавленные пользователи показаны в красной таблице ниже со ссылкой «Изменить» для каждого из них. Теперь давайте перейдем к коду.

Для начала нам нужны «функциональные интерфейсы» . Сначала вы можете испытать желание использовать функциональные интерфейсы, уже предоставленные JDK . Их много, и некоторые из них действительно будут соответствовать вашим потребностям. Проблема в том, что, будучи фанатом сериализации, Wicket будет жаловаться, что ни один из них не является сериализуемым . Поэтому я решил придумать свое:

СОВЕТ: Ничего из этого не имеет смысла, если вы не знаете, как работают лямбды в Java. Сначала прочтите эту статью .

@FunctionalInterface
public interface AjaxAction extends Serializable {
     
    public abstract void onClick(AjaxRequestTarget target);
}
@FunctionalInterface
public interface FormAction extends Serializable {
     
    public abstract void onSubmit();
}
@FunctionalInterface
public interface StringSupplier extends Serializable {
     
    public String get();
}

Скоро мы будем их в действии. Как я уже говорил ранее, обратите внимание, что все они расширяют Serializable .

Динамические метки

Один довольно раздражающий аспект нашей страницы заключается в том, что некоторые ярлыки должны меняться в зависимости от фактического действия, которое мы выполняем. То есть, если мы редактируем существующего пользователя, не имеет смысла показывать « Вставить пользователя» вверху. Ярлык « Редактировать пользователя» подходит лучше. Кнопка « Сохранить » ниже также может измениться на « Обновить ». Не забывайте, что именно поэтому я создал функциональный интерфейс StringSupplier .

Вставка-Edit

Подход, который я использую, заключается в использовании одного HTML-элемента и изменении его значения по мере необходимости, при этом не нужно скрывать и показывать два разных элемента, когда это необходимо. Чтобы дать некоторое представление о том, как это обычно делается:

 

Insert User/Update User

titleLabel = new Label("titleLabel", new Model() {
    @Override
    public String getObject() {
        if (form.getModelObject().getId() == null) {
            return "Insert User";
        } else {
            return "Edit User";
        }
    }
});

Мы бы предоставили метку с «анонимным классом» в качестве модели. Затем метод getObject () на основе текущего состояния объекта модели «Пользователь» (наша сущность) решит, является ли он вставкой или обновлением. Что за хлопоты.

Используя лямбды, то, что я предлагаю, будет выглядеть так (HTML-страница остается прежней):

Прокрутите вправо, если вы не видите всю строку.

titleLabel = ComponentFactory.newLabel("titleLabel", () -> form.getModelObject().getId() == null ? "Insert User" : "Edit User");
add(titleLabel);

Как вы можете видеть в строке 1, у нас есть лямбда, выражение которой является троичным оператором. Обычно это можно сделать с помощью оператора if / else, но это будет выглядеть ужаснее. Я уже показал вам функциональный интерфейс StringSupplier , теперь пришло время увидеть наш вспомогательный класс ComponentFactory .

Как следует из названия, это просто обычный класс с некоторыми статическими фабричными методами для создания компонентов. Вот как выглядит наш фабричный метод newLabel () :

//Remember, StringSupplier is a functional interface that returns a String.
 
public static Label newLabel(String wicketId, StringSupplier supplier) {
    Label label = new Label(wicketId, new Model() {
        @Override
        public String getObject() {
            return supplier.get();
        }
    });
    label.setOutputMarkupId(true);
    return label;
}

Кнопки

Теперь к кнопке «Сохранить / Обновить». Помимо того факта, что его метка также должна меняться в зависимости от состояния модели формы, мы также будем использовать лямбда-выражение для назначения метода, обрабатывающего «событие щелчка». Обычно, без лямбд, вот что мы будем делать:

//this would go inside the class constructor
Button saveUpdateButton = new Button("saveUpdateButton") {
    @Override
    public void onSubmit() {
        //saveUpdate(User) is a private method
        //in this very same class
        saveUpdate(form.getModelObject());
    }
};
saveUpdateButton.add(new AttributeModifier("value", new Model() {
    @Override
    public String getObject() {            
        return form.getModelObject().getId() == null ? "Save" : "Update";
    }
}));
form.add(saveUpdateButton);
//this is a private method inside the same class
private void saveUpdate(User user) {
    //Logic to insert or update an User.
}

Мы создали кнопку, переопределяющую ее метод onSubmit () , а затем добавили поведение AttributeModifier для обработки переключения меток. На нас смотрят 15 строк кода. Теперь лямбда-аналог:

Button saveUpdateButton = ComponentFactory.newButton("saveUpdateButton",
        () -> form.getModelObject().getId() == null ? "Save" : "Update",
        () -> saveUpdate(form.getModelObject()));
form.add(saveUpdateButton);

Вот и все. Обратите внимание, что это может быть просто две строки, но поскольку первый оператор будет слишком длинным, я решил разбить его на 3. Метод newButton () принимает 3 аргумента: идентификатор калитки и два лямбда-выражения, StringSupplier и FormAction соответственно. Вот код:

public static Button newButton(String wicketId, StringSupplier labelSupplier, FormAction action) {
    Button button = new Button(wicketId) {
        @Override
        public void onSubmit() {
            action.onSubmit();
        }
    };
         
    AttributeModifier attrModifier = new AttributeModifier("value", new Model() {
        @Override
        public String getObject() {            
            return labelSupplier.get();
        }
    });
    button.add(attrModifier);
         
    return button;     
}

Ну вот и все. Надеюсь, вам понравилось. Оставьте вопросы ниже, если хотите.

Вы можете получить исходный код здесь .
И снова, вы можете найти хорошее введение в лямбда-выражения здесь