かんがるーさんの日記

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

共有ライブラリを管理するために Sonatype の Nexus Repository Manager OSS を使用する ( その19 )( Spring Framework に依存するライブラリを作成する2 )

概要

共有ライブラリを管理するために Sonatype の Nexus Repository Manager OSS を使用する ( その18 )( Spring Framework に依存するライブラリを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • ksbysample-webapp-lending から RequestAndResponseLogger.java を持ってきて auto-configuration 関連の実装まで進めます。

参照したサイト・書籍

  1. Pro Spring Boot

    Pro Spring Boot

    Pro Spring Boot

    • auto-configuration プロジェクトのサンプルとして Chapter 14 の「The journal-spring-boot-autoconfigure Project」を参考にしました。
  2. Spring Framework Reference Documentation - 12. Spring AOP APIs - 12.3.2 Advice types in Spring
    http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop-api.html#aop-api-advice-types

    • MethodInterceptor インターフェースを実装しようとすると import 候補org.aopalliance.intercept.MethodInterceptororg.springframework.cglib.proxy.MethodInterceptor の2つが出て来るのですが、どちらを使用すればよいか分からなかったので、このページを参照しました。invoke メソッドが書かれていたので org.aopalliance.intercept.MethodInterceptor を使用します。
  3. Spring Boot Reference Guide - 43. Creating your own auto-configuration
    http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html

  4. Spring Boot Reference Guide - 64. Spring Boot Gradle plugin - 64.6 Repackage configuration
    http://docs.spring.io/spring-boot/docs/current/reference/html/build-tool-plugins-gradle-plugin.html#build-tool-plugins-gradle-repackage-configuration

目次

  1. ksbysample-webapp-lending から RequestAndResponseLogger.java を持ってくる
  2. AutoConfiguration クラスを作成する
  3. META-INF/spring.factories を作成する
  4. build してみる
  5. ksbysample-library-depend-spring-1.0.0-RELEASE.jar の中を見てみる

手順

ksbysample-webapp-lending から RequestAndResponseLogger.java を持ってくる

  1. src/main/java の下に ksbysample.library.dependspring.intercepter パッケージを作成します。

  2. ksbysample-webapp-lending の RequestAndResponseLogger.java をコピーして src/main/java/ksbysample/library/dependspring/intercepter の下にペーストします。

  3. 使用するライブラリがないので build.gradle を リンク先のその1の内容 に変更します。変更後、Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして反映します。

  4. RequestAndResponseLogger.java の以下の点を変更します。リンク先の内容 になります。

    • クラスに付与されている @Aspect, @Component アノテーションは削除します。
    • MethodInterceptor インターフェースを実装し、invoke メソッドをオーバーライドします。
    • logginRequestAndResponse メソッドの中身は MethodInterceptor インターフェースの invoke メソッドへ移動します。

AutoConfiguration クラスを作成する

Spring Boot の spring-boot-autoconfigure プロジェクト を見ると AutoConfiguration 用の Bean を作成するクラスはクラス名の末尾に AutoConfiguration が付けられているようなので、RequestAndResponseLoggerAutoConfiguration.java を作成します。

  1. src/main/java/ksbysample/library/dependspring の下に config パッケージを作成します。

  2. src/main/java/ksbysample/library/dependspring/config の下に RequestAndResponseLoggerAutoConfiguration.java を作成し、リンク先の内容 を記述します。

META-INF/spring.factories を作成する

  1. src/main/resources の下に META-INF ディレクトリを作成します。

  2. src/main/resources/META-INF の下に spring.factories を作成し、リンク先の内容 を記述します。

build してみる

  1. clean タスク実行 → Rebuild Project 実行をした後、build タスクを実行します。

    f:id:ksby:20161225102120p:plain

    "FAILURE" の文字が出力され失敗しました。

  2. IntelliJ IDEA のコンソールに出力された内容ではエラーの原因が分からないので、コマンドプロンプトから --stacktrace オプションを付けて build タスクを実行してみます。

    f:id:ksby:20161225102613p:plain f:id:ksby:20161225102732p:plain

    出力されたログを見た感じでは、以下の原因のようです。

    • bootRepackage タスクで失敗している。
    • main クラスがないため失敗している。
  3. main クラスは作成しないのでどうすればよいか調べてみたところ、64.6 Repackage configurationenabled という設定項目を見つけました。これを false に設定すれば bootRepackage タスクは実行されなくなるようです。

  4. build.gradle を リンク先のその2の内容 に変更します。変更後、Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして反映します。

  5. 再度 clean タスク実行 → Rebuild Project 実行をした後、build タスクを実行します。今度は "BUILD SUCCESSFUL" が出力されて成功しました。

    f:id:ksby:20161225103711p:plain

  6. jar ファイルが作成されていることも確認できました。

    f:id:ksby:20161225104015p:plain

ksbysample-library-depend-spring-1.0.0-RELEASE.jar の中を見てみる

ksbysample-library-depend-spring-1.0.0-RELEASE.jar を解凍してみると以下の構成でした。

ksbysample-library-depend-spring-1.0.0-RELEASE.jar
├ generated
├ ksbysample
│ └ library
│    └ dependspring
│       ├ config
│       │ └ RequestAndResponseLoggerAutoConfiguration.class
│       └ intercepter
│          └ RequestAndResponseLogger.class
├ META-INF
│ ├ MANIFEST.MF
│ └ spring.factories
└ .gitkeep

ここまでの内容を commit します。

ソースコード

ksbysample-library-depend-spring/build.gradle

■その1

dependencies {
    // 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-autoconfigure")
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.aspectj:aspectjweaver")
    testCompile("org.springframework.boot:spring-boot-starter-test")

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    compile("org.apache.commons:commons-lang3:3.5")
    compile("com.google.guava:guava:20.0")
    testCompile("org.assertj:assertj-core:3.6.1")
}
  • 以下の記述を追加します。
    • compile("org.springframework.boot:spring-boot-starter-web")
    • compile("org.aspectj:aspectjweaver")
    • testCompile("org.springframework.boot:spring-boot-starter-test")
    • compile("org.apache.commons:commons-lang3:3.5")
    • compile("com.google.guava:guava:20.0")
    • testCompile("org.assertj:assertj-core:3.6.1")
  • あとでテストも書くので AssertJ も追加しておきます。

■その2

dependencyManagement {
    imports {
        mavenBom 'io.spring.platform:platform-bom:2.0.5.RELEASE'
    }
}

bootRepackage {
    enabled = false
}

dependencies {
    ..........
}
  • bootRepackage { enabled = false } を追加します。

RequestAndResponseLogger.java

package ksbysample.library.dependspring.intercepter;

import com.google.common.collect.Iterators;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.StreamSupport;

public class RequestAndResponseLogger implements MethodInterceptor {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final String LOG_REQUEST_INFO = "[req][info  ] ";
    private static final String LOG_REQUEST_HEADER = "[req][header] ";
    private static final String LOG_REQUEST_COOKIE = "[req][cookie] ";
    private static final String LOG_REQUEST_PARAMETER = "[req][param ] ";

    private static final String LOG_RESPONSE_INFO = "[res][info  ] ";
    private static final String LOG_RESPONSE_HEADER = "[res][header] ";

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        loggingRequest(request);
        Object ret = invocation.proceed();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        loggingResponse(response);

        return ret;
    }

    private void loggingRequest(HttpServletRequest request) {
        loggingRequestInfo(request);
        loggingRequestHeaders(request);
        loggingRequestCookies(request);
        loggingRequestParameters(request);
    }

    ..........

RequestAndResponseLoggerAutoConfiguration.java

package ksbysample.library.dependspring.config;

import ksbysample.library.dependspring.intercepter.RequestAndResponseLogger;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RequestAndResponseLoggerAutoConfiguration {

    private static final String REQUESTMAPPING_EXPRESSION = "@annotation(org.springframework.web.bind.annotation.RequestMapping)";

    @Value("${ksbysample.library.request-and-response-logger.execution:}")
    private String execution;

    @Bean
    @ConditionalOnWebApplication
    public Advisor requestAndResponseLoggerAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        String expression = StringUtils.EMPTY;
        if (StringUtils.isNotBlank(this.execution)) {
            expression = this.execution + " && ";
        }
        expression += REQUESTMAPPING_EXPRESSION;
        pointcut.setExpression(expression);
        return new DefaultPointcutAdvisor(pointcut, new RequestAndResponseLogger());
    }

}
  • デフォルトでは @RequestMapping アノテーションが付与された全てのメソッドに RequestAndResponseLogger の invoke メソッドの処理を追加します。
  • ただし適用するパッケージを制限できるよう application.properties に ksbysample.library.request-and-response-logger.execution=execution(* ksbysample.webapp.lending.web..*.*(..)) のように設定を追加すれば反映されるようにします。

META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ksbysample.library.dependspring.config.RequestAndResponseLoggerAutoConfiguration

履歴

2016/12/25
初版発行。