Scoped Value (preview)



Неделю назад вышла java 21. Cегодня разберу интересную фичу в стадии превью — Scoped Value.



Ближайший аналог Scoped Value — ThreadLocal. Это когда мы объявляем переменную



ThreadLocal<Integer> value;



и для каждого потока будет своё независимое значение value.



В бизнес-логике это редко нужно, но фреймворки активно пользуются этим классом. Spring Security использует ThreadLocal для хранения информации о текущем пользователе. Давайте на этом кейсе посмотрим недостатки ThreadLocal, и что предлагает ScopedValue.



Как работает секьюрити:



1️⃣ Когда приходит новый запрос, Spring вытаскивает информацию о пользователе и записывает в ThreadLocal переменную:



public static ThreadLocal<Principal> PRINCIPAL = …



void serve(Request request, Response response) {



var principal = ADMIN;

PRINCIPAL.set(principal);

…}



2️⃣ Бизнес-логика. В любом месте кода можно узнать, кто выполняет запрос:



var principal = PRINCIPAL.get();



Обычно каждый запрос обрабатывается в своём потоке, поэтому данные между запросами не пересекаются.



3️⃣ В конце работы с запросом удаляем информацию из ThreadLocal переменной



Что в итоге:



Не надо передавать Principal в параметрах

Надо явно очищать значение ThreadLocal переменной в конце работы

В любом месте можно вызвать set/remove и всё сломать

Подход несовместим с виртуальными потоками



Scoped Value намерен решить проблемы выше. Как это выглядит:



public static ScopedValue<Principal> PRINCIPAL = …



void serve(Request request, Response response) {



var principal = ADMIN;

ScopedValue.where(PRINCIPAL, principal)

.run(() -> process(request, response));

…}



Переменная PRINCIPAL со значением principal будет доступна только внутри конкретного вызова метода process. Достать значение внутри process:



var principal = PRINCIPAL.get();



Кроме run есть метод call, который возвращает значение из переданной функции:



var result = ScopedValue.where(Server.PRINCIPAL, guest)

.call(() -> getResult());



Сначала кажется, что для java синтаксис Scoped Value очень необычный — как будто переменная главнее основного действия. Но такое в java уже есть, вспомните try-with-resources.



Что получаем:



Видимость переменной задаётся для конкретного вызова метода

У ScopedValue нет метода set, переменную нельзя обнулить/поменять внутри блока

Код совместим с виртуальными потоками



Что вызывает вопросы:



🤔 Сценарии использования



Неизменяемый аналог ThreadLocal, совместимый с Project Loom точно нужен, но не вижу смысла задавать область видимости настолько гранулярно



🤔 Нельзя использовать несколько ScopedValue без использования вложенности. Хотя это легко реализовать по аналогии с try-with-resources



Где использовать: пока вижу только как замену ThreadLocal при переходе на виртуальные потоки.



Фича сейчас в стадии превью, посмотрим, как она будет развиваться. Если будет, конечно:)