Java шпаргалка
Файл application.properties
Большой список всевозможных свойств и описаний к ним
Получение значения параметра свойства
Надо пометить переменную наподобие такого
@Value ( "${app.rest.employee.count-on-page}" ) /* работает только на не статическом поле*/ public int countOnPage; |
RestTemplate send PATCH request
При попытке отправить PATCH запрос, возникает исключение ProtocolException: Invalid HTTP method: PATCH или ResourceAccessException: I/O error on PATCH request
Для решения проблемы следует добавить зависимость:
<!-- for path requests via resttemplate --> < dependency > < groupId >org.apache.httpcomponents</ groupId > < artifactId >httpclient</ artifactId > < version >4.4.1</ version > </ dependency > |
И создавать RestTemplate следующим образом:
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); RestTemplate restTemplate = new RestTemplate(requestFactory); |
Spring инициализация
Запуск своего кода при загрузке Spring
@Component private class Starter implements CommandLineRunner { @Override public void run(String... args) { //todo } } |
i18n Internalization
Поддержка языков при использовании Spring Boot.
Достаточно определить бин:
@Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setCacheSeconds( 3600 ); //refresh cache once per hour messageSource.setDefaultEncoding( "UTF-8" ); messageSource.setFallbackToSystemLocale( false ); messageSource.setBasenames( "classpath:locale/messages/app" ); return messageSource; } |
И разместить бандлы в указанном месте (locale/messages/app). Т.е.:
src/main/resources/locale/messages/app.properties
и
src/main/resources/locale/messages/app_ru.properties
В этом случае если браузер желает русскую локаль, то ему будет отдан app_ru.properties, в противном случае — app.properties
Также можно переопределить логику определения текущей локали с помощют объявления бина :
@Bean public LocaleResolver localeResolver() { SessionLocaleResolver slr = new SessionLocaleResolver(); slr.setDefaultLocale(Locale.forLanguageTag( "ru" )); return slr; } |
Если этого не сделать SpringBoot будет использовать
org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver.class
в нём можно ставить брейкпоинты.
Таким образом, если не переопределять LocaleResolver, то локаль будет определяться их хэдеров запроса.
Более подробно написано тут https://blog.knasys.ru/spring-boot-i18n-thymeleaf/
Аннотации
Как переиспользлвать настроенную аннотацию @Pattern
Если Вы много где используете настроенную аннотацию @Pattern, например,
1 | @Pattern (regexp = "^\\+[0-9]{11,16}$", message = "{constraints.phoneIncorrectFormat}") |
, то Вы можете её сохранить как свою кастомную аннотацию так:
import javax.validation.Constraint; import javax.validation.Payload; import javax.validation.constraints.Pattern; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Pattern (regexp = "^\\+[0-9]{11,16}$" , message = "{constraints.phoneIncorrectFormat}" ) @Target ({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention (RUNTIME) @Documented @Constraint (validatedBy = { }) public @interface PhonePattern { /** * @return the error message template */ String message() default "{constraints.phoneIncorrectFormat}" ; /** * @return the groups the constraint belongs to */ Class<?>[] groups() default { }; /** * @return the payload associated to the constraint */ Class<? extends Payload>[] payload() default { }; } |
Использование констант в @PreAuthorize и thymeleaf sec:authoriz
https://stackoverflow.com/questions/64795958/spring-security-rolesallowed-and-thymeleaf-secauthorize
@Controller //@PreAuthorize("hasAnyRole(T(ru.knastnt.prj.web.admin.MenuEditorController).ALLOWED_ROLES)") @PreAuthorize ( "hasAnyRole(@menuEditorController.ALLOWED_ROLES)" ) public class MenuEditorController { public static final String[] ALLOWED_ROLES = {Role.ADMIN.toString(), Role.EDITOR.toString()}; ... } |
< ul th:fragment = "nav-default" > < li sec:authorize = "hasAnyRole(@menuEditorController.ALLOWED_ROLES)" >< a th:href = "@{${@menuEditorController.MENU_EDITOR_URL}}" >menu editor</ a ></ li > </ ul > |
Отличие @Valid от @Validated
@Valid это «стандартная» аннотация для валидации бина, не привязанная к Спрингу. Даже если вместо Спринга ты будешь использовать другой фреймворк, она будет работать. А вот @Validated относится уже конкретно к Спрингу, т.е. она не «переносимая». Цель та же, что и у @Valid, но со всякими плюшками, например — наличие возможности указать группы валидации.
@Valid еще нужна, если нужно валидировать поля сложного объекта, объектов коллекций и массивов, иначе валидатор пройдется только по верхам.
@NotEmpty List<@NotEmpty String> list; — сработает только на объект списка, что список непустой, внутрянку не проверит.
@Valid @NotEmpty List<@NotEmpty String> list; — проверит и строки внутри коллекции.
Аннотацию @Validated можно проверить если ее установить над классом контроллера, когда аргументом метода является коллекция и ее нужно отвалидировать, без нее валидатор не работает.
Спасибо за ответы Evgeny Nagorny, Sergey Zhukov.
Тестирование
Подмена бина при тестировании
Способ 1: автовайрим этот бин не через @Autowired, а через @MockBean. В этом случее бин подменится для всего контекста.
Способ2: объявляем этот бин в конфигурации, используя другое имя. А затем автовайрим с использованием аннотации Qualifier. Класс теста:
@SpringBootTest @ContextConfiguration (classes = {SmsDeliveryServiceImplTestConfig. class }) class SmsDeliveryServiceTest { @Autowired @Qualifier ( "SmsDeliveryServiceForTesting" ) SmsDeliveryService smsDeliveryService; @Test void testmethod() { ......... |
Класс конфигурации:
@TestConfiguration class SmsDeliveryServiceImplTestConfig { @Bean (name = "SmsDeliveryServiceForTesting" ) public SmsDeliveryService smsDeliveryService(SmsSender smsSender) { Duration[] smsPauseTime = new Duration[]{ Duration.ofMillis( 200 ), Duration.ofMillis( 600 ), Duration.ofMillis( 1400 ) }; Duration timeForResetPauseTime = Duration.ofMillis( 2500 ); TemporalUnit temporalUnit = ChronoUnit.MILLIS; return new SmsDeliveryService(smsSender, smsPauseTime, timeForResetPauseTime, temporalUnit); } } |
Изменение авторизованного пользователя в тесте
Вот тут я задавал вопрос и описывал свою проблему. Там же сам себе ответил. https://stackoverflow.com/questions/64812736/change-authorized-user-in-tests
@Test void allMenusAuthorizePermissions() throws Exception { for (User user : ALL_ROLES_USERS) { log.debug( "User role: " + user.getAuthorities()); if (user == ADMIN || user == EDITOR) { // perform(get(MenuEditorController.MENU_EDITOR_URL).with(SecurityMockMvcRequestPostProcessors.user(user.getUsername()).authorities(user.getAuthorities()))) perform(get(MenuEditorController.MENU_EDITOR_URL).with(SecurityMockMvcRequestPostProcessors.user(user))) .andExpect(status().isOk()); } else { perform(get(MenuEditorController.MENU_EDITOR_URL).with(SecurityMockMvcRequestPostProcessors.user(user))) .andExpect(status().isForbidden()); } } } |
Thymeleaf
Синтаксис
Учебник Thymeleaf: Глава 4. Standard Expression Syntax
th:href со ссылкой на контроллер
Во-первых, зачем писать th:href=»@{/path}» когда можно написать просто href=»/path»? Вот зачем.
Во-вторых чтобы сослаться на какой-то бин в контексте из thymeleaf, можно использовать конструкцию ${@menuEditorController.MENU_EDITOR_URL}, где menuEditorController — имя бина (по-умолчанию это имя класса с маленькой буквы), MENU_EDITOR_URL — какая-то статическая константа (можно также ссылаться и на методы).
Ну и к делу:
< ul th:fragment = "nav-default" > ... < li sec:authorize = "hasAnyRole(@menuEditorController.ALLOWED_ROLES)" > < a th:href = "@{${@menuEditorController.MENU_EDITOR_URL}}" >Редактор меню</ a > </ li > ... </ ul > |
@Controller @RequestMapping (MENU_EDITOR_URL) @PreAuthorize ( "hasAnyRole(@menuEditorController.ALLOWED_ROLES)" ) public class MenuEditorController { public static final String MENU_EDITOR_URL = ADMIN_URL + "/menu" ; public static final String[] ALLOWED_ROLES = {Role.ADMIN.toString(), Role.EDITOR.toString()}; ... } |
Аспекты (Aspects, AOP)
Вводная лекция (логгирование, замер времени выполнения метода)
Примеры Pointcut`s:
"execution(* ru.knastnt.myapp.*.*(..))" - любое возвращаемое значение, любой класс непосредственно в указанном пакете, любой метод класса, любые аргументы метода "execution(* ru.knastnt...*.*(..))" - любое возвращаемое значение, любой класс в указанном пакете и всех дочерних, любой метод класса, любые аргументы метода "execution(* ru.knastnt.repo.ClientDao.*GroupIn(..))" - любое возвращаемое значение, указанный класс в указанном пакете, любой метод класса заканчивающийся на GroupIn, любые аргументы метода "@annotation(AspectAnnotation)" - любой метод помеченный аннотацией |
Правила формирования: тут и тут
Пример аспекта
@Aspect @Component public class MyAspect { @Before ( "execution(* ru.knastnt.prj..*.*(..))" ) public void beforeMethodInvocation(JoinPoint jp){ System.out.println( "Вызов метода с сигнатурой: " + jp.getSignature()); } } |
JSON
JSON ObjectMapper игнорирует аннотацию @JsonFormat(shape = STRING, pattern = «MM.dd.yyyy»).
Там какие-то заморочки с JSR301 и чтобы починить надо немного настроить ObjectMapper:
ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); |
Binding JSON null field to empty collection/list in Object:
@Data public class MyDTO { @JsonSetter (nulls = Nulls.AS_EMPTY) private List<InnerDTO> innerDtos; } |
Теперь если даже в JSON это поле будет как null, то в объект всё равно сбиндится пустой List.
Swagger
Подключение:
< properties > < swagger.version >2.9.2</ swagger.version > </ properties > < dependencies > < dependency > < groupId >io.springfox</ groupId > < artifactId >springfox-swagger-ui</ artifactId > < version >${swagger.version}</ version > </ dependency > < dependency > < groupId >io.springfox</ groupId > < artifactId >springfox-swagger2</ artifactId > < version >${swagger.version}</ version > </ dependency > </ dependencies > |
Эндпоинты:
localhost:8080/v2/api-docs
localhost:8080/v3/api-docs
localhost:8080/swagger-ui/index.html
Настройки:
@Configuration @EnableSwagger2 @PropertySource ( "classpath:configuration.properties" ) public class SwaggerConfiguration { @Bean public Docket configureDocs( @Value ( "${projectVersion}" ) String version) { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo(version)) .useDefaultResponseMessages( false ) .select() .paths(Predicates.not(PathSelectors.regex( "/actuator/*" ))) .paths(Predicates.not(PathSelectors.regex( "/error" ))) .apis(RequestHandlerSelectors.basePackage( "ru.knastnt.myproject" )) .build() .tags( new Tag( "MainController" , "Мой тестовый проект" )); } private ApiInfo apiInfo(String version) { return new ApiInfoBuilder() .title( "Мой проект" ) .description( "REST API к моему проекту" ) .termsOfServiceUrl( "" ) .version(version) .build(); } } |
Документирование контроллера:
@Api ( "Главный контроллер" ) @RestController @RequestMapping ( "/api" ) public class MainController { @ApiOperation ( "Поиск объекта по идентификатору" ) @GetMapping ( "/{objectId}" ) public ObjectDto getObjectById( @ApiParam (value = "Идентификатор объекта" , example = "1" ) @PathVariable Long objectId) { return ...; } } |
Документирование DTO:
@ApiModel ( "Параметры запроса объекта" ) public class ObjectDto { @ApiModelProperty ( "Номер объекта" ) private String objNum; } |
Camunda
Типы заданий
- Human Task
- Generated Forms (генерирование формы исходя из заданных полей)
- Generic Form (указание переменных вручную)
- Embedded form (HTML-код)
- External Form (форма во внешнем приложении)
- Service Task
- Вызов Java кода (Java class / Delegate Expression) – синхронное исполнение
- Expression (выражение)
- Connector (http-connector, mail-connector)
- External (топик) – асинхронное исполнение
- Send Task – отправка сообщения (аналог Service Task)
- Receive Task – прием сообщения (процесс ожидает прихода сообщения)
- Business Rule Task – таблица бизнес-правила / DMN
- Script Task (javaScript, Groovy)
- Manual Task
JPA
Получение единственного результата из базы данных
Generic DAO:
Search search = new Search(MyEntity.class);
return (MyEntity) commonDao.searchUnique(search);
Если объектов не найдено: null;
Если объектов больше одного: IncorrectResultSizeDataAccessException(NonUniqueResultException);
Javax.persistence:
em()
.createQuery(queryStr, MyEntity.class)
.getResultList()
Если объектов не найдено: NoResultException;
Если объектов больше одного: NonUniqueResultException;
Spring DataAccessUtils:
DataAccessUtils.singleResult(em()
.createQuery(queryStr, MyEntity.class)
.getResultList())
Если объектов не найдено: null;
Если объектов больше одного: IncorrectResultSizeDataAccessException(NonUniqueResultException);
LDAP
Теория
Термины и определения: https://pro-ldap.ru/tr/zytrax/apd/
Объектные классы и их атрибуты: https://pro-ldap.ru/tr/zytrax/ape/
Общая информация о работе LDAP: https://pro-ldap.ru/tr/zytrax/ch2/
SPI (Java Service Provider Interface)
Механизм который можно использовать для подмены класса в jar в сторонних библиотек. Более подробно написано здесь #https://habr.com/ru/post/118488/
Например, чтобы подменить какой-то класс, нужно создать его имплементацию в проекте, и создать в каталоге src/main/resources/META-INF/services текстовый документ с именем = адрес заменяемого класса; содержанием = ссылка на свою имплементацию:

Как запустить jar в linux
Запуск в качестве фоновой задачи с выводом содержимого консоли в файл:
cd /var/www
nohup java -jar /var/www/app-1.1.1.jar &
Запуск в качестве фоновой задачи без вывода содержимого консоли в файл:
cd /var/www
nohup java -jar /var/www/app-1.1.1.jar >/dev/null 2>&1 &
Запуск в качестве фоновой задачи без вывода содержимого консоли в файл и с определённым .properties файлом:
cd /var/www
nohup java -jar /var/www/app-1.1.1.jar --spring.config.location=file:///var/www/app.properties >/dev/null 2>&1 &
Генерация классов из xsd-схемы
JAXB модели (dto) генерируются по предоставленным xsd файлам через утилиту xjc (в составе JDK)
cmd -> заходим в папку со схемой,
выполняем команду
«C:\Program Files\Java\jdk1.8.0_321\bin\xjc» Srv.ClntPubl.V2_Elements.xsd -p ru.knastnt.clients.logic.cif.jms_model
указываем корневой xsd элемент (обычно лежит в руте архива со схемой), и package который нужно установить
Mock Web Server для тестов
Как протестировать обращение к внешнему REST-сервису
Заглушка Web сервиса в тестах
Как мокнуть внешний сервис
и т.п…)
< dependency > < groupId >com.squareup.okhttp3</ groupId > < artifactId >mockwebserver</ artifactId > < scope >test</ scope > </ dependency > |
MockWebServer mockWebServer = new okhttp3.mockwebserver.MockWebServer(); mockWebServer.start( 64970 ); mockWebServer.enqueue( new MockResponse() .setResponseCode( 200 ) .setBody(diasoftResponse) .addHeader( "Content-Type" , "application/json" ) ); |
Maven install with source code — установить пакет вместе с исходным кодом
mvn source:jar install
Как сделать Select * from TABLE where UUID like ‘…’
Если Вы желаете отобрать записи, в которых поле с типом UUID содержит какую-то строку, то вот такой запрос работать не будет:
select * from my_table where id like '%2%'
Вместо этого, делайте запрос с приведением типа uuid к text:
select * from my_table where id::text like '%2%'
Если Вам приходится делать запрос через querydsl или RQL, то посмотрите эти примеры:
1
2
3
4
5
6
7 jpaRepository.find(JpaRepository.getQuery(MyEntity.class,
Expressions.predicate(
Ops.LIKE,
Expressions.stringOperation(Ops.STRING_CAST, QMyEntity.myEntity.id),
Expressions.constant("%2%")
)
));
1
2
3
4
5 OPERATORS.put("matchAsText", (node, visitor) -> {
ComparablePath path = (ComparablePath) visitor.getPredicatePath((String) node.getArgument(0));
StringOperation stringOperation = Expressions.stringOperation(Ops.STRING_CAST, path);
return stringOperation.likeIgnoreCase((String) node.getArgument(1));
});