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));
});