はじめに
Spring Boot (Freemarker + Doma2)の構成でアプリケーションを構築しているときの話。
ゆるふわな理解で実装してるのが悪いんだけど、セッションスコープのBeanを定義したときに、ftl上でhoge.fugaのようにプロパティアクセスできなかった。
hoge.getFuga()のようにGetter経由でアクセスすればよかったんだけど、どうせなら原因究明してプロパティアクセスできるようにしたらいいと思って頑張ったらできた。
ついでにJSONとして返すときも変なプロパティがついてたので、つかないようにした
問題の原因
セッションスコープのBeanはcglibで作られたプロキシに包まれていた。
多分このプロキシがThreadLocalな変数を持っていて、リクエストごとに異なったオブジェクトのプロキシになっているんだろう。
対応方針
出力時にプロキシを剥いて生のオブジェクトを取り扱うようにする、Configurationを書いてやればいい。
幸いにして、プロキシはScopedObjectというインターフェースを持っているようで、ScopedObject#getTargetObjectで剥くことができた。
Freemarkerへの対応はこちら
@Configuration
public class FreemarkerConfig {
@Autowired
void configureFreemarkerConfigurer(FreeMarkerConfig config) {
freemarker.template.Configuration templateConfig = config.getConfiguration();
templateConfig.setObjectWrapper(new CustomWrapper());
}
private static class CustomWrapper extends DefaultObjectWrapper {
CustomWrapper() {
super(freemarker.template.Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
}
@Override
protected TemplateModel handleUnknownType(Object obj) throws TemplateModelException {
if (obj instanceof ScopedObject) {
return super.handleUnknownType(((ScopedObject) obj).getTargetObject());
}
return super.handleUnknownType(obj);
}
}
}
Jacksonへの対応がこちら
@Configuration
public class JacksonConfig {
@Autowired
public void configureObjectMapperConfig(ObjectMapper objectMapper) {
objectMapper.registerModule(new ScopedObjectModule());
}
private static class ScopedObjectModule extends Module {
@Getter
private final String moduleName = "scopedObjectModule";
@Override
public Version version() {
return PackageVersion.VERSION;
}
@Override
public void setupModule(SetupContext context) {
context.addSerializers(new Serializers.Base() {
@Override
public JsonSerializer<?> findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) {
Class<?> raw = type.getRawClass();
if (ScopedObject.class.isAssignableFrom(raw)) {
return new ScopedObjectSerializer(type.getSuperClass());
}
return super.findSerializer(config, type, beanDesc);
}
});
}
}
@AllArgsConstructor
private static class ScopedObjectSerializer extends JsonSerializer<ScopedObject> {
private JavaType type;
@Override
public void serialize(ScopedObject value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
serializers.findValueSerializer(type).serialize(value.getTargetObject(), gen, serializers);
}
}
}