2016年1月30日

Spring @Autowired フィールドを持つクラスを手動で生成

初めて Spring を触って、@Autowired の便利さに感動したので・・・

前置き

@Autowired について簡単に説明すると、自動で初期化されるフィールドの目印。
(フィールドだけでなくセッターメソッドに対しても使用可能)
つまり、コンストラクタやフィールドの右辺に、フィールドの初期化処理を書かなくてよくなる。
古い Spring では初期化設定を xml に書く必要があったが、今はアノテーション(C# での カスタム属性)のみで完結できるようになっている。

[@Autowired サンプル]

@Controller
public class HogeController {

    @Autowired
    private HogeService service;

    // 以降、URL 割り当てメソッド
}

@Service
public class HogeService {

    @Autowired
    private HogeDao dao;

    // 以降、サービスメソッド
}

@Component
public HogeDao {}
HogeController にマップされた URL アクセス時、HogeController インスタンスがフレームワークによって生成され、@Autowired フィールドの service にも HogeService インスタンスが割り当てられる。HogeService の dao フィールドも @Autowired なので同様。
DI 目的なら HogeService と HogeDao のインターフェースを用意した方がいいけど、ここでは省略。
@Autowired 可能なクラスは、@Component 派生アノテーション付きで、@ComponentScan 対象である必要がある。(もしくは xml に記述したクラス)

@Autowired 持ちクラスを手動で生成

上記サンプルのような使い方だと、@Autowired 持ちクラスが初期化されるためには、@Autowired フィールドとして生成されるか、@Controller のようにフレームワークから自動生成される必要がある。
つまり、フィールドとしてしか作れない。(@Controller もフレームワークのフィールドみたいなもの)

@Autowired 持ちクラスをフィールドじゃなく、ローカル変数として欲しい(コードで手動生成したい)ケースが稀にあると思う。
java - In Spring, can I autowire new beans from inside an autowired bean? - Stack Overflow の createAutowiredObject で簡単に生成できる。
@Component 付クラスである必要もない。

[createAutowiredObject 使用サンプル]

@Controller
public class HogeController {

    @Autowired
    private AutowireCapableBeanFactory factory;

    private <T> T createAutowiredObject(Class<T> c){
        return factory.createBean(c);
    }

    public void createAutowiredTest(){
        HogeComponent component = createAutowiredObject(HogeComponent.class);
        // HogeComponent のフィールドが初期化されていることを確認
        System.out.println(component.sub);
    }
}

public HogeComponent {
    @Autowired
    public HogeComponentSub sub;
}

@Component
public HogeComponentSub {}

使いどころ

@Component 付きクラスは、デフォルトでシングルトンである。
@Scope("prototype") を付けることで非シングルトンにすることも可能だが、親クラスも同じ @Scope を設定してやる必要がある。(シングルトンのフィールドは同じくシングルトンになる)

親クラスがシングルトンで、その内部で非シングルトンの @Autowired 持ちクラスが欲しい場合、createAutowiredObject のようなメソッドがあるといいかもしれない。
親クラスの @Scope を変えても可能だが、変えられないケースもあると思うので。

おまけ

@Scope の効果を Spring MVC で視覚的に確認できるもの

2016年1月13日

Java Stream API とチェック例外(検査例外)の相性が悪い件

久々に Java を触ることがあって、Java 8 だったので Stream API を使ってみたところ・・・
色々言いたいことはあるけど、一番の問題はチェック例外(検査例外)だと感じた。
チェック例外とは、簡単に言うと catch または throws してないとコンパイラに怒られる例外のこと。
Java 以外の言語ではほとんど採用されてないレア機能。

で、何が問題かというと、下記のようなケースでコンパイルできない。

/**
 * source にある valueType 型を返すメソッドを実行して、結果をリストで返す。
 */
@SuppressWarnings("unchecked")
public static <T> List<T> getValues(Object source, final Class<T> valueType){
    return Arrays.stream(source.getClass().getDeclaredMethods())
        .filter(m -> m.getReturnType().equals(valueType))
        .map(m -> (T)m.invoke(source)) // この行でコンパイルエラー
        .collect(Collectors.toList());
}

理由は、Stream.map がチェック例外を投げるラムダ式を引数に取れないため。

※ チェック例外を考慮した FunctionalInterface が標準 API に用意されてないこと(2016/1/12 時点)を考慮すると、ラムダ式にチェック例外を混ぜてほしくないのだろう。しかし、標準 API の中でチェック例外を投げるメソッド(上記の Method.invoke 等)は結構あるため、使う側としては混ぜざるを得ない。

検査例外を実行時例外にラップ

ラムダ式内でチェック例外をキャッチ、実行時例外にラップする。

@SuppressWarnings("unchecked")
public static <T> List<T> getValuesWrap(Object source, final Class<T> valueType){
    return Arrays.stream(source.getClass().getDeclaredMethods())
        .filter(m -> m.getReturnType().equals(valueType))
        .map(m -> {
            try{
                return (T)m.invoke(source);
            } catch( IllegalAccessException | InvocationTargetException e ){
                throw new RuntimeException(e);
            }
        })
        .collect(Collectors.toList());
}

この方法だとチェック例外を殺してしまうが、標準 API のみだとどうしようもない。
また、ラムダ式に try-catch が入り込んでソースが汚くなっている。

チェック例外を投げるラムダ式から標準 API のラムダ式に変換

lambda - How can I throw CHECKED exceptions from inside Java 8 streams? - Stack Overflow にある LambdaExceptionUtil を使う。(もしくは自作)

@SuppressWarnings("unchecked")
public static <T> List<T> getValuesRethrow(Object source, final Class<T> valueType){
    return Arrays.stream(source.getClass().getDeclaredMethods())
        .filter(m -> m.getReturnType().equals(valueType))
        .map(rethrowFunction(m ->  (T)m.invoke(source)))
        .collect(Collectors.toList());
}

rethrowFunction で、チェック例外を考慮した Function_WithExceptions から 標準 API の Function に変換している。
チェック例外を殺すことには変わりないが、ソースの見た目は最初の方法よりましになっている。
チェック例外は不要、という場合はこの方法が無難。
Stream API だけでなく、他のチェック例外無しラムダ式を引数にするメソッドにも利用できる。

後、面白いのが下記のような方法で実行時例外にラップせずにチェック例外を黙らせていること
Java の割と知られているハックらしい。

@SuppressWarnings("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
    throw (E)exception;
}

チェック例外を投げるラムダ式を引数に取れる Stream API を使う

JeffreyFalgout/ThrowingStream · GitHub を使う。(もしくは自作)

@SuppressWarnings("unchecked")
public static <T> List<T> getValuesThrowing(Object source, final Class<T> valueType) throws Exception {
    return ThrowingStream.of(Arrays.stream(source.getClass().getDeclaredMethods()), Exception.class)
        .filter(m -> m.getReturnType().equals(valueType))
        .map(m ->  (T)m.invoke(source))
        .collect(Collectors.toList());
}

ThrowingStream.of でチェック例外を考慮したメソッドを備える Strem を作成している。
現時点(2016/1/12)で、ThrowingStream.of に指定できる例外は 1 つだけなので、複数のチェック例外がある場合は継承元を指定するしかない。
指定した例外を ThrowingStream.collect で投げてくれるので、一応チェック例外は殺してない。

愚痴

この問題については、Java 開発元がどうにかすべきだと思う。
上記のようなメソッド・クラスを用意するか、いっそコンパイルオプションにチェック例外無効を用意するとか・・・

いずれ解決方法が提供されることを祈りつつ、可能ならベター Java を使っていきたい・・・