Open6
Java の ScopedValue の使いどころ
Java 24 でプレビュー版の ScopedValue は、子クラスから親クラス(インターフェース)に情報を渡す時に便利かもしれない。
以下は「デフォルトメソッドをオーバーライドした子供のメソッド」から名前と呼び出し回数を「親のデフォルトメソッド」に伝える例。
import java.util.concurrent.atomic.AtomicInteger;
void main() {
var myClass = new MyClass();
myClass.doSomething();
myClass.doSomething();
myClass.doSomething();
}
interface MyInterface {
default void doSomething() {
var context = MethodContext.current();
System.out.printf("%s: %d%n", context.name(), context.count());
}
}
@SuppressWarnings("preview")
class MyClass implements MyInterface {
private final AtomicInteger counter = new AtomicInteger();
@Override
public void doSomething() {
var context = new MethodContext("doSomething", counter.incrementAndGet());
ScopedValue.where(MethodContext.METHOD_CONTEXT, context)
.run(MyInterface.super::doSomething);
}
}
@SuppressWarnings("preview")
record MethodContext(String name, int count) {
public static ScopedValue<MethodContext> METHOD_CONTEXT = ScopedValue.newInstance();
static MethodContext current() {
return METHOD_CONTEXT.get();
}
}
main
メソッドの実行結果は次のとおり
doSomething: 1
doSomething: 2
doSomething: 3
Doma では、 Dao の実装クラスで定義されたデータソースを取得するために次のようなコードが必要である。
@Dao
public interface EmployeeDao {
default void doSomething() {
var config = Config.get(this);
var dataSource = config.getDataSource();
}
}
ScopedValue を使うと次のように書き換えられる。
@Dao
public interface EmployeeDao {
default void doSomething() {
var config = Config.get();
var dataSource = config.getDataSource();
}
}
this
を渡す必要がなくなるだけだが、 get
メソッドの定義は次の通り Object
を受け取るので ScopedValue
を使った方が安全と言える。
static Config get(Object provider) {
if (provider instanceof ConfigProvider) {
ConfigProvider p = (ConfigProvider) provider;
return p.getConfig();
}
throw new DomaIllegalArgumentException(
"provider", Message.DOMA2218.getMessage("provider", ConfigProvider.class.getName()));
}
this
を渡す以外だと Hibernate Data Repositories や Jakarta Data のようにアクセサメソッドを用意しないといけない。
Doma でもしやるならこうなるだろう。
@Dao
public interface EmployeeDao {
default void doSomething() {
var config = getConfig();
var dataSource = config.getDataSource();
}
Config getConfig();
}
しかし、getConfig
メソッドが外部に公開されてしまうため好ましくない。
ここで述べた ScopedValue の使い方は、子クラスをアノテーションプロセッサーなどで自動生成するときに特に効果的だと思う。
ScopedValue の正式に Java の標準になったら Doma に MethodContext
のような record を導入してユーティリティ的なメソッドを用意するかもしれない。
@Dao
public interface EmployeeDao {
default void doSomething() {
MethodContext context = MethodContext.current();
Config config = context.config();
DataSource dataSource = context.dataSource();
QueryDsl queryDsl = context.queryDsl();
Method method = context.method();
}
}