Spring АйО
7.67K subscribers
263 photos
158 videos
340 links
Русскоязычное сообщество Spring-разработчиков.

Habr: bit.ly/433IK46
YouTube: bit.ly/4h3Ci0x
VK: bit.ly/4hF0OG8
Rutube: bit.ly/4b4UeX6
Яндекс Музыка: bit.ly/3EIizWy

Канал для общения: @spring_aio_chat
Download Telegram
👩‍💻 Java-рантаймы с точки зрения Spring Boot 👩‍💻

На первый взгляд, выбор правильного Java-рантайма для вашего проекта на Spring Boot может показаться тривиальным. В конце концов, все популярные рантаймы основываются на коде OpenJDK и предлагают одинаковые программные интерфейсы.

Но не все рантаймы реализованы одинаково. В этой статье мы обсудим различные показатели, которые могут повлиять на ваше решение выбрать определенный дистрибутив Java для Spring Boot приложения.

🔗 https://habr.com/ru/companies/spring_aio/articles/819899/

#JVM #Java #SpringBoot
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Новые возможности конструкторов в Java (часть 1)

Не так давно в Java была добавлена интересная возможность для конструкторов. Теперь в них можно размещать операторы перед явным вызовом другого конструктора, такого как super(..) или this(..). Эта фича была предложена в JEP 447 и впервые представлена в качестве preview-фичи в JDK 22.

Ранее в Java вызов конструктора суперкласса (super(..)) или другого конструктора текущего класса (this(..)) должен был быть первой строкой в теле конструктора. Другими словами, до вызова конструктора нельзя было выполнять никакие проверки или инициализации:


public class PositiveBigInteger extends BigInteger {

public PositiveBigInteger(long value) {
super(value); // Вызов конструктора суперкласса должен быть первым
if (value <= 0) throw new IllegalArgumentException("Value must be positive");
}

}


Начиная с Java 22 мы можем сначала выполнять проверки или инициализацию, и только затем обращаться к конструктору суперкласса:


public class PositiveBigInteger extends BigInteger {

public PositiveBigInteger(long value) {
if (value <= 0) throw new IllegalArgumentException("Value must be positive");
super(value);
}

}


Благодаря этой фичи мы можем улучшить читаемость и поддержку кода, размещая логику валидации и подготовки аргументов там, где нам это действительно нужно.

Но у этой фичи есть некоторые ограничения. Например, мы не можем ссылаться на создаваемый экземпляр и инициализировать его поля до обращения к super(..) или this(..).


class A {

int i;

A() {
this.i++; // Ошибка
this.hashCode(); // Ошибка
System.out.print(this); // Ошибка
super();
}

}


В Java 23 эту фичу предлагают протестировать повторно с одним значительным изменением: позволить телу конструктора инициализировать поля класса до явного вызова другого конструктора. Про это мы расскажем в следующей части этого поста.

Ставьте 🔥 если хотите вторую часть!

#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Новые возможности конструкторов в Java (часть 2)

Не так давно мы рассказывали про гибкие конструкторы в Java. В 23 версии Java появится возможность инициализировать поля в том же классе перед явным вызовом конструктора (JEP-482). А пока эта фича не реализована, мы вынуждены использовать вспомогательные методы, если нам необходимо проиницизировать поля.

Рассмотрим класс, который принимает аргумент типа Certificate и должен преобразовать его в массив байтов для конструктора суперкласса.

Java ≤ 22:


public class Sub extends Super {

private static byte[] prepareByteArray(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null) throw new IllegalArgumentException(..);
return switch (publicKey) {
case RSAKey rsaKey -> ...
case DSAPublicKey dsaKey -> ...
default -> ...
};
}

public Sub(Certificate certificate) {
super(prepareByteArray(certificate));
}

}


В коде выше метод prepareByteArray выполняет необходимую подготовку аргументов. Затем этот метод вызывается внутри конструктора Sub как часть вызова super.

Java ≥ 23:

Можно будет сделать тоже самое, но без дополнительных “приседаний” в виде вынесения логики которую мы хотим выполнить в конструкторе в отдельный метод:


public class Sub extends Super {
public Sub(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null) throw new IllegalArgumentException(..);
byte[] certBytes = switch (publicKey) {
case RSAKey rsaKey -> ...
case DSAPublicKey dsaKey -> ...
default -> ...
};
super(certBytes);
}
}


Также иногда возникает необходимость передать одно и то же значение в несколько аргументов конструктора суперкласса. Ранее это можно было сделать только с использованием вспомогательного конструктора.

Рассмотрим пример, связанный с системой регистрации студентов в университете. У нас есть базовый класс Person, который представляет человека, и класс Student, который наследует Person. Каждый студент имеет идентификатор, который также используется в качестве номера студенческого билета и учетной записи в университете

Java ≤ 22:


class Person {
private String id;
private String accountId;

public Person(String id, String accountId) {
this.id = id;
this.accountId = accountId;
}
}

class Student extends Person {
private Student(String id) {
super(id, id); // Передача одного и того же значения дважды
}

public Student(String firstName, String lastName) {
this(generateStudentId(firstName, lastName));
}

private static String generateStudentId(String firstName, String lastName) {
// Генерация уникального идентификатора студента
return firstName + ", " + lastName + "-" + System.currentTimeMillis();
}
}


В этом примере вспомогательный конструктор Student(String id) используется для передачи одного и того же id в два аргумента конструктора суперкласса Person.

Java ≥ 23:


class Person {
private String id;
private String accountId;

public Person(String id, String accountId) {
this.id = id;
this.accountId = accountId;
}
}

class Student extends Person {
public Student(String firstName, String lastName) {
String id = generateStudentId(firstName, lastName);
super(id, id); // Передача одного и того же значения дважды
}

private static String generateStudentId(String firstName, String lastName) {
// Генерация уникального идентификатора студента
return firstName + ", " + lastName + "-" + System.currentTimeMillis();
}
}


Ставьте 🔥 если понравился пост и вы были бы рады видеть больше разборов JEP

#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Scoped Values в Java (Часть 1)

Судя по реакциям на предыдущих постах про JEP, они вам нравятся, так что продолжаем разбирать свежые JEP'ы.

Представим себе небольшой веб-фреймворк, который обрабатывает HTTP-запросы. Фреймворк создает контекст для каждого запроса и передает его через методы:


@Override
public void handle(Request request, Response response) {
var userInfo = readUserInfo();
}

private UserInfo readUserInfo() {
return (UserInfo) framework.readKey("userInfo", context);
}


Ранее для передачи контекста использовались переменные типа ThreadLocal:


private final static ThreadLocal<FrameworkContext> CONTEXT = new ThreadLocal<>();

void serve(Request request, Response response) {
var context = createContext(request);
CONTEXT.set(context);
Application.handle(request, response);
}

public PersistedObject readKey(String key) {
var context = CONTEXT.get();
var db = getDBConnection(context);
db.readKey(key);
}


Scoped Values API впервые был представлен в JDK 20 и прошел несколько итераций и улучшений в последующих версиях JDK (JEP 429, JEP 446, JEP 464, JEP 481). Scoped Values представляют собой новый механизм для передачи неизменяемых данных между методами в одном потоке и между дочерними потоками. Этот механизм проще в использовании по сравнению с ThreadLocal и обладает меньшими затратами по времени и памяти.

У подхода, использующего ThreadLocal, есть несколько недостатков:

1. Неограниченная изменяемость — переменные могут изменяться в любое время любым кодом в потоке
2. Неограниченное время жизни — переменные могут существовать дольше, чем необходимо, что может приводить к утечкам памяти
3. Высокая стоимость наследования — при создании дочерних потоков переменные должны копироваться, что увеличивает затраты по памяти

Scoped Values позволяют избежать этих проблем, обеспечивая одноразовую запись и ограниченное время жизни значений:


final static ScopedValue<FrameworkContext> CONTEXT = ScopedValue.newInstance();

void serve(Request request, Response response) {
var context = createContext(request);
ScopedValue.runWhere(CONTEXT, context, () -> Application.handle(request, response));
}

public PersistedObject readKey(String key) {
var context = CONTEXT.get();
var db = getDBConnection(context);
db.readKey(key);
}


В данном случае метод ScopedValue.runWhere связывает значение с текущим потоком на время выполнения лямбда-выражения, после чего связь уничтожается, что улучшает производительность и безопасность кода.

В 23 версии Java фича будет пересмотрена повторно с одним изменением: тип параметра операции метода ScopedValue.callWhere будет являться новым функциональным интерфейсом, который позволит компилятору Java делать вывод о том, может ли быть выброшено проверяемое исключение. Подробнее про ScopedValue.callWhere и улучшения, связанные с ним, поговорим во второй части.

Ставьте 🔥 если хотите вторую часть!

#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
🖥 Scoped Values в Java (Часть 2)

Недавно мы рассказывали про Scoped Values. Одной из ключевых особенностей Scoped Values (JEP-481) является отсутствие метода set, в отличие от ThreadLocal, что позволяет гарантировать, что после привязки значения к run/call методам конкретных Runnable и Callable, оно не может быть в них изменено:


private static final ScopedValue<String> X = ScopedValue.newInstance();

void foo() {
ScopedValue.runWhere(X, "hello", () -> bar()); //привязываем значение "hello" для метода bar()
}

void bar() {
System.out.println(X.get()); // "hello"
ScopedValue.runWhere(X, "goodbye", () -> baz()); //привязываем значение "goodbye" для метода baz()
System.out.println(X.get()); // "hello"
}

void baz() {
System.out.println(X.get()); // "goodbye"
}


1. В методе foo() устанавливаем значение "hello" для области видимости внутри метода bar()
2. В методе bar() запрашиваем значение. Получаем "hello"
3. В методе bar() устанавливаем значение "goodbye" для области видимости внутри метода baz()
4. В методе bar() запрашиваем значение. Получаем "hello". Тем самым убеждаемся, что после установки значения для области видимости внутри метода baz() оно не поменялось для метода bar()
5. В методе baz() запрашиваем значение. Получаем "goodbye".

Важно отметить, что привязка "goodbye" действует только внутри вложенной области видимости. После завершения выполнения метода baz() значение X в bar() возвращается к "hello". Таким образом, тело метода bar() не может изменить привязку, видимую этим методом, но может изменить привязку, видимую его вызываемыми методами. После завершения метода foo() X возвращается в состояние без привязки. Такое вложение гарантирует ограниченный срок действия для совместного использования нового значения.

Scoped Values API также предоставляет метод callWhere, который позволяет получить результат выполнения:


var result = ScopedValue.callWhere(X, "hello", () -> bar());


В этом случае callWhere выполняет код внутри области видимости, где X привязан к значению "hello", и возвращает результат выполнения.

Подводя итог, складывается ощущение, что нововведение предоставляет более безопасный и эффективный способ передачи данных между потоками по сравнению с использованием ThreadLocal. Однако с уверенностью можно будет об этом говорить только после того, как фича выйдет из preview и войдет в состав языка на постоянной основе.

Ставьте 🔥 если разбор JEP был полезен и вы хотели бы видеть больше подобных обзоров

#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
😲 Java 23 - Launch Stream

Уже через 7 часов на YouTube канале Java пройдет прямая трансляция, посвящённая релизу Java под номером 23.

Расписание трансляции (время GMT+3 МСК):

Deep Dive into Java 23's Features!
16:00: Welcome and event agenda
16:05: Java Language Update by Gavin Bierman
16:20: Java Language Features in Action
16:35: Integrity by Default with Ron Pressler
16:55: Markdown Documentation Comments with Jonathan Gibbons
17:10: ZGC: Generational Mode by Default with Stefan Karlson

Java Outreach in 2024
17:25: Java Playground Updates with Denys Makogon and Heather Stephens
17:40: Java Community Report from Sharat Chander and Jim Grisanzio

Java Preview Features and Future work
18:10: Sneak Peek at GraalVM Updates with Alina Yurenko and Fabio Niephaus
18:30: Class-File API with Brian Goetz
18:45: State of Project Leyden with John Rose
18:15: Enjoy Your Evening!

😉 https://www.youtube.com/watch?v=QG9xKpgwOI4

Также про JEP'ы, включенные в этот релиз, компания JetBrains записала целую серию видео. Смотреть можно здесь: 😉 JEP Explained.
Please open Telegram to view this post
VIEW IN TELEGRAM
👩‍💻 JEP 467: Markdown в JavaDoc

С выходом Java 23 произошли важные изменения в формате написания комментариев — теперь они могут быть написаны с использованием Markdown. Ранее разработчики использовали HTML и JavaDoc-теги для оформления таких комментариев, что могло создавать сложности, особенно для тех, кто не знаком с HTML. Теперь, благодаря поддержке Markdown, комментарии станет писать проще и удобнее.

Зачем это нужно?

HTML с течением времени стал менее популярен для ручного написания. Сегодня чаще используют языки разметки, ориентированные на удобство разработчиков. Использование HTML для JavaDoc создавало трудности, так как он тяжеловесен и сложен для быстрого написания, особенно для новых разработчиков.

Преимущества использования Markdown

Markdown стал популярным благодаря своей простоте и удобочитаемости. Он идеально подходит для написания JavaDoc, которая обычно состоит из текстовых блоков, списков, ссылок и выделенного текста. Вместо сложных HTML-тегов можно использовать гораздо более лаконичные конструкции Markdown.

Вот так выглядит комментарий к методу hashCode с использованием HTML и JavaDoc-тегов:


/**
* Возвращает хэш-код для объекта.
* <p>
* Общий контракт метода {@code hashCode}:
* <ul>
* <li>Если метод вызывается несколько раз на одном объекте в ходе выполнения программы,
* он должен возвращать одно и то же значение, если данные объекта не изменялись.
* <li>Если два объекта равны, вызов метода {@code hashCode} должен вернуть одинаковое значение.
* <li>Если объекты не равны, метод может возвращать одинаковые хэш-коды, но этого следует избегать.
* </ul>
* @return хэш-код для объекта.
*/


Этот же комментарий в Markdown:


/// Возвращает хэш-код для объекта.
///
/// Общий контракт метода `hashCode`:
///
/// - Если метод вызывается несколько раз на одном объекте в ходе выполнения программы,
/// он должен возвращать одно и то же значение, если данные объекта не изменялись.
/// - Если два объекта равны, вызов метода `hashCode` должен вернуть одинаковое значение.
/// - Если объекты не равны, метод может возвращать одинаковые хэш-коды, но этого следует избегать.
///
/// @return хэш-код для объекта.


Для использования Markdown введён новый формат комментариев: строки начинаются с трёх слэшей (`///`), вместо традиционного синтаксиса /** ... */.

Основные различия:
- Пробел между абзацами обозначается пустой строкой, как и в Markdown.
- Списки создаются с помощью маркеров - вместо HTML-тегов <ul> и <li>.
- Подчёркивание (`_`) используется для выделения текста, вместо HTML-тегов <em>.
- Теги вроде {@code} заменяются обратными апострофами (\`...\`).

В то же время, важные теги, такие как @param и @return, сохраняются и могут использоваться вместе с Markdown-разметкой.

🔗 С полным описанием JEP можно ознакомиться по ссылке: https://openjdk.org/jeps/467

#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM
⭐️ CascadeType.ALL и @ManyToMany

Использовать CascadeType.ALL для @ManyToMany не рекомендуется, так как это может привести к непредсказуемым результатам во время удаления JPA сущностей. Вместо этого следует использовать CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST и CascadeType.REFRESH.

Подробнее об этом рассказано в отдельном видео!

😉 СМОТРЕТЬ НА YOUTUBE
😄 СМОТРЕТЬ В VK ВИДЕО
🥰 СМОТРЕТЬ НА RUTUBE
Please open Telegram to view this post
VIEW IN TELEGRAM
👩‍💻 JEP 485: Stream Gatherers

С выходом JDK 24 Stream API станет еще мощнее благодаря новому инструменту — Stream Gatherers. Этот подход позволит разработчикам создавать собственные промежуточные операции в потоках, повышая гибкость и выразительность программного кода.

Почему это важно?

До сих пор Stream API предлагал фиксированный набор операций, таких как map, filter, distinct, и другие. Однако некоторые задачи не могли быть легко решены встроенными методами. Например, если вы хотите выделить уникальные строки на основе их длины, это требовало сложных обходных путей:


record DistinctByLength(String str) {
@Override public boolean equals(Object obj) {
return obj instanceof DistinctByLength other && str.length() == other.str.length();
}
@Override public int hashCode() {
return str.length();
}
}

var result = Stream.of("foo", "bar", "baz", "quux")
.map(DistinctByLength::new)
.distinct()
.map(DistinctByLength::str)
.toList();

// result ==> [foo, quux]


С выходом фичи задача будет решаться гораздо проще:


var result = Stream.of("foo", "bar", "baz", "quux")
.gather(Gatherers.distinctBy(String::length))
.toList();

// result ==> [foo, quux]


Как работает Gatherer?

Gatherer — это интерфейс, задающий логику преобразования элементов потока. Его работа строится на четырех функциях:

1. initializer — создает объект для хранения состояния.
2. integrator — обрабатывает элементы потока, при необходимости записывая данные в состояние.
3. combiner — объединяет результаты для параллельных потоков.
4. finisher — выполняет финальные действия, такие как добавление оставшихся данных.

Например, gatherer windowFixed группирует элементы в списки фиксированного размера:


Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.gather(Gatherers.windowFixed(3))
.toList();

// result ==> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]


Что это дает разработчику?

1. Гибкость: Возможность определять операции любой сложности.
2. Параллельность: Поддержка многопоточности при использовании combiner.
3. Снижение сложности: Простое выражение сложных операций без использования обходных решений.
4. Поддержка Stateful Stream Processing подхода

Заключение

JEP 485 — это достойный вклад в развитие Stream API, предоставляющий разработчикам инструменты для создания более выразительных и производительных потоков.

🔗 С полным описанием JEP можно ознакомиться по ссылке: https://openjdk.org/jeps/485

#Java #JEP
Please open Telegram to view this post
VIEW IN TELEGRAM