3.2. オンライン版クラウド拡張開発プロジェクトの作成


本ガイドラインでは、クラウドネイティブなアプリケーション向けの開発プロジェクトを作成する方法について説明する。

Macchinetta Server Framework for Java (1.x) の マルチプロジェクト構成のブランクプロジェクト を元に、カスタマイズを加えることで作成する。


3.2.1. 開発プロジェクトの作成

開発プロジェクトの作成方法は、 Macchinetta Server Framework for Java (1.x) Development Guideline 開発プロジェクトの作成 を参照されたい。

3.2.2. 開発プロジェクトのカスタマイズ

開発プロジェクトの作成 で作成したプロジェクトを クラウドネイティブなアプリケーション向けにカスタマイズが必要な箇所がいくつか存在する。

カスタマイズが必要な箇所を以下に示す。

3.2.2.1. Spring Bootの利用

Spring Cloud との親和性を高めるために、 オンライン版クラウド拡張開発プロジェクトでは Spring Boot を利用する。 Spring Bootを使用すると、プロジェクトのアーカイブ方式として「実行可能jarファイル」と「デプロイ可能warファイル」が選択できるが、 本ガイドラインでは、Macchinetta Server Framework for Java (1.x) のノウハウを活用するため、デプロイ可能warファイル を採用する。 Spring Bootを使用したデプロイ可能warファイルの作成方法の詳細はSpring Bootの公式リファレンス Traditional deployment を参照されたい。

Spring Bootを使用するとBean定義など多くの設定が自動で行われる。 このような自動設定の仕組みのことをSpring Boot Auto-configurationといい、アプリケーション開発者は最小限の設定を行うだけでアプリケーションを構築することができる。 詳しくはSpring Bootの公式リファレンス Auto-configuration を参照されたい。

3.2.2.2. 依存ライブラリの追加

  • project/pom.xml (プロジェクトrootのParent POM)
<dependencyManagement>
    <!-- (1) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Dalston.SR4</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
</dependencyManagement>
  • project/xxx-web/pom.xml
<dependencies>
    <!-- (2) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- (3) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>
    <!-- (4) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-client</artifactId>
    </dependency>
<dependencies>
項番
依存ライブラリ
説明
(1)
spring-cloud-dependencies
Spring Cloud のBOM。 プロジェクトのParent POMのdependencyManagementに定義することで、Spring Cloud関連の依存ライブラリのバージョンを解決する。 1.0.1.RELEASE で利用するOSSのバージョンについては、 フレームワークスタック を参照されたい。
(2)
spring-boot-starter Spring Boot の機能を実現するために必要なライブラリの依存関係を集約したもので、 Spring Boot特有のAuto-configuration、ロギング、YAMLなどが利用できるようになる。
(3)
spring-boot-configuration-processor spring-boot-configuration-processorの依存ライブラリを追加することで、 Spring Bootの@ConfigurationPropertiesアノテーションを使用して定義したプロパティのメタデータを生成することができる。 詳細については、Spring Boot公式リファレンス Generating your own meta-data using the annotation processor を参照されたい。
(4)
spring-cloud-config-client Spring Cloud Config を利用するための依存ライブラリ。 spring-cloud-config-clientに依存したSpring Bootアプリケーションとしてビルドすることで、Spring Cloud Configが利用できる。

3.2.2.3. エントリポイントの作成

Spring Bootを利用して、デプロイ可能なwarファイルを作成するために必要な設定クラスを作成する。 このクラスはSpring Bootのエントリポイントとして、アプリケーションの起動時に読み込まれ、Springアプリケーションに必要なサーブレットやフィルタ等の情報を設定する。

  • Bootstrap.java
package com.example.xxx.app;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ImportResource;

//(1)
@ImportResource({ "classpath*:META-INF/spring/applicationContext.xml", "classpath*:META-INF/spring/spring-security.xml",
                  "classpath*:/META-INF/spring/spring-mvc.xml"}) //(2)
//(3)
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
                                      JmxAutoConfiguration.class, WebMvcAutoConfiguration.class })
//(4)
public class Bootstrap extends SpringBootServletInitializer {

    //(5)
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        //(6)
        setRegisterErrorPageFilter(false);
        return application.sources(Bootstrap.class);
    }
}
項番
説明
(1)
Spring Frameworkのアノテーションコンフィグの仕組みである@ImportResourceを使用してXMLのBean定義ファイルを読み込んでいる。 ここでは、classpath*:META-INF/spring/配下のapplicationContext.xmlspring-security.xmlを読み込むように設定している。
(2)
web.xmlにおいてDispathcerServletcontextConfigLocationで指定していたclasspath*:/META-INF/spring/spring-mvc.xmlを追加する。 Spring Bootを使用した際の制約で、DispatcherServletではなくエントリポイントでロードする必要がある。 詳細は DIコンテナの構築タイミングによりSpring Bootの機能が一部動作しない を参照されたい。
(3)
@EnableAutoConfigurationexclude属性を使用することで、特定のコンフィギュレーションクラスをAuto-configurationの適用対象から除外できる。 本ガイドラインで作成するプロジェクトでは、 DataSourceAutoConfigurationJmxAutoConfigurationWebMvcAutoConfigurationを除外する必要がある。
(4) (5)
デプロイ可能なwarファイルを作成するためにSpringBootServletInitializerを継承したクラスを作成し、configureメソッドをオーバーライドする。 この実装を行うことで、通常はSpringが提供するContextLoaderListenerが行っているサーブレットコンテキストの構築がSpring Bootによって行われる。
(6)
エラー画面表示の不具合 への対応。Spring BootのErrorPageFilterを無効にしている。

Note

項番(2)で説明されているAuto-configurationクラスについて、除外対象のクラスと除外理由は以下の通り。

除外対象クラス
説明
DataSourceAutoConfiguration
データソースを設定するAuto-configurationクラス。Spring Bootがデータソースが一つであることを想定しているため、 Macchinetta Server Framework for Java (1.x) のブランクプロジェクトのように複数のデータソースが定義されている場合、 NoUniqueBeanDefinitionExceptionが発生する。 これを回避するにはDataSourceAutoConfigurationをAuto-configurationから除外するか、 データソースの1つにprimary=trueを設定する必要がある。 このクラスを除外せずに複数のデータソースを定義する方法は、Spring Boot公式リファレンス Configure Two DataSource を参照されたい。
JmxAutoConfiguration
JMXを設定するAuto-configurationクラス。デフォルトでは同一サーバに複数のAPを起動した場合、 JMXのドメインが重複してBeanが登録できずUnableToRegisterMBeanExceptionが発生するため除外する。
WebMvcAutoConfiguration
Spring MVCを設定するAuto-configurationクラス。 除外しない場合、<mvc:view-resolvers>で作成したBeanが上書きされてしまうため不具合が発生する。 詳細は WebMvcAutoConfigurationによる不具合 を参照されたい。
  • web.xml

エントリポイントの作成にともなって、web.xmlに下記変更を加える。

<!-- (1) -->
<!-- 削除 ここから -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath*:META-INF/spring/applicationContext.xml
        classpath*:META-INF/spring/spring-security.xml
    </param-value>
</context-param>
<!-- 削除 ここまで -->
<!-- omitted -->
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value></param-value> <!-- (2) -->
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
項番
説明
(1)
ContextLoaderListenerを削除する。 SpringBootServletInitializerContextLoaderListenerを登録しているので、web.xmlでの定義は不要になる。
(2)
DIコンテナの構築タイミングによりSpring Bootの機能が一部動作しない への対処。 DispathcerServletcontextConfigLocation属性の設定値を削除する。 contextConfigLocation属性を削除してしまうと例外が発生するので、 空を設定することにより、 DispathcerServletにダミーのコンテキストを設定しこれを回避する。 また、自分でダミーファイルをデフォルト指定(WEB-INF/appServlet-servlet.xml)に 作成することで、contextConfigLocation属性を削除しても例外が発生しなくなる。

3.2.3. オンライン版クラウド拡張開発プロジェクトで考慮すべき点・制約事項

オンライン版クラウド拡張開発プロジェクトを作成するにあたり、下記について考慮しなければならない。

3.2.3.1. Spring Boot使用に伴う制約事項

3.2.3.1.1. Logbackの拡張の利用

Spring BootではLogbackの拡張を行っており追加の設定を行うことができるが、 デフォルトの設定ファイル名(logback.xml)では読み込みのタイミングが早すぎるため、Spring BootによるLogbackの拡張を利用することができない。

この拡張を利用するには、Spring Bootではデフォルトのファイル名ではなく、-springのサフィックスを付けたlogback-spring.xmlを使用する必要がある。

詳細は、Spring Bootの公式リファレンス Custom log configuration を参照されたい。

また、Logbackの設定例は Macchinetta Server Framework for Java (1.x) Development Guideline Logbackの設定 を参照されたい。

Warning

Spring Cloud Configを利用しlogging.pathの設定値をConfigサーバに持たせる場合、Configサーバからプロパティを取得まするまでの間のログが意図しないディレクトリに出力されてしまう。 これはCustom log configurationに記載されているような設定ファイル名が存在するとSpring Bootが自動で読み込んでしまうが、logging.pathが未解決のためログの出力先を制御することができないため発生する。 logbackを利用する場合、logback.xmllogback-spring.xml以外の名前を利用すれば良い。 設定ファイル名をSpring Bootが読み込みに行かない独自のファイル名に設定し、logging.configのプロパティを設定することで意図しないログ出力を制御することができる。 この方法を取った場合、logbackが読み込まれてからlogging.pathが解決されるまでの間のログはSpring Bootのデフォルトの設定で標準出力に出力される。

設定ファイル名をappName-logback-spring.xmlとし、Configサーバに持たせるapplication-development.ymlにプロパティを設定した場合の例を以下に示す。

  • application-development.yml
logging:
  config: classpath:appName-logback-spring.xml

Configサーバの使用方法については、 環境依存値の外部管理 を参照されたい。

3.2.3.2. Spring Bootで組み込みTomcatを使用しない場合の制約事項

組み込みTomcatを使用しない場合、以下の制約が発生する。

3.2.3.2.1. DIコンテナの構築タイミングによりSpring Bootの機能が一部動作しない

DIコンテナの構築タイミングによって、Spring Bootの機能が一部動作しないことがある。 例えば、DispatcherServletで行われたコンポーネントスキャンでは、Spring Boot ActuatorにCustom HealthIndicatorを@Componentで定義しても動作させることができない。

Spring Bootではエントリポイントで、ContextLoaderListenerを登録し、コンテキストの読み込みを行っている。 また、Macchinetta Server Framework for Java (1.x) のブランクプロジェクトではDispatcherServletでもコンテキストの読み込みを行っている。 読み込みは、ContextLoaderListenerDispatcherServletの順で行われるため、 DispatcherServlet側で行われたコンポーネントスキャンでは、Spring Bootの機能への組み込みに間に合わず、動作しないことがある。

正常に動作させるためには、DispatcherServletで読み込みを行っていたXMLファイルをエントリポイントで読み込む必要がある。

Note

Custom HealthIndicatorの例はあくまで一例であり、類似の意図しない動作が発生する可能性があるためDIコンテナの構築には注意されたい。

3.2.3.2.2. トランザクショントークンチェックを使用するための設定方法が異なる

Macchinetta Server Framework for Java (1.x) Development Guideline トランザクショントークンチェックを使用するための設定 に記載されている設定方法を使用してもトランザクショントークンチェックが正常に動作しない。 これは、組み込みTomcatを使用しない場合にSpring BootによるrequestDataValueProcessorの 上書きが行われることにより、JSPにトランザクショントークンが埋め込まれないためである。

参考:spring-boot#4676

以下のような実装を行うことでトランザクショントークンチェックを有効にすることが可能である。

  • RequestDataValueProcessorPostProcessor
// (1)
public class RequestDataValueProcessorPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        // (2)
        ConstructorArgumentValues cav = new ConstructorArgumentValues();
        List<RequestDataValueProcessor> values = new ArrayList<RequestDataValueProcessor>();
        values.add(new TransactionTokenRequestDataValueProcessor());
        values.add(new CsrfRequestDataValueProcessor());
        cav.addGenericArgumentValue(values);
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(CompositeRequestDataValueProcessor.class, cav, null);

        // (3)
        registry.removeBeanDefinition("requestDataValueProcessor");
        registry.registerBeanDefinition("requestDataValueProcessor", rootBeanDefinition);
    }
}
項番 内容
(1)
BeanDefinitionRegistryPostProcessorを実装することで、Beanのインスタンス化前にBean定義の変更を行うことができる。
(2)
Bean定義を行うオブジェクトを生成する。
ここでは、TransactionTokenRequestDataValueProcessorCsrfRequestDataValueProcessorを併用する CompositeRequestDataValueProcessorを定義している。

Note

トランザクショントークンチェックとCSRFトークンチェックを併用したい場合、CsrfRequestDataValueProcessorを追加する必要があるので留意されたい。

(3)
作成したCompositeRequestDataValueProcessorオブジェクトでDIコンテナにrequestDataValueProcessorのBean名で登録されたオブジェクトを上書きする。
  • xxx-web/src/main/resources/META-INF/spring/spring-mvc.xml
<!-- (1) -->
<bean class="com.example.xxx.app.RequestDataValueProcessorPostProcessor"/>
項番 内容
(1)
作成したBean定義の上書きを行うクラスのBean定義を行う。

3.2.3.2.3. Spring Boot Actuatorのエンドポイントのポートが変更できない

組み込みTomcatを使用しない場合、Spring Boot Actuatorのエンドポイントが使用するポートを アプリケーションが使用するポートと別に設定することができない。 そのため、クラウドベンダーが提供するロードバランサの機能を使用してエンドポイントのURLへの外部アクセスを遮断する必要がある。

詳細については、 ヘルスチェック を参照されたい。

3.2.3.2.4. Spring Cloud Configのリフレッシュ機能が使用できない

Spring Cloud Configを使用して構築したConfig Clientは設定変更を反映させるrefreshエンドポイント を利用することができるが、組み込みTomcatを使用しない場合は当該機能を利用することができない。

詳細については、 環境依存値の外部管理 を参照されたい。

3.2.3.2.5. Filterが自動で登録され意図しない動作が発生する

Spring Bootのデフォルトでは、アプリケーションコンテキスト上のすべてのFilterを自動で登録する。 本ガイドラインでは、Filterの登録をweb.xmlを使用して行っているため、Filterが二重登録されるなど意図しない動作が発生する可能性がある。

以下のような実装を行うことでフィルタの自動登録を制御することが可能である。

  • DefaultFiltersBeanFactoryPostProcessor
//(1)
public class DefaultFiltersBeanFactoryPostProcessor implements
                                                   BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) throws BeansException {
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) bf;

        //(2)
        String[] beanNames = beanFactory.getBeanNamesForType(Filter.class);
        for (String beanName : beanNames) {
            BeanDefinition definition = BeanDefinitionBuilder
                    .genericBeanDefinition(FilterRegistrationBean.class)
                    .setScope(BeanDefinition.SCOPE_SINGLETON)
                    .addConstructorArgReference(beanName)
                    .addConstructorArgValue(new ServletRegistrationBean[] {})
                    .addPropertyValue("enabled", false).getBeanDefinition();

            beanFactory.registerBeanDefinition(beanName
                    + "FilterRegistrationBean", definition);
        }
    }
}
項番 内容
(1)
BeanFactoryPostProcessorを実装することで、Beanのインスタンス化前にプロパティの変更を行うことができる。
(2)
ConfigurableListableBeanFactoryの実装クラスであるDefaultListableBeanFactoryからデフォルトで登録されるFilterのBean名を取得し、すべてのFilterを無効にしている。
  • xxx-web/src/main/resources/META-INF/spring/spring-mvc.xml
<!-- (1) -->
<bean class="com.example.xxx.app.DefaultFiltersBeanFactoryPostProcessor"/>
項番 内容
(1)
作成したDefaultFiltersBeanFactoryPostProcessorのBean定義を追加する。

3.2.3.3. WebMvcAutoConfigurationによる不具合

Spring BootのAuto-configurationにより設定されるWebMvcAutoConfigurationによって <mvc:view-resolvers>で作成したBeanが上書きされてしまうことで下記不具合が発生する。

これらの問題に対処したソースコード例は エントリポイントの作成 を参照されたい。

3.2.3.3.1. ViewResolverが上書きされViewの解決ができない

Macchinetta Server Framework for Java (1.x) Development Guideline HTMLを応答する に従って<mvc:view-resolvers>を使用していると、Viewの解決ができなくなる不具合が発生する。

これは、Spring Bootを非組み込みTomcatで使用する場合に、<mvc:view-resolvers>で定義した ViewResolverWebMvcAutoConfigurationによって上書きされてしまいViewの解決ができなくなるからで、 WebMvcAutoConfigurationをAuto-configurationから除外することで回避できる。

3.2.3.3.2. エラー画面表示の不具合

Spring Bootを非組み込みTomcatで使用する場合、上記のViewResolverが上書きされてしまうことに加え、 デフォルトで動作するErrorPageFilterが意図せぬ動作をしてしまうことで、 システム例外発生時などで定義したエラー画面が表示されず、真っ白な画面が表示されてしまう。

これは、エントリポイントのconfigureメソッドでErrorPageFilterを無効化することと、 WebMvcAutoConfigurationをAuto-configurationから除外することで回避できる。