Файл 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://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") );