Use the top ten mistakes often make the Spring Framework

Spring is arguably one of the most popular Java framework, but also a strong need to tame the beast. While it's fairly easy to grasp the basic concepts, but to become a powerful Spring developers still need a lot of time and effort.

In this article, we will introduce some common mistakes in Spring, especially for Web applications and Spring Boot. As  Spring Boot official website  said, Spring Boot on how to build applications Production-Ready maintained a  fairly stubborn point of view , this article will try to imitate this view, and provides an overview of some skills, these skills will be well integrated into the standard Spring Boot to the development of web applications.

If you are not very familiar with Spring Boot, but still want to try something at the next mentioned, I have created this article to a GitHub repository . If you are confused in the reading process, I propose to clone the code down and use the code on the local computer.

1. a common mistake: too much attention to the underlying

We are addressing this common mistake, because the  "not invented here"  syndrome is very common in software development. Some common symptoms include frequent rewriting code, many developers have this symptom.

While understanding the internal structure of a particular library and its implementation, to a large extent is good and necessary (it can be a good learning process), but as a software engineer, continue to deal with the same underlying implementation details personal career development is harmful. Like there is this abstraction Spring framework is there for a reason, it will liberate you from repeatedly in manual labor, and allows you to focus on the details of a higher level - the domain objects and business logic.

Therefore, to accept the abstract. The next time you face a particular problem, first a quick search to determine whether the library to solve this problem have been integrated into the Spring; now, you might find a suitable ready-made solutions. For example, a useful library, in the rest of this article, I will use the example  Project Lombok  comment. Lombok is used as the model code generator, lazy developers do not want to have problems familiar with this library. For example, look at the use of Lombok " standard Bean the Java " is what it looks like:

@Getter
@Setter
@NoArgsConstructor
public class Bean implements Serializable {
    int firstBeanProperty;
    String secondBeanProperty;
}

As you can imagine, the above code is compiled as follows:

public class Bean implements Serializable {
    private int firstBeanProperty;
    private String secondBeanProperty;

    public int getFirstBeanProperty() {
        return this.firstBeanProperty;
    }

    public String getSecondBeanProperty() {
        return this.secondBeanProperty;
    }

    public void setFirstBeanProperty(int firstBeanProperty) {
        this.firstBeanProperty = firstBeanProperty;
    }

    public void setSecondBeanProperty(String secondBeanProperty) {
        this.secondBeanProperty = secondBeanProperty;
    }

    public Bean() {
    }
}

However, please note that if you intend to use the IDE in Lombok, it may need to install a plug-in in  here  to find Intellij IDEA version of the plug.

2. Common Mistakes two: internal structure "leaked"

Open your internal structure, it is never a good idea, because it caused no flexibility in service design, thus contributing to poor coding practices. "Leaked" internal mechanism can be accessed from the performance of some of the API endpoint database structure. For example, the following POJO ( "Plain Old Java Object") class represents a table in the database:

@Entity
@NoArgsConstructor
@Getter
public class TopTalentEntity {

    @Id
    @GeneratedValue
    private Integer id;

    @Column
    private String name;

    public TopTalentEntity(String name) {
        this.name = name;
    }

}

Assume that there is an end point, he needs to access  TopTalentEntity the data. Return  TopTalentEntity instance may be tempting, but more flexible solution is to create a new class to represent the API endpoint  TopTalentEntity data.

@AllArgsConstructor
@NoArgsConstructor
@Getter
public class TopTalentData {
    private String name;
}

In this way, changes to the database backend will not need to make any additional changes in the service layer. Under consideration,  TopTalentEntity add a "password" field to store user passwords Hash values in the database - if no  TopTalentData connection is like, forget to change the front-end services, will be accidentally exposed to unnecessary confidential information.

3. Common Mistakes three: the lack of separation of concerns

With the growth of the size of the program, gradually, code organization has become an increasingly important issue. Ironically, most of the good software engineering principles began to collapse on the scale - especially without much consideration of the application architecture design situation. A mistake is to confuse the code concerns developers most commonly committed, it is very easy to do!

Typically, the break  separation of concerns  is the new feature simply "inverted" in existing classes. Of course, this is a good short-term solution (for starters, it requires less input), but it will inevitably become a problem in the future, whether it is during testing, maintenance, or somewhere during the between the two. Consider the following controller, it returns from the database  TopTalentData.

@RestController
public class TopTalentController {

    private final TopTalentRepository topTalentRepository;

    @RequestMapping("/toptal/get")
    public List<TopTalentData> getTopTalent() {
        return topTalentRepository.findAll()
                .stream()
                .map(this::entityToData)
                .collect(Collectors.toList());
    }

    private TopTalentData entityToData(TopTalentEntity topTalentEntity) {
        return new TopTalentData(topTalentEntity.getName());
    }

}

At first, the code seems to be no particular problem; it provides from a  TopTalentEntity retrieved instance out  TopTalentDataof the List. However, under careful observation, we can see that  TopTalentController actually does something in this; that is, it maps the request to a specific endpoint, retrieve data from the database and from the  TopTalentRepository entity converts the received one format to another. A "cleaner" solution is to separate these concerns to their own class. It might look like this:

@RestController
@RequestMapping("/toptal")
@AllArgsConstructor
public class TopTalentController {

    private final TopTalentService topTalentService;

    @RequestMapping("/get")
    public List<TopTalentData> getTopTalent() {
        return topTalentService.getTopTalent();
    }
}

@AllArgsConstructor
@Service
public class TopTalentService {

    private final TopTalentRepository topTalentRepository;
    private final TopTalentEntityConverter topTalentEntityConverter;

    public List<TopTalentData> getTopTalent() {
        return topTalentRepository.findAll()
                .stream()
                .map(topTalentEntityConverter::toResponse)
                .collect(Collectors.toList());
    }
}

@Component
public class TopTalentEntityConverter {
    public TopTalentData toResponse(TopTalentEntity topTalentEntity) {
        return new TopTalentData(topTalentEntity.getName());
    }
}

Another advantage of such a hierarchy is that it allows us to determine where the function resides by checking the class name. In addition, during the test, if necessary, we can easily implement analog to replace any class.

4. Common Mistakes four: the lack exception handling or mishandling

Consistency is not the theme of Spring (or Java) are unique, but still is an important aspect to consider when handling Spring project. While coding style may be controversial (usually whole-house team or has agreed), but with a common standard will ultimately greatly improve productivity. This is especially true for more than a team; consistency allows the exchange take place without the need to spend a lot of resources on hand and the transfer does not require the same kind of responsibility does not provide lengthy explanations.

Consider a Spring project contains various configuration files, services, and controller. To maintain consistency in the naming semantics, you can create a structure easily searchable any new developers to manage the code in its own way; for example, adding a suffix to the configuration Config class, the service layer to the end of the Service, as well as control Used ending Controller.

Closely related to the theme of consistency, server-side error handling deserve special emphasis. If you've ever had to deal with poor preparation of the API abnormal response, you probably know the reason - correctly resolve the anomaly would be a painful thing, but to determine the cause of these abnormalities first occurred is even more painful.

As an API developer, ideally you want to overwrite all user-facing end, and convert them into common error format. This usually means that there is a common error code and description, rather than escaping to solve the problem: a) return a "500 Internal Server Error" message. b) direct return stack exception information to the user. (In fact, these should be avoided at all costs to go, because in addition to the client difficult to handle, it also exposes your internal information).

For example, a common error response format may be a long way:

@Value
public class ErrorResponse {

    private Integer errorCode;
    private String errorMessage;

}

Things like this often encountered in the most popular API, because you can easily and systematically recorded, the results are often very good. The exception into this format by providing a method to  @ExceptionHandler be done (case notes found in Chapter VI) comments.

5. Common Error 5: improper multithreading

Whether desktop or Web application, either Spring or No Spring, multi-threading are hard to break. Problems caused by the parallel execution of the program due to be creepy and elusive, and often difficult to debug - in fact, due to the nature of the problem, once you realize that you are dealing with a parallel implementation of the problem, you may have to completely give up a debugger, and "manual" check the code until you find the root cause of the error on. Unfortunately, these problems are not cookie-cutter solutions; depending on the scene to assess the situation, and then to solve the problem you think is the best angle.

Of course, ideally, you want to completely avoid multi-threading errors. Similarly, the kind of one size fits all approach does not exist, but there are some multi-threaded debugging and prevent erroneous practical considerations:

5.1. Avoid global state

First of all, keep in mind the "global status" issues. If you are creating a multithreaded application, you should pay close attention to anything that may globally modify, if possible, they will all be deleted. If a global variable must be maintained for a reason that can be modified, please carefully use  Synchronization , and program performance tracking to determine because there is no time to wait for the introduction of the new system and result in reduced performance.

5.2. Avoid variability

This directly from the  functional programming , and applies of OOP, class declaration should be avoided and changes state. In short, this means giving up the setter methods, and has a private final fields on all model classes. The only time their value changes that occur during construction. In this way, you can be sure will not be contention issues, and access to object properties will always provide the correct values.

5.3. Record key data

Evaluate your program Exceptions can occur where all critical data and pre-recorded. If an error occurs, you can get the information would be glad to explain what the request is received, and to better understand why your application errors. Need again to note that the introduction of additional log file I / O, can seriously affect the performance of the application, so please do not abuse the log.

5.4. Reuse existing realization

Whenever you need to create your own thread (for example: issued to different service asynchronous requests), to re-create their own solutions instead of using the existing security implementations. This is largely meant to be used  ExecutorServices  and Java 8 simple functional  CompletableFutures  to create a thread. Spring also allows  DeferredResult  to asynchronous request processing class.

6. Common Mistakes six: Do not use authentication based annotation

Suppose we need a service before the end of TopTalent to add new TopTalent. In addition, based on the assumption for some reason, every new term requires 10 characters in length. A method for doing this may be 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) {
        // throw some exception
    }

    topTalentService.addTopTalent(topTalentData);
}

However, the above methods (except for poor construction) is not a true "clean" solution. We are checking the validity of more than one type (ie  TopTalentData not be empty, TopTalentData.name must not be empty, and  TopTalentData.name 10 characters in length), and throws an exception when the data is invalid.

In Spring by integrating  the Hibernate Validator , verification data can be more cleanly. Let us first reconstruction  addTopTalent method to support authentication:

@RequestMapping("/put")
public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) {
    topTalentService.addTopTalent(topTalentData);
}

@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) {
    // handle validation exception
}

In addition, we must also point out that we want to  TopTalentData verify what type of property:

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

Now, Spring will intercept its requests and validate parameters before calling the method - without using additional manual testing.

Another way to achieve the same functionality is to create our own notes. Although you usually only need to go beyond  the built-in constraint set of Hibernate  when using custom annotations, in this case, we assume that @Length does not exist. You can create two additional classes to validate the string length, one for validation attributes for a comment:

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

Note that, in these cases, the separation of concerns when best practice requires property is null, it is marked as valid ( isValid method  s == null), if this is the additional requirement attribute is used  @NotNull annotations.

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

7. Common Error 7 :( still) xml-based configuration

Although previous versions of Spring require XML, but now most of the configuration can be done through Java code or annotations; XML configuration just as an additional unnecessary boilerplate code.

(And its accompanying GitHub repository) herein are uses annotations to configure Spring, Spring Bean to know which should be connected, because the top-level directory of the package has to be scanned in  @SpringBootApplication making a statement that complex annotation, as follows:

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

Complex notes (via  Spring documentation  for more information) just which packages to Spring Tips should scan to retrieve Bean. In our case, this means that the top-level package (co.kukurin) will be used to retrieve:

  • @Component (TopTalentConverterMyAnnotationValidator)
  • @RestController (TopTalentController)
  • @Repository (TopTalentRepository)
  • @Service ( TopTalentService) Class

If we have any additional  @Configuration comment like, they will check the Java-based configuration.

8. Common Mistakes Eight: Ignore profile

In the service-side development, a frequently encountered problem is to distinguish between different types of configuration, usually a production configuration and development configuration. Switching from the test each time to deploy the application, various configurations do not replace manual entry, more effective method is to use profile.

Consider such a situation: You are using in-memory database for local development, and using a MySQL database in a production environment. Essentially, this means that you need to use a different URL, and (hopefully) different credentials to access both. Let's see how you can do it two different profiles:

8.1. APPLICATION.YAML file

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

8.2. APPLICATION-DEV.YAML file

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

Suppose you do not want to accidentally carry out any operation on the production database when you modify the code so that the default configuration file to dev makes sense. Then, on the server, you can provide  -Dspring.profiles.active=prod to the JVM configuration file manually to override parameters. In addition, also the operating system environment variable to the desired default profile.

9. Common Mistakes Nine: can not accept dependency injection

Spring dependency injection using proper means which allow to connect together all the necessary configuration by scanning all objects; this is useful for decoupling relations, also makes testing easier, not tight between classes by coupled to do something like this:

public class TopTalentController {

    private final TopTalentService topTalentService;

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

Let us make connections for our Spring:

public class TopTalentController {

    private final TopTalentService topTalentService;

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

Misko Hevery of Google talk  in-depth explanation of dependency injection "why", so, let's see it in practice is how to use. In a separation of concerns (common mistake # 3), we have created a service and controller classes. Suppose we want to  TopTalentServicetest controller under the premise of correct behavior. We can insert a simulated object via a separate configuration instead of the actual service class to achieve:

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

Then, we can tell by using Spring  SampleUnitTestConfig as its configuration class to inject mock objects:

@ContextConfiguration(classes = { SampleUnitTestConfig.class })

After that, we can use the context configuration Bean injected into unit tests.

10. Common Errors ten: the lack of testing, or improper testing

Although the concept of unit testing has been around a long time, but many developers seem either to "forget" to do it (especially if it is not "necessary" time), or just to add it after the fact. This is obviously not desirable, because the test should not only verify the correctness of the code, the document should also be a program under different scenarios of how to behave.

When testing a Web service, only rarely "pure" unit testing, because communication is usually required to call Spring through HTTP  DispatcherServlet, and view when receiving a real  HttpServletRequest time what happens (to make it a "integrated" test, treatment verification, sequence, etc.). Assured REST , REST services for simplified testing of Java DSL, on MockMVC, has been shown to provide a very elegant solution. Consider the following code snippet 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 The class  TopTalentService is connected to an analog implementation  TopTalentController , and all other classes are inferred to by a subordinate class package directory where scanning application package standard. RestAssuredMockMvc Just to set a lightweight environment, and  /toptal/get sends an endpoint  GET request.

11. Spring become masters

Spring framework is a powerful, very easy to use, but requires some investment and time before they can fully grasp. In the long run, spend time familiarizing framework definitely improve your productivity, and ultimately help you write cleaner code, become a better developer.

Looking for more resources, Spring an In Action  is a central theme of Spring covers a lot of good practical books.

Original: https://www.toptal.com/spring/top-10-most-common-spring-framework-mistakes

Author: Toni Kukurin

Translator: Wan wanted

Guess you like

Origin www.cnblogs.com/springforall/p/11261736.html