Применение паттернов проектирования в Java
Паттерны проектирования находят широкое применение в Java благодаря её объектно-ориентированной природе и богатой экосистеме.
1. Разработка корпоративных приложений
Java часто используется для создания сложных серверных систем, таких как банковские платформы или системы управления контентом. В таких проектах паттерны проектирования помогают структурировать код.
Например:
Фреймворк Spring: Использует паттерн "Внедрение зависимостей" для управления жизненным циклом объектов. Это позволяет разработчикам сосредоточиться на бизнес-логике, а не на создании и настройке объектов.
Hibernate: Применяет паттерн "Фасад" для упрощения работы с базой данных, скрывая сложность SQL-запросов за простым API.
2. Создание библиотек и API
Паттерны проектирования упрощают создание библиотек, которые другие разработчики могут использовать.
Например:
В Java API паттерн "Итератор" используется в коллекциях (например, List или Set), чтобы обеспечить единообразный способ перебора элементов.
Паттерн "Фабричный метод" применяется в классе java.util.Calendar, где метод getInstance() создает объекты в зависимости от локализации.
3. Управление многопоточностью
Java широко используется для многопоточного программирования, и паттерны помогают управлять сложностью.
Например:
Паттерн "Синглтон" часто используется для создания единственного экземпляра менеджера ресурсов, такого как пул соединений с базой данных.
Паттерн "Монитор" встроен в Java на уровне языка (через ключевое слово synchronized), что упрощает синхронизацию потоков.
4. Упрощение тестирования
Паттерны способствуют написанию тестируемого кода. Например, использование "Внедрения зависимостей" позволяет легко заменять реальные компоненты на заглушки (mocks) во время тестирования.
Проблемы и критика паттернов проектирования
Хотя паттерны проектирования чрезвычайно полезны, их применение не лишено недостатков:
Избыточная сложность: Иногда использование паттерна усложняет код там, где можно было бы обойтись более простым решением.
Злоупотребление: Новички могут применять паттерны везде, даже когда они не нужны, что приводит к "паттерн-ориентированному" программированию.
Контекстная зависимость: Не все паттерны подходят для всех ситуаций. Например, "Синглтон" может быть проблематичным в многопоточных приложениях, если не позаботиться о синхронизации.
В Java-разработке важно понимать, когда и какой паттерн использовать, чтобы избежать ненужной сложности. Опытные разработчики часто комбинируют паттерны, адаптируя их под конкретные задачи.
Для более подробного изучения паттернов, предлагаю к просмотру 4 ранее записанных видео:
Порождающие паттерны проектирования на Java
Структурные паттерны проектирования на Java
Поведенческие паттерны проектирования на Java. Часть 1
Поведенческие паттерны проектирования на Java. Часть 2
Для полезного чтения, рекомендую сайт (может не работать без VPN)
#Java #для_новичков #beginner #on_request #patterns
Паттерны проектирования находят широкое применение в Java благодаря её объектно-ориентированной природе и богатой экосистеме.
1. Разработка корпоративных приложений
Java часто используется для создания сложных серверных систем, таких как банковские платформы или системы управления контентом. В таких проектах паттерны проектирования помогают структурировать код.
Например:
Фреймворк Spring: Использует паттерн "Внедрение зависимостей" для управления жизненным циклом объектов. Это позволяет разработчикам сосредоточиться на бизнес-логике, а не на создании и настройке объектов.
Hibernate: Применяет паттерн "Фасад" для упрощения работы с базой данных, скрывая сложность SQL-запросов за простым API.
2. Создание библиотек и API
Паттерны проектирования упрощают создание библиотек, которые другие разработчики могут использовать.
Например:
В Java API паттерн "Итератор" используется в коллекциях (например, List или Set), чтобы обеспечить единообразный способ перебора элементов.
Паттерн "Фабричный метод" применяется в классе java.util.Calendar, где метод getInstance() создает объекты в зависимости от локализации.
3. Управление многопоточностью
Java широко используется для многопоточного программирования, и паттерны помогают управлять сложностью.
Например:
Паттерн "Синглтон" часто используется для создания единственного экземпляра менеджера ресурсов, такого как пул соединений с базой данных.
Паттерн "Монитор" встроен в Java на уровне языка (через ключевое слово synchronized), что упрощает синхронизацию потоков.
4. Упрощение тестирования
Паттерны способствуют написанию тестируемого кода. Например, использование "Внедрения зависимостей" позволяет легко заменять реальные компоненты на заглушки (mocks) во время тестирования.
Проблемы и критика паттернов проектирования
Хотя паттерны проектирования чрезвычайно полезны, их применение не лишено недостатков:
Избыточная сложность: Иногда использование паттерна усложняет код там, где можно было бы обойтись более простым решением.
Злоупотребление: Новички могут применять паттерны везде, даже когда они не нужны, что приводит к "паттерн-ориентированному" программированию.
Контекстная зависимость: Не все паттерны подходят для всех ситуаций. Например, "Синглтон" может быть проблематичным в многопоточных приложениях, если не позаботиться о синхронизации.
В Java-разработке важно понимать, когда и какой паттерн использовать, чтобы избежать ненужной сложности. Опытные разработчики часто комбинируют паттерны, адаптируя их под конкретные задачи.
Для более подробного изучения паттернов, предлагаю к просмотру 4 ранее записанных видео:
Порождающие паттерны проектирования на Java
Структурные паттерны проектирования на Java
Поведенческие паттерны проектирования на Java. Часть 1
Поведенческие паттерны проектирования на Java. Часть 2
Для полезного чтения, рекомендую сайт (может не работать без VPN)
#Java #для_новичков #beginner #on_request #patterns
YouTube
Порождающие паттерны проектирования на Java. Онлайн встреча от 04.08.2024
Онлайн встреча нашего канала, которую мы провели совместно с подписчиками.
Сегодня мы рассмотрели на примерах следующие паттерны проектирования:
- Фабричный метод (Factory Method)
- Абстрактная фабрика (Abstract Factory)
- Прототип (Prototype)
- Одиночка…
Сегодня мы рассмотрели на примерах следующие паттерны проектирования:
- Фабричный метод (Factory Method)
- Абстрактная фабрика (Abstract Factory)
- Прототип (Prototype)
- Одиночка…
Распространённые ошибки
1. Логика в контроллерах
Признак: контроллер становится "сервисом", содержащим условия, циклы, доступ к БД. Это нарушает принципы чистой архитектуры и усложняет тестирование.
2. Использование Entity в представлении
Передача @Entity напрямую в шаблон может привести к:
Утечке данных (например, паролей).
Ошибкам LazyInitializationException.
Сильной связанности представления с базой данных.
Решение: использовать DTO или ViewModel.
3. Жёсткая связность между слоями
View не должно зависеть от Repository, а Controller — от Entity. Каждый слой должен взаимодействовать только с соседним.
4. Отсутствие DTO
Использование одной и той же модели во всех сценариях ведёт к путанице и проблемам безопасности. Лучше использовать отдельные классы:
UserCreateRequest
UserResponse
UserUpdateRequest
Рекомендации по проектированию
Структура проекта
Хорошей практикой является разделение кода по слоям:
URL-дизайн
Соблюдайте RESTful-стиль:
GET /users — получить список пользователей.
GET /users/{id} — получить конкретного пользователя.
POST /users — создать.
PUT /users/{id} — обновить.
DELETE /users/{id} — удалить.
Использование DTO
Расширения и адаптации MVC
SPA + API
При использовании Vue, React или Angular, представление полностью переносится на фронтенд. В этом случае Spring работает как REST API с @RestController, и классическая схема MVC трансформируется в «REST + JSON».
Поддержка реактивности
Spring WebFlux реализует неблокирующую модель с Mono и Flux, сохраняя при этом логическую структуру MVC. Подходит для высоконагруженных и асинхронных приложений.
Тестирование компонентов MVC
Контроллеры — @WebMvcTest, MockMvc.
Сервисы — @SpringBootTest или с моками (@MockBean).
Репозитории — @DataJpaTest.
#Java #для_новичков #beginner #on_request #mvc
1. Логика в контроллерах
Признак: контроллер становится "сервисом", содержащим условия, циклы, доступ к БД. Это нарушает принципы чистой архитектуры и усложняет тестирование.
2. Использование Entity в представлении
Передача @Entity напрямую в шаблон может привести к:
Утечке данных (например, паролей).
Ошибкам LazyInitializationException.
Сильной связанности представления с базой данных.
Решение: использовать DTO или ViewModel.
3. Жёсткая связность между слоями
View не должно зависеть от Repository, а Controller — от Entity. Каждый слой должен взаимодействовать только с соседним.
4. Отсутствие DTO
Использование одной и той же модели во всех сценариях ведёт к путанице и проблемам безопасности. Лучше использовать отдельные классы:
UserCreateRequest
UserResponse
UserUpdateRequest
Рекомендации по проектированию
Структура проекта
Хорошей практикой является разделение кода по слоям:
com.example.myapp
├── controller
├── service
│ └── impl
├── repository
├── dto
├── model
├── config
URL-дизайн
Соблюдайте RESTful-стиль:
GET /users — получить список пользователей.
GET /users/{id} — получить конкретного пользователя.
POST /users — создать.
PUT /users/{id} — обновить.
DELETE /users/{id} — удалить.
Использование DTO
public class UserResponse {
private Long id;
private String name;
}
public class UserCreateRequest {
private String name;
private String email;
}
Расширения и адаптации MVC
SPA + API
При использовании Vue, React или Angular, представление полностью переносится на фронтенд. В этом случае Spring работает как REST API с @RestController, и классическая схема MVC трансформируется в «REST + JSON».
Поддержка реактивности
Spring WebFlux реализует неблокирующую модель с Mono и Flux, сохраняя при этом логическую структуру MVC. Подходит для высоконагруженных и асинхронных приложений.
Тестирование компонентов MVC
Контроллеры — @WebMvcTest, MockMvc.
Сервисы — @SpringBootTest или с моками (@MockBean).
Репозитории — @DataJpaTest.
#Java #для_новичков #beginner #on_request #mvc
Настройка OAuth2 для Gmail
Для Gmail предпочтительно использовать OAuth2.
Пример конфигурации:
Примечание: Зарегистрируйте приложение в Google Cloud Console и используйте библиотеку google-auth-library-oauth2-http для получения accessToken.
Создание сервиса отправки почты
Создадим сервис EmailService для отправки писем.
Конфигурация асинхронности:
#Java #middle #on_request #JavaMailSender
Для Gmail предпочтительно использовать OAuth2.
Пример конфигурации:
@Configuration
public class MailConfig {
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587);
mailSender.setUsername("your.email@gmail.com");
Properties props = mailSender.getJavaMailProperties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.auth.mechanisms", "XOAUTH2");
// Настройка OAuth2 токена через Google API Client Library
// Подробности: https://developers.google.com/identity/protocols/oauth2
return mailSender;
}
}
Примечание: Зарегистрируйте приложение в Google Cloud Console и используйте библиотеку google-auth-library-oauth2-http для получения accessToken.
Создание сервиса отправки почты
Создадим сервис EmailService для отправки писем.
@Service
public class EmailService {
@Autowired
private JavaMailSender mailSender;
@Autowired
private TemplateEngine templateEngine;
private static final Logger logger = LoggerFactory.getLogger(EmailService.class);
public void sendSimpleEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
mailSender.send(message);
}
public void sendHtmlEmail(String to, String subject, String htmlBody) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(to);
helper.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
helper.setText(htmlBody, true);
mailSender.send(message);
}
public void sendEmailWithAttachment(String to, String subject, String text, File attachment) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(to);
helper.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
helper.setText(text);
FileSystemResource file = new FileSystemResource(attachment);
helper.addAttachment(attachment.getName(), file);
mailSender.send(message);
}
@Async
public CompletableFuture<Void> sendSimpleEmailAsync(String to, String subject, String text IMPORTANT: text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
message.setText(text);
mailSender.send(message);
return CompletableFuture.completedFuture(null);
}
public void sendTemplatedEmail(String to, String subject, String username) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
Context context = new Context();
context.setVariable("username", username);
String htmlBody = templateEngine.process("email-template", context);
helper.setTo(to);
helper.setSubject(MimeUtility.encodeText(subject, "UTF-8", null));
helper.setText(htmlBody, true);
mailSender.send(message);
}
}
Конфигурация асинхронности:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.initialize();
return executor;
}
}
#Java #middle #on_request #JavaMailSender
Примеры кода
Простой текст:
HTML-письмо:
Вложение:
Обработка ошибок и логирование
JavaMailSender может выбрасывать исключения, которые нужно обрабатывать.
Основные исключения:
Пример отлова:
Повторные попытки
Используйте @Retryable из Spring Retry:
Зависимость:
Мониторинг и метрики
Для продакшен-приложений полезно отслеживать метрики отправки писем с помощью Spring Actuator:
Зависимость:
Безопасность и защита данных
Никогда не храните пароли в открытом виде в git-репозиториях.
Рекомендации:
Используйте переменные окружения или секреты (например, Kubernetes Secrets).
Применяйте Spring Cloud Vault или HashiCorp Vault.
Используйте шифрование с Jasypt.
Настройте OAuth2 для Gmail.
Пример с переменными окружения:
#Java #middle #on_request #JavaMailSender
Простой текст:
SimpleMailMessage message = new SimpleMailMessage();
message.setTo("user@example.com");
message.setSubject("Тестовое письмо");
message.setText("Привет из Spring Boot!");
mailSender.send(message);
HTML-письмо:
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo("user@example.com");
helper.setSubject(MimeUtility.encodeText("HTML письмо", "UTF-8", null));
helper.setText("<h1>Привет!</h1><p>Это HTML письмо.</p>", true);
mailSender.send(message);
Вложение:
File file = new File("/path/to/file.pdf");
FileSystemResource resource = new FileSystemResource(file);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo("user@example.com");
helper.setSubject(MimeUtility.encodeText("Письмо с вложением", "UTF-8", null));
helper.setText("Смотри вложение!");
helper.addAttachment("file.pdf", resource);
mailSender.send(message);
Обработка ошибок и логирование
JavaMailSender может выбрасывать исключения, которые нужно обрабатывать.
Основные исключения:
MailAuthenticationException
MailSendException
MessagingException
Пример отлова:
try {
mailSender.send(message);
} catch (MailException e) {
logger.error("Ошибка отправки письма", e);
throw e;
}
Повторные попытки
Используйте @Retryable из Spring Retry:
@Retryable(value = MailException.class, maxAttempts = 3)
public void sendSimpleEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
mailSender.send(message);
}
Зависимость:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
Мониторинг и метрики
Для продакшен-приложений полезно отслеживать метрики отправки писем с помощью Spring Actuator:
@Service
public class EmailService {
@Autowired
private JavaMailSender mailSender;
@Autowired
private MeterRegistry meterRegistry;
public void sendSimpleEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(subject);
message.setText(text);
try {
mailSender.send(message);
meterRegistry.counter("email.sent.success").increment();
} catch (MailException e) {
meterRegistry.counter("email.sent.failure").increment();
throw e;
}
}
}
Зависимость:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
Безопасность и защита данных
Никогда не храните пароли в открытом виде в git-репозиториях.
Рекомендации:
Используйте переменные окружения или секреты (например, Kubernetes Secrets).
Применяйте Spring Cloud Vault или HashiCorp Vault.
Используйте шифрование с Jasypt.
Настройте OAuth2 для Gmail.
Пример с переменными окружения:
spring.config.import=optional:configserver:http://config-server
spring.mail.password=${MAIL_PASSWORD}
#Java #middle #on_request #JavaMailSender
Тестирование отправки почты
Интеграционные тесты с Testcontainers:
Зависимость:
Unit-тестирование:
Интеграция с очередями
Для отложенной отправки используйте RabbitMQ(ну или другой брокер сообщений):
Зависимость:
Распространенные ошибки и подводные камни
Неверная конфигурация SMTP (порт, шифрование).
Проблемы с двухфакторной авторизацией (особенно Gmail).
Ограничения на массовую рассылку (например, Gmail: ~500 писем/день для бесплатных аккаунтов).
Проблемы с кодировкой (например, кириллица в теме письма).
Отсутствие MIME-типа у вложений.
Блокировка сервером (например, Mail.ru требует DKIM и SPF).
Решение для кодировки:
#Java #middle #on_request #JavaMailSender
Интеграционные тесты с Testcontainers:
@Testcontainers
@SpringBootTest
public class EmailServiceTest {
@Container
private static final GreenMailContainer greenMail = new GreenMailContainer();
@Autowired
private EmailService emailService;
@Test
void shouldSendEmail() {
emailService.sendSimpleEmail("test@example.com", "Test Subject", "Test Body");
assertEquals(1, greenMail.getReceivedMessages().length);
}
}
Зависимость:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>greenmail</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
Unit-тестирование:
@MockBean
private JavaMailSender mailSender;
@Test
void shouldSendMail() {
SimpleMailMessage message = new SimpleMailMessage();
emailService.sendSimpleEmail("to", "subject", "body");
verify(mailSender, times(1)).send(any(SimpleMailMessage.class));
}
Интеграция с очередями
Для отложенной отправки используйте RabbitMQ(ну или другой брокер сообщений):
@Service
public class EmailService {
@Autowired
private JavaMailSender mailSender;
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendEmailToQueue(String to, String subject, String text) {
EmailMessage email = new EmailMessage(to, subject, text);
rabbitTemplate.convertAndSend("emailQueue", email);
}
@RabbitListener(queues = "emailQueue")
public void processEmail(EmailMessage email) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email.getTo());
message.setSubject(email.getSubject());
message.setText(email.getText());
mailSender.send(message);
}
@Data
static class EmailMessage {
private String to;
private String subject;
private String text;
}
}
Зависимость:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
Распространенные ошибки и подводные камни
Неверная конфигурация SMTP (порт, шифрование).
Проблемы с двухфакторной авторизацией (особенно Gmail).
Ограничения на массовую рассылку (например, Gmail: ~500 писем/день для бесплатных аккаунтов).
Проблемы с кодировкой (например, кириллица в теме письма).
Отсутствие MIME-типа у вложений.
Блокировка сервером (например, Mail.ru требует DKIM и SPF).
Решение для кодировки:
helper.setSubject(MimeUtility.encodeText("Тема на кириллице", "UTF-8", null));
#Java #middle #on_request #JavaMailSender