Spring АйО
8.43K subscribers
303 photos
209 videos
402 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
🗓 Еженедельный дайджест №5

Для тех, кто был слишком занят на неделе или просто пропустил некоторые посты, публикуем дайджест!

Переопределение и дополнение свойств для Spring Boot тестов – собрали в одном месте 6 различных вариантов переопределения свойств

7 основных способов оптимизировать кеширование в Spring Boot – узнали, что, где и как кэшировать, а что лучше не стоит

Самая любимая фича в Java 22 – освежили в памяти все фичи, которые вошли в 22-й релиз Java

Declarative Gradle: рывок или прорыв? – выяснили, что из себя представляет новый проект от Gradle, и попробовали его в действии

Новые возможности конструкторов в Java (часть 2) – продолжили изучать нововведения, связанные с конструкторами объектов

😌@spring_aio
Please open Telegram to view this post
VIEW IN TELEGRAM
11👍9🔥7
🦥 Spring Tips: Аннотация @Lazy

По умолчанию Spring инициализирует все sigleton-бины во время запуска приложения. Аннотация @Lazy позволяет изменить это поведение, инициализируя бины только по мере необходимости. Используя аннотацию @Lazy можно существенно снизить потребление памяти и уменьшить время запуска приложения, отметив бины, которые используются в приложении не так часто и занимают немало памяти.

Способы применения

Чтобы сделать бин лениво инициализируемым, нужно отметить его аннотацией @Lazy в месте его объявления:


@Lazy
@Component
public class MyComponent {
//...
}

@Configuration
@Lazy
public class LazyConfig {
// Все объявленные в этом конфиге бины будут ленивыми
}

@Configuration
public class AppConfig {
//Этот конкретный бин будет ленивым
@Bean
@Lazy
public MyBean myBean() {
return new MyBean();
}
}


А также в месте его инжекции:


@Service
public class MyService {
private final MyBean myBean;
private final MyComponent myComponent;

public MyService(@Lazy MyBean myBean, @Lazy MyComponent myComponent) {
this.myBean = myBean;
this.myComponent = myComponent;
}
}


Если не воспользоваться аннотацией @Lazy в месте объявления бина или в месте его инжекции, то он НЕ БУДЕТ ленивым.

Ленивая инициализация контекста по умолчанию

Чтобы сделать инициализацию ленивой для всего контекста, можно добавить следущее свойство в конфигурационный файл:


spring.main.lazy-initialization=true


Однако стоит быть осторожным с этим подходом. Ошибки инициализации могут возникнуть не во время запуска приложения, а позже – в рантайме, что усложнит их обнаружение и исправление (что в целом справедливо и при ленивой инициализации лишь некоторых бинов). Но если вы всё таки решите использовать глобальную ленивую инициализацию, можно исключить определённые бины, инициализируя их жадно (во время запуска приложения) с помощью @Lazy(false):


@Service
@Lazy(false)
public class MyService {
// код
}


А много ли бинов лениво инициализируется на вашем проекте? Может быть вообще все? Поделитесь своим опытом в комментариях, будет интересно почитать!

#SpringBoot #SpringTips
Please open Telegram to view this post
VIEW IN TELEGRAM
👍316🔥6
🖥 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
🔥59👍92
👩‍💻 Flyway + Spring Boot: настройка и написание миграций баз данных

Благодаря этой статье вы узнаете, как подключить и настроить Flyway в Spring Boot приложении, а также как сгенерировать скрипты инициализации и миграции схемы базы данных.

📚 Подробнее читайте на Хабре
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥62
#ВопросЭксперту: Есть ли аналог Criteria API в Spring Data JDBC?

Мы рады представить вам новую рубрику, в которой эксперты сообщества Spring АйО будут отвечать на актуальные и интересные вопросы. Для дебютного выпуска мы пригласили Михаила Поливаху, контрибьютора в Spring Data JDBC, который любезно согласился принять участие. Большое спасибо ему за это! Если вам понравится этот формат, поддержите нас лайками и репостами.

Итак, ниже ответ Михаила на заявленный в заголовке вопрос.

–––

Друзья, по поводу аналога Criteria API в Spring Data JDBC и механизма динамического построения запросов в целом.

TL;DR: Аналог, какой-никакой, имеется, он даже работает😅. Но он менее функциональный, чем тот же Spring Data Specification API.

Long answer:
Если говорить про динамическое построение запроса в целом, то мы можем разбить это на несколько частей:

1. Динамическое построение условий.

Например, LIKE, BETWEEN, IS NOT NULL и любые другие keyword-ы, которые предназначены для фильтрации набора данных, но которые никак не влияют на структуру запроса в целом. Вот это в Spring Data JDBC имеется. Основные классы, которыми вы будете оперировать в таком случае, - Criteria и Query. Использовать это API достаточно просто, единственное - вам придется использовать напрямую JdbcAggregateTemplate. На данный момент распознавание методов с Query как параметром метода в Repository/CrudRepository не поддерживается (речь не про default методы, а именно про абстрактные методы в интерфейсе, которые воспринимаются как PartTreeJdbcQuery запросы), и скорее всего не будет поддерживаться, так как мало кому это пока было нужно (то есть никакого аналога интерфейсу JpaSpecificationExecutor в Spring Data JDBC нет).

Вот пример использования:

Сущность:


@Data
@Table("users")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {

@Id
@EqualsAndHashCode.Include
private Long id;

private String status;

private String name;

private OffsetDateTime createdAt;
}


И пример запроса:


@Autowired
private JdbcAggregateTemplate repository;

@Test
void testCriteriaApi() {
Criteria criteria = Criteria
.where("status").is("READY").ignoreCase(true)
.and("created_at").between(OffsetDateTime.now().minusDays(2), OffsetDateTime.now().plusDays(2))
.and("name").like("%J%");

Query query = Query.query(criteria);

Page<User> allByQuery = repository.findAll(query, User.class, Pageable.unpaged());

Assertions.assertThat(allByQuery.getTotalElements()).isEqualTo(3);
}


Надеюсь, здесь все понятно. Код довольно легко читаемый, вот какой SQL мы сгенерируем:


SELECT
"users"."id" AS "id",
"users"."name" AS "name",
"users"."status" AS "status",
"users"."created_at" AS "created_at"
FROM
"users"
WHERE
UPPER("users"."status") = UPPER(?)
AND "users".created_at BETWEEN ?
AND ?
AND "users"."name" LIKE ?


2. Динамическое построение структуры запроса

Теперь, есть другая часть той же Criteria API - это уже динамическое построение самой структуры запроса. Частично с этим помогает CriteriaQuery из Criteria API. Это все возможные группировки, формирование селект листа, формирование подзапросов и т.п. Вот этого в Spring Data JDBC нет и в ближайшее время не планируется. Это огромный пласт работы, который предстоит сделать, если, конечно, это кому-то нужно. Сейчас там конь не валялся по этому вопросу.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20👍105
Audio
✌️ Java - лучший язык программирования!?

Оказывается, нейросеть Suno не только умеет сочинять классные песни, но и отлично разбирается в программировании! На запрос "The best programming language" она написала песню про Java 🥰

Как перестать подпевать? 😂
java-java is super cool
java-java not just a tool
java here and java there
java language is superb
Please open Telegram to view this post
VIEW IN TELEGRAM
17🔥13😁9
🗓 Еженедельный дайджест №6

Для тех, кто был слишком занят на неделе или просто пропустил некоторые посты, публикуем дайджест!

Spring Tips: Аннотация @Lazyразобрались, как лениво инициализировать бины, и обсудили, стоит ли вообще это делать

Scoped Values в Java (Часть 1)продолжили изучать нововведения Java, которые появятся в нашем любимом языке программирования в ближайшее время

Flyway + Spring Boot: настройка и написание миграций баз данныхузнали, как можно быстро и просто писать скрипты миграции баз данных

#ВопросЭксперту: Есть ли аналог Criteria API в Spring Data JDBC?запустили новую рубрику, в первом выпуске которой дебютировал Михаил Поливаха

Java - лучший язык программирования!?нашли еще один аргумент в пользу Java для спора о лучшем языке программирования

😌@spring_aio
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥133👍1
🛡 Кастомные валидаторы в Spring Boot: Как сделать валидацию под себя

Валидация входных данных — важная часть любой системы, обеспечивающая целостность данных и предотвращение ошибок. Иногда стандартные аннотации Jakarta Bean Validation (например, @NotNull, @Size, @Pattern) не покрывают все наши потребности, и нам приходится писать свои собственные валидаторы. В этом посте мы разберем два подхода к созданию кастомных валидаторов в Spring Boot, а также узнаем, как настроить отображение ошибки валидации.

1. Кастомные валидации с мета-аннотациями

Если стандартных аннотаций не хватает, можно создать свою собственную аннотацию для валидации. Это удобно, если вам нужно сочетать несколько стандартных аннотаций в одной или переиспользовать уже имеющиеся аннотации. Например, давайте создадим аннотацию @CardNumber, которая проверяет, соответствует ли строка формату номера кредитной карты.


@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Pattern(regexp = "([0-9]{4}-){3}[0-9]{4}$", message = "Invalid credit card number")
@Constraint(validatedBy = {})
public @interface CardNumber {
String message() default "Invalid credit card number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}


В этом примере аннотация @CardNumber использует @Pattern для проверки формата номера кредитной карты. Такой подход упрощает код, потому что вся логика валидации сконцентрирована в одной аннотации.


@PostMapping("/card")
void checkCardNumber(@RequestParam @Valid @CardNumber String cardNumber) {
}



@Test
public void validCardNumber() throws Exception {
mockMvc.perform(post("/card")
.param("cardNumber", "1111-1111-1111-1111"))
.andExpect(status().isOk())
.andDo(print());
}

@Test
public void invalidCardNumber() throws Exception {
mockMvc.perform(post("/card")
.param("cardNumber", "1111-1111-1111"))
.andExpect(status().is(400))
.andDo(print());
}


2. Реализация собственных валидаторов

Когда стандартные аннотации не подходят, можно создать собственные валидаторы. Для этого нужно реализовать интерфейс ConstraintValidator.

Предположим, нам нужно валидировать пароли с определенными требованиями. Для этого создадим аннотацию @ValidPassword и валидатор PasswordValidator.


@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
public @interface ValidPassword {
String message() default "Invalid password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int minLength() default 8;
String specialChars() default "!@#$%^&*()";
}

public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
private int minLength;
private String specialChars;

@Override
public void initialize(ValidPassword constraintAnnotation) {
this.minLength = constraintAnnotation.minLength();
this.specialChars = constraintAnnotation.specialChars();
}

@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
if (password == null) {
return false;
}

boolean hasUpperCase = !password.equals(password.toLowerCase());
boolean hasLowerCase = !password.equals(password.toUpperCase());
boolean hasDigit = password.chars().anyMatch(Character::isDigit);
boolean hasSpecialChar = password.chars().anyMatch(ch -> specialChars.indexOf(ch) >= 0);
boolean isLongEnough = password.length() >= minLength;

return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar && isLongEnough;
}
}


В данном примере аннотация @ValidPassword проверяет, что пароль содержит и верхний, и нижний регистр, цифры, специальные символы и имеет достаточную длину. Логика валидации инкапсулирована в PasswordValidator.

Продолжение в комментариях 👇

#SpringBoot #SpringTips
Please open Telegram to view this post
VIEW IN TELEGRAM
👍317🔥4
☁️ Soft Assertions в AssertJ

Бывало ли у вас такое, что тест падает на первом же assertion'e из десяти? Вы исправляете ошибку, запускаете тест снова, и он падает на втором assertion'e. И так десять раз. Выматывает, не так ли?

На самом деле, есть способ ускорить этот процесс — использовать soft assertions. С их помощью тест выполнится полностью, даже если один или несколько assertion'ов упадут, и вы сразу увидите все ошибки.

В новой статье от Михаила Поливахи, эксперта сообщества Spring АйО, вы узнаете, что такое soft assertions и как ими пользоваться.

📚 Подробнее читайте на Хабре
Please open Telegram to view this post
VIEW IN TELEGRAM
👍189🔥7
Spring Tips: Переопределение свойств через переменные окружения

Часто в файле application.properties (или application.yml) объявляются свойства, значения которых содержат переменные вида ${ENV_PROPERTY_KEY:defaultValue}:


spring.datasource.url=jdbc:postgresql://${POSTGRES_HOST:localhost}/${POSTGRES_DB_NAME:local_dev_db}
spring.datasource.username=${POSTGRES_USERNAME:root}
spring.datasource.password=${POSTGRES_PASSWORD:root}
spring.datasource.driver-class-name=org.postgresql.Driver


Такой подход позволяет переопределять свойства при запуске приложения в разных средах (local, test, prod и т.д.).

Например, если мы решили запустить наше приложение через Docker Compose, предварительно собрав его в Docker образ, то передача значений для объявленных нами переменных окружения будет выглядеть следующим образом:


spring_test_app:
image: spring_test_app:latest
build:
context: .
dockerfile: docker/Dockerfile
args:
DOCKER_BUILDKIT: 1
restart: "no"

# задаём конкретные значения тем самым переменным окружения
environment:
POSTGRES_HOST: postgres:5432
POSTGRES_DB_NAME: test_stand_db
POSTGRES_USERNAME: admin
POSTGRES_PASSWORD: admin
ports:
- "8080:8080"


В этом примере переменные из application.properties переопределяются через переменные окружения при запуске Docker контейнера.

Переопределение свойств напрямую

Но на самом деле, можно обойтись без дополнительных переменных в application.properties, переопределив Spring-свойства напрямую.

Теперь application.properties выглядит следующим образом:


spring.datasource.url=jdbc:postgresql://localhost:5432/local_dev_db
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=org.postgresql.Driver


А код сервиса нашего приложения в Docker Compose следующим образом:


spring_test_app:
image: spring_test_app:latest
build:
context: .
dockerfile: docker/Dockerfile
args:
DOCKER_BUILDKIT: 1
restart: "no"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres/test_stand_db
SPRING_DATASOURCE_USERNAME: admin
SPRING_DATASOURCE_PASSWORD: admin
ports:
- "8080:8080"


Для spring.datasource.url, spring.datasource.username и spring.datasource.password будут использованы те значения, которые мы указали в Docker Compose файле.

За счёт чего это становится возможным?

Spring Boot использует определенный порядок загрузки свойств приложения, чтобы обеспечить разумное переопределение значений. Переменные окружения загружаются позже и перезаписывают свойства, заданные в application.properties. Полный порядок загрузки можно найти в документации.

Relaxed Binding в Spring Boot

Кстати, обратите внимание на стиль написания названий свойств. Spring Boot использует концепцию Relaxed Binding, которая позволяет указать название свойства без полного совпадения. Например:


@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {
var firstName: String? = null
}


Для этого класса можно использовать следующие стили именования свойств:

- my.main-project.person.first-name — Kebab стиль для .properties/.yaml файлов
- my.main-project.person.firstName — CamelCase стиль
- my.main-project.person.first_name — Underscore стиль
- MY_MAINPROJECT_PERSON_FIRSTNAME — Uppercase + Underscore стиль для переменных окружения. При использовании Uppercase + Underscore стиля следует учитывать, что многие операционные системы ограничивают имена переменных окружения. Например, в Linux переменные могут содержать только буквы (a-z, A-Z), цифры (0-9) и символ подчеркивания (_). Подробнее читайте в документации.

В приведённом выше примере с Docker Compose файлом мы как раз воспользовались Uppercase + Underscore стилем, чтобы указать значения для нужных нам свойств. Именно такой вариант именования переменных окружения является негласным для Linux.

Ставь 🔥 если знал про это и 🤔 если слышишь впервые)

#SpringBoot #SpringTips
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥37🤔24👍121
👩‍💻 Генерация HTTP клиентов для Spring Boot приложения по OpenAPI спецификации

В новом переводе от команды Spring АйО вы узнаете, как можно сгенерировать код HTTP клиентов для Spring Boot приложения по OpenAPI спецификации, используя плагин openapi-generator для Gradle.

В статье вы найдете:

💡 Пошаговую инструкцию по использованию openapi-generator для Gradle
⚙️ Настройки для генератора, которые помогут оставить только нужный код
🍃 Пример конфигурации сгенерированных Spring-бинов

📚 Подробнее читайте на Хабре
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17👍104🤔1
😲 Awesome System Design

Нашли интересный репозиторий (больше 10к звезд на GitHub!) с материалами по системному проектированию:

– 18 статей по ключевым концепциям проектирования систем
– 26 статей по основным блокам проектирования систем
– Разбор 50 задач по проектированию на YouTube, разбитых по уровню сложности
– Ответы на вопросы по проектированию системы на интервью
– Ссылки на статьи, книги и каналы YouTube

🔗 https://github.com/ashishps1/awesome-system-design-resources

Сохраняйте, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥35👍144
🗓 Еженедельный дайджест №7

Для тех, кто был слишком занят на неделе или просто пропустил некоторые посты, публикуем дайджест!

Кастомные валидаторы в Spring Boot: Как сделать валидацию под себявспомнили основные нюансы реализации собственных аннотаций и валидаторов

Soft Assertions в AssertJ – Михаил Поливаха (эксперт сообщества Spring АйО) объяснил, как и в каких ситуации стоит использовать soft assertions

Spring Boot Tips: Переопределение свойств через переменные окруженияузнали, как переопределять свойства через переменные окружения без их явного указания в application.properties

Генерация HTTP клиентов для Spring Boot приложения по OpenAPI спецификациивыяснили, как можно сгенерировать код HTTP-клиентов и доработать его при необходимости

Awesome System Designнашли отличный репозиторий с материалами по системному дизайну на GitHub

😌@spring_aio
Please open Telegram to view this post
VIEW IN TELEGRAM
👍95🔥5
⚡️ Вышел Hibernate 7.0.0.Beta1!

Основное, на что стоит обратить внимание:

* Переход на Jakarta Persistence версии 3.2
* Более строгая проверка доменной модели
* Новая схема mapping.xsd c расширенными возможностями маппинга
* Переход на Hibernate Models с Hibernate Commons Annotations (HCANN)

📚 Подробнее читайте на Хабре

#spring_news #hibernate
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13👍7🤯2