発生した問題
DTOの属性にはアノテーションがあり、レストインターフェイスがシリアライズのためのオブジェクトを返すときに機能しますが、このアノテーションがある場所で機能しないことがわかり、最終的にリフレクションが問題を引き起こしていることがわかりました。
返されるオブジェクト:AppMessageDTO。
private List<MessageReceiverDTO> receiver;
public List<MessageReceiverDTO> getReceiver() {
return receiver;
}
public void setReceiver(List<MessageReceiverDTO> receiver) {
this.receiver = receiver;
}
MessageReceiverDTO: @Encryptが機能しないのはここです。
private ReceiverTypeEnum receiverType;
@Encrypt
private Long receiverKey;
private String receiverName;
...get/setを省略する
Encryptが機能するポイントは、AppMessageDTOが残りのインターフェイスのリターンをシリアライズするときです。したがって、問題は、シリアライズが受信配列内のオブジェクトプロパティのアノテーションをインターセプトしないことです。
AppMessageDTOオブジェクトのソースを投稿し、以下は、mybatisが構築した、検索するためのSQLです。
AppMessageDTO appMessageDTO = messageMapper.selectFullMessageById(messageId, tenantId);
問題の原因
核心的な問題は、シリアライゼーション、つまりオブジェクトを構築するためにmybatisをクエリする際に、リフレクションが使われることです。
そのため、最終的なオブジェクトはAppMessageDTOオブジェクトとして出力されますが、その中のReceiverプロパティはListプロパティではなくListであるため、シリアライズはアノテーションを調査していません。
解析
この属性はデータベースにテキストとして保存され、カスタムtypeHandlerを使用してオブジェクトに変換されます。
<result column="receiver" property="receiver" jdbcType="VARCHAR" javaType="java.util.List" typeHandler="org.hippius.common.util.ReciverTypeHandler"/>
カスタム型変換は、Jacksonから直接シリアライズとデシリアライズを使用します。
return new ObjectMapper().readValue(rs.getString(columnName), java.util.List.class);
通常はこれで問題ありませんし、たいていの場合はこれで十分なのですが、ここではListの汎用型が指定されていないため、戻り値はListになります。
通常の状況下でappMessageDTO.setReceiver(リスト)を使用する場合は、コンパイルされていないので、私はそれがmybatisコンストラクトがエラーを報告する必要があると思ったが、実際にはあなたが時間を呼び出すためにリフレクションを使用する場合、それは正常に実行することができ、mybatisコンストラクトは、冒頭の問題につながったリフレクションの使用です。
オブジェクトに照会たデータをデバッグとして送信します。
問題解決
CollectionType listType = MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, MessageReceiverDTO.class);
return MAPPER.readValue(content, listType);
カスタムtypeHandlerを書き換えて、手動で汎用型を定義します。
拡張機能
Method setReceiver = null;
//リフレクションを手動で呼び出すには
AppMessageDTO appMessageDTO = new AppMessageDTO();
try {
setReceiver = AppMessageDTO.class.getMethod("setReceiver", List.class);
List<Long> list = new ArrayList<>();
list.add(1L);
setReceiver.invoke(appMessageDTO,list);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
//ジェネリックを使った定義でオブジェクトを受け取る。
List<MessageReceiverDTO> receiver = appMessageDTO.getReceiver();
//ここでgetClass()は、型変換エラー!というエラーを報告する。
//System.out.println(receiver.get(0).getClass());
//なぜなら、そうするとコンパイル時に問題が発生するからだ。
System.out.println(receiver.get(0) instanceof MessageReceiverDTO);
//コンパイルエラー
//System.out.println(receiver.get(0) instanceof Long);
//は普通に追加できる
List<MessageReceiverDTO> receiver2 = new ArrayList<>();
receiver2.add(receiver.get(0));
//コンパイルエラー
//LinkedHashMap hash = receiver.get(0);
//receiver2.add(new LinkedHashMap());
概要
- 変換した後にgetClass()メソッドを呼び出すとエラーになるという、なんともファンタジックな仕様なので、getClass()メソッドの内部で型判定が行われたのでしょう。
- このように見た場合、定義は弱い型対応であり、実際のオブジェクトの型を表現することも、実際のオブジェクトの型を制限することもありません。
- コンパイルに通らないコードの中には実際に動くものもあります。コンパイルは根本的に、ただコーディングすることを制限しているように感じられ、通過するコードの一部は間違っており、通過しないコードの一部は代わりに間違っています。
- この場合、型を決定する必要があるのであれば、ジェネリックを使わずにリストを使ってオブジェクトを受け取り、instanceofを使ってチェックする必要があります。





