---
title: 【JavaFX GlassFish Advent Calendar 2013 14日目  JavaFXからREST Webサービスにアクセスする
tags: []
categories: ["Programming", "Java", "JavaFX", "AdventCalendar", "2013"]
date: 2013-12-14T14:11:16Z
updated: 2013-12-14T16:47:32Z
---

この記事は[JavaFX Advent Calendar 2013][1]の14日目、昨日はwatermintさんの[JavaFXアプリケーションの手触り感をととのえる、Fextile][2]でした。

またこの記事は[GlassFish Advent Calendar 2013][3]の14日目、昨日は蓮沼さんの[GlassFishの構成項目とモニタリング項目][4]でした。
(ごめんなさい！JAX-RSでサーバーサイドをつくってGlassFish Advent Calendarの記事にもする予定でしたけどタイムオーバー！クライアントだけで許してください！)

ぼくのJavaFX力は[@skrbさんのハンズオン][5]を少し触ったくらいで、JavaFXの細かい話は知りません・・(当日となりでJavaEEハンズオンの講師をしていたので受けていない・・)

ぼくはJavaFXの詳細機能より、アプリのアーキテクチャ的なことのほうが興味があり、[以前紹介したThin Server Architecture][6]をJavaFXで実現するべく、今回はJavaFXからRESTを叩くHello World的なものに挑戦してみました。

たたくRESTサービスは超簡単なもので、先日S[pring Frameworkが出したチュートリアル集][7]であつかっているものを使います。Backbone.jsで叩く例は[こちら][8]。

こんなやつです。[http://rest-service.guides.spring.io/greeting?name=yourname][9]

実装する上でのポイントは

* JAX-RS Clientを使う
* Webサービスには別スレッドからアクセスする

です。ハンドラメソッドから直接REST Client叩けば簡単なのですが、せっかくなので将来的なユーザビリティを意識して、[この記事][10]にあるように`javafx.concurrent.Service`をつかって別スレッドでアクセスしてみます。

ソースコードを貼っていきます・・

ちなみに、雛形をNetBeans 7.4でつくって、開発はIntelliJ IDEA 13で行いました。

### pom.xml

JAX-RS Clientを使うために`jersey-client`を使うのと、JSONを扱うために`jersey-media-json-jackson`が必要です。

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>javafx</groupId>
        <artifactId>javafx-rest</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>javafx-rest</name>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <mainClass>javafx.rest.MainApp</mainClass>
        </properties>
    
        <organization>
            <!-- Used as the 'Vendor' for JNLP generation -->
            <name>Your Organisation</name>
        </organization>
    
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey.core</groupId>
                <artifactId>jersey-client</artifactId>
                <version>2.4.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.glassfish.jersey.media</groupId>
                <artifactId>jersey-media-json-jackson</artifactId>
                <version>2.4.1</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>2.6</version>
                    <executions>
                        <execution>
                            <id>unpack-dependencies</id>
                            <phase>package</phase>
                            <goals>
                                <goal>unpack-dependencies</goal>
                            </goals>
                            <configuration>
                                <excludeScope>system</excludeScope>
                                <excludeGroupIds>junit,org.mockito,org.hamcrest</excludeGroupIds>
                                <outputDirectory>${project.build.directory}/classes</outputDirectory>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>1.2.1</version>
                    <executions>
                        <execution>
                            <id>unpack-dependencies</id>
    
                            <phase>package</phase>
                            <goals>
                                <goal>exec</goal>
                            </goals>
                            <configuration>
                                <executable>${java.home}/../bin/javafxpackager</executable>
                                <arguments>
                                    <argument>-createjar</argument>
                                    <argument>-nocss2bin</argument>
                                    <argument>-appclass</argument>
                                    <argument>${mainClass}</argument>
                                    <argument>-srcdir</argument>
                                    <argument>${project.build.directory}/classes</argument>
                                    <argument>-outdir</argument>
                                    <argument>${project.build.directory}</argument>
                                    <argument>-outfile</argument>
                                    <argument>${project.build.finalName}.jar</argument>
                                </arguments>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.7</source>
                        <target>1.7</target>
                        <compilerArguments>
                            <bootclasspath>${sun.boot.class.path}${path.separator}${java.home}/lib/jfxrt.jar</bootclasspath>
                        </compilerArguments>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>

### エントリポイント
 
 この辺はテンプレですね。
    
    package javafx.rest;
    
    import javafx.application.Application;
    
    import static javafx.application.Application.launch;
    
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    
    public class MainApp extends Application {
    
        @Override
        public void start(Stage stage) throws Exception {
            Parent root = FXMLLoader.load(getClass().getResource("/fxml/GreetView.fxml"));
    
            Scene scene = new Scene(root);
            scene.getStylesheets().add("/styles/Styles.css");
    
            stage.setTitle("Greet FX");
            stage.setScene(scene);
            stage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    
    }

### モデルクラス

REST APIのスキーマにあわせてモデルを作成します。

    package javafx.rest;
    
    public class Greet {
        private Long id;
        private String content;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        @Override
        public String toString() {
            return "Greet{" +
                    "id=" + id +
                    ", content='" + content + '\'' +
                    '}';
        }
    }

### ビュー
テキストフィールドとボタンがあるシンプルなビューです。


<a href='/api/v1/files/00121/fx1.png'><img src='/api/v1/files/00121/fx1.png' /></a>


    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import java.lang.*?>
    <?import java.util.*?>
    <?import javafx.scene.*?>
    <?import javafx.scene.control.*?>
    <?import javafx.scene.layout.*?>
    
    <AnchorPane id="AnchorPane" prefHeight="120.0" prefWidth="320.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="javafx.rest.GreetController">
      <children>
        <Button fx:id="button" layoutX="220.0" layoutY="37.0" onAction="#handleAction" prefHeight="21.999908447265625" text="greet" />
        <Label fx:id="label" layoutX="33.0" layoutY="66.0" minHeight="16.0" minWidth="69.0" />
        <TextField fx:id="name" layoutX="33.0" layoutY="36.0" onAction="#handleAction" prefWidth="162.0" />
      </children>
    </AnchorPane>



### 非同期サービスの実装

RESTサービスにアクセスする処理を実行する`javafx.concurrent.Task`をつくる`avafx.concurrent.Service`実装クラスです。引数はコントローラーからバインドしてもらうために`javafx.beans.property.StringProperty`をつかいます。プロパティは櫻庭さんのハンズオンで習いましたね。RESTにアクセスする処理は`GreetRestClient`に外だししました。

    package javafx.rest;
    
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.concurrent.Service;
    import javafx.concurrent.Task;
    
    public class GreetService extends Service<Greet> {
        private GreetRestClient greetRestClient = new GreetRestClient();
    
        private StringProperty name = new SimpleStringProperty();
    
        public void setName(String name) {
            this.name.set(name);
        }
    
        public String getName() {
            return name.get();
        }
    
        public StringProperty nameProperty() {
            return name;
        }
    
        @Override
        protected Task<Greet> createTask() {
            return new Task<Greet>() {
                @Override
                protected Greet call() throws Exception {
                    return greetRestClient.greet(name.get());
                }
            };
        }
    
    }

### REST Webサービスアクセス

JAX-RS Clientを使います。簡単です。

(スタンドアローンのJersey ClientでJSONをパースするには、どうも`new ClientConfig().register(new JacksonFeature())`が必要なようで、これの設定を調べるのに一番時間がかかった・・・)

    package javafx.rest;
    
    import org.glassfish.jersey.client.ClientConfig;
    import org.glassfish.jersey.jackson.JacksonFeature;
    
    import javax.ws.rs.client.Client;
    import javax.ws.rs.client.ClientBuilder;
    import javax.ws.rs.core.MediaType;
    
    
    public class GreetRestClient {
    
        Client client = ClientBuilder.newClient(new ClientConfig().register(new JacksonFeature()));
    
        public Greet greet(String name) {
    
            if (name == null || name.isEmpty()) {
                return null;
            }
            Greet greet = client.target("http://rest-service.guides.spring.io").path("greeting").queryParam("name", name)
                    .request(MediaType.APPLICATION_JSON_TYPE)
                    .get(Greet.class);
            return greet;
        }
    }
    
### コントローラークラス

いよいよコントローラーです。`initialize`メソッドでテキストフィールドのプロパティを`GreetService`にバインドして、コールバックの設定もします。

折角なのでリクエストしている間は再入力できないようはテキストフィールドとボタンを非活性にして、レスポンスがきたら活性にもどしています。(エラー処理はしていない・・)


`Service`の使い方が正しいのかどうかわかっていません。。

    package javafx.rest;
    
    import java.net.URL;
    import java.util.ResourceBundle;
    
    import javafx.concurrent.WorkerStateEvent;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.fxml.FXML;
    import javafx.fxml.Initializable;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.control.TextField;
    
    public class GreetController implements Initializable {
    
        @FXML
        private Label label;
    
    
        @FXML
        private Button button;
    
        @FXML
        private TextField name;
    
    
        GreetService greetService = new GreetService();
    
        @FXML
        private void handleAction(ActionEvent event) {
            label.setText("Now loading...");
            greetService.restart();
            disable();
        }
    
        @Override
        public void initialize(URL url, ResourceBundle rb) {
            greetService.nameProperty().bind(name.textProperty());
            greetService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
                @Override
                public void handle(WorkerStateEvent event) {
                    Greet greet = (Greet) event.getSource().getValue();
                    if (greet != null) {
                        label.setText(greet.getContent() + " (id=" + greet.getId() + ")");
                    }
                    enable();
                }
            });
            greetService.start();
        }
    
        void disable() {
            name.setDisable(true);
            button.setDisable(true);
        }
    
        void enable() {
            name.setDisable(false);
            button.setDisable(false);
        }
    }

### 実行！

こんな感じです。

<a href='/api/v1/files/00122/fx2.png'><img src='/api/v1/files/00122/fx2.png' /></a>

この記事は<a href="http://www.amazon.co.jp/exec/obidos/ASIN/1430244259/ikam-22/ref=nosim/" name="amazletlink" target="_blank">Java EE 7 Recipes: A Problem-Solution Approach (Recipes Apress)</a>の「18-5. Incorporating REST Services into JavaFX Applications」を参考にしました。


<div class="amazlet-box" style="margin-bottom:0px;"><div class="amazlet-image" style="float:left;margin:0px 12px 1px 0px;"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/1430244259/ikam-22/ref=nosim/" name="amazletlink" target="_blank"><img src="http://ecx.images-amazon.com/images/I/51Unc3fZmoL._SL160_.jpg" alt="Java EE 7 Recipes: A Problem-Solution Approach (Recipes Apress)" style="border: none;" /></a></div><div class="amazlet-info" style="line-height:120%; margin-bottom: 10px"><div class="amazlet-name" style="margin-bottom:10px;line-height:120%"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/1430244259/ikam-22/ref=nosim/" name="amazletlink" target="_blank">Java EE 7 Recipes: A Problem-Solution Approach (Recipes Apress)</a><div class="amazlet-powered-date" style="font-size:80%;margin-top:5px;line-height:120%">posted with <a href="http://www.amazlet.com/" title="amazlet" target="_blank">amazlet</a> at 13.12.14</div></div><div class="amazlet-detail">Josh Juneau <br />Apress (2013-05-22)<br />売り上げランキング: 106,234<br /></div><div class="amazlet-sub-info" style="float: left;"><div class="amazlet-link" style="margin-top: 5px"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/1430244259/ikam-22/ref=nosim/" name="amazletlink" target="_blank">Amazon.co.jpで詳細を見る</a></div></div></div><div class="amazlet-footer" style="clear: left"></div></div>

本当はJAX-RSでサーバーサイドを実装するところまでやってGlassFish Advent Calendarのネタにもなるようにしたかったけど、間に合わなかった！ごめんなさい蓮沼さん！
明日こそはサーバーサイドをつくる！


  [1]: http://www.adventar.org/calendars/146
  [2]: http://watermint.org/post/69825099011/designing-ui-framework-for-javafx
  [3]: http://www.adventar.org/calendars/149
  [4]: http://www.coppermine.jp/docs/programming/2013/12/glassfish-monitoring.html
  [5]: http://javainthebox.net/publication/20130824JJUGJavaFXHoL/text/20130824JavaFXHoL02.html#
  [6]: /#/entries/176
  [7]: http://spring.io/guides/
  [8]: http://spring.io/guides/gs/consuming-rest-backbone/
  [9]: http://rest-service.guides.spring.io/greeting?name=yourname
  [10]: http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm
