Java for Beginner
678 subscribers
563 photos
157 videos
12 files
866 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Применение паттернов проектирования в 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
Распространённые ошибки

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.

Пример конфигурации:
@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
Примеры кода

Простой текст:
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:
@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
3. Основные возможности ObjectMapper

ObjectMapper предоставляет широкий набор функций для настройки сериализации и десериализации.

Вот ключевые возможности:

3.1. Базовые операции

import com.fasterxml.jackson.databind.ObjectMapper;

public class Example {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();

// POJO
public class User {
private String name;
private int age;

// Getters and setters
}

// Сериализация
User user = new User("Alice", 30);
String json = mapper.writeValueAsString(user);
System.out.println(json); // {"name":"Alice","age":30}

// Десериализация
String jsonInput = "{\"name\":\"Bob\",\"age\":25}";
User deserializedUser = mapper.readValue(jsonInput, User.class);
System.out.println(deserializedUser.getName()); // Bob
}
}


3.2. Конфигурация ObjectMapper

ObjectMapper позволяет настраивать поведение через методы конфигурации:
ObjectMapper mapper = new ObjectMapper();

// Игнорировать неизвестные поля при десериализации
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

// Включить форматирование JSON (pretty print)
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);

// Игнорировать null-поля при сериализации
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);


3.3. Аннотации

Jackson предоставляет множество аннотаций для управления сериализацией/десериализацией:
@JsonProperty("customName"): Переименовывает поле в JSON.
@JsonIgnore: Исключает поле из сериализации/десериализации.
@JsonInclude(JsonInclude.Include.NON_NULL): Исключает null-поля.
@JsonFormat: Задает формат для дат, чисел и других типов.
@JsonTypeInfo и @JsonSubTypes: Поддержка полиморфизма.


Пример:
public class User {
@JsonProperty("full_name")
private String name;

@JsonIgnore
private String password;

@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;
}


3.4. Работа с Generics и сложными типами

Для работы с коллекциями или сложными типами используется TypeReference:
List<User> users = mapper.readValue(json, new TypeReference<List<User>>(){});


3.5. Потоковая обработка

Для больших данных можно использовать потоковый API:
JsonGenerator generator = mapper.getFactory().createGenerator(new FileOutputStream("output.json"));
generator.writeStartObject();
generator.writeStringField("name", "Alice");
generator.writeEndObject();
generator.close();


3.6. Кастомные сериализаторы/десериализаторы


Для нестандартных типов данных можно написать свои сериализаторы:
public class CustomSerializer extends StdSerializer<CustomType> {
public CustomSerializer() {
super(CustomType.class);
}

@Override
public void serialize(CustomType value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("custom", value.getValue());
gen.writeEndObject();
}
}

// Регистрация
mapper.registerModule(new SimpleModule().addSerializer(CustomType.class, new CustomSerializer()));


#Java #middle #on_request #ObjectMapper
4. Взаимодействие с Spring

ObjectMapper глубоко интегрирован с Spring Framework, особенно в модуле Spring Web (REST API). Spring использует Jackson для обработки JSON в контроллерах и взаимодействия с клиентами через HTTP.

4.1. Spring Boot и Jackson

Spring Boot автоматически настраивает ObjectMapper как бин, если в зависимостях есть jackson-databind. Вы можете кастомизировать его через свойства или конфигурацию.

Конфигурация через application.properties:
spring.jackson.serialization.indent-output=true
spring.jackson.deserialization.fail-on-unknown-properties=false
spring.jackson.date-format=yyyy-MM-dd


Кастомизация через Java-конфигурацию:
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
}


4.2. Использование в контроллерах

Spring автоматически использует ObjectMapper для сериализации ответов и десериализации запросов:

@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public User createUser(@RequestBody User user) {
// user автоматически десериализуется из JSON
return user;
}

@GetMapping
public List<User> getUsers() {
// Список автоматически сериализуется в JSON
return List.of(new User("Alice", 30));
}
}


4.3. Кастомизация для HTTP

Spring использует MappingJackson2HttpMessageConverter для обработки JSON.

Вы можете настроить его, чтобы использовать кастомный ObjectMapper:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(customObjectMapper());
converters.add(converter);
}

@Bean
public ObjectMapper customObjectMapper() {
return new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true);
}
}



5. Взаимодействие с другими библиотеками


ObjectMapper часто используется в связке с другими библиотеками, которые работают с JSON или требуют сериализации/десериализации.

5.1. MongoDB

Spring Data MongoDB использует Jackson для сериализации объектов в BSON (бинарный JSON).

Вы можете настроить MongoTemplate для использования кастомного ObjectMapper:
@Configuration
public class MongoConfig {
@Bean
public MongoTemplate mongoTemplate(MongoDatabaseFactory factory) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return new MongoTemplate(factory, new MappingMongoConverter(new DefaultDbRefResolver(factory), new MongoMappingContext()));
}
}


5.2. Kafka

В Apache Kafka ObjectMapper используется для сериализации/десериализации сообщений.

Например:
@Bean
public KafkaTemplate<String, User> kafkaTemplate(ProducerFactory<String, User> producerFactory) {
return new KafkaTemplate<>(producerFactory);
}

@KafkaListener(topics = "user-topic")
public void listen(@Payload String json, ConsumerRecord<String, String> record) throws IOException {
User user = new ObjectMapper().readValue(json, User.class);
System.out.println("Received: " + user);
}


5.3. Elasticsearch

Spring Data Elasticsearch также использует Jackson для работы с JSON. Вы можете настроить RestHighLevelClient с кастомным ObjectMapper.

5.4. Другие форматы

Jackson поддерживает модули для работы с другими форматами (XML, YAML, CBOR). Например:
ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
String yaml = yamlMapper.writeValueAsString(user);


#Java #middle #on_request #ObjectMapper
6. Оптимизация и лучшие практики

Переиспользование ObjectMapper:
Создавайте один экземпляр ObjectMapper на приложение, чтобы избежать накладных расходов на инициализацию.
Используйте Spring для внедрения ObjectMapper как бина.


Потоковая обработка:
Для больших JSON используйте JsonParser и JsonGenerator вместо полной загрузки в JsonNode.

Кэширование:
ObjectMapper автоматически кэширует метаданные классов. Избегайте частого создания новых экземпляров.

Обработка ошибок:
Обрабатывайте исключения, такие как JsonProcessingException, для повышения надежности.
Настройте DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES для игнорирования неизвестных полей.


Тестирование:
Проверяйте сериализацию/десериализацию в тестах, особенно для сложных объектов.
Используйте библиотеки, такие как jsonassert, для сравнения JSON.


Безопасность:
Избегайте десериализации непроверенных данных, чтобы предотвратить атаки (например, уязвимости в стиле XXE).
Используйте
@JsonIgnoreProperties для ограничения полей.


7. Распространенные проблемы и их решения

Циклические ссылки:

Используйте @JsonIdentityInfo для обработки циклических зависимостей:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class User {
private Long id;
private List<User> friends;
}


Неизвестные поля:
Настройте mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).

Неправильные форматы дат:
Используйте @JsonFormat или настройте глобальный формат через mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")).

Производительность:
Для больших объектов используйте потоковую обработку или минимизируйте использование JsonNode.


8. Пример реального сценария

Допустим, вы разрабатываете REST API для управления пользователями. JSON-запросы и ответы должны быть в определенном формате, а некоторые поля — опциональными.

POJO:
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
@JsonProperty("full_name")
private String name;

@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;

@JsonIgnore
private String password;

// Getters and setters
}


Контроллер:
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
// Логика сохранения
return ResponseEntity.ok(user);
}
}


Конфигурация:
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule()); // Для LocalDate
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
}


#Java #middle #on_request #ObjectMapper