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 を使っていきたい・・・

2015年12月5日

Firefox のボタン(<button>, <input type=button>)の大きさ(幅・高さ)が違う場合の対処法

width や height を指定せず、padding や内部の文字列で大きさを確保するボタン(<button> や <input type="button">)では、IE/Chrome と比べて Firefox のボタンサイズが大きくなる。
文字の幅は、同じフォントを指定してもブラウザによって異なるのが普通だが(レンダリングエンジンが異なるため)、Firefox のみ明らかに大きくなる。

[問題になる CSS の例]

button {
    padding: 5px;
    border: 1px solid lime;
    letter-spacing: 0px;

    /* ブラウザ間の差が少ないメイリオを使用 */
    font: 15px/22px Meiryo;
    cursor: pointer;
}
[プレビュー]
[開発者ツールの画面キャプチャ]
IE Chrome Firefox (Firebug)
IE button inspection Chrome button inspection Firefox button inspection
※ Firefox のボタンサイズが、縦方向 2px、横方向 6px 大きい。

原因と対処法

困った時の stackoverflow。
Firefox はボタンフォーカス時の点線の幅と padding が確保されているため、他のブラウザより大きくなるのだとか。
これ → Firefox focused button

[ボタンフォーカスの点線を消す CSS]

button::-moz-focus-inner {
    padding: 0;
    border: 0;
}
/* input[type=button] なども同様に指定可能 */
[プレビュー]
[Firebug の画面キャプチャ]
Firefox button inspection

ボタンフォーカスの点線を消したくない場合、margin を工夫することで大きさを合わせることが可能。

button::-moz-focus-inner {
    padding: 0;
    margin: -1px; /* 点線の幅分のマイナスマージン */
}

ただし、最初にも書いたが、ブラウザによる文字の幅の差はどうすることもできないので、ボタンの大きさ完全に一致させたい場合は、button の width を指定するしかない。

検証環境

  • Windows 7 64bit
  • Internet Explorer 11
  • Firefox 42.0
  • Chrome 48.0.2564.8

2015年11月19日

<iframe> を動的生成する際の注意点

javascript で src 無しの <iframe> とその中身を構築した際、はまったことがあったのでメモ。

  • 動的に追加した <iframe> の中身をいじる際は、
    iframe.contentWindow.documentdocument.write() を使う
    または
    <iframe> のロード完了を待つ。(<iframe> 自身に onload イベントがある)
  • <iframe> に onload イベントを設置する際は、DOM への追加前に行う。
    DOM に <iframe> を追加した瞬間、onload イベントが処理されるブラウザがあるため (Chrome)

[サンプルコード]

// 単純化のため、jquery 使用

// 良い例
// IE:OK / Firefox:OK / Chrome:OK
$(function(){
    $('<iframe>').load(function(){
        // ロード完了後、DOM を変更できる。
        var ibody = this.contentWindow.document.body;
        $(ibody).append('<div>ブロック要素</div>');
    }).appendTo('body');
    // イベント設置後に DOM へ追加
});


// 悪い例(1)
// IE:OK / Firefox:NG / Chrome:OK
// Chrome は iframe の DOM 追加後、即時ロード完了する模様
// IE は iframe のロード完了を待たなくても大丈夫っぽい??
$(function(){
    var iframe = $('<iframe>').appendTo('body');
    var ibody = iframe[0].contentWindow.document.body;
    $(ibody).append('<div>ブロック要素</div>');
});


// 悪い例(2)
// IE:OK / Firefox:OK / Chrome:NG
// Chrome は iframe が即時ロード完了するので、DOM 追加後に load イベントを付けても無意味
$(function(){
    var iframe = $('<iframe>').appendTo('body');
    iframe.load(function(){
        var ibody = this.contentWindow.document.body;
        $(ibody).append('<div>ブロック要素</div>');
    });
});
[環境]
  • Windows 7 64bit
  • Internet Explorer 11
  • Firefox 42.0
  • Chrome 48.0.2564.8

2015年10月31日

highlight.js で行番号を表示

ブログ内の SyntaxHighlighterhighlight.js 移行作業がようやく終わったので投稿。

highlight.js には行番号を表示する機能が付いてない。
とりあえず検索してみたところ、ちょうどいいプラグインがあったのでそれの紹介。(当ブログでも使用中)
wcoder/highlightjs-line-numbers.js

使用方法は、highlight.js を適用した要素(普通は <code>)に対して hljs.lineNumbersBlock(要素) を実行。
もしくは、hljs.initLineNumbersOnLoad() を仕掛けておく。
[使用例]

[].forEach.call(document.querySelectorAll('pre > code'), function(elem){
    // highlight.js は <code> 内の先頭と末尾の改行を無視してくれないので、ここで削除
    // ※ HTML を書く際、先頭と末尾に改行を入れない方法もある
    elem.textContent = elem.textContent.replace(/^[\r\n]+|[\r\n]+$/g, '');

    // highlight.js の適用
    hljs.highlightBlock(elem);

    // highlightjs-line-numbers.js の適用
    hljs.lineNumbersBlock(elem);
});

また、行番号ブロックのスタイル(.hljs-line-numbers)を自分で用意する必要がある。
[使用例(当ブログのスタイル・2015/10/31)]

pre > code.hljs.hljs-line-numbers {
    background-color: #f5f5f5;
    border-right: 0 none;
    color: #707070;
    text-align: right;
    min-width: 14px;
    /* 以降は選択できないようにするためのスタイル */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

highlight.js に移行した理由

  • C# の新しいキーワードに対応 (素の SyntaxHighlighter は var 辺りから未対応)
  • 旧 Visual Studio 風ハイライトが選択可能
  • ハイライトする言語を選択してパッキングと Minify が公式サイトで可能
  • 外部ファイルの数が少ない (プラグインを除けば js と css の 2 ファイル)

2015年9月25日

python の加算代入演算子(+=)(プラスイコール)の注意点

python の一部のオブジェクト(list 等)では、「a = a + b」と「a += b」が同一視できない。
他言語経験者が思わぬバグを埋め込んでしまいそうな罠仕様・・・


>>> A = a = [1]
>>> a = a + [2]
>>> a, A, a is A
([1, 2], [1], False)
>>> # list の加算(+)は、元の a を変更しない
... # 加算の結果(新しい領域)を a に代入しているので、a と A は別オブジェクト
...

>>> B = b = [1]
>>> b += [2]
>>> b, B, b is B
([1, 2], [1, 2], True)
>>> # list の加算代入(+=)は、元の b を変更する
... # 加算代入しても新しい領域は割り当てられないので、b と B は同一オブジェクト
...

この理由は、list には 加算代入演算子(+=)用の __iadd__ メソッドが定義してあり、加算演算子(+)用の __add__ メソッド とは異なる動作になっているため。
※ list.__iadd__ の動作は list.extend と同じ。

__iadd__ メソッドが定義されてないオブジェクト(tuple 等)は、加算代入でも __add__ メソッドが使用される。
つまり、「a = a + b」と「a += b」 が同一視できる。

組み込みオブジェクトの大半には __iadd__ メソッドが定義されていないため、基本的に list とそのサブクラスを扱う際に注意すればいい。
加算代入演算子(+=)だけでなく、乗算代入演算子(*=)なども同様に注意。
ちなみに、__i???__ メソッドは、ミュータブルなオブジェクトに実装されるものらしい。

参考URL