かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その2 )( Project の作成 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その1 )( 概要 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Project の作成

参照したサイト・書籍

  1. spring-projects/spring-session - Add @EnableSpringHttpSession
    https://github.com/spring-projects/spring-session/issues/231

目次

  1. GitHub 上で ksbysample-boot-miscellaneous レポジトリを新規作成してクローンする
  2. IntelliJ IDEA の起動し、Project を新規作成する
  3. build.gradle を変更する
  4. Gradle の設定を変更する
  5. Project language level を SDK default に変更する
  6. JUnit によるテスト実行時の spring.profiles.active を設定する
  7. src/main/resources の下に static, templates ディレクトリを作成する
  8. ksbysample-webapp-lending からファイルをコピーする(その1)
  9. 続く。。。

手順

GitHub 上で ksbysample-boot-miscellaneous レポジトリを新規作成してクローンする

  1. GitHub 上で ksbysample-boot-miscellaneous レポジトリを新規作成します。

  2. SourceTree で作成した ksbysample-boot-miscellaneous レポジトリを C:\project-springboot\ksbysample-boot-miscellaneous へクローンします。

    f:id:ksby:20170721055314p:plain

  3. C:\project-springboot\ksbysample-boot-miscellaneous の下に .gitignore を新規作成し、リンク先の内容 に変更します。この .gitignore は Spring Integration のサンプルを作成している ksbysample-boot-integration レポジトリ のものをコピーしたものです。

  4. develop ブランチ、feature/1-issue ブランチを作成します。

IntelliJ IDEA の起動し、Project を新規作成する

  1. IntelliJ IDEA を起動します。他の Project を開いている場合には、メイン画面のメニューから「File」->「Close Project」を選択して Project をクローズします。

  2. 「Welcome to IntelliJ IDEA」画面が表示されます。画面中央の「Create New Project」をクリックします。

  3. 「New Project」画面が表示されます。画面左側のリストから「Gradle」を選択し、画面右側は以下の画像の状態にして「Next」ボタンをクリックします。

    f:id:ksby:20170721062816p:plain

  4. GroupId、ArtifactId、Version を入力する画面が表示されます。以下の点を変更した後、「Next」ボタンをクリックします。

    • 「GroupdId」に “project” を入力します。
    • 「ArtifactId」に “boot-npm-geb-sample” を入力します。

    f:id:ksby:20170721063040p:plain

  5. Gradle の設定をする画面が表示されます。何も変更せず「Next」ボタンをクリックします。

    f:id:ksby:20170721063319p:plain

  6. Project name、Project location を入力する画面が表示されます。以下の点を変更した後、「Finish」ボタンをクリックします。

    • 「Project location」に “C:\project-springboot\ksbysample-boot-miscellaneous\boot-npm-geb-sample” を入力します。

    f:id:ksby:20170721063522p:plain

  7. 「Directory Does Not Exist」ダイアログが表示されますので「OK」ボタンをクリックします。

    f:id:ksby:20170721064011p:plain

  8. IntelliJ IDEA のメイン画面が表示されて Project Tool Window に以下のディレクトリ構造が表示されます。

    ※src/main/java や src/test/java に以下の画像のように色が付いていることを確認します。

    f:id:ksby:20170721064632p:plain

build.gradle を変更する

  1. build.gradle を リンク先の内容 に変更します。

  2. コマンドプロンプトを起動して gradlew wrapper コマンドを実行し、gradle を 3.5 へバージョンアップします。

    f:id:ksby:20170721070921p:plain

  3. Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

Gradle の設定を変更する

  1. メイン画面のメニューから「File」->「Settings…」を選択して「Settings」ダイアログを表示します。

  2. 画面左上の検索フィールドに “Gradle” と入力して画面左側のツリーから「Build, Execution, Deployment」-「Build Tools」-「Gradle」を選択した後、以下の点を変更します。

    • 「Create directories for empty content roots automatically」をチェックします。
    • 画面右側の「Gradle JVM」で「Use Project JDK」を選択します。

    f:id:ksby:20170722000056p:plain

  3. 「OK」ボタンをクリックして「Settings」ダイアログを閉じます。

  4. 再度 Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。src/man/groovy, src/test/groovy ディレクトリが自動作成されます。

    f:id:ksby:20170722084510p:plain

Project language level を SDK default に変更する

  1. メイン画面のメニューから「File」->「Project Structure…」を選択して「Project Structure」ダイアログを表示します。

  2. 画面左側のツリーから「Project Settings」-「Project」を選択し、画面右側で「Project language level」の設定を「SDK default」に変更します。

    f:id:ksby:20170721210919p:plain

  3. 「OK」ボタンをクリックして「Project Structure」ダイアログを閉じます。

JUnit によるテスト実行時の spring.profiles.active を設定する

  1. メイン画面のメニューから「Run」->「Edit Configurations…」を選択します。

  2. 「Run/Debug Configuraitons」ダイアログが表示されます。左側のツリーで「Defaults」-「JUnit」を選択した後、右側の画面の「VM options」の末尾に -Dspring.profiles.active=unittest を追加します。

    f:id:ksby:20170721211628p:plain

  3. 「OK」ボタンをクリックして「Run/Debug Configuraitons」ダイアログを閉じます。

src/main/resources の下に static, templates ディレクトリを作成する

  1. Project Tool Window で src/main/resources の下に static, templates ディレクトリを作成します。

ksbysample-webapp-lending からファイルをコピーする

  1. 以下の画像の階層のパッケージを作成します。

    f:id:ksby:20170722085123p:plain

  2. ksbysample-webapp-lending から以下のファイルをコピーします。

    ※リンクのあるファイルはリンク先の内容に変更します。

    ■src/main/java

続く。。。

長くなったので2回に分けます。

ソースコード

.gitignore

# built application files
*.apk
*.ap_

# files for the dex VM
*.dex

# Java class files
*.class

# generated files
**/bin/
**/gen/

# Local configuration file (sdk path, etc)
local.properties

# Eclipse project files
.classpath
.project

# Proguard folder generated by Eclipse
**/proguard/

# Intellij project files
*.iml
*.ipr
*.iws
**/.idea/
**/out/

#Gradle
.gradletasknamecache
**/.gradle/
**/build/
**/bin/

build.gradle

group 'ksbysample'
version '1.5.4-RELEASE'

buildscript {
    ext {
        springBootVersion = '1.5.4.RELEASE'
    }
    repositories {
        mavenCentral()
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        // for Error Prone ( http://errorprone.info/ )
        classpath("net.ltgt.gradle:gradle-errorprone-plugin:0.0.10")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'groovy'
apply plugin: 'net.ltgt.errorprone'
apply plugin: 'checkstyle'
apply plugin: 'findbugs'
apply plugin: 'pmd'

sourceCompatibility = 1.8
targetCompatibility = 1.8

task wrapper(type: Wrapper) {
    gradleVersion = '3.5'
}

[compileJava, compileTestGroovy, compileTestJava]*.options*.compilerArgs = ['-Xlint:all,-options,-processing,-path']
compileJava.options.compilerArgs += ['-Xep:RemoveUnusedImports:WARN']

// for Doma 2
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

configurations {
    // for Doma 2
    domaGenRuntime
}

checkstyle {
    configFile = file("${rootProject.projectDir}/config/checkstyle/google_checks.xml")
    toolVersion = '8.0'
    sourceSets = [project.sourceSets.main]
}

findbugs {
    toolVersion = '3.0.1'
    sourceSets = [project.sourceSets.main]
    ignoreFailures = true
    effort = "max"
    excludeFilter = file("${rootProject.projectDir}/config/findbugs/findbugs-exclude.xml")
}

tasks.withType(FindBugs) {
    // Gradle 3.3以降 + FindBugs Gradle Plugin を組み合わせると、"The following errors occurred during analysis:"
    // の後に "Cannot open codebase filesystem:..." というメッセージが大量に出力されるので、以下の doFirst { ... }
    // のコードを入れることで出力されないようにする
    doFirst {
        def fc = classes
        if (fc == null) {
            return
        }
        fc.exclude '**/*.properties'
        fc.exclude '**/*.xml'
        fc.exclude '**/META-INF/**'
        fc.exclude '**/static/**'
        fc.exclude '**/templates/**'
        classes = files(fc.files)
    }
    reports {
        xml.enabled = false
        html.enabled = true
    }
}

pmd {
    toolVersion = "5.8.1"
    sourceSets = [project.sourceSets.main]
    ignoreFailures = true
    consoleOutput = true
    ruleSetFiles = rootProject.files("/config/pmd/pmd-project-rulesets.xml")
    ruleSets = []
}

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        // BOM は https://repo.spring.io/release/io/spring/platform/platform-bom/Brussels-SR3/
        // の下を見ること
        mavenBom("io.spring.platform:platform-bom:Brussels-SR3") {
            bomProperty 'guava.version', '22.0'
            bomProperty 'thymeleaf.version', '3.0.6.RELEASE'
            bomProperty 'thymeleaf-extras-springsecurity4.version', '3.0.2.RELEASE'
            bomProperty 'thymeleaf-layout-dialect.version', '2.2.2'
            bomProperty 'thymeleaf-extras-data-attribute.version', '2.0.1'
            bomProperty 'thymeleaf-extras-java8time.version', '3.0.0.RELEASE'
        }
    }
}

bootRepackage {
    mainClass = 'ksbysample.webapp.lending.Application'
}

dependencies {
    def spockVersion = "1.1-groovy-2.4"
    def domaVersion = "2.16.1"
    def lombokVersion = "1.16.18"
    def errorproneVersion = "2.0.15"
    def powermockVersion = "1.7.0"

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Appendix A. Dependency versions ( http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions ) 参照
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf") {
        exclude group: "org.codehaus.groovy", module: "groovy"
    }
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-freemarker")
    compile("org.springframework.boot:spring-boot-starter-mail")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-devtools")
    compile("org.springframework.session:spring-session")
    compile("com.google.guava:guava")
    compile("org.apache.commons:commons-lang3")
    compile("org.codehaus.janino:janino")
    compile("com.h2database:h2")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.springframework.security:spring-security-test")
    testCompile("org.yaml:snakeyaml")
    testCompile("org.mockito:mockito-core")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    compile("com.integralblue:log4jdbc-spring-boot-starter:1.0.1")
    testCompile("org.dbunit:dbunit:2.5.3")
    testCompile("com.icegreen:greenmail:1.5.5")
    testCompile("org.assertj:assertj-core:3.8.0")
    testCompile("org.spockframework:spock-core:${spockVersion}")
    testCompile("org.spockframework:spock-spring:${spockVersion}")
    testCompile("com.google.code.findbugs:jsr305:3.0.2")

    // for lombok
    compileOnly("org.projectlombok:lombok:${lombokVersion}")
    testCompileOnly("org.projectlombok:lombok:${lombokVersion}")

    // for Doma
    compile("org.seasar.doma:doma:${domaVersion}")
    domaGenRuntime("org.seasar.doma:doma-gen:${domaVersion}")

    // for Error Prone ( http://errorprone.info/ )
    errorprone("com.google.errorprone:error_prone_core:${errorproneVersion}")
    compileOnly("com.google.errorprone:error_prone_annotations:${errorproneVersion}")

    // PowerMock
    testCompile("org.powermock:powermock-module-junit4:${powermockVersion}")
    testCompile("org.powermock:powermock-api-mockito:${powermockVersion}")
}

bootRun {
    jvmArgs = ['-Dspring.profiles.active=develop']
}

test {
    jvmArgs = ['-Dspring.profiles.active=unittest']
}

// for Doma-Gen
task domaGen {
    doLast {
        // まず変更が必要なもの
        def rootPackageName = 'ksbysample.webapp.bootnpmgeb'
        def daoPackagePath = 'src/main/java/ksbysample/webapp/bootnpmgeb/dao'
        def dbUrl = 'jdbc:h2:mem:bootnpmgebdb'
        def dbUser = 'sa'
        def dbPassword = ''
        def tableNamePattern = '.*'
        // おそらく変更不要なもの
        def importOfComponentAndAutowiredDomaConfig = "${rootPackageName}.util.doma.ComponentAndAutowiredDomaConfig"
        def workDirPath = 'work'
        def workDaoDirPath = "${workDirPath}/dao"

        // 作業用ディレクトリを削除する
        clearDir("${workDirPath}")

        // 現在の Dao インターフェースのバックアップを取得する
        copy() {
            from "${daoPackagePath}"
            into "${workDaoDirPath}/org"
        }

        // Dao インターフェース、Entity クラスを生成する
        ant.taskdef(resource: 'domagentask.properties',
                classpath: configurations.domaGenRuntime.asPath)
        ant.gen(url: "${dbUrl}", user: "${dbUser}", password: "${dbPassword}", tableNamePattern: "${tableNamePattern}") {
            entityConfig(packageName: "${rootPackageName}.entity", useListener: false)
            daoConfig(packageName: "${rootPackageName}.dao")
            sqlConfig()
        }

        // 生成された Dao インターフェースを作業用ディレクトリにコピーし、
        // @ComponentAndAutowiredDomaConfig アノテーションを付加する
        copy() {
            from "${daoPackagePath}"
            into "${workDaoDirPath}/replace"
            filter {
                line ->
                    line.replaceAll('import org.seasar.doma.Dao;', "import ${importOfComponentAndAutowiredDomaConfig};\nimport org.seasar.doma.Dao;")
                            .replaceAll('@Dao', '@Dao\n@ComponentAndAutowiredDomaConfig')
            }
        }

        // @ComponentAndAutowiredDomaConfig アノテーションを付加した Dao インターフェースを
        // dao パッケージへ戻す
        copy() {
            from "${workDaoDirPath}/replace"
            into "${daoPackagePath}"
        }

        // 元々 dao パッケージ内にあったファイルを元に戻す
        copy() {
            from "${workDaoDirPath}/org"
            into "${daoPackagePath}"
        }

        // 作業用ディレクトリを削除する
        clearDir("${workDirPath}")
    }
}

/* -----------------------------------------------------------------------------
 * メソッド定義部
 ---------------------------------------------------------------------------- */

void clearDir(String dirPath) {
    delete dirPath
}

Application.java

package ksbysample.webapp.bootnpmgeb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * ???
 */
@ImportResource("classpath:applicationContext-${spring.profiles.active}.xml")
@SpringBootApplication(exclude = {JpaRepositoriesAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
@ComponentScan("ksbysample")
@EnableSpringHttpSession
public class Application {

    ..........
  • @EnableRedisHttpSession@EnableSpringHttpSession に変更します。
  • @EnableGlobalMethodSecurity(prePostEnabled = true) を削除します。

ControllerAndEventNameLogger.java

    @Around(value = "execution(* ksbysample.webapp.bootnpmgeb.web..*.*(..)) && @annotation(loggingEventName)"
            , argNames = "pjp, loggingEventName")
    public Object logginControllerAndEventName(ProceedingJoinPoint pjp, LoggingEventName loggingEventName)
            throws Throwable {
        ..........
  • execution(* ksbysample.webapp.lending.web..*.*(..))execution(* ksbysample.webapp.bootnpmgeb.web..*.*(..)) に変更します。

MethodLogger.java

    @SuppressWarnings({"PMD.UnusedPrivateMethod"})
    @Pointcut("execution(* ksbysample.webapp.bootnpmgeb.web..*.*(..))"
            + "&& @within(org.springframework.stereotype.Controller)")
    private void pointcutControllerMethod() {
    }

    @SuppressWarnings({"PMD.UnusedPrivateMethod"})
    @Pointcut("execution(* ksbysample.webapp.bootnpmgeb.service..*.*(..))"
            + "&& @within(org.springframework.stereotype.Service)")
    private void pointcutServiceMethod() {
    }
  • execution(* ksbysample.webapp.lending.web..*.*(..))execution(* ksbysample.webapp.bootnpmgeb.web..*.*(..)) に変更します。
  • execution(* ksbysample.webapp.lending.service..*.*(..))execution(* ksbysample.webapp.bootnpmgeb.service..*.*(..)) に変更します。

RequestAndResponseLogger.java

    private static final String POINTCUT_ALL_CLASS_AND_METHOD_UNDER_APPLICATION_PACKAGE
            = "execution(* ksbysample.webapp.bootnpmgeb..*.*(..))";
  • execution(* ksbysample.webapp.lending..*.*(..))execution(* ksbysample.webapp.bootnpmgeb..*.*(..)) に変更します。

ApplicationConfig.java

package ksbysample.webapp.bootnpmgeb.config;

import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import javax.sql.DataSource;

/**
 * ???
 */
@Configuration
public class ApplicationConfig {

    private final MessageSource messageSource;

    /**
     * @param messageSource {@link MessageSource} bean
     */
    public ApplicationConfig(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    /**
     * Controller クラスで直接 {@link Validator} を呼び出すために Bean として定義している
     * また Hibernate Validator のメッセージを ValidationMessages.properties ではなく
     * messages.properties に記述できるようにするためにも使用している
     *
     * @return new {@link LocalValidatorFactoryBean}
     */
    @Bean
    public Validator mvcValidator() {
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
        localValidatorFactoryBean.setValidationMessageSource(this.messageSource);
        return localValidatorFactoryBean;
    }

    /**
     * @return Tomcat JDBC Connection Pool の DataSource オブジェクト
     */
    @Bean
    @ConfigurationProperties("spring.datasource.tomcat")
    public DataSource dataSource() {
        return DataSourceBuilder.create()
                .type(org.apache.tomcat.jdbc.pool.DataSource.class)
                .build();
    }

}
  • 以下の Bean のみ残します。
    • mvcValidator Bean
    • dataSource Bean

WebSecurityConfig.java

package ksbysample.webapp.bootnpmgeb.config;

import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * ???
 */
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 認証の対象外にしたいURLがある場合には、以下のような記述を追加します
                // 複数URLがある場合はantMatchersメソッドにカンマ区切りで対象URLを複数列挙します
                // .antMatchers("/country/**").permitAll()
                //
                // この Web アプリケーションでは Spring Security を CSRF対策で使用したいだけなので、
                // 全ての URL を認証の対象外にする
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated();
    }

}

履歴

2017/07/22
初版発行。