Springことはじめ

投稿者: | 2016-05-20

これまでJavaScriptの勉強を進めてきましたが、Polymerを使ったカスタムエレメントの作成がひと段落ついたので、次のトピックに進めていきたいと思います。今度はフロントエンド技術とは打って変わってバックエンド、それもSpring Frameworkについて備忘程度にまとめていきたいと思います。

それにしてもSpring関連の日本語書籍ってあまりない(というかフレームワークにフォーカスを当てた日本語書籍が少ない気がする)ので、今回は勉強材料として洋書を手に取ってみました。

米Amazonのレビューもよさげですし、何よりKindle版だと安い!ので、軽い気持ちで購入してみました。中身をみると500ページ越えでびっくりしたのですが、とりあえず行けるところまで行こうと思います。まずはチュートリアルレベルの内容についてまとめていきます。

IntelliJでSpringプロジェクトを作る

IntelliJの標準ではなく、gradleで作っていきます。まずは適当にディレクトリを切ってプロジェクト作成。

gradle init --type java-library

続いてIntelliJ側で、作成したbuild.gradleを開いて編集します。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.bmuschko:gradle-tomcat-plugin:2.2.4'
    }
}

apply plugin: 'war'
apply plugin: 'com.bmuschko.tomcat'

repositories {
    jcenter()
    mavenCentral()
}

dependencies {
    compile 'javax:javaee-web-api:7.0'
    compile 'org.springframework:spring-webmvc:4.2.6.RELEASE'
    compile 'log4j:log4j:1.2.16'
}

SpringでWebAPを作る、というところが目標なのでTomcatプラグイン、warプラグインを入れていますが、Springを使うだけなら必要ないです。また、ライブラリとしてSpringMVCを読み込んでいますが、Coreパッケージだけなら「org.springframework:spring-core:4.2.6.RELEASE」でいいと思います(SpringMVCにはSpring coreも含まれている)。

アプリケーションを組む

今回はWebアプリではなく、スタンドアロンなアプリを組みます、それもSpringの機能を試すためだけのとても簡素なものです。クラス構造としてはController、Service、Daoクラスを用意して、モデルを各クラスでやりとりしながら処理を進めていくものです。

各クラス定義

モデルクラス(lombok使えばよかったな…)

package com.dyama;

public class SamplePersonModel {
    private long id;
    private String name;
    private int age;

    public SamplePersonModel(long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "id: " + id + ", name: " + name + ", age: " + age;
    }
}

Daoクラス(ダミーです)

package com.dyama;

import org.apache.log4j.Logger;

import java.util.HashMap;
import java.util.Map;

// DBアクセスを模した、ダミークラス
public class SamplePersonDao {
    private static Logger logger = Logger.getLogger(SamplePersonDao.class);

    public SamplePersonDao() {
        logger.info("initializing");
    }

    private Map<Long, SamplePersonModel> personModelMap = new HashMap<>();

    public SamplePersonModel getSamplePersonModel(long id) {
        return personModelMap.get(id);
    }

    public boolean createPersonModel(SamplePersonModel samplePersonModel) {
        personModelMap.put(samplePersonModel.getId(), samplePersonModel);
        return true;
    }
}

Serviceクラス

package com.dyama;

import org.apache.log4j.Logger;

public class SamplePersonService {
    private static Logger logger = Logger.getLogger(SamplePersonService.class);
    private SamplePersonDao samplePersonDao;

    public SamplePersonService() {
        logger.info("initializing");
    }

    public void setSamplePersonDao(SamplePersonDao samplePersonDao) {
        logger.info("Setting samplePersonDao property");
        this.samplePersonDao = samplePersonDao;
    }

    public SamplePersonModel getSamplePersonModel(long id) {
        return samplePersonDao.getSamplePersonModel(id);
    }

    public boolean createSamplePerson(SamplePersonModel samplePersonModel) {
        return samplePersonDao.createPersonModel(samplePersonModel);
    }
}

Controllerクラス

package com.dyama;

import org.apache.log4j.Logger;

public class SamplePersonController {
    private static Logger logger = Logger.getLogger(SamplePersonController.class);

    private SamplePersonService samplePersonService;

    public SamplePersonController() {
        logger.info("initializing");
    }

    public void setSamplePersonService(SamplePersonService samplePersonService) {
        logger.info("Setting samplePersonService property");
        this.samplePersonService = samplePersonService;
    }

    public boolean register() {
        return samplePersonService.createSamplePerson(
                new SamplePersonModel(1, "d-yama", 30)
        );
    }

    public SamplePersonModel getPerson() {
        return samplePersonService.getSamplePersonModel(1L);
    }
}

見てわかる通り、ここまではSpringに依存しないコード、すなわちPOJOなJavaクラスです。

bean定義ファイルを用意する

Spring Coreのメイン機能はDIを実現したIoCコンテナです。DIってそもそも何?については書籍の他にこれこれのスライドを参考にしました。各クラスの生成や、依存している別クラスのインスタンスの取得をビジネスロジックから分離するデザインパターン、と私は認識しています。Spring IoCコンテナは実際にオブジェクトを生成したり、依存関係を解決するオブジェクトです。

IoCコンテナに管理をお願いするための設定情報を用意する必要があるのですが、Springではその設定情報をXML、アノテーション、Javaコードと様々な方法で書くことができるらしいです。まずはXMLベース、通称bean定義ファイルから作ってみたいと思います。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <bean id="sample-controller" class="com.dyama.SamplePersonController">
        <property name="samplePersonService" ref="sample-service" />
    </bean>
    <bean id="sample-service" class="com.dyama.SamplePersonService">
        <property name="samplePersonDao" ref="sample-dao"/>
    </bean>
    <bean id="sample-dao" class="com.dyama.SamplePersonDao" />
</beans>

beanタグがIoCコンテナに管理をお願いしたいオブジェクト(=bean)を表しています。idがユニークな識別子、classが生成対象のクラスを表します。また、propertyタグにはbeanが依存している別のオブジェクトの設定情報を表しています。nameは依存しているオブジェクトを表すクラスの変数名、ref属性はインジェクションするbeanを表しています。このように設定しておくとSpring側でbeanに紐づいているクラスのsetterを使って依存オブジェクトをインジェクションしてくれます(=setter base injection)。

IoCコンテナからオブジェクトを取得する

最後にIoCコンテナからオブジェクトを取り出して、ビジネスロジックを実行します。以下がアプリケーションエントリポイントのコードです。

package com.dyama;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SampleApp {
    private static Logger logger = Logger.getLogger(SampleApp.class);

    public static void main(String args[]) {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "classpath:META-INF/spring/applicationContext.xml"
        );

        SamplePersonController samplePersonController = (SamplePersonController) context.getBean("sample-controller");

        logger.info(samplePersonController.register());
        logger.info(samplePersonController.getPerson());
    }
}

IoCコンテナを表しているのはApplicationContextオブジェクトです。引数にbean定義ファイルを渡してコンストラクトすることによりIoCコンテナオブジェクトを取得します。IoCコンテナオブジェクトがインスタンス化される際、引数に渡したbean定義ファイルを元にオブジェクトの生成、依存関係の解決を実施します。続いてIoCコンテナが管理するオブジェクトを取得するには、getBeanメソッドを使用します。getBeanメソッドの引数に取得したいbeanのidを渡してやることによってオブジェクトを取得することができます。このアプリケーションの出力結果は以下の通りです。

INFO  org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6aa8ceb6: startup date [Fri May 20 12:01:31 JST 2016]; root of context hierarchy
INFO  org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/spring/applicationContext.xml]
INFO  com.dyama.SamplePersonController - initializing
INFO  com.dyama.SamplePersonService - initializing
INFO  com.dyama.SamplePersonDao - initializing
INFO  com.dyama.SamplePersonService - Setting samplePersonDao property
INFO  com.dyama.SamplePersonController - Setting samplePersonService property
INFO  com.dyama.SampleApp - true
INFO  com.dyama.SampleApp - id: 1, name: d-yama, age: 30

ログの内容から、オブジェクトの生成⇒依存関係の解決、という順序で処理が進んでいることがわかります。

まとめ

Springの勉強を始めました、ということでシンプルなサンプルコードを書いてみました。ビジネスロジックの部分はSpringに依存せずに書くことができたので、コードの分離がよくできているなあ、と思いました。参考書籍の1章まで読んでみて、特に読みづらいところもなかったのでこのまま学習を進めることができそうです。