へっぽこITパパのブログ

へっぽこITエンジニアの雑記です

Java8の@FunctionalInterfaceを使う

本家はこちら

関数インターフェースのPredicateとかFunctionがあるのであんまり使わないけど、引数とか3つ以上のものはないのでつくる必要がある。

基本的に抽象メソッドを1つしかもっていなければOK。defaultメソッドはいくつもっていてもOKです。(ラムダ式のときに何を使うかわからなくなっちゃうからかなと)

@FunctionalInterface
interface SampleInterface {
      boolean filter(String a);
}

こんな感じでカンタンに作れる

使うときはこんな感じでカンタンに使える。

SampleInterfacle sampleFilter = s -> "a".equals(s);
sampleFilter.filter("a");

なんかもうカンタン過ぎますね

ちなみに引数3つは、こんな感じ

@FunctionalInterface
interface SampleInterface {
      boolean filter(String a, String b, String c);
}

SampleInterfacle sampleFilter = (s, t , u)  -> "a".equals(s);
sampleFilter.filter("a","b","c");

こんな感じで使える。なんかもうカンタン過ぎますね。みんなもっと使いましょう!

Javaの関数インターフェース再入門

本家はこちら

Javaのよく使ってる関数インターフェース群をまとめようかとおもう。 現場ごとに説明のためのドキュメントを書くのがしんどくなってきたからというのが本音かも。

predicate

断言するって意味。戻り値はBoolean。 引数1つとって

Predicate<String> matchA = s -> s.matches(".*a.*");  // 'a'があればtrue
Predicate<String> matchB = s -> s.matches(".*b.*"); // 'b'があればtrue
Predicate<String> noA = matchA.negate(); // 'a'があったらfalse

// 合成
Predicate<String> ab = matchA.and(matchB); // 'a'と'b'があったらtrue
Predicate<String> ab = matchA.or(matchB); // 'a'または'b'があったらtrue

実行はtest(T)でできる。カンタン。streamのfilterとかで使える。テストもカンタン。

matchA.test("a");

これで実行できる。

ちなみに2つ引数とることもできる

BiPredicate<String,String> matchAB = (s,t) -> s.matches(".*a.*") && t.matches(".*a.*");

suppplier

供給するって意味

引数なし、戻り値あり。引数ないけど、それ単体で何かが発生する。 なにかと組み合わせてデータ流す感じかな

Supplier<String> supplier = () -> {
     SecureRandom random = new SecureRandom();
     return String.valueOf(random.nextInt());
};

ランダムintを返却する

supplier.get()

consumer

消費するって意味。 実際に引数とって、そのあと処理する。戻り値なし。

下記のような感じ

Consumer<String> show = s -> System.out.println(s);

下記のように使う

s.accept("s");

2つ引数とることも可能

BiConsumer<String,Integer> biShow = (s,i) -> {
    System.out.println(s);
    System.out.println(i);
};

Function

機能として使える関数インターフェース

引数と戻り値を持てる。 引数2つまで持てるものもある。(BiFunction)

下記のように定義できる

Function<Integer,String> exchangeString = t -> String.valueOf(t);
BiFunction<Integer,Integer,String> biExchange = (t,u) -> String.valueOf(t + u);

下記のように使える

exchangeString.apply(1);
biExchange.apply(1,2);

springboot devtools のauto restart を疑似高速化する

本家はこちら

きっかけ

springbootとintellijなどIDE使ってると、コードの補完はめちゃされるし、Javaって便利だなーとか先人の方たちありがとう!って気持ちでコード書いてるんですけど、コード変更時に変更内容がHotswapで反映されるならいいんですけど、そうじゃないとrestartがかかって、プロジェクトが少し大きくなってくると15秒くらいまたされるのでもっと短縮できないかなーって思いました。

やったこと

DIの対象になるクラスを全部遅延ロード(lazyload)してしまえばいいかと思い、それを実装してみました。 本番環境でやるのはリスキーなので、local環境のみで行うようにしています。

@Profile("local")
@Configuration
public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            beanFactory.getBeanDefinition(beanName).setLazyInit(true);
        }
    }
}

これで15秒から5秒後半くらいになりました!最高

Started WebApplication in 14.565 seconds (JVM running for 16.236)
↓
Started WebApplication in 5.759 seconds (JVM running for 6.871)

リスク

初期化が遅らせているので、初期化順でなにかを行う処理を挟み込んでたりしたら影響がありますが、今日そんなコード書いてる人なんてあんまりいないだろうし、local環境だけなんでまぁ影響ないかなと。

spring-context-indexerと組み合わせているとちょっと動かないケースがあってハマることがあるので、オフにできるならオフにすること。 まだなんでかはわかってないです。

まとめ

これでサクサク開発できるので、しばらく試してみようかと思います

springboot devtools のauto restart を疑似高速化する

本家はこちら

きっかけ

springbootとintellijなどIDE使ってると、コードの補完はめちゃされるし、Javaって便利だなーとか先人の方たちありがとう!って気持ちでコード書いてるんですけど、コード変更時に変更内容がHotswapで反映されるならいいんですけど、そうじゃないとrestartがかかって、プロジェクトが少し大きくなってくると15秒くらいまたされるのでもっと短縮できないかなーって思いました。

やったこと

DIの対象になるクラスを全部遅延ロード(lazyload)してしまえばいいかと思い、それを実装してみました。 本番環境でやるのはリスキーなので、local環境のみで行うようにしています。

@Profile("local")
@Configuration
public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            beanFactory.getBeanDefinition(beanName).setLazyInit(true);
        }
    }
}

これで15秒から5秒後半くらいになりました!最高

Started WebApplication in 14.565 seconds (JVM running for 16.236)
↓
Started WebApplication in 5.759 seconds (JVM running for 6.871)

リスク

初期化が遅らせているので、初期化順でなにかを行う処理を挟み込んでたりしたら影響がありますが、今日そんなコード書いてる人なんてあんまりいないだろうし、local環境だけなんでまぁ影響ないかなと。

spring-context-indexerと組み合わせているとちょっと動かないケースがあってハマることがあるので、オフにできるならオフにすること。 まだなんでかはわかってないです。

まとめ

これでサクサク開発できるので、しばらく試してみようかと思います

mybatisでカスタムクエリをxmlで書くのは辛いので、freemarker templateにする

本家はこちら

mybatisでカスタムでクエリ書くときに、標準だとxml形式で書かなきゃいけなくて、結構しんどい。

カンタンに書くと下のような感じ

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jp.sample.MapperCustom">
  <delete id="deleteAll" parameterType="map">
    delete from ${table}
  </delete>

  <insert id="bulkInsert" parameterType="map">
    insert into ${table}
    (code)
    values
    <foreach item="v" collection="list" separator=",">
      (#{v})
    </foreach>
  </insert>
</mapper> 

つらいポイント - xml形式なので、余計なものが先頭に来てつらい - 繰り返しがつらい

なんかたくさんあるかと思いきや、あんまり挙げられなかったのだが、とにかくXMLというのが辛い。不等号もxml形式なので、CDATAを利用してかかないければならないのとか、そうしないなら、エスケープしないといけないのが辛かった。

Groovyとかkotlinだとmulti line strings が使用できる話だが、Groovyだと大きいJarを入れないとだし、kotlinだとnull値が。。。みたいな問題に悩まされるんじゃないかという気がしたので、mybatis freemarker templateを使う方法を選択した。

使い方

ライブラリの追加

gradleのdependenciesに下記を追加

compile("org.mybatis.scripting:mybatis-freemarker:$mybatisFreemakerVersion")

freemarkerテンプレートを置き場指定

src/main/resources 直下にmybatis-freemarker.propertiesのファイルを設置する 内容は、mybatis-freemarkerを使う際にどのディレクトリを参照するかなので、classpath内の任意のディレクトリパスを記述する。

basePackage=mybatis/sql

freemarker templateにクエリを書く

src/main/resources/mybatis/sql/sample.ftl

select
  name
from
  sample
where
  id in (${ids?join(", ")})
  and
  name = <@p name="n">

(${ids?join(", ")})とか<@p name="n"> とかのfreemarker templateでかけるのは嬉しい。xml形式でない分可読性も高くなると思うし、使い勝手がよくなる。

mapperインターフェース内にfreemarker templateの呼び出しを書く

@Lang(FreeMarkerLanguageDriver.class)
@Select("sample.ftl")
List<Name> findNamesByIds(@Param("n") String name,@Param("ids") List<Integer> ids);

以上のように書けば、実行できる。なんかかなりスッキリしたよい感じになる。デメリットとしては、1

mybatisでカスタムクエリをxmlで書くのは辛いので、freemarker templateにする

本家はこちら

mybatisでカスタムでクエリ書くときに、標準だとxml形式で書かなきゃいけなくて、結構しんどい。

カンタンに書くと下のような感じ

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jp.sample.MapperCustom">
  <delete id="deleteAll" parameterType="map">
    delete from ${table}
  </delete>

  <insert id="bulkInsert" parameterType="map">
    insert into ${table}
    (code)
    values
    <foreach item="v" collection="list" separator=",">
      (#{v})
    </foreach>
  </insert>
</mapper> 

つらいポイント - xml形式なので、余計なものが先頭に来てつらい - 繰り返しがつらい

なんかたくさんあるかと思いきや、あんまり挙げられなかったのだが、とにかくXMLというのが辛い。不等号もxml形式なので、CDATAを利用してかかないければならないのとか、そうしないなら、エスケープしないといけないのが辛かった。

Groovyとかkotlinだとmulti line strings が使用できる話だが、Groovyだと大きいJarを入れないとだし、kotlinだとnull値が。。。みたいな問題に悩まされるんじゃないかという気がしたので、mybatis freemarker templateを使う方法を選択した。

使い方

ライブラリの追加

gradleのdependenciesに下記を追加

compile("org.mybatis.scripting:mybatis-freemarker:$mybatisFreemakerVersion")

freemarkerテンプレートを置き場指定

src/main/resources 直下にmybatis-freemarker.propertiesのファイルを設置する 内容は、mybatis-freemarkerを使う際にどのディレクトリを参照するかなので、classpath内の任意のディレクトリパスを記述する。

basePackage=mybatis/sql

freemarker templateにクエリを書く

src/main/resources/mybatis/sql/sample.ftl

select
  name
from
  sample
where
  id in (${ids?join(", ")})
  and
  name = <@p name="n">

mapperインターフェース内にfreemarker templateの呼び出しを書く

@Lang(FreeMarkerLanguageDriver.class)
@Select("sample.ftl")
List<Name> findNamesByIds(@Param("n") String name,@Param("ids") List<Integer> ids);

以上のように書けば、実行できる。なんかかなりスッキリしたよい感じになる。デメリットとしては、1SQLで1ファイル使うので結構ファイルの数は増える。ディレクトリ等を使えば、スッキリはすると思うので、大したデメリットにはならないと思う。

SpringReactive(webflux)でAOPする際に@EnableReactiveMethodSecurityの@hasAuthoritiesを使うとうまくいかないのを回避する

本家はこちら

下記のようにcontrollerでのリクエストのログを認証情報付きで出力しようとする。

    @AfterReturning(value = "execution(* xxx.xxx.controller..*.*(..))")
    public void execNormalControllerMethod(JoinPoint jp) {
            /// ログ出力処理
    }

しかし、下記のように、@PreAuthorizeするとエラーになる。

@RestController
@RequestMapping("sample")
public class SampleController {
       @RequestMapping
       @PreAuthorize("hasAuthority('ADMIN_ROLE')")
        public Mono index(@AuthenticationPrincipal UserDetails useDetails) {
             return Mono.empty();
        }
}

エラーは下記

No MethodInvocation found: Check that an AOP invocation is in progress, and that the ExposeInvocationInterceptor is upfront in the interceptor chain. Specifically, note that advices with order HIGHEST_PRECEDENCE will execute before ExposeInvocationInterceptor!

下記にて対処可能 @EnableReactiveMethodSecurityのOrderを最上位にすれば動作する

@EnableReactiveMethodSecurity(order = Ordered.HIGHEST_PRECEDENCE)

以上、設定して再起動すればOK