Оптимизация и масштабирование
1. Хранение и ротация ключей
Для HMAC-SHA ключи должны безопасно храниться (например, в Vault или AWS KMS).
Для RSA/ECDSA используйте ротацию ключей с поддержкой JWK (JSON Web Key) для автоматического обновления публичных ключей.
2. Масштабирование в микросервисах
Централизуйте выдачу токенов через отдельный сервис (например, Auth Service).
Используйте JWK для распространения публичных ключей между сервисами.
3. Кэширование токенов
Кэшируйте проверенные токены в Redis или другом in-memory хранилище, чтобы снизить нагрузку на парсинг и валидацию.
Используйте TTL кэша, соответствующий exp токена.
4. Логирование и мониторинг
Логируйте попытки использования невалидных или истекших токенов для анализа атак.
Мониторьте время парсинга и валидации токенов, чтобы выявить узкие места.
Пример интеграции с Spring Security
JWT часто используется в связке с Spring Security для защиты REST API.
Пример конфигурации:
#Java #middle #on_request #Jwt
1. Хранение и ротация ключей
Для HMAC-SHA ключи должны безопасно храниться (например, в Vault или AWS KMS).
Для RSA/ECDSA используйте ротацию ключей с поддержкой JWK (JSON Web Key) для автоматического обновления публичных ключей.
2. Масштабирование в микросервисах
Централизуйте выдачу токенов через отдельный сервис (например, Auth Service).
Используйте JWK для распространения публичных ключей между сервисами.
3. Кэширование токенов
Кэшируйте проверенные токены в Redis или другом in-memory хранилище, чтобы снизить нагрузку на парсинг и валидацию.
Используйте TTL кэша, соответствующий exp токена.
4. Логирование и мониторинг
Логируйте попытки использования невалидных или истекших токенов для анализа атак.
Мониторьте время парсинга и валидации токенов, чтобы выявить узкие места.
Пример интеграции с Spring Security
JWT часто используется в связке с Spring Security для защиты REST API.
Пример конфигурации:
import io.jsonwebtoken.Jwts;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor("secret".getBytes()))
.build()
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
if (username != null) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
username, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
SecurityContextHolder.clearContext();
}
}
chain.doFilter(request, response);
}
}
#Java #middle #on_request #Jwt
👍4
August 2
Модульность и многомодульные проекты в Gradle
Gradle поддерживает многомодульные проекты, позволяя организовать проект в виде иерархии подмодулей, каждый из которых имеет собственный build.gradle или build.gradle.kts. Это обеспечивает модульность, разделение ответственности и повторное использование кода.
Преимущества:
Разделение функциональности на независимые модули (например, API, ядро, реализация).
Упрощение тестирования и поддержки.
Параллельная сборка модулей для повышения производительности.
Повторное использование конфигураций и зависимостей.
settings.gradle(.kts) — управление include-проектами
Файл settings.gradle (или settings.gradle.kts) определяет структуру многомодульного проекта, включая корневое имя и подмодули.
Основные функции:
Указание имени корневого проекта: rootProject.name.
Включение подмодулей через include.
Настройка репозиториев и плагинов через pluginManagement.
Пример (Groovy DSL):
Kotlin DSL:
Нюансы:
Структура multi-module проекта: Parent + Children
Многомодульный проект состоит из корневого (parent) проекта и подмодулей (children), каждый из которых имеет собственный build.gradle.
Пример структуры:
Parent проект:
Корневой build.gradle задает общие настройки, плагины и зависимости для всех подмодулей.
Пример:
Children проекты:
Каждый подмодуль имеет собственный build.gradle, определяющий специфические задачи, зависимости и конфигурации.
Пример (module-api/build.gradle):
В памяти: Корневой проект загружает модель для всех подмодулей, включая их задачи и зависимости. Каждый подмодуль добавляет объект Project и граф задач, увеличивая потребление памяти.
Для оптимизации используйте --configure-on-demand:
Project Access: project(":module")
Подмодули доступны через объект project с использованием их пути, определенного в settings.gradle.
Пример:
Это добавляет module-core как зависимость для текущего модуля.
Нюансы:
Путь начинается с : для корневого проекта (например, :module-core:submodule для вложенных модулей).
Доступ к свойствам другого модуля:println project(':module-core').version
#Java #middle #Gradle #Task #include_projects
Gradle поддерживает многомодульные проекты, позволяя организовать проект в виде иерархии подмодулей, каждый из которых имеет собственный build.gradle или build.gradle.kts. Это обеспечивает модульность, разделение ответственности и повторное использование кода.
Преимущества:
Разделение функциональности на независимые модули (например, API, ядро, реализация).
Упрощение тестирования и поддержки.
Параллельная сборка модулей для повышения производительности.
Повторное использование конфигураций и зависимостей.
В памяти: Каждый модуль создает собственный объект Project в модели Gradle, увеличивая потребление памяти пропорционально количеству модулей (обычно 50-100 МБ на модуль). Граф задач (DAG) для всех модулей хранится в памяти, что может достигать 1-2 ГБ для крупных проектов.
settings.gradle(.kts) — управление include-проектами
Файл settings.gradle (или settings.gradle.kts) определяет структуру многомодульного проекта, включая корневое имя и подмодули.
Основные функции:
Указание имени корневого проекта: rootProject.name.
Включение подмодулей через include.
Настройка репозиториев и плагинов через pluginManagement.
Пример (Groovy DSL):
rootProject.name = 'my-project'
include 'module-api', 'module-core', 'module-web'
Kotlin DSL:
rootProject.name = "my-project"
include("module-api", "module-core", "module-web")
Нюансы:
Каждый подмодуль должен иметь собственный build.gradle в папке с таким же именем (например, module-api/build.gradle).
Подмодули могут быть вложенными:include 'module-core:submodule'
В памяти: settings.gradle загружается на фазе инициализации, создавая модель проекта с объектами Project для каждого модуля. Это минимальная фаза по потреблению памяти (50-100 МБ), но сложные настройки (например, pluginManagement) могут увеличить overhead.
Структура multi-module проекта: Parent + Children
Многомодульный проект состоит из корневого (parent) проекта и подмодулей (children), каждый из которых имеет собственный build.gradle.
Пример структуры:
my-project/
├── build.gradle
├── settings.gradle
├── module-api/
│ └── build.gradle
├── module-core/
│ └── build.gradle
├── module-web/
│ └── build.gradle
Parent проект:
Корневой build.gradle задает общие настройки, плагины и зависимости для всех подмодулей.
Пример:
allprojects {
repositories {
mavenCentral()
}
}
subprojects {
apply plugin: 'java'
dependencies {
testImplementation 'junit:junit:4.13.2'
}
}
Children проекты:
Каждый подмодуль имеет собственный build.gradle, определяющий специфические задачи, зависимости и конфигурации.
Пример (module-api/build.gradle):
plugins {
id 'java-library'
}
dependencies {
api 'org.apache.commons:commons-lang3:3.12.0'
}
В памяти: Корневой проект загружает модель для всех подмодулей, включая их задачи и зависимости. Каждый подмодуль добавляет объект Project и граф задач, увеличивая потребление памяти.
Для оптимизации используйте --configure-on-demand:
./gradlew build --configure-on-demand
Project Access: project(":module")
Подмодули доступны через объект project с использованием их пути, определенного в settings.gradle.
Пример:
dependencies {
implementation project(':module-core')
}
Это добавляет module-core как зависимость для текущего модуля.
Нюансы:
Путь начинается с : для корневого проекта (например, :module-core:submodule для вложенных модулей).
Доступ к свойствам другого модуля:println project(':module-core').version
В памяти: Gradle хранит все объекты Project в памяти, обеспечивая доступ через project(). Это увеличивает модель проекта, особенно для больших иерархий модулей.
#Java #middle #Gradle #Task #include_projects
👍2
August 4
Sharing Logic между проектами
Совместное использование логики между модулями повышает повторное использование кода и упрощает поддержку.
Common Logic
В корневом build.gradle:
Используйте блоки allprojects и subprojects для общих настроек:
Скриптовые плагины:
Создайте файл common.gradle:
Подключите в подмодулях:
Кастомные плагины:
Создайте плагин для общей логики (см. раздел "Создание собственных плагинов" в предыдущей статье).
Пример применения:
Version Catalogs (libs.versions.toml)
Version Catalogs — это централизованный способ управления версиями зависимостей и плагинов, введенный в Gradle 7.0.
Настройка (gradle/libs.versions.toml):
Использование:
Kotlin DSL:
Преимущества:
Централизованное управление версиями.
Улучшенная читаемость и автодополнение в IDE.
Упрощение обновления версий.
#Java #middle #Gradle #Task #include_projects
Совместное использование логики между модулями повышает повторное использование кода и упрощает поддержку.
Common Logic
В корневом build.gradle:
Используйте блоки allprojects и subprojects для общих настроек:
subprojects {
apply plugin: 'java'
version = '1.0.0'
repositories {
mavenCentral()
}
}
Скриптовые плагины:
Создайте файл common.gradle:
apply plugin: 'java'
dependencies {
testImplementation 'junit:junit:4.13.2'
}
Подключите в подмодулях:
apply from: "$rootDir/gradle/common.gradle"
Кастомные плагины:
Создайте плагин для общей логики (см. раздел "Создание собственных плагинов" в предыдущей статье).
Пример применения:
subprojects {
apply plugin: 'com.example.common-plugin'
}
В памяти: Общая логика уменьшает дублирование кода, но увеличивает сложность модели проекта, так как Gradle загружает и парсит дополнительные скрипты или плагины.
Version Catalogs (libs.versions.toml)
Version Catalogs — это централизованный способ управления версиями зависимостей и плагинов, введенный в Gradle 7.0.
Настройка (gradle/libs.versions.toml):
[versions]
spring-boot = "2.7.18"
junit = "4.13.2"
[libraries]
spring-core = { group = "org.springframework", name = "spring-core", version.ref = "spring-boot" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
Использование:
plugins {
alias(libs.plugins.spring.boot)
}
dependencies {
implementation libs.spring.core
testImplementation libs.junit
}
Kotlin DSL:
plugins {
alias(libs.plugins.spring.boot)
}
dependencies {
implementation(libs.spring.core)
testImplementation(libs.junit)
}
Преимущества:
Централизованное управление версиями.
Улучшенная читаемость и автодополнение в IDE.
Упрощение обновления версий.
В памяти: Version Catalog загружается как часть модели проекта, добавляя минимальный overhead (10-20 МБ), но упрощает управление зависимостями, снижая вероятность конфликтов.
#Java #middle #Gradle #Task #include_projects
👍2
August 4
Сценарии и практики разделения: Core/Api/Impl
Разделение проекта на модули (Core, Api, Impl) — распространенная практика в корпоративной разработке для обеспечения модульности и повторного использования.
Core:
Содержит общую бизнес-логику, утилиты, модели данных.
Пример:
Api:
Определяет публичные интерфейсы, DTO или контракты.
Пример:
Impl:
Реализует интерфейсы из Api, добавляя конкретную функциональность.
Пример: module-impl/build.gradle:
Структура:
Практики:
Используйте api в модуле module-api для экспорта публичных интерфейсов.
Минимизируйте зависимости между модулями, чтобы избежать циклических зависимостей.
Централизуйте версии через Version Catalog или dependencyManagement в корневом build.gradle.
Gradle Composite Builds
Composite Builds позволяют включать другие Gradle-проекты как зависимости, без необходимости публикации в репозиторий.
Настройка:
В settings.gradle корневого проекта:
В build.gradle используйте проект как зависимость:
Использование:
Полезно для разработки связанных проектов, находящихся в разных репозиториях.
Gradle автоматически разрешает зависимости между проектами.
Процесс сборки с помощью task-graph (Task Avoidance, Parallel Build)
Gradle строит граф задач (Directed Acyclic Graph, DAG) для определения порядка выполнения задач в многомодульных проектах.
Task Avoidance:
Gradle использует инкрементальную сборку, пропуская задачи, чьи входные/выходные данные не изменились (up-to-date checks).
Пример:
Parallel Build:
Gradle поддерживает параллельное выполнение задач с флагом --parallel:
#Java #middle #Gradle #Task #include_projects
Разделение проекта на модули (Core, Api, Impl) — распространенная практика в корпоративной разработке для обеспечения модульности и повторного использования.
Core:
Содержит общую бизнес-логику, утилиты, модели данных.
Пример:
module-core/build.gradle:plugins {
id 'java-library'
}
dependencies {
implementation 'org.apache.commons:commons-lang3:3.12.0'
}
Api:
Определяет публичные интерфейсы, DTO или контракты.
Пример:
module-api/build.gradle:plugins {
id 'java-library'
}
dependencies {
api project(':module-core')
}
Impl:
Реализует интерфейсы из Api, добавляя конкретную функциональность.
Пример: module-impl/build.gradle:
plugins {
id 'java'
}
dependencies {
implementation project(':module-api')
}
Структура:
my-project/
├── module-core/
│ └── build.gradle
├── module-api/
│ └── build.gradle
├── module-impl/
│ └── build.gradle
├── build.gradle
├── settings.gradle
Практики:
Используйте api в модуле module-api для экспорта публичных интерфейсов.
Минимизируйте зависимости между модулями, чтобы избежать циклических зависимостей.
Централизуйте версии через Version Catalog или dependencyManagement в корневом build.gradle.
В памяти: Каждый модуль добавляет объект Project, задачи и зависимости в модель Gradle, увеличивая потребление памяти. Разделение на Core/Api/Impl уменьшает размер каждого модуля, но увеличивает общее количество объектов в памяти.
Gradle Composite Builds
Composite Builds позволяют включать другие Gradle-проекты как зависимости, без необходимости публикации в репозиторий.
Настройка:
В settings.gradle корневого проекта:
includeBuild '../other-project'
В build.gradle используйте проект как зависимость:
dependencies {
implementation project(':other-project:module-x')
}
Использование:
Полезно для разработки связанных проектов, находящихся в разных репозиториях.
Gradle автоматически разрешает зависимости между проектами.
В памяти: Composite Builds загружают модели всех включенных проектов в память, значительно увеличивая overhead (100-500 МБ на проект). Используйте с осторожностью для крупных систем.
Процесс сборки с помощью task-graph (Task Avoidance, Parallel Build)
Gradle строит граф задач (Directed Acyclic Graph, DAG) для определения порядка выполнения задач в многомодульных проектах.
Task Avoidance:
Gradle использует инкрементальную сборку, пропуская задачи, чьи входные/выходные данные не изменились (up-to-date checks).
Пример:
tasks.named('compileJava') {
inputs.files('src/main/java')
outputs.dir('build/classes/java/main')
}
В памяти: Gradle хранит хэши входов/выходов в памяти и ~/.gradle/caches, добавляя небольшой overhead (10-50 МБ).
Parallel Build:
Gradle поддерживает параллельное выполнение задач с флагом --parallel:
./gradlew build --parallel
#Java #middle #Gradle #Task #include_projects
👍2
August 4
Раздел 4: Управляющие конструкции
Условные операторы. if / else в Java
Условные операторы if и else в Java позволяют выполнять разные части кода в зависимости от определенных условий. Они являются основой для управления потоком программы, позволяя принимать решения на основе значений переменных или выражений.
1. Что такое if и else в Java?
if и else — это ключевые слова в Java, которые используются для выполнения кода, если определенное условие истинно (true) или ложно (false). Они помогают программе выбирать, какой код запускать в зависимости от ситуации.
Зачем нужны if и else?
Принятие решений: Например, проверять, достаточно ли у пользователя денег для покупки.
Управление потоком: Позволяют программе выполнять разные действия в разных случаях.
Читаемость: Делают код понятным, показывая, какие действия зависят от условий.
Гибкость: Позволяют писать программы, которые реагируют на разные входные данные.
2. Синтаксис if и else
if проверяет условие, и если оно истинно (true), выполняется блок кода. else указывает, что делать, если условие ложно (false). Условие — это выражение, которое возвращает boolean (true или false).
Общий синтаксис:
Простой пример:
Вариации синтаксиса:
Простой if (без else):
Многоуровневый if-else (else if):
Однострочный if (без фигурных скобок, если только одна команда):
Примечания к синтаксису:
Условие в скобках должно возвращать boolean (true или false).
Фигурные скобки {} обязательны, если блок кода содержит больше одной строки.
else и else if необязательны.
3. Типы конструкций if / else
3.1. Простой if
Используется, когда нужно выполнить код только при истинном условии.
Пример:
3.2. if с else
Выполняет один блок кода, если условие истинно, и другой — если ложно.
Пример:
3.3. if с else if
Позволяет проверять несколько условий последовательно.
Пример:
3.4. Вложенные if
if внутри другого if для более сложных проверок.
Пример:
4. Правильное применение if / else
Чтобы писать понятный и эффективный код с if и else, следуйте этим рекомендациям:
4.1. Простота условий
Пишите простые и понятные условия. Разбивайте сложные выражения на переменные.
Пример:
#Java #для_новичков #beginner #if #else
Условные операторы. if / else в Java
Условные операторы if и else в Java позволяют выполнять разные части кода в зависимости от определенных условий. Они являются основой для управления потоком программы, позволяя принимать решения на основе значений переменных или выражений.
1. Что такое if и else в Java?
if и else — это ключевые слова в Java, которые используются для выполнения кода, если определенное условие истинно (true) или ложно (false). Они помогают программе выбирать, какой код запускать в зависимости от ситуации.
Зачем нужны if и else?
Принятие решений: Например, проверять, достаточно ли у пользователя денег для покупки.
Управление потоком: Позволяют программе выполнять разные действия в разных случаях.
Читаемость: Делают код понятным, показывая, какие действия зависят от условий.
Гибкость: Позволяют писать программы, которые реагируют на разные входные данные.
2. Синтаксис if и else
if проверяет условие, и если оно истинно (true), выполняется блок кода. else указывает, что делать, если условие ложно (false). Условие — это выражение, которое возвращает boolean (true или false).
Общий синтаксис:
if (условие) {
// Код, который выполняется, если условие истинно
} else {
// Код, который выполняется, если условие ложно
}
Простой пример:
int age = 18;
if (age >= 18) {
System.out.println("Вы взрослый!");
} else {
System.out.println("Вы несовершеннолетний!");
}
Вывод: Вы взрослый!
Вариации синтаксиса:
Простой if (без else):
if (age >= 18) {
System.out.println("Вы можете голосовать!");
}
Многоуровневый if-else (else if):
int score = 85;
if (score >= 90) {
System.out.println("Отлично!");
} else if (score >= 70) {
System.out.println("Хорошо!");
} else {
System.out.println("Попробуйте еще раз!");
}
Вывод: Хорошо!
Однострочный if (без фигурных скобок, если только одна команда):
if (age >= 18) System.out.println("Вы взрослый!");
Примечания к синтаксису:
Условие в скобках должно возвращать boolean (true или false).
Фигурные скобки {} обязательны, если блок кода содержит больше одной строки.
else и else if необязательны.
3. Типы конструкций if / else
3.1. Простой if
Используется, когда нужно выполнить код только при истинном условии.
Пример:
int temperature = 25;
if (temperature > 20) {
System.out.println("На улице тепло!");
}
Вывод: На улице тепло!
3.2. if с else
Выполняет один блок кода, если условие истинно, и другой — если ложно.
Пример:
int number = 10;
if (number % 2 == 0) {
System.out.println("Число четное!");
} else {
System.out.println("Число нечетное!");
}
Вывод: Число четное!
3.3. if с else if
Позволяет проверять несколько условий последовательно.
Пример:
int grade = 75;
if (grade >= 90) {
System.out.println("Оценка: A");
} else if (grade >= 80) {
System.out.println("Оценка: B");
} else if (grade >= 70) {
System.out.println("Оценка: C");
} else {
System.out.println("Оценка: D");
}
Вывод: Оценка: C
3.4. Вложенные if
if внутри другого if для более сложных проверок.
Пример:
int age = 20;
boolean hasID = true;
if (age >= 18) {
if (hasID) {
System.out.println("Можно войти!");
} else {
System.out.println("Нужен документ!");
}
} else {
System.out.println("Слишком молод!");
}
Вывод: Можно войти!
4. Правильное применение if / else
Чтобы писать понятный и эффективный код с if и else, следуйте этим рекомендациям:
4.1. Простота условий
Пишите простые и понятные условия. Разбивайте сложные выражения на переменные.
Пример:
// Плохо: сложное условие
if (score >= 70 && score <= 100 && isExamPassed) {
System.out.println("Экзамен сдан!");
}
// Хорошо: разбиваем на части
boolean isValidScore = score >= 70 && score <= 100;
if (isValidScore && isExamPassed) {
System.out.println("Экзамен сдан!");
}
#Java #для_новичков #beginner #if #else
👍3
August 5
4.2. Избегайте лишних условий
Не проверяйте условия, которые можно упростить или убрать.
Пример:
4.3. Используйте else if для взаимоисключающих условий
Если условия взаимосвязаны, используйте else if, чтобы не проверять лишние условия.
Пример:
4.4. Избегайте глубоких вложений
Слишком много вложенных if делают код сложным для чтения. Используйте переменные или методы для упрощения.
Пример:
4.5. Проверяйте на null
При работе с объектами всегда проверяйте на null, чтобы избежать NullPointerException.
Пример:
5. Назначение if / else
if и else выполняют важные функции в программировании:
5.1. Управление потоком
Позволяют программе выбирать, какой код выполнять, в зависимости от условий.
5.2. Обработка разных случаев
Помогают обрабатывать разные сценарии, например, успех или ошибку.
5.3. Улучшение читаемости
Делают логику программы понятной, показывая, как данные влияют на поведение.
5.4. Гибкость
Позволяют писать код, который адаптируется к разным входным данным.
6. Работа if / else под капотом
Понимание, как if и else работают в JVM, поможет писать более эффективный код.
6.1. Компиляция в байт-код
Компилятор Java (javac) переводит if и else в инструкции условного перехода в байт-коде.
Условие в if преобразуется в сравнение (например, if_icmpgt для сравнения чисел), а затем JVM решает, какой блок кода выполнить.
Пример:
Байт-код (упрощенно):
6.2. Память и стек
Стек операндов: Условие if вычисляется в стеке операндов JVM. Например, для x > 0 JVM загружает x и 0, сравнивает их и сохраняет результат (true или false).
Локальные переменные: Переменные, используемые в условии (например, x), хранятся в стеке вызовов.
Куча: Если в условии используются объекты (например, name != null), они находятся в куче, а их ссылки — в стеке.
6.3. Оптимизация в JVM
JIT-компиляция: JIT-компилятор может оптимизировать if/else, встраивая часто используемые условия в машинный код.
Короткое замыкание: Если условие в if использует логические операторы (&&, ||), JVM пропускает ненужные вычисления.
Константные условия: Если условие всегда true или false (например, if (true)), компилятор может убрать ненужный код.
Пример оптимизации:
6.4. Ошибки в памяти
Глубокие вложенности: Слишком много вложенных if увеличивают глубину стека вызовов, но это редко вызывает проблемы.
NullPointerException: Работа с объектами без проверки на null в условии может привести к ошибке.
Неэффективные условия: Сложные условия, такие как a > b && b > c && c > d, могут замедлить выполнение, если не оптимизированы.
Пример ошибки:
#Java #для_новичков #beginner #if #else
Не проверяйте условия, которые можно упростить или убрать.
Пример:
// Плохо: избыточное условие
if (isActive == true) {
System.out.println("Активен!");
}
// Хорошо: упрощение
if (isActive) {
System.out.println("Активен!");
}
4.3. Используйте else if для взаимоисключающих условий
Если условия взаимосвязаны, используйте else if, чтобы не проверять лишние условия.
Пример:
int x = 5;
if (x > 0) {
System.out.println("Положительное");
} else if (x < 0) {
System.out.println("Отрицательное");
} else {
System.out.println("Ноль");
}
4.4. Избегайте глубоких вложений
Слишком много вложенных if делают код сложным для чтения. Используйте переменные или методы для упрощения.
Пример:
// Плохо: глубокая вложенность
if (age >= 18) {
if (hasTicket) {
if (isVenueOpen) {
System.out.println("Вход разрешен!");
}
}
}
// Хорошо: упрощение
boolean canEnter = age >= 18 && hasTicket && isVenueOpen;
if (canEnter) {
System.out.println("Вход разрешен!");
}
4.5. Проверяйте на null
При работе с объектами всегда проверяйте на null, чтобы избежать NullPointerException.
Пример:
String name = null;
if (name != null) {
System.out.println("Имя: " + name);
} else {
System.out.println("Имя не задано!");
}
5. Назначение if / else
if и else выполняют важные функции в программировании:
5.1. Управление потоком
Позволяют программе выбирать, какой код выполнять, в зависимости от условий.
5.2. Обработка разных случаев
Помогают обрабатывать разные сценарии, например, успех или ошибку.
5.3. Улучшение читаемости
Делают логику программы понятной, показывая, как данные влияют на поведение.
5.4. Гибкость
Позволяют писать код, который адаптируется к разным входным данным.
6. Работа if / else под капотом
Понимание, как if и else работают в JVM, поможет писать более эффективный код.
6.1. Компиляция в байт-код
Компилятор Java (javac) переводит if и else в инструкции условного перехода в байт-коде.
Условие в if преобразуется в сравнение (например, if_icmpgt для сравнения чисел), а затем JVM решает, какой блок кода выполнить.
Пример:
int x = 5;
if (x > 0) {
System.out.println("Положительное");
}
Байт-код (упрощенно):
iload x // Загружаем x в стек
ifgt label // Если x > 0, перейти к метке
return // Иначе выйти
label:
invokevirtual // Вызов System.out.println
return
6.2. Память и стек
Стек операндов: Условие if вычисляется в стеке операндов JVM. Например, для x > 0 JVM загружает x и 0, сравнивает их и сохраняет результат (true или false).
Локальные переменные: Переменные, используемые в условии (например, x), хранятся в стеке вызовов.
Куча: Если в условии используются объекты (например, name != null), они находятся в куче, а их ссылки — в стеке.
6.3. Оптимизация в JVM
JIT-компиляция: JIT-компилятор может оптимизировать if/else, встраивая часто используемые условия в машинный код.
Короткое замыкание: Если условие в if использует логические операторы (&&, ||), JVM пропускает ненужные вычисления.
Константные условия: Если условие всегда true или false (например, if (true)), компилятор может убрать ненужный код.
Пример оптимизации:
if (false) {
System.out.println("Никогда не выполнится");
}
Компилятор полностью удалит этот блок из байт-кода.
6.4. Ошибки в памяти
Глубокие вложенности: Слишком много вложенных if увеличивают глубину стека вызовов, но это редко вызывает проблемы.
NullPointerException: Работа с объектами без проверки на null в условии может привести к ошибке.
Неэффективные условия: Сложные условия, такие как a > b && b > c && c > d, могут замедлить выполнение, если не оптимизированы.
Пример ошибки:
String text = null;
if (text.equals("Hello")) { // Ошибка: NullPointerException
System.out.println("Совпадение!");
}
Исправление:
if (text != null && text.equals("Hello")) {
System.out.println("Совпадение!");
}
#Java #для_новичков #beginner #if #else
👍3
August 5
Конфигурация, профили, параметры и свойства в Gradle: Управление сборкой
Системные свойства, переменные окружения
Gradle поддерживает настройку через системные свойства и переменные окружения, которые задают параметры JVM и сборки.
Системные свойства:
Задаются через -D в командной строке или в gradle.properties.
Пример:
Доступ в build.gradle:
Переменные окружения:
Доступны через System.getenv().
Пример:
Полезны для передачи внешних параметров (например, API-ключи, пути).
Приоритет:
Системные свойства (-D) > gradle.properties > переменные окружения.
Нюансы:
Используйте переменные окружения для чувствительных данных с осторожностью, предпочтительно шифруйте их.
Проверяйте наличие переменных:
gradle.properties: Project-level, global (~/.gradle)
Файл gradle.properties используется для определения свойств Gradle, JVM и проекта.
Project-level (projectDir/gradle.properties):
Применяется к конкретному проекту.
Пример:
Global (~/.gradle/gradle.properties):
Применяется ко всем проектам на машине.
Пример:
Использование:
Свойства доступны через project.property:println project.version
Нюансы:
Храните чувствительные данные (например, ключи API) в ~/.gradle/gradle.properties с ограниченными правами (chmod 600).
Используйте для централизованных настроек, таких как JVM-память или включение кэширования.
Использование -P, -D, --info, --debug, --scan
Gradle предоставляет командные параметры для настройки и отладки сборки.
-P:
Задает свойства проекта.
Пример:
Доступ:
-D:
Задает системные свойства JVM.
Пример:
--info:
Выводит информационные логи.
Пример:
--debug:
Выводит подробные логи для отладки.
Пример:
--scan:
Генерирует Build Scan для анализа сборки.
Пример:
Требует настройки плагина:
Нюансы:
Используйте --scan для анализа производительности и выявления узких мест.
Параметры -P и -D имеют приоритет над gradle.properties.
Lazy vs Eager Configuration
Gradle поддерживает два подхода к конфигурации: eager (немедленная) и lazy (ленивая).
Eager Configuration:
Вычисления выполняются на фазе конфигурации, даже если задача не выполняется.
Пример:
Lazy Configuration:
Вычисления откладываются до фазы выполнения с использованием Provider или Property.
Пример:
#Java #middle #Gradle #Task #deploy
Системные свойства, переменные окружения
Gradle поддерживает настройку через системные свойства и переменные окружения, которые задают параметры JVM и сборки.
Системные свойства:
Задаются через -D в командной строке или в gradle.properties.
Пример:
./gradlew build -Dorg.gradle.jvmargs=-Xmx2048m
Доступ в build.gradle:
println System.getProperty('org.gradle.jvmargs')
Переменные окружения:
Доступны через System.getenv().
Пример:
println System.getenv('JAVA_HOME')
Полезны для передачи внешних параметров (например, API-ключи, пути).
Приоритет:
Системные свойства (-D) > gradle.properties > переменные окружения.
В памяти: Системные свойства и переменные окружения загружаются как часть конфигурации JVM, добавляя минимальный overhead (менее 10 МБ). Gradle кэширует их значения в модели проекта.
Нюансы:
Используйте переменные окружения для чувствительных данных с осторожностью, предпочтительно шифруйте их.
Проверяйте наличие переменных:
if (System.getenv('CI')) {
println 'Running in CI environment'
}
gradle.properties: Project-level, global (~/.gradle)
Файл gradle.properties используется для определения свойств Gradle, JVM и проекта.
Project-level (projectDir/gradle.properties):
Применяется к конкретному проекту.
Пример:
version=1.0.0
org.gradle.jvmargs=-Xmx2048m
org.gradle.parallel=true
Global (~/.gradle/gradle.properties):
Применяется ко всем проектам на машине.
Пример:
org.gradle.caching=true
org.gradle.jvmargs=-Xmx4096m
Использование:
Свойства доступны через project.property:println project.version
В памяти: Свойства загружаются как часть модели проекта на фазе инициализации, добавляя минимальный overhead (5-10 МБ). Глобальный gradle.properties парсится для всех сборок, увеличивая время инициализации.
Нюансы:
Храните чувствительные данные (например, ключи API) в ~/.gradle/gradle.properties с ограниченными правами (chmod 600).
Используйте для централизованных настроек, таких как JVM-память или включение кэширования.
Использование -P, -D, --info, --debug, --scan
Gradle предоставляет командные параметры для настройки и отладки сборки.
-P:
Задает свойства проекта.
Пример:
./gradlew build -PmyProperty=value
Доступ:
println project.hasProperty('myProperty') ? project.myProperty : 'default'
-D:
Задает системные свойства JVM.
Пример:
./gradlew build -Dorg.gradle.jvmargs=-Xmx2048m
--info:
Выводит информационные логи.
Пример:
./gradlew build --info
--debug:
Выводит подробные логи для отладки.
Пример:
./gradlew build --debug
--scan:
Генерирует Build Scan для анализа сборки.
Пример:
./gradlew build --scan
Требует настройки плагина:
plugins {
id 'com.gradle.build-scan' version '3.17.4'
}
buildScan {
termsOfServiceUrl = 'https://gradle.com/terms-of-service'
termsOfServiceAgree = 'yes'
}
В памяти: Параметры -P и -D добавляют свойства в модель проекта или JVM, минимально влияя на память. Логи --info и --debug увеличивают объем вывода, а --scan загружает метаданные сборки в память (50-100 МБ для крупных проектов).
Нюансы:
Используйте --scan для анализа производительности и выявления узких мест.
Параметры -P и -D имеют приоритет над gradle.properties.
Lazy vs Eager Configuration
Gradle поддерживает два подхода к конфигурации: eager (немедленная) и lazy (ленивая).
Eager Configuration:
Вычисления выполняются на фазе конфигурации, даже если задача не выполняется.
Пример:
task example {
def value = computeExpensiveValue()
doLast {
println value
}
}
Lazy Configuration:
Вычисления откладываются до фазы выполнения с использованием Provider или Property.
Пример:
task example {
def value = providers.provider { computeExpensiveValue() }
doLast {
println value.get()
}
}
В памяти: Ленивая конфигурация снижает потребление памяти на фазе конфигурации, так как значения вычисляются только при необходимости. Eager-конфигурация загружает все значения сразу, увеличивая overhead (до 100-200 МБ для сложных скриптов).
#Java #middle #Gradle #Task #deploy
👍1
August 6
Конфигурационные API: Provider, Property, ListProperty
Gradle предоставляет API для ленивой конфигурации, что улучшает производительность и гибкость.
Provider:
Интерфейс для ленивых значений, вычисляемых при необходимости.
Пример:
Property:
Для управления одиночными значениями.
Пример:
ListProperty:
Для управления списками значений.
Пример:
Kotlin DSL:
Нюансы:
Используйте Provider для динамических вычислений.
Property и ListProperty обеспечивают строгую типизацию, особенно в Kotlin DSL.
Ext-переменные (ext {})
Блок ext позволяет определять пользовательские свойства для проекта или объектов.
Пример:
Kotlin DSL:
Использование:
Для хранения общих констант (например, версий зависимостей).
Для передачи данных между задачами или модулями.
Нюансы:
Избегайте чрезмерного использования ext в пользу Version Catalog для версий зависимостей.
Используйте для кастомных настроек, не связанных с зависимостями.
buildSrc/ директория
Директория buildSrc позволяет определять кастомную логику (плагины, задачи) в проекте.
Структура:
Пример плагина (buildSrc/src/main/groovy/com/example/MyPlugin.groovy):
Настройка (buildSrc/build.gradle):
Использование:
Kotlin DSL:
Создайте buildSrc/src/main/kotlin/com/example/MyPlugin.kt:
Настройте buildSrc/build.gradle.kts:
Нюансы:
buildSrc автоматически доступен всем модулям проекта.
Используйте для кастомных задач и плагинов, специфичных для проекта.
Для повторно используемых плагинов предпочтительнее создавать отдельный проект и публиковать в репозиторий.
#Java #middle #Gradle #Task #deploy
Gradle предоставляет API для ленивой конфигурации, что улучшает производительность и гибкость.
Provider:
Интерфейс для ленивых значений, вычисляемых при необходимости.
Пример:
def versionProvider = providers.provider { project.version }
task printVersion {
doLast {
println "Version: ${versionProvider.get()}"
}
}
Property:
Для управления одиночными значениями.
Пример:
task example {
def outputFile = objects.property(String)
outputFile.set('build/output.txt')
doLast {
println "Output: ${outputFile.get()}"
}
}
ListProperty:
Для управления списками значений.
Пример:
task example {
def files = objects.listProperty(String)
files.set(['file1.txt', 'file2.txt'])
doLast {
files.get().each { println it }
}
}
Kotlin DSL:
tasks.register("example") {
val outputFile = objects.property<String>()
outputFile.set("build/output.txt")
doLast {
println("Output: ${outputFile.get()}")
}
}
В памяти: Provider, Property и ListProperty хранят ссылки на значения, а не сами значения, минимизируя потребление памяти до фазы выполнения. Это снижает overhead на фазе конфигурации (экономия 10-50 МБ на задачу).
Нюансы:
Используйте Provider для динамических вычислений.
Property и ListProperty обеспечивают строгую типизацию, особенно в Kotlin DSL.
Ext-переменные (ext {})
Блок ext позволяет определять пользовательские свойства для проекта или объектов.
Пример:
ext {
myVersion = '1.0.0'
myProperty = 'value'
}
task printExt {
doLast {
println project.ext.myVersion
}
}
Kotlin DSL:
extra["myVersion"] = "1.0.0"
extra["myProperty"] = "value"
tasks.register("printExt") {
doLast {
println(project.extra["myVersion"])
}
}
Использование:
Для хранения общих констант (например, версий зависимостей).
Для передачи данных между задачами или модулями.
В памяти: ext-переменные хранятся как часть модели проекта, добавляя минимальный overhead (менее 10 МБ). Однако большое количество переменных может усложнить модель.
Нюансы:
Избегайте чрезмерного использования ext в пользу Version Catalog для версий зависимостей.
Используйте для кастомных настроек, не связанных с зависимостями.
buildSrc/ директория
Директория buildSrc позволяет определять кастомную логику (плагины, задачи) в проекте.
Структура:
my-project/
├── buildSrc/
│ ├── src/main/groovy/com/example/MyPlugin.groovy
│ ├── build.gradle
├── build.gradle
├── settings.gradle
Пример плагина (buildSrc/src/main/groovy/com/example/MyPlugin.groovy):
package com.example
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.tasks.register('myTask') {
doLast {
println 'Hello from buildSrc plugin!'
}
}
}
}
Настройка (buildSrc/build.gradle):
plugins {
id 'groovy'
}
Использование:
apply plugin: 'com.example.my-plugin'
Kotlin DSL:
Создайте buildSrc/src/main/kotlin/com/example/MyPlugin.kt:
package com.example
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("myTask") {
doLast {
println("Hello from buildSrc plugin!")
}
}
}
}
Настройте buildSrc/build.gradle.kts:
plugins {
`kotlin-dsl`
}
В памяти: buildSrc компилируется как отдельный проект, добавляя собственный classpath и зависимости в JVM. Это увеличивает потребление памяти (100-200 МБ), особенно если buildSrc включает сложные плагины.
Нюансы:
buildSrc автоматически доступен всем модулям проекта.
Используйте для кастомных задач и плагинов, специфичных для проекта.
Для повторно используемых плагинов предпочтительнее создавать отдельный проект и публиковать в репозиторий.
#Java #middle #Gradle #Task #deploy
👍1
August 6
Shared Build Logic через init-скрипты
Init-скрипты позволяют задавать глобальную логику, применяемую ко всем Gradle-сборкам.
Настройка:
Создайте файл ~/.gradle/init.d/my-init.gradle:
Kotlin DSL (~/.gradle/init.d/my-init.gradle.kts):
Использование:
Init-скрипты автоматически применяются ко всем проектам.
Можно указать скрипт явно:
Нюансы:
Используйте для глобальных настроек (например, репозитории, JVM-параметры).
Избегайте сложной логики, чтобы не замедлять инициализацию.
Gradle Wrapper (gradlew): настройка и версионирование
Gradle Wrapper (gradlew) — это скрипт, обеспечивающий воспроизводимость сборки с фиксированной версией Gradle.
Настройка:
Сгенерируйте Wrapper:
Создает файлы:
gradle-wrapper.properties:
Использование:
Запускайте сборку через Wrapper:
Wrapper загружает указанную версию Gradle, если она отсутствует в ~/.gradle/wrapper.
Версионирование:
Обновите версию:
Используйте конкретную версию для согласованности в CI/CD.
Нюансы:
Всегда включайте Wrapper в репозиторий для воспроизводимости.
Проверяйте целостность distributionUrl для безопасности (используйте HTTPS).
Настройте прокси в gradle-wrapper.properties, если требуется:systemProp.http.proxyHost=proxy.example.com
systemProp.http.proxyPort=8080
#Java #middle #Gradle #Task #deploy
Init-скрипты позволяют задавать глобальную логику, применяемую ко всем Gradle-сборкам.
Настройка:
Создайте файл ~/.gradle/init.d/my-init.gradle:
allprojects {
repositories {
mavenCentral()
}
}
gradle.projectsLoaded {
rootProject.tasks.register('globalTask') {
doLast {
println 'Global task from init script'
}
}
}
Kotlin DSL (~/.gradle/init.d/my-init.gradle.kts):
allprojects {
repositories {
mavenCentral()
}
}
gradle.projectsLoaded {
rootProject.tasks.register("globalTask") {
doLast {
println("Global task from init script")
}
}
}
Использование:
Init-скрипты автоматически применяются ко всем проектам.
Можно указать скрипт явно:
./gradlew build --init-script my-init.gradle
В памяти: Init-скрипты загружаются на фазе инициализации, добавляя задачи и конфигурации в модель проекта. Это увеличивает overhead (10-50 МБ), но распределяется между всеми сборками.
Нюансы:
Используйте для глобальных настроек (например, репозитории, JVM-параметры).
Избегайте сложной логики, чтобы не замедлять инициализацию.
Gradle Wrapper (gradlew): настройка и версионирование
Gradle Wrapper (gradlew) — это скрипт, обеспечивающий воспроизводимость сборки с фиксированной версией Gradle.
Настройка:
Сгенерируйте Wrapper:
gradle wrapper --gradle-version 8.1
Создает файлы:
my-project/
├── gradlew
├── gradlew.bat
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ ├── gradle-wrapper.properties
gradle-wrapper.properties:
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
Использование:
Запускайте сборку через Wrapper:
./gradlew build
Wrapper загружает указанную версию Gradle, если она отсутствует в ~/.gradle/wrapper.
Версионирование:
Обновите версию:
./gradlew wrapper --gradle-version 8.2
Используйте конкретную версию для согласованности в CI/CD.
В памяти: Wrapper загружает Gradle в отдельный JVM-процесс, добавляя базовый overhead (200-300 МБ). Кэширование дистрибутива в ~/.gradle/wrapper минимизирует сетевые запросы.
Нюансы:
Всегда включайте Wrapper в репозиторий для воспроизводимости.
Проверяйте целостность distributionUrl для безопасности (используйте HTTPS).
Настройте прокси в gradle-wrapper.properties, если требуется:systemProp.http.proxyHost=proxy.example.com
systemProp.http.proxyPort=8080
#Java #middle #Gradle #Task #deploy
👍1
August 6
Switch/case в Java
Оператор switch в Java позволяет выбирать, какой код выполнять, в зависимости от значения переменной. Он часто используется вместо множества if-else условий, когда нужно проверить одно значение на совпадение с несколькими вариантами. С Java 14 switch также поддерживает switch expressions (выражения), которые делают код короче и удобнее.
1. Что такое switch в Java?
switch — это условный оператор, который проверяет значение переменной и выполняет соответствующий блок кода в зависимости от совпадения с заданными вариантами (case). Switch expressions — это улучшенная версия switch, которая позволяет возвращать значение и писать более компактный код.
Зачем нужен switch?
Простота выбора: Удобен, когда нужно выбрать действие из нескольких вариантов.
Читаемость: Делает код чище, чем длинная цепочка if-else.
Гибкость: Поддерживает разные типы данных и новые возможности (switch expressions).
Эффективность: В некоторых случаях работает быстрее, чем if-else.
2. Синтаксис switch
2.1. Классический switch (оператор)
Классический switch выполняет блок кода для совпадающего case и использует break, чтобы выйти из конструкции.
Синтаксис:
Пример:
2.2. Switch Expression (с Java 14)
Switch expressions позволяют возвращать значение и использовать стрелочный синтаксис (->) для упрощения кода. Они не требуют break, так как автоматически завершают выполнение.
Синтаксис:
Пример:
Примечания к синтаксису:
Поддерживаемые типы:
Классический switch: byte, short, int, char, String, enum.
Switch expression: Те же типы + возможность возвращать значения.
break: В классическом switch нужен, чтобы избежать "проваливания" (fall-through) в следующий case.
default: Выполняется, если ни один case не совпал. Необязателен, но рекомендуется.
Стрелочный синтаксис (->): Используется в switch expressions для компактности.
#Java #для_новичков #beginner #switch_case
Оператор switch в Java позволяет выбирать, какой код выполнять, в зависимости от значения переменной. Он часто используется вместо множества if-else условий, когда нужно проверить одно значение на совпадение с несколькими вариантами. С Java 14 switch также поддерживает switch expressions (выражения), которые делают код короче и удобнее.
1. Что такое switch в Java?
switch — это условный оператор, который проверяет значение переменной и выполняет соответствующий блок кода в зависимости от совпадения с заданными вариантами (case). Switch expressions — это улучшенная версия switch, которая позволяет возвращать значение и писать более компактный код.
Зачем нужен switch?
Простота выбора: Удобен, когда нужно выбрать действие из нескольких вариантов.
Читаемость: Делает код чище, чем длинная цепочка if-else.
Гибкость: Поддерживает разные типы данных и новые возможности (switch expressions).
Эффективность: В некоторых случаях работает быстрее, чем if-else.
2. Синтаксис switch
2.1. Классический switch (оператор)
Классический switch выполняет блок кода для совпадающего case и использует break, чтобы выйти из конструкции.
Синтаксис:
switch (выражение) {
case значение1:
// Код для значения1
break;
case значение2:
// Код для значения2
break;
default:
// Код, если ни один case не совпал
}
Пример:
int day = 3;
switch (day) {
case 1:
System.out.println("Понедельник");
break;
case 2:
System.out.println("Вторник");
break;
case 3:
System.out.println("Среда");
break;
default:
System.out.println("Другой день");
}
Вывод: Среда
2.2. Switch Expression (с Java 14)
Switch expressions позволяют возвращать значение и использовать стрелочный синтаксис (->) для упрощения кода. Они не требуют break, так как автоматически завершают выполнение.
Синтаксис:
switch (выражение) {
case значение1 -> результат1;
case значение2 -> результат2;
default -> результат_по_умолчанию;
}
Пример:
int day = 3;
String dayName = switch (day) {
case 1 -> "Понедельник";
case 2 -> "Вторник";
case 3 -> "Среда";
default -> "Другой день";
};
System.out.println(dayName);
Вывод: Среда
Примечания к синтаксису:
Поддерживаемые типы:
Классический switch: byte, short, int, char, String, enum.
Switch expression: Те же типы + возможность возвращать значения.
break: В классическом switch нужен, чтобы избежать "проваливания" (fall-through) в следующий case.
default: Выполняется, если ни один case не совпал. Необязателен, но рекомендуется.
Стрелочный синтаксис (->): Используется в switch expressions для компактности.
#Java #для_новичков #beginner #switch_case
👍3
August 7