Spring Boot + Vaadin (Vaadin4Spring)

## はじめに

Spring Boot + Apache Wicket はできてきた感があるので、Vaadinにも挑戦してみる。

## Vaadin4Spring

Spring Boot + Vaddin の先駆として、[peholmst/vaadin4spring](https://github.com/peholmst/vaadin4spring)がある。まずはこれを使わせてもらう。

README.mdの通りに、まず、[SPRING INITIALIZR](http://start.spring.io)で、Webプロジェクトを作成する。

次に、pom.xmlの`dependencies`に`spring-boot-vaadin`, `vaadin-themes`, `vaadin-client-compiled`を登録する

```xml
<!-- Vaadin4Spring -->
<!-- 記事の執筆時点でspring-boot-vaadin 0.0.3.1のvaadinは7.3.6なので、後ほど実行時にはコンソールにアラートが発生する。バージョンを合わせたいときは調整する -->
<dependency>
  <groupId>org.vaadin.spring</groupId>
  <artifactId>spring-boot-vaadin</artifactId>
  <version>0.0.3.1</version>
</dependency>
<dependency>
  <groupId>com.vaadin</groupId>
  <artifactId>vaadin-themes</artifactId>
  <version>7.3.9</version>
</dependency>
<dependency>
  <groupId>com.vaadin</groupId>
  <artifactId>vaadin-client-compiled</artifactId>
  <version>7.3.9</version>
</dependency>
```

## ログイン画面を作ってみる

VaadinのWikiに[Creating a simple login view](https://vaadin.com/wiki/-/wiki/Main/Creating%20a%20simple%20login%20view)という記事があるので、これを参考にし、簡単なログイン画面を作ってみる。

画面を制御するための`MainUI`クラス、ログイン画面を表示する`LoginView`クラス、ログイン後の画面を表示する`LoginedView`クラスの3種類を作成する。

### MainUI

以下、Wikiの`SimpleLoginUI`を引用し改変している。

```java
package com.example.ui;

import org.springframework.beans.factory.annotation.Autowired;
import org.vaadin.spring.VaadinUI;
import org.vaadin.spring.navigator.SpringViewProvider;

import com.vaadin.annotations.Theme;
import com.vaadin.navigator.Navigator;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;

@Theme("valo")
@VaadinUI
public class MainUI extends UI {

  @Autowired
  private SpringViewProvider viewProvider;

  @Override
  protected void init(VaadinRequest request) {
    Navigator navigator = new Navigator(this, this);
    navigator.addProvider(viewProvider);
    navigator.addViewChangeListener(new ViewChangeListener() {

      @Override
      public boolean beforeViewChange(ViewChangeEvent event) {

        boolean isLoggedIn = getSession().getAttribute("user") != null;
        boolean isLoginView = event.getNewView() instanceof LoginView;

        if (!isLoggedIn && !isLoginView) {
          getNavigator().navigateTo(LoginView.NAME);
          return false;
        } else if (isLoggedIn && isLoginView) {
          return false;
        }
        return true;
      }

      @Override
      public void afterViewChange(ViewChangeEvent event) {

      }
    });
    setNavigator(navigator);
  }
}
```

`@VaadinUI`で、URLでアクセス可能なUIであることを設定。Vaadinではページ遷移は`Navigator`で行うが、これをアノテーションで管理するための`SpringViewProvider`をInjectする。

`@Theme("valo")`で、Vaadinの綺麗なCSSテーマである[Valo](https://vaadin.com/valo)を利用してくれる。

### LoginView

以下、Wikiの`SimpleLoginView`を引用し改変している。

```java
package com.example.ui;

import org.vaadin.spring.UIScope;
import org.vaadin.spring.navigator.VaadinView;

import com.vaadin.annotations.Theme;
import com.vaadin.data.validator.AbstractValidator;
import com.vaadin.data.validator.EmailValidator;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.shared.ui.MarginInfo;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.PasswordField;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.Reindeer;

@UIScope
@VaadinView(name = LoginView.NAME)
public class LoginView extends CustomComponent implements View {

  public static final String NAME = "";

  private final TextField user;

  private final PasswordField password;

  private final Button loginButton;

  public LoginView() {
    setSizeFull();

    user = new TextField("ユーザー名:");
    user.setWidth("300px");
    user.setRequired(true);
    user.setInputPrompt("例：user@example.com");
    user.addValidator(new EmailValidator("ユーザ名かパスワードが不正です"));
    user.setInvalidAllowed(false);

    password = new PasswordField("パスワード:");
    password.setWidth("300px");
    password.addValidator(new PasswordValidator());
    password.setRequired(true);
    password.setValue("");
    password.setNullRepresentation("");

    loginButton = new Button("ログイン", e -> {
      if (!user.isValid() || !password.isValid()) {
        return;
      }

      String username = user.getValue();
      String password = this.password.getValue();

      boolean isValid = username.equals("test@test.com")
                        && password.equals("passw0rd");

      if (isValid) {
        getSession().setAttribute("user", username);
        getUI().getNavigator().navigateTo(LoginedView.NAME);
      } else {
        this.password.setValue(null);
        this.password.focus();
      }
    });

    VerticalLayout fields = new VerticalLayout(user, password, loginButton);
    fields.setCaption("ログインしてください (test@test.com/passw0rd)");
    fields.setSpacing(true);
    fields.setMargin(new MarginInfo(true, true, true, false));
    fields.setSizeUndefined();

    VerticalLayout viewLayout = new VerticalLayout(fields);
    viewLayout.setSizeFull();
    viewLayout.setComponentAlignment(fields, Alignment.MIDDLE_CENTER);
    viewLayout.setStyleName(Reindeer.LAYOUT_BLUE);
    setCompositionRoot(viewLayout);
  }

  @Override
  public void enter(ViewChangeEvent event) {
    user.focus();
  }

  private static final class PasswordValidator extends AbstractValidator<String> {

    public PasswordValidator() {
      super("ユーザ名かパスワードが不正です");
    }

    @Override
    protected boolean isValidValue(String value) {
      return value != null ? value.length() >= 8 && value.matches(".*\\d.*") : false;
    }

    @Override
    public Class<String> getType() {
      return String.class;
    }
  }

}
```

ちょっとバリデーションなどが設定されていてごちゃごちゃしてる感があるけど、原則的には`TextField`や`Button`のコンポーネントに設定を付け加えてあるだけ。

`@UIScope`でスコープを設定する。`@VaadinView`は、ページとなるViewにマッピングされる名称。（Vaadin的に、初期ページは空文字でマッピングしないといけない...？ちょっと自信ない）。

ボタンのリスナーにラムダ式を使えるのがすっきりしてて素敵。


### LoginedView

以下、Wikiの`SimpleLoginMainView`を引用し改変している。

```java
package com.example.ui;

import org.vaadin.spring.UIScope;
import org.vaadin.spring.navigator.VaadinView;

import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.ui.Button;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Label;

@UIScope
@VaadinView(name = LoginedView.NAME)
public class LoginedView extends CustomComponent implements View {

  public static final String NAME = "LoginedView";

  private Label text = new Label();

  private Button logout = new Button("ログアウト", e -> {
    getSession().setAttribute("user", null);
    getUI().getNavigator().navigateTo(NAME);
  });

  public LoginedView() {
    setCompositionRoot(new CssLayout(text, logout));
  }

  @Override
  public void enter(ViewChangeEvent event) {
    String username = String.valueOf(getSession().getAttribute("user"));
    text.setValue("ようこそ、 " + username);
  }
}
```

## 実行結果

----

![Spring_Boot+Vaadin_01a.png](https://qiita-image-store.s3.amazonaws.com/0/8064/c173ad7a-c72e-0d25-01c0-ff167c63feb9.png)

----

![Spring_Boot+Vaadin_01b.png](https://qiita-image-store.s3.amazonaws.com/0/8064/a3a98cbb-f149-6772-3e5d-54015946e261.png)

---

Vaadinを使うと（Layoutの使い方に慣れが必要そうだけど）HTMLを書かずにWebシステムを構築していける。Spring4Vaadinを使えば、Navigatorなどの面倒になりがちな設定もアノテーションを拾って自動設定してくれる。慣れてくると爽快に開発できそうだなあ。

なお、ここまでのソースコードは[gishi-yama/springboot-vaadin-sample/tree/61086783ec](https://github.com/gishi-yama/springboot-vaadin-sample/tree/61086783ec8264f5347da8a3ca7efd22f5189be3)にpushしてある。