Java шпаргалка

Java шпаргалка

Файл application.properties

Большой список всевозможных свойств и описаний к ним

https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#data-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

https://github.com/spring-cloud/spring-cloud-netflix/issues/1777

Для решения проблемы следует добавить зависимость:

        <!-- 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
(Просмотрено 1 323 раз, 1 раз за сегодня)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *