Spring АйО
7.67K subscribers
262 photos
152 videos
338 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
Вышел Spring Boot 3.3.0. В обновлении представлены следующие нововведения:

🚀 Поддержка CDS для ускорения времени запуска.
🔍 Улучшение Observability.
🔐 Новые возможности Spring Security.
🧵 Поддержка виртуальных потоков для вебсокетов.
Обновление зависимостей и многое другое.

#Spring #SpringBoot

🔗 https://spring.io/blog/2024/05/23/spring-boot-3-3-0-available-now

Подписывайтесь:
😌@spring_aio
Please open Telegram to view this post
VIEW IN TELEGRAM
Иногда полезно освежить в памяти базовые принципы фреймворка. В Spring Framework центральное место занимает Bean. В своем видео Джош Лонг рассказывает об истории Bean и его жизненном цикле.

#SpringTips #SpringBoot

📱 https://www.youtube.com/watch?v=Z5hxolai4Tk

Подписывайтесь:
😌@spring_aio
Please open Telegram to view this post
VIEW IN TELEGRAM
Spring Стартер Недели

Как сделать CRUD за 5 минут? Создаем модель данных, репозиторий, контроллер с 5-8 методами и, возможно, несколько dto. Если печатать со скоростью 600 символов в минуту, то можно успеть 🙈

Или берем Spring Data Rest, и получаем круды для своих репозиториев из коробки. И не только круды. Можно выставить наружу любой метод доступа к данным, прикрутить пагинацию и сортировку.

Однако, быстрый старт в начале может обернуться болью в процессе кастомизации или добавлении сложной (и не очень) логики. Тем не менее, выглядит как вполне себе хорошее решение для небольших REST сервисов, почти что Low Code 🙃.

Больше информации о данном стартере можно получить из доклада Рустама Курамшина на JPoint 2023.

📱 https://www.youtube.com/watch?v=roaGUHaWPxw

🔗 https://spring.io/projects/spring-data-rest

#SpringStarter #SpringBoot

Подписывайтесь:
😌@spring_aio
Please open Telegram to view this post
VIEW IN TELEGRAM
🕐 Тонкое управление Scheduled задачами в Spring

Какими способами можно настроить время и условие запуска Scheduled задач в Spring?

Первый способ - через @Profile над сервисом, в котором объявлена задача, чтобы указать профили, в которых она должна (или не должна) запускаться.


java
@Profile("test,!prod")
class Teapot {
@Scheduled(cron = "0 15 10 15 * ?")
void makeTea() {
log.info("I'am a teapot.");
}
}


Но в таком варианте, мы управляем только условием запуска, но не можем настроить переодичность.

Другой вариант, это объявление cron expression в пропертях:


#application-test.properties
teapot.make_tea.cron=0 15 10 15 * ?



@Scheduled(cron = "${teapot.make_tea.cron}")
void makeTea() {
log.info("I'am a teapot.");
}


Но как тогда в таком случае отключить джобу? В качестве cron expression можно использовать дефис(-), что означает, что джобу запускать не нужно вовсе. И тогда мы обходимся без явного перечисления профилей в @Profile.


#application-prod.properties
teapot.make_tea.cron=-


Такой подход особо полезен при использовании spring-cloud-config, тогда нет необходимости делать передеплой приложения, чтобы выключить, или донастроить джобу. Нужно только не забыть повесить @RefreshScope на класс.

#SpringBoot #SpringTips
🎉 Встречайте нашу дебютную статью на Habr!

👩‍💻 Блеск и нищета нового Scrolling API в Spring Data

В статье мы разобрали новое Scrolling API, которое предназначено для эффективной пагинации. В частности, Scrolling API приносит KeySet пагинацию в Spring Data, которая в теории может быть сильно быстрее стандратной и всем известной offset пагинации.

Однако, в процессе исследования выяснилось, что с PostgreSQL новое API работает не совсем так, как ожидается. А ведь это одна из самых популярных СУБД в мире!

Заинтриговали? Переходите по ссылке, оставляйте комментарии, и, конечно, ждем ваших лайков)

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

#SpringBoot #SpringData

Подписывайтесь:
😌@spring_aio
Please open Telegram to view this post
VIEW IN TELEGRAM
🌟 Статическая и динамическая конфигурация в Spring Boot: что выбрать?

В мире облачных сервисов правильная настройка конфигурации играет ключевую роль. В этой статье рассматриваются два подхода: статическая и динамическая конфигурация.

🔧 Статическая конфигурация удобна предсказуемостью и простотой развертывания. Пример на Spring Boot:


@Value("${myapp.staticValue}")
private String staticValue;

public void printStaticValue() {
System.out.println(staticValue);
}


🔄 Динамическая конфигурация обеспечивает гибкость и адаптивность. Например, для изменения конфигураций во время выполнения приложения можно использовать Spring Cloud:


@RefreshScope
@RestController
public class MessageRestController {

@Value("${message:Hello default}")
private String message;

@GetMapping("/message")
public String getMessage() {
return this.message;
}
}


Spring Cloud предоставляет возможность создания config сервера, который может подгружать новые конфигурации из Git. Ознакомьтесь с полной статьей для более детального погружения и примеров кода!

📚 Читать далее

#SpringBoot #SpringCloud
👩‍💻 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
🐢 Неожиданное падение производительности при переходе на Spring Boot 3.3.0 👩‍💻

Hibernate подложил небольшую свинью, конвертируя JPQL в SQL. Внезапно, запрос поиска записей по списку id с передачей пустого списка деградирует в table full scan.

Однако, Hibernate пофиксил баг в течении 10 часов после поста в запрещенную в РФ соцсеть. Осталось дождаться новой версии Spring Boot, которая включит себе этот фикс.

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

#SpringBoot #Hibernate #BreakingNews
Please open Telegram to view this post
VIEW IN TELEGRAM
Spring Tip: Customizer интерфейсы для кастомизации бинов

Одной из главных причин популярности Spring Boot является его способность автоматически конфигурировать (auto-configuration) множество компонентов, существенно упрощая жизнь разработчикам. Однако иногда возникает необходимость слегка подправить настройки этих компонентов без отказа от всех преимуществ автоматической конфигурации.

Предположим, вам нужно кастомизировать настройки кэша в вашем приложении. Вместо того чтобы полностью переопределять CacheManager, вы можете использовать Customizer интерфейс, чтобы внести необходимые изменения:


@Bean
CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
return cacheManager -> cacheManager.setAllowNullValues(false);
}


Аналогичным образом можно настроить и любые другие компоненты:


//Кастомизация свойств Hibernate
@Bean
HibernatePropertiesCustomizer hibernatePropertiesCustomizer() {
return properties -> properties.put("hibernate.integrator_provider",
(IntegratorProvider) () -> List.of(new BeanValidationIntegrator()));
}

//Кастомизация Jackson2ObjectMapperBuilderCustomizer
@Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
builder.serializationInclusion(JsonInclude.Include.NON_EMPTY);
builder.featuresToEnable(
SerializationFeature.WRITE_ENUMS_USING_TO_STRING,
DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
};
}


Но бывает и такое, что подходящего Customizer интерфейса просто нет. В таком случае, можно использовать BeanPostProcessor для кастомизации уже инициализированных бинов:


//Кастомизация springLiquibase бина
@Bean
BeanPostProcessor liquibaseBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof SpringLiquibase springLiquibase) {
springLiquibase.setContexts("my-context");
}
return bean;
}
};
}


#SpringBoot #SpringTips
Please open Telegram to view this post
VIEW IN TELEGRAM
👨‍💻 Spring Tips: работаем со свойствами

Более миллиона раз разработчики обращались к вопросу на StackOverflow о том, как получить значение из application.properties. Возможно, вы удивитесь (хотя я очень сильно сомневаюсь в этом), но отмеченный как верный ответ с @Value – это не единственный способ. И далеко не всегда самый удобный.

Способ №1: @Value

Аннотация @Value используется для инъекции отдельных значений из файла конфигурации. Например:


@Value("${property.name}")
private String propertyName;


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

Способ №2: @ConfigurationProperties

Аннотация @ConfigurationProperties используется для группировки связанных свойств в объект класса. Свойства могут настраиваться через .properties и .yaml файлы, переменные окружения и т.д.

Типичное использование @ConfigurationProperties может выглядеть следующим образом:


@ConfigurationProperties(prefix = "app")
@Component
@Getter
@Setter
public class ApplicationProperties {
private String name;
}

@SpringBootTest(properties = "app.name=my-app")
class ApplicationPropertiesTest {

@Autowired
private ApplicationProperties applicationProperties;

@Test
void appName() {
var appName = applicationProperties.getName();
assertEquals("my-app", appName);
}
}


Да, нужно будет создать отдельный класс, зато преимуществ у этого подхода относительно @Value довольно много. Перечислим некоторые из них:

– Группировка по префиксу
– Возможность иерархического представления свойств
– Это бин и этим всё сказано :)
@ConfigurationPropertiesBinding
– Кто сможет назвать ещё парочку преимуществ в комментариях?)

Отдельно отметим @ConfigurationPropertiesBinding. Допустим, нам нужно добавить новое свойство version, которое будет представлять класс Version для обработки семантического версионирования (major.minor.patch).


@ConfigurationProperties(prefix = "app")
@Component
@Getter
@Setter
public class ApplicationProperties {
private String name;
private Version version;
}


Для преобразования строкового значения из конфигурации в объект класса Version нам потребуется конвертор, который как раз будет отмечен аннотацией @ConfigurationPropertiesBinding:


@ConfigurationPropertiesBinding
@Component
class SemVerPropertyConverter implements Converter<String, Version> {

@Override
public Version convert(String source) {
return StringUtils.hasLength(source) ? Version.parse(source) : null;
}
}


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


@SpringBootTest(properties = {
"app.name=my-app",
"app.version=1.1.0"
})
class ApplicationPropertiesTest {

@Autowired
private ApplicationProperties applicationProperties;

@Test
void appVersion() {
Version version = applicationProperties.getVersion();
assertEquals(1, version.getMajorVersion());
assertEquals(1, version.getMinorVersion());
assertEquals(0, version.getPatchVersion());
}
}


Подробнее про @ConfigurationProperties и @ConfigurationPropertiesBinding вы можете прочитать в документации.

Способ №3: Environment

А если вы хотите обойтись без специальных аннотаций, можно использовать Environment:


@Autowired
private Environment env;

public void someMethod() {
String property = env.getProperty("property.name");
}


#SpringBoot #SpringTips
Please open Telegram to view this post
VIEW IN TELEGRAM
🛡 Spring Tips: Service Layer Validation

Валидация данных – ключевой аспект любого приложения. В Spring она часто используется в параметрах методов @RestController, например:


@RestController
@RequestMapping("/api/products")
public class ProductController {

@GetMapping("/search")
public ResponseEntity<List<Product>> searchProducts(@RequestParam @NotNull @Size(min = 3, max = 50) String name,
@RequestParam @NotNull @Min(0) @Max(10000) Double price) {
// Логика поиска продуктов
List<Product> products = productService.search(name, price);
return new ResponseEntity<>(products, HttpStatus.OK);
}
}


Валидация в сервисном слое

Валидация помогает гарантировать, что данные, поступающие в приложение, соответствуют требованиям. Для валидации в сервисном слое нужно добавить аннотацию @Validated над сервисом:


@Service
@Validated
public class EmailService {
public void send(@Email String email,
@Length(max = 10) String subject,
@NotBlank String body) {
// бизнес-логика
}
}


Теперь метод send вызовет ошибку, если данные не проходят валидацию:


emailService.send(
"i am not email",
"I am too loooooooooong",
""
);



jakarta.validation.ConstraintViolationException: send.body: must not be blank, send.email: must be a well-formed email address, send.subject: length must be between 0 and 10
...


Валидация DTO в методах сервиса

Чтобы не дублировать поля в разных методах, разработчики часто используют DTO. Аннотации валидации применимы и здесь:


public record EmailRequest(
@Email String email,
@Length(max = 10) String subject,
@NotBlank String body
) {
}


Но в этом случае помимо @Validated над классом, нужно также не забыть добавить @Valid перед типом параметра в методе:


@Service
@Validated
public class EmailService {
public void sendBatch(
@Valid List<EmailRequest> requests
) {
// do work
}
}


Следующий код вызовет ошибку:


emailService.sendBatch(
List.of(
new EmailRequest("not email", "test", "Hello"),
new EmailRequest("alex@spring.aio", "I am too loooooooooong", "")
)
);



jakarta.validation.ConstraintViolationException: sendBatch.requests[1].subject: length must be between 0 and 10, sendBatch.requests[1].body: must not be blank, sendBatch.requests[0].email: must be a well-formed email address


Валидация элементов коллекций

Кстати, точно также можно валидировать и элементы коллекций, а также ключи и значения в Map:


@Service
@Validated
public class EmailService {
public void search(
Map<@NotBlank String, @NotBlank String> searchParams
) {
// do work
}
}


Следующий код вызовет ошибку:


searchService.search(Map.of("", ""));



jakarta.validation.ConstraintViolationException: search.searchParams<K>[].<map key>: must not be blank, search.searchParams[].<map value>: must not be blank


#SpringBoot #SpringTips #Validation
Please open Telegram to view this post
VIEW IN TELEGRAM