Оглавление:
Статический анализатор Idea Analize
Статический анализатор SonarCube
JPA Entity классы с Kotlin
Unit тестирование
Интеграционное тестирование в проекте
Покрытие тестами
Spring profiles
CORS filter
Тестовый запуск
Создание запускаемого файла и его запуск
Publishing SpringBoot "FAT" jar
Интеграционное тестирование
Примеры тестов httpie
DataJpa tests
RestAssured tests
Нагрузочное тестирование
Micrometer
Prometheus
Пример просмотра использования CPU в Prometheus
Запуск prometheus в docker
Docker
Grafana
Кеширование
Сборка Jenkins
Nexus
Просмотр ресурсов с помощью Java Mission Control
Логирование
Использование "ChatGPT-EasyCode" в Idea
Использование "ChatGPT-EasyCode" в VSCode
Частный параметр конфигурации в application.yaml
Переопределение значения переменных application.yaml
Цель
Cоздать небольшое приложение на Kotlin с использованием Spring Boot. Справочник товаров со следующими типами:
- Настольные компьютеры
- Ноутбуки
- Мониторы
- Жесткие диски
Каждый товар имеет следующие свойства:
- номер серии
- производитель
- цена
- количество единиц продукции на складе
Дополнительные свойства:
- Настольные компьютеры имеют форм-фактор: десктопы, неттопы, моноблоки
- Ноутбуки подразделяются по размеру: 13, 14, 15, 17 дюймовые
- Мониторы имеют диагональ
- Жесткие диски имеют объем
Необходимо реализовать back-end приложение, которое имеет RESTful HTTP методы выполняющие:
- Добавление товара
- Редактирование товара
- Просмотр всех существующих товаров по типу
- Просмотр товара по идентификатору
В качестве базы данных использовать in memory database, например H2.
Результат: https://github.com/cherepakhin/shop_kotlin.
Статический анализатор Idea Analize
Проверка кода. Вызывается из контекстного меню Analize - Inspect Code.
Статический анализатор SonarCube
https://github.com/cherepakhin/shop_kotlin/blob/dev/doc/sonarqube/use_sonarcube.md
JPA Entity классы с Kotlin
Источник: https://habr.com/ru/companies/haulmont/articles/572574/
Примеры из источника: https://github.com/Klimenkoob/spring-kotlin-hibernate
Рекомендации:
- Data class not recommended for JPA Entity . Warning in Idea. Почему не использовать Data-классы? Потому что они финальны сами по себе, имеют по всем полям определенные equals, hashCode и toString. А это недопустимо в связке с Hibernate.
- Явно помечать ключевым словом open все Entity. Согласно спецификации JPA, все классы и свойства, связанные с JPA, не должны быть final. В отличие от Java, в Kotlin классы, свойства и методы по умолчанию final. Поэтому их нужно явно помечать ключевым словом open.
- Явно определять hashCode(), equals(), toString(). При определении внимание на lazy поля (как и в Java).
- Добавить nullable = false
@ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "client_id", nullable = false) lateinit var client: Client
- Kotlin классы по умолчанию final. Для Entity это проблема. Можно применить plugin "allopen":
plugins { kotlin("plugin.allopen") } ... allopen { annotation("javax.persistence.Entity") }
Unit тестирование
./gradlew test
Примеры отбора тестов:
./gradlew test --tests '*EntityTest' ./gradlew test --tests '*Rest*' ./gradlew test --tests ProductDTOTest ./gradlew test --tests '*TestIntegration' ./gradlew test --tests '*MockMvcTest'
Интеграционное тестирование в проекте
Вообще, интеграционные тесты должны быть в отдельном проекте (см. https://github.com/cherepakhin/shop_kotlin_restassured_test). В этом проекте оставил только тесты работы с базой данных. Имена интеграционных тестов должны заканчиваться ..TestIntegration. Прогнать все, кроме интеграционных (включены только *Test, исключены *TestIntegration):
./gradlew clean test --tests *Test
Только интеграционные:
./gradlew clean test --tests *TestIntegration* ./gradlew clean test --tests *TestIntegration
Другие примеры:
gradle test --tests org.gradle.SomeTest.someSpecificFeature gradle test --tests *SomeTest.someSpecificFeature gradle test --tests *SomeSpecificTest gradle test --tests all.in.specific.package* gradle test --tests *IntegTest gradle test --tests *IntegTest*ui* gradle test --tests *IntegTest.singleMethod gradle someTestTask --tests *UiTest someOtherTestTask --tests *WebTest*ui
Включено протоколирование тестов build.gradle.kts:
...
tasks.withType<Test> {
...
// Show test log
testLogging {
events("passed", "skipped", "failed")
}
...
}
Вывод в консоль:
... ProductServiceImplMockTest > getByGroupProductN() PASSED ProductServiceIntegrationTest > existByN() PASSED ProductServiceIntegrationTest > notExistByN() PASSED ProductServiceIntegrationTest > checkSortByName_ByDslFilterByName() PASSED ...
с events("standardOut", "started", "passed", "skipped", "failed") логируется вывод в консоль.
Покрытие тестами
Использован jacoco. Отчет формируется при прогоне тестов
./gradlew test jacocoTestReport
Отчет будет в папке build/reports/jacoco/test/html. В отчете НЕТ информации о результатах тестирования, только протестирован участок кода или нет.
Пример отчета по конкретному классу:
Красным или желтым выделены непротестированные участки кода, зеленым протестировано.
Spring profiles
Настроены два Spring профиля: application-prod.yml и application-dev.yml. В этом же файле указан профиль по умолчанию:
spring: profiles: active: dev application: name: shop_kotlin
Запуск с указанием профиля:
java -D"spring.profiles.active=dev" -jar app.jar
или установить env переменную:
SPRING_PROFILES_ACTIVE = dev
Запуск с gradle:
./gradlew bootRun --args='--spring.profiles.active=dev'
или
SPRING_PROFILES_ACTIVE=dev ./gradlew clean bootRun
CORS filter
При выполнении запросов может быть получена ошибка: "Ошибка: "Access to XMLHttpRequest at 'http://localhost:8080/api/hello' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.". Доходчиво описано в https://sysout.ru/nastrojka-cors-v-spring-security/. Суть в двух словах: по умолчанию Spring не позволяет делать кроссдоменные запросы. CORS не нужно отключать, а наборот включить и настроить. Другими словами, нужно настроить разрешения в приложении откуда разрешать запросы. Примеры заголовков:
Access-Control-Allow-Origin: * Access-Control-Allow-Origin: http://localhost:9999
Необходимые настройки сделаны в https://github.com/cherepakhin/shop_kotlin/blob/dev/src/main/kotlin/ru/perm/v/shopkotlin/config/CorsFilter.kt
Тестовый запуск
./gradlew bootRun
Создание запускаемого файла и его запуск
Создание:
./gradlew bootJar
(bootJar не bootRun!!!)
Собранный файл будет в папке ./build/libs/
запуск с RAM 256Мб:
shop_kotlin/$ java -Xmx256M -jar build/libs/shop_kotlin-0.1.20.jar
или так:
shop_kotlin/$ cd build/libs shop_kotlin/build/libs$ java -Xmx256M -jar shop_kotlin-0.1.20.jar
Publishing SpringBoot "FAT" jar
Настройка:
publishing { repositories { maven { url = uri("https://v.perm.ru:8082/repository/ru.perm.v/") isAllowInsecureProtocol = true // for publish to nexus "./gradlew publish" // export NEXUS_CRED_USR=admin // echo $NEXUS_CRED_USR credentials { username = System.getenv("NEXUS_CRED_USR") password = System.getenv("NEXUS_CRED_PSW") } } } publications { create("maven"){ artifact(tasks["bootJar"]) // build and publish bootJar } }
Примеры тестов httpie
Echo запрос для простой проверки работоспособности (:8980/shop_kotlin/ базовый путь проекта):
$ http :8980/shop_kotlin/api/echo/aaa HTTP/1.1 200 Connection: keep-alive Content-Length: 3 Content-Type: text/plain;charset=UTF-8 Date: Thu, 15 Jun 2023 07:30:38 GMT Keep-Alive: timeout=60 aaa
Поиск по имени Product:
$ http :8980/shop_kotlin/api/group_product/find?name='Comp' [ { "haveChilds": true, "id": 2, "name": "Computers", "parentId": 1 }, { "haveChilds": false, "id": 3, "name": "Desktop Computers", "parentId": 2 }
POST запрос на изменение Product:
$ http POST :8980/shop_kotlin/api/product/ < ./src/test/json_test/product.json
Интеграционное тестирование
Два варианта тестирования - cо Spring @DataJpaTest и через RestAssured (это bdd тестирование). Совершенно разные тесты, для совершенно разных целей. DataJpaTest на уровне БД, RestAssured - сквозное тестирование от rest до БД.
DataJPA test
Тестируется работа с базой данных с использованием @DataJpaTest. Находятся в пакете https://github.com/cherepakhin/shop_kotlin/blob/dev/src/test/kotlin/ru/perm/v/shopkotlin/datajpatest.
RestAssured
Для этих тестов сделан отдельный проект https://github.com/cherepakhin/shop_kotlin_reastassured_test
Результат тестов:
Docker
Файл Dockerfile лежит в корне проекта:
FROM eclipse-temurin:11 VOLUME /tmp ARG JAR_FILE COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-jar","/app.jar"]
Создан скрипт /docker_build.sh. для создания docker образа.
Работа с Docker:
# create docker image $./docker_build.sh # run app $ docker run -p 8080:8980 shop_kotlin/app $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c70ef82f3a54 shop_kotlin/app "java -jar /app.jar" 43 seconds ago Up 42 seconds 0.0.0.0:8080->8980/tcp inspiring_germain # simple test $ http :8080/api/group_product/find?name='Comp' $ docker ps -a # 2d1325e1222a shop_kotlin:0.24.0105 "/cnb/process/web" ... # stop docker app $ docker stop 2d1 # rm container shop_kotlin 2d1325e1222a (id from docker ps -a) $ docker container rm 2d1 # rm image $ docker image ls shop_kotlin 0.24.0105 94560d28efb3 ... $ docker image rm 945 Untagged: shop_kotlin:0.24.0105 # clear ALL images $ docker image prune -a # clear all $ docker system prune -af
ИЛИ средствами gradle:
Создание docker image:
$ ./gradlew bootBuildImage ... [creator] Saving docker.io/library/shop_kotlin:0.1.18... [creator] *** Images (f80a4e623a3d): [creator] docker.io/library/shop_kotlin:0.1.18 Successfully built image 'docker.io/library/shop_kotlin:0.1.18' ...
В файле META-INF/MANIFEST.MF указать Main-Class
Main-Class: ru.perm.v.shopkotlin.ShopKotlinApplication
Запуск docker image:
$ docker run -p 8980:8980 -p 8988:8988 docker.io/library/shop_kotlin:0.1.18
(из контейнера docker открыты порты: 8980 - основной порт, 8988 - spring actuator)
$ docker container ls
Проверка:
$ http http://127.0.0.1:8980/shop_kotlin/api/echo/aaa
HTTP/1.1 200
Connection: keep-alive
Content-Length: 3
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
aaa
Swagger
Swagger доступен по адресу http://127.0.0.1:8980/shop_kotlin/api/swagger-ui/
Spring Actuator
Spring Actuator предназначен для получения информации о работающем приложении - статус приложения (жив/нет), использовании памяти, cpu и т.п.. Подключен по адресу http://127.0.0.1:8988/shop_kotlin/api/actuator
порт указан в application.yaml:
management: server: port: 8988
Использование из командной строки:
$ http http://127.0.0.1:8988/shop_kotlin/api/actuator { "_links": { "beans": { "href": "http://127.0.0.1:8988/shop_kotlin/api/actuator/beans", "templated": false }, "caches": { "href": "http://127.0.0.1:8988/shop_kotlin/api/actuator/caches", "templated": false }, "caches-cache": { "href": "http://127.0.0.1:8988/shop_kotlin/api/actuator/caches/{cache}", "templated": true }, ....
Пример "Сколько памяти используется СЕЙЧАС?":
$ http http://127.0.0.1:8988/shop_kotlin/api/actuator/metrics/jvm.memory.used { "availableTags": [ { "tag": "area", "values": [ "heap", "nonheap" ] }, { "tag": "application", "values": [ "shop_kotlin" ] }, { "tag": "id", "values": [ "G1 Survivor Space", "Compressed Class Space", "CodeCache", "G1 Old Gen", "Metaspace", "G1 Eden Space" ] } ], "baseUnit": "bytes", "description": "The amount of used memory", "measurements": [ { "statistic": "VALUE", "value": 169351144.0 } ], "name": "jvm.memory.used" }
(Нагрузочные тесты показали следующие результаты: 64M - приложение выпадает с OutOfMemory, со 128M - тесты проходят)
Micrometer
Micrometer - это , наверное, базовый инструмент для исследования работающего приложения и снятия характеристик. Входит в пакет spring-boot-starter-actuator:
implementation 'org.springframework.boot:spring-boot-starter-actuator'
Пример запроса:
$ http http://127.0.0.1:8988/vacancy/api/actuator/metrics/http.server.requests
Ответ:
{ "availableTags": [ { "tag": "exception", "values": [ "HttpMediaTypeNotSupportedException", "HttpMessageNotReadableException", "MethodArgumentTypeMismatchException", "None" ] }, { "tag": "method", "values": [ "PUT", "GET" ] }, { "tag": "application", "values": [ "vacancy" ] }, { "tag": "uri", "values": [ "/company/find", "/company/{n}", "/company/sortByColumn/{column}" ] }, { "tag": "outcome", "values": [ "CLIENT_ERROR", "SUCCESS" ] }, { "tag": "status", "values": [ "400", "415", "200" ] } ], "baseUnit": "seconds", "description": null, "measurements": [ { "statistic": "COUNT", "value": 212.0 }, { "statistic": "TOTAL_TIME", "value": 2.386371417 }, { "statistic": "MAX", "value": 0.0 } ], "name": "http.server.requests" }
Prometheus
Prometheus - это сервис (программа) работающая на одной из машин. Задача Prometheus опрашивать, снимать и хранить характеристики с узлов, заданных в конфигурации. На моем сервере запущен Prometheus. Prometheus ОПРАШИВАЕТ приложение, согласно заданию (ниже yaml), и собирает метрики (metrics_path+targets). Пример задания для опроса в файле doc/prometheus/prometheus.yml. Содержимое doc/prometheus/prometheus.yml поместить в настройки prometheus. Пример задания:
scrape_configs: - job_name: "shop_kotlin(1.20)" scrape_interval: 5s metrics_path: "/shop_kotlin/api/actuator/prometheus" static_configs: - targets: ["192.168.1.20:8988"]
Опрашивать сервис '192.168.1.20:8988/shop_kotlin/api/actuator/prometheus' каждые 5 сек.
Для просмотра получаемых prometheus-ом метрик выполнить:
$ http http://127.0.0.1:8788/api/actuator/prometheus
(Использован httpie)
Ответ:
# HELP jvm_threads_daemon_threads The current number of live daemon threads # TYPE jvm_threads_daemon_threads gauge jvm_threads_daemon_threads 13.0 # HELP hikaricp_connections Total connections ...
Подключение к Prometheus из браузера: http://192.168.1.20:9090/targets
http://192.168.1.20:9090/graph
Основной экран: 192.168.1.20 - адрес хоста с prometheus http://192.168.1.20:9090/targets
Приложение остановлено:
Приложение запущено:
Пример просмотра использования CPU в Prometheus
Prometheus запущен на 192.168.1.20:9090
Перейти на http://192.168.1.20:9090 В меню "Graph":
- В "Expression" ввести system_cpu_usage
- Перейти на Graph
- Отрегулировать период (н.п. 15 мин.)
Итоговый запрос: http://192.168.1.20:9090/graph?g0.range_input=15m&g0.expr=system_cpu_usage&g0.tab=0
Запуск prometheus в docker.
docker run -d -p 9090:9090 -v "/$(pwd)/for_prometheus/prometheus.yml":/etc/prometheus/prometheus.yml prom/prometheus
(внимание на кавычки)
Просмотр состояния prometheus, запущенного в docker:
Вычисление ID container
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e081bb1f500c prom/prometheus "/bin/prometheus --c…" 4 minutes ago Up 4 minutes 0.0.0.0:9090->9090/tcp reverent_newton
Просмотр логов контейнера prometheus (e08 id контейнера)
$ docker logs e08 --follow ts=2023-09-11T13:13:17.850Z caller=main.go:1009 level=info msg="Server is ready to receive web requests." ts=2023-09-11T13:13:17.850Z caller=manager.go:1009 level=info component="rule manager" msg="Starting rule manager..." ...
Grafana
Graphana отображает метрики, собранные Prometheus. На домашнем сервере развернута Grafana. На картинках ниже отображено использование CPU двух приложений, расположенных на двух разных хостах в упрощенном и полном формате:
Запуск grafana:
$ sudo service grafana-server start
Остановка grafana:
$ sudo service grafana-server stop
user(pass): admin/admin
Нагрузочное тестирование
Экономия средств при росте производительности — горячая тема в индустрии. Подробнее о нагрузочном тестировании и метриках в этом проекте https://github.com/cherepakhin/shop_kotlin_yandex_tank_test
Кеширование
Кеширование сделано для RestController:
@GetMapping("/") @Cacheable("allGroupProductDTO") @ApiOperation("Get all groups of product") fun all(): List { ... }
Использован org.springframework.cache. Ручная проверка работы кеш:
# Очистка кэша $ http :8780/api/group_product/clear_cache # Для проверки, несколько раз сделать get запрос. # При этом в лог будет только один запрос к репозиторию. $ http :8780/api/group_product/
Сборка Jenkins
Сборка происходит в Jenkins, развернутом на домашнем сервере. Pipeline для Jenkins описан в файле ./Jenkinsfile
Установка и настройка домашнего Jenkins описана в https://v.perm.ru/main/index.php/50-organizatsiya-sobstvennogo-ci-cd
Для ограничения памяти при сборке Gradle, добавлен параметр org.gradle.jvmargs=-Xmx512M в gradle.properties:
org.gradle.jvmargs=-Xmx512M
Nexus
В проекте используется внешняя зависимость из домашнего NEXUS репозитория https://github.com/cherepakhin/shop_kotlin_extdto:
implementation("ru.perm.v:shop_kotlin_extdto:0.0.3")
Эта библиотека используется в нескольких проектах (DTO для обмена между сервисами).
Deploy to NEXUS repository
Возможен с использованием Jenkins (в Jenkisfile "stage('Publish to Nexus')") или ручной deploy в Nexus с личного компьютера.
Для deploy выполнить:
$ ./gradlew publish
Путь к репозиторию установлен в build.gradle.kts:
... publishing { repositories { maven { url = uri("http://v.perm.ru:8081/repository/ru.perm.v/") ...
Для установки переменных доступа к Nexus repository выполнить в shell (переменные будут использованы в build.gradle.kts, см.ниже):
$ export NEXUS_CRED_USR=admin $ export NEXUS_CRED_PSW=pass
Результат deploy:
Секция publishing в build.gradle.kts полностью:
... publishing { repositories { maven { url = uri("https://v.perm.ru:8082/repository/ru.perm.v/") isAllowInsecureProtocol = true // example export variables in shell // export NEXUS_CRED_USR=admin // verify: // echo $NEXUS_CRED_USR credentials { username = System.getenv("NEXUS_CRED_USR") password = System.getenv("NEXUS_CRED_PSW") } } } publications { register("mavenJava", MavenPublication::class) { groupId artifactId version from(components["java"]) } }
Просмотр ресурсов с помощью Java Mission Control
jmc-8.3.1_linux-x64/JDK Mission Control$ jmc
Логирование
Настройка сделана в application.yaml:
logging: level: root: info file: path: log/
Использование "ChatGPT-EasyCode" в Idea
Пример работы плагина Idea EasyCode для автодополнения кода :
Серым выделена подсказка EasyCode. "TAB" - принять предложение.
Использование "ChatGPT-EasyCode" в VSCode
Вещь!
А отечественный GigaCode, в течении нескольких недель, отвечаЛ унылым
и в конце концов какие-то шестеренки провернулись:
В IntelliJ IDEA 2023.3.2 (Community Edition):
Частный параметр конфигурации в application.yaml
Бывает нужно добавить СВОЙ параметр конигурации в application.yaml для удобства тестирования, разработки, развертывания. Пример описан в проекте https://github.com/cherepakhin/camel_rest в разделе "Дополнительно". В application.yaml добавлен ЧАСТНЫЙ параметр конфигурации "myconfig".
Описание:
@ConfigurationProperties("myconfig") @ConstructorBinding data class MyConfig(val testDirectory: String)
Объявление в application.yaml:
myconfig: testDirectory: file:~/temp/testarea
Использование:
@RestController class ParamCtrl { @Autowired lateinit var myConfig: MyConfig @GetMapping("/test_directory") fun getTestDirectory(): String? { logger.info("GET param test_directory=${myConfig.testDirectory}") return myConfig.testDirectory } }
Переопределение значения переменных application.yaml
Можно задать значения переменных application.yaml через специальную переменную SPRING_APPLICATION_JSON. Пример замены порта приложения на 8960 (в application.yaml server: port=8980):
// экспорт переменной $ export SPRING_APPLICATION_JSON='{"server":{"port":8960}}' // проверка $ java -jar build/libs/spring_config_k-0.0.1-SNAPSHOT.jar .... INFO 23327 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8960 (http) ....
Пример: https://github.com/cherepakhin/spring_config_k
TODO
cache on rest (сделано)RestAssured tests (сделано)(https://github.com/cherepakhin/shop_kotlin_reastassured_test)by lazy (сделано)Docker (сделано)Swagger (сделано)Actuator (сделано)flyway (использована БД в памяти url: jdbc:h2:mem:easybotdb). Миграции не требуются.- webmvc тесты (ProductRestMockMvcTest)
тесты service слоя с СУБД @DataJpaTest (сделано)- вертикальные БД (?)
Различные SQL запросы через native Sql и Spring JPA(order by, group by) (сделано)- Pagination
Grafana (сделано)- ElasticSearch
- Авторизация
Примечания:
В программе не используются слои controller и service, т.к. проект сделан только для демонстрации связки Spring Boot и Kotlin, и не планируется какая-либо бизнес-логика. Конвертация в DTO сделана в REST контроллерах.
Поля в entity классах д.б. обозначены "open" (н.п. open val name: String = ""). В Kotlin используется следующий подход - можно наследовать от суперклассов и переопределять их свойства и функции только в том случае, если они снабжены префиксом "open".
Имя для "id" поля в entity классах названы "n". Причины:
- В некоторых БД "id" ключевое слово
- В interface repository есть отдельные методы repository.getById(), repository.findById(). Имеется ввиду поиск по primary key. Это путает.
Правила использования модификатора lateinit:
- используется только совместно с ключевым словом var;
- свойство может быть объявлено только внутри тела класса (не в основном конструкторе);
- тип свойства не может быть нулевым и примитивным;
- у свойства не должно быть пользовательских геттеров и сеттеров;
- с версии Kotlin 1.2 можно применять к свойствам верхнего уровня и локальным переменным.
- сообщает что инициализация будет как-то сделана (в примере ниже аннотациями @Mock, @InjectMocks). @Mock mockProductService будут инжектированы в @InjectMocks productRest. Пример:
internal class ProductRestTest { @Mock private lateinit var mockProductService: ProductService @InjectMocks private lateinit var productRest: ProductRest ... }
Ключевое слово byLazy
val catName: String by lazy { getName() }
свойство объявлено как VAL, но присвоение будет выполнено при первом использовании и потом уже не будет инициализироваться.
Ссылки о lazy и lateinit:
- https://github.com/cherepakhin/kotlin_in_action
- https://developer.alexanderklimov.ru/android/kotlin/lateinit.php
- https://www.baeldung.com/kotlin/lazy-initialization
- https://bimlibik.github.io/posts/kotlin-lateinit-and-lazy/
Статические члены и компаньон объекты в Kotlin:
Как правило объекты-компаньоны используются для объявления переменных и функций, к которым требуется обращаться без создания экземпляра класса. Либо для объявления констант. По сути они своего рода замена статическим членам класса (в отличие от Java, в Kotlin нет статики).
- Как бы static:
class Someclass { ... object AsStaticObject { fun create() { ... } } } ... // использование val someClass = SomeClass.AsStaticObject.create()
- Companion Object (вложенный объект. Метод create() принадлежит родительскому классу):
class SomeClass { companion object { fun create() } } ... val someClass = SomeClass.create()
Различия между val и const val в Kotlin
val MY_VAL = 1 const val MY_CONST_VAL = 2
- Обе эти переменные немодифицируемые
- MY_VAL станет приватной переменной, для доступа к которой будет создан геттер
- MY_CONST_VAL будет "заинлайнена", то есть компилятор заменит все полученные значения этой переменной на само значение
- Kotlin const, var, and val Keywords
Ошибка "ERROR: Cannot resume build because FlowNode 19"
Ответ тут https://community.jenkins.io/t/error-cannot-resume-build-because-flownode-19/9477/3
В двух словах: добавить в Jenkinsfile "максмальную выживаемость":
pipeline { agent any options { durabilityHint 'MAX_SURVIVABILITY' } ... }
По мелочи:
-
В Windows на сетевом диске компилировалось с проблемами query-dsl. После переноса на диск C: проблемы ушли.
-
Если появилась ошибка: "./gradlew: строка 2: $'\r': команда не найдена", то выполнить:
-
Похоже, при расшаривании папки для Windows и работе в Windows изменился символ конца строки.
- Смена версии gradlew: в ./gragle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
- Jetty рекомендуют, как более оптимизированный контейнер. Для замены сделать:
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") { exclude("org.springframework.boot:spring-boot-starter-tomcat") } implementation("org.springframework.boot:spring-boot-starter-jetty") // jetty uses less memory ... }
- Описание Data class: https://kotlinlang.ru/docs/reference/data-classes.html. "Классы данных не могут быть абстрактными, open, sealed или inner. Компилятор автоматически формирует следующие члены данного класса из свойств, объявленных в основном конструкторе: equals()/hashCode(), toString() в форме "User(name=John, age=42)", компонентные функции componentN(), которые соответствуют свойствам, в соответствии с порядком их объявления. Основной конструктор должен иметь как минимум один параметр. Все параметры основного конструктора должны быть отмечены, как val или var."