Spring Framework often committed ten errors, you have not made a mistake?

1. a mistake: too much attention to the underlying

We are addressing this common mistake, because "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 Project Lombok annotation in the example. 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 Java Bean" 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 version can be found Intellij IDEA plug-in here.

2. Error 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 TopTalentEntitythe data. Return TopTalentEntityinstance may be tempting, but more flexible solution is to create a new class to represent the API endpoint TopTalentEntitydata.

@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, TopTalentEntityadd a "password" field to store user passwords Hash values in the database - if no TopTalentDataconnection is like, forget to change the front-end services, will be accidentally exposed to unnecessary confidential information.

3. Error 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!

In general, the separation of concerns is breaking new functionality 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 TopTalentEntityretrieved instance out TopTalentDataof the List. However, under careful observation, we can see that TopTalentControlleractually does something in this; that is, it maps the request to a specific endpoint, retrieve data from the database and from the TopTalentRepositoryentity 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 Toptalent Controller { 

    private final Toptalent Service talent service; 

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

@AllArgsConstructor 
@Service 
public class Toptalent Service { 

    private final Toptalent talent Repository repository; 
    private final Toptalent Entity Converter talent entity converter; 

    public List <Data Toptalent> getTopTalent () { 
        return topTalentRepository.findAll () 
                .stream()
                .map (talent entity converter :: 
                .collect (Collectors.toList ()); 
    } 
} 

@Component 
public class Toptalent Entity Converter { 
    public Toptalent Data toResponse (Toptalent Entity talent entity) { 
        return new Toptalent Data (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. Error 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 @ExceptionHandlerbe done (case notes found in Chapter VI) comments.

5. Error Five: multithread mishandling

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 performance tracking program 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 function directly from 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 asynchronous request processing by DeferredResult class.

6. Error six: do not use annotation-based authentication

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 more than one type of validity (ie TopTalentData not be empty, TopTalentData.name not be empty, and TopTalentData.name to 10 characters in length), and throws an exception when the data is invalid.

The data are verified to be more cleanly by integrating Hibernate validator in Spring. Let us first of all to support the reconstruction addTopTalent method validation:

@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 verify what properties in TopTalentData class:

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 usually only when you need to go beyond the built-in set of constraints Hibernate use 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 ( isValidmethod s == null), if this is the additional requirement attribute is used @NotNullannotations.

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


7. 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 @SpringBootApplicationmaking a statement that complex annotation, as follows:

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

Composite comment (You can learn more by Spring documentation) 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 (TopTalentConverter, MyAnnotationValidator)

  • @RestController (TopTalentController)

  • @Repository (TopTalentRepository)

  • @Service( TopTalentService) Class

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

8. Error 8: 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=prodto the JVM configuration file manually to override parameters. In addition, also the operating system environment variable to the desired default profile.

9. Error 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 Toptalent Controller { 

    private final Toptalent Service talent service; 

    public Toptalent Controller () { 
        this.topTalentService Toptalent = new Service (); 
    } 
}

Let us make connections for our Spring:

public class Toptalent Controller { 

    private final Toptalent Service talent service; 

    public Toptalent Controller (Toptalent Service talent service) { 
        this.topTalentService = talent service; 
    } 
}

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 SampleUnitTestConfigas 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. Error 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 HttpServletRequesttime what happens (to make it a "integrated" test, treatment verification, sequence, etc.). REST Assured, a test REST services to simplify Java DSL, on MockMVC, it has been shown to provide a very elegant solution. Consider the following code snippet with dependency injection:

@RunWith(SpringJUnit4Cla***unner.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"));
    }

}

SampleUnitTestConfigThe class TopTalentServiceis connected to an analog implementation TopTalentController, and all other classes are inferred to by a subordinate class package directory where scanning application package standard. RestAssuredMockMvcJust to set a lightweight environment, and /toptal/getsends an endpoint GETrequest.




Guess you like

Origin blog.51cto.com/14453419/2423078
Recommended