Java for Beginner
715 subscribers
646 photos
173 videos
12 files
1.01K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Обзор JSON Web Tokens (JWT) в Java

JSON Web Tokens (JWT) — это стандарт для создания компактных, самодостаточных токенов, используемых для безопасной передачи информации между сторонами в виде JSON-объекта. JWT широко применяется для аутентификации и авторизации в веб-приложениях, особенно в REST API. В Java экосистема библиотек, таких как jjwt и java-jwt, предоставляет мощные инструменты для работы с JWT.


Структура JWT

JWT состоит из трех основных частей, разделенных точками (.):
Header — содержит метаданные о токене, такие как тип (typ: JWT) и алгоритм подписи (например, alg: HS256 или RS256).
Payload — содержит полезные данные (claims), такие как идентификатор пользователя (sub), время выпуска (iat), срок действия (exp) и кастомные данные.
Signature — подпись, созданная с использованием секретного ключа или пары ключей (для асимметричных алгоритмов), для проверки целостности и подлинности токена.


Каждая часть кодируется в Base64Url и объединяется в строку вида: Header.Payload.Signature.


Пример JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c


Использование JWT в Java

Наиболее популярная библиотека для работы с JWT в Java — это io.jsonwebtoken:jjwt. Она поддерживает создание, парсинг и валидацию токенов с использованием различных алгоритмов подписи.

Установка зависимости

Добавьте зависимость в pom.xml для Maven:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.6</version>
</dependency>


Создание JWT

Пример создания JWT с использованием HMAC-SHA256:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;

public class JwtExample {
public static String createJwt(String subject, long ttlMillis) {
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); // Генерация ключа
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + ttlMillis))
.signWith(key)
.compact();
}
}


Парсинг и валидация JWT


Пример проверки и извлечения данных из токена:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;

public class JwtExample {
public static Claims parseJwt(String jwt, String secret) {
Key key = Keys.hmacShaKeyFor(secret.getBytes());
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(jwt)
.getBody();
}
}



Управление памятью и производительность

1. Размер токена и влияние на память

JWT компактны, но их размер зависит от содержимого payload и используемого алгоритма.

Например:
HMAC-SHA256 (симметричный) создает токены меньшего размера, так как используется один ключ.
RSA/ECDSA (асимметричные алгоритмы) увеличивают размер подписи, что может быть заметно при большом количестве токенов.
Payload с большим количеством claims (например, сложные JSON-объекты) увеличивает объем токена, что влияет на объем передаваемых данных и потребление памяти.


Рекомендации:
Минимизируйте количество claims в payload. Храните только необходимые данные, такие как sub, iat, exp.
Используйте сжатие (например, JWS Compression с DEF в jjwt) для уменьшения размера токена, если это допустимо.



#Java #middle #on_request #Jwt
August 2
2. Кэширование ключей

Создание и парсинг ключей (особенно для асимметричных алгоритмов, таких как RSA) — дорогостоящая операция с точки зрения CPU и памяти.

Например:
Генерация RSA-ключей требует значительных вычислительных ресурсов.
Повторное декодирование Base64-строк для ключей при каждом запросе увеличивает нагрузку.


Рекомендации:
Кэшируйте ключи в памяти (например, в ConcurrentHashMap или с использованием Spring Cache).
Используйте пул ключей для многопоточных приложений, чтобы избежать создания новых экземпляров Key для каждого запроса.


Пример кэширования ключа:

private static final Key SIGNING_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);

public static String createJwt(String subject, long ttlMillis) {
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + ttlMillis))
.signWith(SIGNING_KEY)
.compact();
}


3. Многопоточность

Библиотека jjwt потокобезопасна, но неправильное управление ключами или токенами может привести к проблемам.


Например:
Неправильное использование ThreadLocal для хранения временных ключей может привести к утечкам памяти.
Частое создание JwtParser без повторного использования увеличивает потребление ресурсов.


Рекомендации:
Создавайте и конфигурируйте JwtParserBuilder один раз и переиспользуйте его.
Используйте ThreadLocal только для временных данных, которые очищаются после обработки запроса.


Пример потокобезопасного парсера:
private static final JwtParser JWT_PARSER = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor("secret".getBytes()))
.build();

public static Claims parseJwt(String jwt) {
return JWT_PARSER.parseClaimsJws(jwt).getBody();
}



Нюансы безопасности

1. Выбор алгоритма подписи
HMAC-SHA (HS256, HS384, HS512): Быстрее, но требует безопасного хранения секретного ключа на всех серверах. Утечка ключа компрометирует всю систему.
RSA/ECDSA: Медленнее, но безопаснее, так как публичный ключ используется для проверки, а приватный хранится только на сервере, выдающем токены.
None-алгоритм: Никогда не используйте alg: none, так как это позволяет подделывать токены без подписи.


Рекомендации:
Для микросервисов с централизованным управлением ключами предпочтительнее RSA/ECDSA.

Используйте jjwt с настройкой require("alg"), чтобы предотвратить атаки с изменением алгоритма:
Jwts.parserBuilder()
.require("alg", "RS256")
.setSigningKey(publicKey)
.build();


2. Срок действия токена
Короткий срок действия (exp) снижает риск использования украденных токенов, но увеличивает нагрузку на сервер из-за частого обновления токенов (refresh tokens).

Рекомендации:
Устанавливайте exp в пределах 15-60 минут для access-токенов.
Используйте refresh-токены с более длинным сроком действия и строгим контролем (например, храните их в базе данных с возможностью отзыва).


3. Уязвимости
JWT Header Injection: Атакующий может изменить заголовок, чтобы подменить алгоритм (например, с RS256 на HS256). Всегда проверяйте алгоритм при парсинге.
Weak Keys: Слабые или предсказуемые ключи для HMAC-SHA делают токены уязвимыми для brute-force атак.
Payload Tampering: Если токен не подписан или подпись не проверяется, злоумышленник может изменить payload.


Рекомендации:
Используйте ключи достаточной длины (например, 256 бит для HS256).
Проверяйте подпись токена на каждом запросе.
Включайте jti (JWT ID) для отслеживания и отзыва токенов.



#Java #middle #on_request #Jwt
August 2
Оптимизация и масштабирование

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
August 2
Модульность и многомодульные проекты в Gradle

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
August 4
Sharing Logic между проектами

Совместное использование логики между модулями повышает повторное использование кода и упрощает поддержку.

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
August 4
Сценарии и практики разделения: Core/Api/Impl

Разделение проекта на модули (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
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 {
// Код, который выполняется, если условие ложно
}


Простой пример:
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
August 5
4.2. Избегайте лишних условий
Не проверяйте условия, которые можно упростить или убрать.

Пример:
// Плохо: избыточное условие
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
August 5
Конфигурация, профили, параметры и свойства в Gradle: Управление сборкой

Системные свойства, переменные окружения

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
August 6
Конфигурационные API: Provider, Property, ListProperty

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
August 6