The 10 most common mistakes when working with the Spring platform. Part 2

Salute, Khabrovsk. So the translation of the second part of an article prepared especially for students of the course "Developer on the Spring Framework" arrived in time. The first part can be read here .







Spring is perhaps one of the most popular Java development platforms. This is a powerful, but rather difficult to learn tool. Its basic concepts are fairly easy to understand and learn, but it takes time and effort to become an experienced Spring developer.



In this article, we will look at some of the most common mistakes made when working in Spring and related, in particular, to the development of web applications and the use of the Spring Boot platform. As noted on the Spring Boot website , Spring Boot takes a standardized approach to creating ready-to-use applications, and this article will follow this approach. It will give a number of recommendations that can be effectively used in the development of standard web applications based on Spring Boot.



In case you are not very familiar with the Spring Boot platform, but want to experiment with the examples in this article, I created a GitHub repository with additional materials for this article . If at some point you are a little confused reading this article, I would advise you to create a clone of this repository and experiment with the code on your computer.










Common Mistake # 6: Not using annotation-based data validation



Let's imagine that our TopTalent service from the previous examples needs an endpoint to add new TopTalent data. Also, let's assume that for some really important reason, each name you add must be exactly 10 characters long. This can be implemented, for example, as follows:



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





However, the above code is not only poorly structured, but it is not really a “clean” solution. We perform several types of data validation (namely, we verify that the TopTalentData



object TopTalentData



not null, that the value of the TopTalentData.name field is not null, and that the length of the TopTalentData.name



field is 10 characters), and also throw an exception if the data is incorrect.



All this can be done more accurately using the Hibernate validator in Spring. Let's first rewrite the addTopTalent



method, adding support for data validation:



 @RequestMapping("/put") public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) { topTalentService.addTopTalent(topTalentData); } @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) { //    }
      
      





In addition, we need to indicate what property validation we want to perform in the TopTalentData



class:



 public class TopTalentData { @Length(min = 10, max = 10) @NotNull private String name; }
      
      





Spring will now intercept the request and verify it before calling the method, so no additional manual checks will be required.



The desired goal can also be achieved by creating your own annotations. Under real conditions, it usually makes sense to use your own annotations only when your needs exceed the capabilities of the Hibernate built-in set of restrictions , but for this example, let's imagine that @Length



annotations @Length



not exist. You can create a data validator that checks the length of a string by creating two additional classes: one for validation and one for annotating properties:



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







Please note that in these cases, the correct application of the principle of separation of responsibility requires marking the property as valid if its value is null (s == null



in the isValid



method), and then use the @NotNull



annotation if this is additionally required for this property:



 public class TopTalentData { @MyAnnotation(value = 10) @NotNull private String name; }
      
      





Common Mistake # 7: Using Legacy XML-Based Configurations



Using XML was a necessity when working with previous versions of Spring, but now most of the configuration tasks can be implemented using Java code and annotations. XML configurations now act as additional and optional template code.



In this article (and also in the companion GitHub repository materials), annotations are used to configure Spring, and Spring knows which JavaBean components need to be bound, since the root package is annotated using the @SpringBootApplication composite annotation - like this:



 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
      
      





This composite annotation (see the Spring documentation for more on this) simply tells the Spring platform which packages to scan to extract the JavaBean components. In our particular case, this means that the following co.kukurin subpackages will be used for binding:





If we have additional classes that have the @Configuration



annotation, they will also be checked for Java configuration.



Common Mistake # 8: Not Using Configuration Profiles



When developing server systems, a common problem is switching between different configurations (as a rule, these are configurations for the operational and development environments). Instead of manually changing various parameters at each transition between the test and operating modes, it is more efficient to use configuration profiles.



Imagine the case when in the local development environment you use the database in RAM, and in the environment of the actual operation of your application, the MySQL database is used. This essentially means that you will use different URLs and, presumably, different credentials to access each of these databases. Let's see how this can be implemented using two configuration files:



FILE 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:
      
      







FILE APPLICATION-DEV.YAML



 spring.datasource.url: 'jdbc:h2:mem:' spring.datasource.platform: h2
      
      







It must be assumed that when working with code, you would not want to perform some random actions with a database intended for the operating environment, so it makes sense to select the profile for the development environment (DEV) as the default profile. Subsequently, you can manually override the configuration profile on the server by specifying -Dspring.profiles.active=prod



for the JVM. In addition, the default configuration profile can be specified in the operating system environment variable.



Common mistake number 9. Failure to use dependency injection mechanism



Proper use of the dependency injection mechanism in Spring means that Spring can bind all your objects by scanning all the necessary configuration classes. This is useful for loosening interdependencies and greatly facilitates testing. Instead of tightly linking classes, something like this:



 public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController() { this.topTalentService = new TopTalentService(); } }
      
      





... we let the Spring platform bind:



 public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController(TopTalentService topTalentService) { this.topTalentService = topTalentService; } }
      
      





Mishko Hevery’s lecture on the Google Tech Talks channel explains in detail why dependency injection should be used, but here we’ll see how this mechanism is used in practice. In the division of responsibility (“Common Mistake # 3”), we created the service and controller classes. Suppose we want to test a controller, assuming that the TopTalentService



class TopTalentService



working correctly. We can insert a simulator object instead of the current service implementation by creating a separate configuration class:



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





After that, we can implement the simulator object by SampleUnitTestConfig



Spring platform that we need to use SampleUnitTestConfig



as the configuration source:

@ContextConfiguration(classes = { SampleUnitTestConfig.class })







Subsequently, this will allow us to use contextual configuration to embed the custom JavaBean component in a unit test.



Common mistake number 10. Lack of testing or incorrect testing



Despite the fact that the idea of ​​unit testing is by no means new, it seems that many developers either “forget” about it (especially if it is not mandatory ) or spend it too late. Obviously, this is wrong, because the tests not only allow you to check the correct operation of the code, but also serve as documentation showing how the application should behave in various situations.

When testing web services, you rarely run exceptionally “clean” unit tests, because for a HTTP connection you usually need to use the Spring DispatcherServlet



servlet and see what happens when you receive a real HttpServletRequest



request (that is, it turns out an integration test that uses validation, serialization, etc.). An elegant and proven solution is to use REST Assured , a Java library for convenient testing of REST services, with MockMVC. Consider the following code fragment with dependency injection:



 @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



binds the surrogate implementation of the TopTalentService



class to the TopTalentController



, and all other classes are bound using the standard configuration obtained by scanning packages based on the package of the Application class. RestAssuredMockMvc



simply used to set up a lightweight environment and send a GET



request to the /toptal/get



.



Use Spring Professionally



Spring is a powerful platform that is easy to get started with, but it takes time and some effort to fully master it. If you take the time to become familiar with this platform, in the end it will undoubtedly increase the efficiency of your work, help you create cleaner code and increase your qualifications as a developer.



I recommend that you pay attention to Spring In Action - this is a good, application-oriented book that discusses many important topics related to the Spring platform.



At this point, the translation of this article came to an end.

Read the first part .



All Articles