Джеймс Гослинг — канадский программист и инженер, создавший Java в начале 90-х годов. Его цель была ясна: создать язык, позволяющий писать программы, независимые от платформы. Так появился лозунг "Write Once, Run Anywhere", который революционизировал разработку ПО, позволяя Java-приложениям работать на любых устройствах с поддержкой JVM.
Недавно Джеймс сообщил, что уходит на заслуженный отдых:
Я наконец-то вышел на пенсию. После долгих лет работы программистом, пришло время просто наслаждаться жизнью. Последние 7 лет в Amazon были отличными, несмотря на COVID-19 и производственные проблемы. У меня есть длинный список сайд-проектов, которые нужно завершить. Будет весело.
Мы, как Spring-разработчики, благодарим Джеймса. Без его новаторских идей и разработки Java, возможно, у нас не было бы нашего любимого фреймворка 💚
Please open Telegram to view this post
VIEW IN TELEGRAM
❤58👍13🔥8
Для тех, кто был слишком занят на неделе или просто пропустил некоторые посты, публикуем дайджест!
– Как управлять запуском различных сервисов в Docker Compose? Узнали, что профили есть не только в Spring!
– Почему String Templates не будет в Java 23? Получили хоть и не всеобъемлющий, но ответ на вопрос, почему String Templates были удалены из Java после нескольких итераций preview.
– GigaIDE — новая IDE от Сбера. Что тут говорить. Самая громкая новость последних дней. 200+ комментариев под постом на Хабре не дадут соврать.
– Тестирование Spring Boot приложений. Лучшие практики. Судя по комментариям, некоторые из перечисленных практик могли показаться очевидными, но услышать их хотя бы раз точно стоит.
– Новый компилятор K2 в Kotlin. Часть 2. Продолжили изучать новые возможности K2 в Kotlin вместе с Михаилом Поливахой.
– Markdown в IntelliJ IDEA как отдельный вид искусства. Рисуем диаграммы и вызываем bash команды. Рассказали, как можно использовать Markdown файлы в IntelliJ IDEA по максимуму.
– Знакомьтесь, James Gosling: Отец Java. А завершили неделю с одной стороны замечательной, с другой стороны немного грустной новостью про уход на пенсию Джеймса Гослинга.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍22🔥7❤5
Валидация данных – ключевой аспект любого приложения. В 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
👍44🔥13❤2👌1
Forwarded from Java Developer
Благодаря этой статье вы узнаете, как подключить и настроить Liquibase в Spring Boot приложении, сгенерировать скрипты инициализации и миграции схемы БД, а также дополнить уже существующие changelog файлы новыми скриптами миграции.
Ссылка на статью — КЛИК
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍6👌4
В новом переводе от команды Spring АйО вы узнаете, как аннотация
@Transactional помогла решить проблему с утечкой соединений и обеспечила стабильность системы. Недавно мы обнаружили критическую проблему внутри слоя репозиториев в нашем микросервисном Spring приложении: неправильная обработка исключения приводила к неожиданным сбоям и нарушению работы сервиса во время тестирования производительности.
📚Подробнее читайте на Хабр: https://habr.com/ru/companies/spring_aio/articles/827642/
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15👍7❤2
Spring Data упрощает создание запросов, но стандартные методы не всегда подходят под конкретные задачи. В таких случаях мы можем создать собственные реализации методов репозиториев. Например, если мы хотим, чтобы запрос формировался динамически на основе фильтра и возвращал DTO с меньшим количеством полей, нам понадобится создать fragment interface и кастомный метод с собственной реализацией.
//Кастомный интерфейс
public interface CustomizedUserRepository {
List<UserDto> findAllUsers(UserFilter filter);
}
//Реализация нашего интерфейса, которая будет использоваться Spring'ом
public class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
private final EntityManager em;
public CustomizedUserRepositoryImpl(JpaContext jpaContext) {
em = jpaContext.getEntityManagerByManagedType(User.class);
}
//Реализация нашего метода
@Override
public List<UserDto> findAllUsers(UserFilter filter) {
var cb = em.getCriteriaBuilder();
var query = cb.createQuery(UserDto.class);
var root = query.from(User.class);
var emailPath = root.<String>get(User_.EMAIL);
var usernamePath = root.<String>get(User_.USERNAME);
query.multiselect(root.get(User_.ID), emailPath, usernamePath);
var predicates = new ArrayList<Predicate>();
var email = filter.email();
if (StringUtils.hasLength(email)) {
predicates.add(cb.like(cb.lower(emailPath), "%" + email.toLowerCase() + "%"));
}
var username = filter.username();
if (StringUtils.hasLength(username)) {
predicates.add(cb.like(cb.lower(usernamePath), "%" + username.toLowerCase() + "%"));
}
query.where(predicates.toArray(new Predicate[]{}));
return em.createQuery(query).getResultList();
}
}
//Использование кастомного интерфейса
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {}
Теперь при инжекции
UserRepository, кастомный метод будет доступен для вызова:
List<UserDto> users = userRepository.findAllUsers(new UserFilter("Maksim", null));
Важные замечания
1.
CustomizedUserRepositoryImpl — полноценный Spring-бин, поддерживающий инжекцию других бинов и специфическую функциональность (AOT, Lifecycle Callbacks и т.д.).2. Инжекция
UserRepository может привести к циклической зависимости. Чтобы избежать этого, его можно получить через ApplicationContext.getBean().3. Spring пытается автоматически обнаружить пользовательские fragment интерфейсы, если классы следуют соглашению об именовании с постфиксом
Impl. Модифицировать значение по умолчанию можно через атрибут аннотации @EnableJpaRepositories:
@EnableJpaRepositories(repositoryImplementationPostfix = "MyPostfix")
4. Репозитории могут включать несколько пользовательских реализаций, которые имеют более высокий приоритет, чем базовая реализация, что позволяет переопределять базовые методы. Например, создадим кастомный интерфейс и переопределим метод
save:
public interface CustomizedSave<T> {
<S extends T> S save(S entity);
}
class CustomizedSaveImpl<T> implements CustomizedSave<T> {
@Override
public <S extends T> S save(S entity) {
// наша кастомная реализация
}
}
Теперь, если мы объявим следующий репозиторий:
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {}
то при вызове метода
userRepository.save(user) будет использован метод из нашей реализации CustomizedSaveImpl. 5. В примерах выше мы рассматривали Spring Data JPA, но эта концепция поддерживается для всех модулей Spring Data (MongoDB, Redis, JDBC и т.д.).
Подробнее про реализацию кастомных репозиториев читайте в документации.
#SpringBoot #SpringTips #CustomRepository
Please open Telegram to view this post
VIEW IN TELEGRAM
👍24🔥8👌3
Пожалуй, почти каждый Spring разработчик сталкивается в своей практике с версионированием баз данных. На эту тему есть отличный доклад на Joker 2023 от Александра Шустанова, в котором спикер сравнивает 2 самых популярных инструмента для миграций БД: Flyway и Liquibase. Редакция Spring АйО приводит транскрипт доклада, для тех, у кого нет 45 минут для просмотра видео.
В статье вас ожидает обзор ключевых особенностей Liquibase и Flyway, а также сравнение их возможностей. Расскажем, когда нужно создавать миграции, как облегчить этот процесс и уменьшить количество ошибок. Осветим некоторые малоизвестные возможности этих инструментов и развеем мифы о других.
📚 Подробнее читайте на Хабре
Please open Telegram to view this post
VIEW IN TELEGRAM
👍25🔥8❤5👎1
Если вы еще не слышали о GigaIDE, то это определенно результат хорошо проведенного отпуска в последние две недели. Во-первых, потому что релиз GigaIDE сопровождалось обширной маркетинговой поддержкой (целый GigaConf!), а во-вторых, из-за большого скандала, последовавшего сразу за релизом.
Теперь, когда ажиотаж утих, можем спокойно разобраться, что же из себя представляет GigaIDE на самом деле.
Please open Telegram to view this post
VIEW IN TELEGRAM
😁23❤6👍4🤔4🔥3👎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
🔥57👍6❤4
This media is not supported in your browser
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
😁52🔥6👍1
Для тех, кто был слишком занят на неделе или просто пропустил некоторые посты, публикуем дайджест!
Spring Boot Tips: Service Layer Validation – разобрались с тем, как валидировать данные на уровне сервисов с помощью аннотаций Valid, Validated и т.д.
Liquibase + Spring Boot: настройка и написание миграций баз данных – узнали, как можно автоматизировать процесс написания скриптом миграций для Liquibase.
Как избежать утечек соединений в Spring Boot приложении? – рассмотрели интересный кейс использования аннотации Transactional, который позволил решить проболему с утечками соединений.
Spring Boot Tips: Кастомные реализации репозиториев – выяснили, как можно "подложить" Spring'у свои реализации репозиториев.
Переписывая историю: от инструментов версионирования БД к практике – погрузились в сравнение Liquibase и Flyway вместе с Александром Шустановым и транскриптом его доклада на Joker 2023.
Первый обзор на GigaIDE - Российская IntelliJ IDEA от СБЕРа – посмотрели в действии, что же из себя представляет GigaIDE в сравнении с IntelliJ IDEA CE и Ultimate.
Новые возможности конструкторов в Java (часть 1) – узнали, что за собой скрывает JEP №447 и какие возможности он привнесёт в нашу любимую Java.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15👍7❤3
При разработке на Spring Boot часто возникает необходимость переопределять конфигурационные свойства для тестов. Представим, что у нас есть
application.properties в директории src/main/resources со следующими свойствами:
spring.application.name=properties
spring.jpa.open-in-view=false
Мы хотим изменить
spring.application.name и добавить новое свойство server.port в тестах. Интуитивно разработчики создают application.properties в src/test/resources, ожидая, что он дополнит основной файл. Однако это не так (подробное объяснение можно найти здесь). Рассмотрим несколько способов переопределения свойств в тестах.1. @ActiveProfiles
Создайте файл
application-test.properties в src/test/resources и активируйте его в тестах.
@SpringBootTest
@ActiveProfiles("test")
class ActiveProfileTest {
@Autowired
private Environment env;
@Test
void test() {
Assertions.assertEquals("new-name", env.getProperty("spring.application.name"));
Assertions.assertEquals(false, env.getProperty("spring.jpa.open-in-view", Boolean.class));
Assertions.assertEquals(8099, env.getProperty("server.port", Integer.class));
}
}
2. Директория config
Spring загружает конфигурационные файлы в определенном порядке. Файлы в
src/test/resources/config имеют приоритет и переопределяют свойства из src/main/resources. Более подробно ознакомиться с тем, по какому принципу Spring загружает свойства можно в документации.3. @DynamicPropertySource
Используйте динамическую подмену свойств, если значения известны только во время выполнения.
@SpringBootTest
class DynamicPropertySourceTest {
@Autowired
private Environment env;
@DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("spring.application.name", () -> "new-name");
registry.add("server.port", () -> 8099);
}
@Test
void test() {
Assertions.assertEquals("new-name", env.getProperty("spring.application.name"));
Assertions.assertEquals(false, env.getProperty("spring.jpa.open-in-view", Boolean.class));
Assertions.assertEquals(8099, env.getProperty("server.port", Integer.class));
}
}
4. @TestPropertySource с параметром locations
Укажите путь к
*.properties файлу с нужными значениями.
@SpringBootTest
@TestPropertySource(locations = "classpath:test.properties")
class PropertiesSourceLocationTest {
@Autowired
private Environment env;
@Test
void test() {
Assertions.assertEquals("new-name", env.getProperty("spring.application.name"));
Assertions.assertEquals(false, env.getProperty("spring.jpa.open-in-view", Boolean.class));
Assertions.assertEquals(8099, env.getProperty("server.port", Integer.class));
}
}
Продолжение в комментариях
#SpringBoot #SpringTips
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19👍9❤4
В условиях растущих требований к производительности современных приложений, кеширование становится одним из ключевых инструментов для их удовлетворения.
В новом переводе от команды Spring АйО вы узнаете про 7 основных техник оптимизации кеширования в Spring Boot, которые могут помочь значительно улучшить производительность. От выбора идеальных кандидатов для кеширования до реализации асинхронного кеша и мониторинга метрик кеша.
📚 Подробнее читайте на Хабре
Please open Telegram to view this post
VIEW IN TELEGRAM
👍20🔥10❤4
Java 22 принесла множество новых фич, но наиболее обсуждаемой стала String Templates. Хотя эта preview-фича не попадет в будущий релиз Java 23, огорчаться не стоит. В Java 22 достаточно других интересных обновлений. Помните их?
Если забыли, самое время освежить память: https://habr.com/ru/articles/801467/.
Ну и, конечно же, выбрать любимую фичу
Поделитесь в комментариях, какая фича Java 22 вам нравится больше всего?
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤3👍3
Полгода назад команда Gradle представила новый продукт — Declarative Gradle. Команда Spring АйО, со своей стороны, провела исследование этого продукта и готова поделиться результатами.
TL;DR: технология всё ещё активно развивается, но пока что не поддерживается привычными инструментами, такими как IntelliJ IDEA. Несмотря на это, нам удалось запустить Spring Boot приложение!
📚 Подробнее читайте на Хабре
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12🤔8❤3😁3🔥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
🔥40👍3❤2