
Spring — пожалуй, одна из самых популярных платформ разработки на языке Java. Это мощный, но довольно сложный в освоении инструмент. Его базовые концепции довольно легко понять и усвоить, но для того чтобы стать опытным разработчиком на Spring, потребуются время и определенные усилия.
В этой статье мы рассмотрим некоторые из самых распространенных ошибок, совершаемых при работе в Spring и связанных, в частности, с разработкой веб-приложений и использованием платформы Spring Boot. Как отмечается на веб-сайте Spring Boot, в Spring Boot используется стандартизованный подход к созданию готовых к эксплуатации приложений, и данная статья будет придерживаться этого подхода. В ней будет дан ряд рекомендаций, которые можно эффективно использовать при разработке стандартных веб-приложений на базе Spring Boot.
На тот случай, если вы не очень хорошо знакомы с платформой Spring Boot, но хотите поэкспериментировать с примерами, приведенными в статье, я создал GitHub-репозиторий с дополнительными материалами для этой статьи. Если в какой-то момент вы немного запутались, читая эту статью, я бы посоветовал вам создать клон этого репозитория и поэкспериментировать с кодом на своем компьютере.
Распространенная ошибка № 6. Неиспользование валидации данных на основе аннотаций
Давайте представим, что нашей службе TopTalent из предыдущих примеров требуется конечная точка для добавления новых данных TopTalent. Также давайте предположим, что по какой-то действительно важной причине каждое добавляемое имя должно иметь длину ровно 10 символов. Реализовать это можно, например, следующим образом:
@RequestMapping("/put") public void addTopTalent(@RequestBody TopTalentData topTalentData) { boolean nameNonExistentOrHasInvalidLength = Optional.ofNullable(topTalentData) .map(TopTalentData::getName) .map(name -> name.length() == 10) .orElse(true); if (nameNonExistentOrInvalidLength) { // выдача исключения } topTalentService.addTopTalent(topTalentData); }
Однако приведенный выше код не только плохо структурирован, но и не является действительно «чистым» решением. Мы выполняем несколько видов валидации данных (а именно — проверяем, что объект TopTalentData не равен null, что значение поля TopTalentData.name не равно null и что длина поля TopTalentData.name составляет 10 символов), а также выдаем исключение, если данные неправильные.
Все это можно сделать более аккуратно, используя валидатор Hibernate в Spring. Давайте сначала перепишем метод addTopTalent, добавив поддержку валидации данных:
@RequestMapping("/put") public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) { topTalentService.addTopTalent(topTalentData); } @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) { // обработка исключения валидации }
Кроме того, нам нужно указать, валидацию какого свойства мы хотим выполнить в классе TopTalentData:
public class TopTalentData { @Length(min = 10, max = 10) @NotNull private String name; }
Теперь Spring будет перехватывать запрос и проверять его до вызова метода, поэтому никаких дополнительных проверок вручную проводить не потребуется.
Нужной цели также можно достичь путем создания собственных аннотаций. В реальных условиях собственные аннотации обычно имеет смысл использовать только тогда, когда ваши потребности превышают возможности встроенного набора ограничений Hibernate, но для этого примера давайте представим, что аннотации @Length не существует. Вы можете создать средство проверки данных, проверяющее длину строки, создав два дополнительных класса: один — для валидации, а другой — для аннотирования свойств:
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = { MyAnnotationValidator.class }) public @interface MyAnnotation { String message() default "String length does not match expected"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int value(); } @Component public class MyAnnotationValidator implements ConstraintValidator<MyAnnotation, String> { private int expectedLength; @Override public void initialize(MyAnnotation myAnnotation) { this.expectedLength = myAnnotation.value(); } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { return s == null || s.length() == this.expectedLength; } }
Обратите внимание, что в этих случаях правильное применение принципа разделения ответственности требует отмечать свойство как валидное, если его значение равно null (s == nullв методе isValid), а затем использовать аннотацию @NotNull, если это дополнительно требуется для данного свойства:
public class TopTalentData { @MyAnnotation(value = 10) @NotNull private String name; }
Распространенная ошибка № 7. Использование устаревших конфигураций на основе XML
Использование XML было необходимостью при работе с предыдущими версиями Spring, но сейчас большую часть задач конфигурирования можно реализовать с помощью Java-кода и аннотаций. XML-конфигурации сейчас выступают в качестве дополнительного и необязательного к применению шаблонного кода.
В этой статье (а также в материалах сопутствующего GitHub-репозитория) для конфигурирования Spring используются аннотации, и Spring знает, какие компоненты JavaBean необходимо привязать, так как корневой пакет аннотируется с помощью составной аннотации @SpringBootApplication — вот так:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Эта составная аннотация (подробнее о ней см. в документации по Spring) просто указывает платформе Spring, какие пакеты нужно просканировать для извлечения компонентов JavaBean. В нашем конкретном случае это означает, что для привязки будут использоваться следующие подпакеты co.kukurin:
- Component (TopTalentConverter, MyAnnotationValidator)
- @RestController (TopTalentController)
- Repository (TopTalentRepository)
- Service (TopTalentService) classes
Если у нас есть дополнительные классы, имеющие аннотацию @Configuration, они также будут проверены на наличие Java-конфигурации.
Распространенная ошибка № 8. Неиспользование профилей конфигурации
При разработке серверных систем распространенной проблемой является переключение между разными конфигурациями (речь, как правило, идет о конфигурациях для среды эксплуатации и среды разработки). Вместо того чтобы вручную менять различные параметры при каждом переходе между тестовым и рабочим режимом, эффективнее использовать профили конфигурации.
Представим себе случай, когда в локальной среде разработки вы используете базу данных в оперативной памяти, а в среде реальной эксплуатации вашего приложения используется база данных MySQL. Это по сути означает, что вы будете использовать различные URL-адреса и, надо полагать, различные учетные данные для доступа к каждой из этих БД. Давайте посмотрим, как это может быть реализовано с помощью двух конфигурационных файлов:
ФАЙЛ APPLICATION.YAML
# set default profile to 'dev' spring.profiles.active: dev # production database details spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal' spring.datasource.username: root spring.datasource.password:
ФАЙЛ APPLICATION-DEV.YAML
spring.datasource.url: 'jdbc:h2:mem:' spring.datasource.platform: h2
Надо полагать, что, работая с кодом, вы бы не хотели совершить некие случайные действия с базой данных, предназначенной для среды эксплуатации, поэтому имеет смысл в качестве профиля по умолчанию выбрать профиль для среды разработки (DEV). Впоследствии на сервере можно вручную переопределить профиль конфигурации, указав для JVM параметр -Dspring.profiles.active=prod. Кроме того, профиль конфигурации, который следует использовать по умолчанию, можно указать в переменной среды операционной системы.
Распространенная ошибка № 9. Неиспользование механизма внедрения зависимостей
Правильное использование механизма внедрения зависимостей в Spring означает, что Spring может связывать все ваши объекты, сканируя все необходимые конфигурационные классы. Это полезно для ослабления взаимозависимостей и значительно облегчает тестирование. Вместо тесного связывания классов примерно следующим образом:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController() { this.topTalentService = new TopTalentService(); } }
… мы позволяем платформе Spring выполнить привязку:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController(TopTalentService topTalentService) { this.topTalentService = topTalentService; } }
В лекции Мишко Хевери на канале Google Tech Talks подробно объясняется, зачем следует использовать внедрение зависимостей, а здесь мы посмотрим, как этот механизм используется на практике. В разделе, посвященном разделению ответственности («Распространенная ошибка № 3»), мы создали классы службы и контроллера. Предположим, мы хотим протестировать контроллер, предполагая, что класс TopTalentService работает правильно. Мы можем вставить объект-имитатор вместо действующей реализации службы, создав отдельный конфигурационный класс:
@Configuration public class SampleUnitTestConfig { @Bean public TopTalentService topTalentService() { TopTalentService topTalentService = Mockito.mock(TopTalentService.class); Mockito.when(topTalentService.getTopTalent()).thenReturn( Stream.of("Mary", "Joel").map(TopTalentData::new).collect(Collectors.toList())); return topTalentService; } }
После этого мы можем внедрить объект-имитатор, указав платформе Spring, что в качестве источника конфигурации нужно использовать SampleUnitTestConfig:
@ContextConfiguration(classes = { SampleUnitTestConfig.class })
Впоследствии это позволит нам использовать контекстную конфигурацию для внедрения пользовательского компонента JavaBean в модульный тест.
Распространенная ошибка № 10. Недостаток тестирования или неправильное тестирование
Несмотря на то что идея модульного тестирования отнюдь не нова, кажется, что многие разработчики либо «забывают» о нем (особенно если оно не является обязательным), либо проводят его слишком поздно. Очевидно, что это неправильно, потому что тесты не только позволяют проверить правильность работы кода, но и служат в качестве документации, показывающей, как приложение должно вести себя в различных ситуациях.
При тестировании веб-служб вы редко проводите исключительно «чистые» модульные тесты, так как для соединения по HTTP-протоколу обычно требуется задействовать сервлет Spring DispatcherServlet и посмотреть, что происходит при получении реального запроса HttpServletRequest (то есть это получается интеграционный тест, в котором используются валидация, сериализация и пр.). Элегантное и проверенное решение — использование REST Assured, Java-библиотеки для удобного тестирования REST-служб, с MockMVC. Рассмотрим следующий фрагмент кода с внедрением зависимостей:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { Application.class, SampleUnitTestConfig.class }) public class RestAssuredTestDemonstration { @Autowired private TopTalentController topTalentController; @Test public void shouldGetMaryAndJoel() throws Exception { // given MockMvcRequestSpecification givenRestAssuredSpecification = RestAssuredMockMvc.given() .standaloneSetup(topTalentController); // when MockMvcResponse response = givenRestAssuredSpecification.when().get("/toptal/get"); // then response.then().statusCode(200); response.then().body("name", hasItems("Mary", "Joel")); } }
SampleUnitTestConfig привязывает суррогатную реализацию класса TopTalentService к TopTalentController, а все остальные классы привязываются с использованием стандартной конфигурации, полученной при сканировании пакетов, основанных на пакете класса Application. RestAssuredMockMvc просто используется для задания облегченной среды и отправки запроса GET конечной точке /toptal/get.
Используйте Spring на профессиональном уровне
Spring — мощная платформа, с которой легко начать работу, но для ее полного освоения требуются время и некоторые усилия. Если вы потратите время на то, чтобы хорошо ознакомиться с этой платформой, в конечном итоге это, несомненно, поднимет эффективность вашей работы, поможет вам создавать более чистый код и повысит вашу квалификацию как разработчика.
Рекомендую обратить внимание на Spring In Action — это хорошая, ориентированная на практическое применение книга, в которой рассмотрено множество важных тем, связанных с платформой Spring.
На этом перевод данной статьи подошел к концу.
Читать первую часть.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/465063/
Добавить комментарий