Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What are the top ten mistakes you often make when using Spring Framework

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/02 Report--

What are the top ten mistakes often made in using Spring Framework? aiming at this problem, this article introduces the corresponding analysis and solution in detail, hoping to help more partners who want to solve this problem to find a more simple and feasible way.

Spring is arguably one of the most popular Java frameworks and a powerful beast that needs to be tamed. Although its basic concepts are quite easy to grasp, it still takes a lot of time and effort to become a powerful Spring developer.

In this article, we will introduce some common errors in Spring, especially for Web applications and Spring Boot. As the Spring Boot website says, Spring Boot maintains a rather stubborn view of how Production-Ready applications should be built. This article will try to imitate this view and provide an overview of some techniques that will be well integrated into standard Spring Boot web application development.

If you are not familiar with Spring Boot yet, but still want to try some of the things mentioned next, I have also created a GitHub repository for this article. If you are confused during reading, I suggest clone the code and use it on your local computer.

1. Common mistake one: pay too much attention to the bottom

We are solving this common mistake because the "not created by me" syndrome is very common in the field of software development. Symptoms include frequent rewriting of common code, which is common to many developers.

Although understanding the internal structure and implementation of a particular library is to a large extent good and necessary (it can also be a good learning process), as a software engineer, constantly dealing with the same underlying implementation details is harmful to a person's development career. Abstract frameworks like Spring exist for a reason, freeing you from repetitive manual work and allowing you to focus on higher levels of detail-domain objects and business logic.

Therefore, accept abstraction. The next time you are faced with a specific problem, do a quick search to determine if the library to solve the problem has been integrated into Spring; now you may find a suitable off-the-shelf solution. For example, a very useful library, in the rest of this article, I will use Project Lombok annotations in the examples. Lombok is used as a boilerplate code generator in the hope that lazy developers will not encounter problems when they are familiar with the library. For example, take a look at what a "standard Java Bean" using Lombok looks like:

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

As you might expect, 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 () {}}

Note, however, that if you plan to use Lombok in IDE, you will probably need to install a plug-in, and you can find the Intellij IDEA version of the plug-in here.

two。 Common mistake 2: internal structure "leakage"

Exposing your internal structure is never a good idea because it creates inflexibility in service design and promotes bad coding practices. The internal mechanism of "disclosure" is to make the database structure accessible from some API endpoints. For example, the following POJO ("Plain Old Java Object") class represents a table in the database:

@ Entity@NoArgsConstructor@Getterpublic class TopTalentEntity {@ Id @ GeneratedValue private Integer id; @ Column private String name; public TopTalentEntity (String name) {this.name = name;}}

Suppose there is an endpoint that needs to access TopTalentEntity data. It may be tempting to return an TopTalentEntity instance, but a more flexible solution is to create a new class to represent the TopTalentEntity data on the API endpoint.

@ AllArgsConstructor@NoArgsConstructor@Getterpublic class TopTalentData {private String name;}

In this way, making changes to the back end of the database will not require any additional changes at the service layer. Consider adding a "password" field to TopTalentEntity to store the Hash value of the user's password in the database-- if you don't have a connector like TopTalentData and forget to change the service front end, you will accidentally expose some unnecessary secret information.

3. Common mistake 3: lack of separation of concerns

With the growth of program size, code organization has gradually become a more and more important issue. Ironically, most good software engineering principles begin to collapse on scale-especially if you don't think too much about program architecture. One of the most common mistakes developers make is to confuse code concerns, which is easy to do!

Often, the way to break the separation of concerns is to simply "pour" new functionality into existing classes. Of course, this is a good short-term solution (it requires less input for beginners), but it will inevitably become a problem in the future, whether during testing, maintenance, or somewhere in between. Consider the following controller, which will return TopTalentData from the database.

@ RestControllerpublic class TopTalentController {private final TopTalentRepository topTalentRepository; @ RequestMapping ("/ toptal/get") public List getTopTalent () {return topTalentRepository.findAll () .stream () .map (this::entityToData) .operations (Collectors.toList ());} private TopTalentData entityToData (TopTalentEntity topTalentEntity) {return new TopTalentData (topTalentEntity.getName ());}}

At first, this code seemed to have no particular problem; it provided a List of TopTalentData retrieved from the TopTalentEntity instance. On closer inspection, however, we can see that TopTalentController actually does something here; that is, it maps requests to specific endpoints, retrieves data from the database, and converts entities received from TopTalentRepository into another format. A "cleaner" solution is to separate these concerns into their own classes. It might look like this:

@ RestController@RequestMapping ("/ toptal") @ AllArgsConstructorpublic class TopTalentController {private final TopTalentService topTalentService; @ RequestMapping ("/ get") public List getTopTalent () {return topTalentService.getTopTalent ();}} @ AllArgsConstructor@Servicepublic class TopTalentService {private final TopTalentRepository topTalentRepository; private final TopTalentEntityConverter topTalentEntityConverter Public List getTopTalent () {return topTalentRepository.findAll () .stream () .map (topTalentEntityConverter::toResponse) .map (Collectors.toList ());} @ Componentpublic class TopTalentEntityConverter {public TopTalentData toResponse (TopTalentEntity topTalentEntity) {return new TopTalentData (topTalentEntity.getName ());}}

Another advantage of this hierarchy is that it allows us to determine where the function resides by checking the class name. In addition, during testing, we can easily replace any class with a mock implementation if necessary.

4. Common mistake 4: lack of exception handling or improper handling

Consistent themes are not unique to Spring (or Java), but they are still an important aspect to consider when dealing with Spring projects. While coding styles can be controversial (usually agreed upon within the team or the entire company), having a common standard can ultimately greatly increase productivity. This is especially true for multiplayer teams; consistency allows communication to take place without spending a lot of resources on hand-to-hand handover or providing lengthy explanations for different types of responsibilities.

Consider a Spring project that contains various profiles, services, and controllers. By being semantically consistent in naming, you can create a structure that is easy to search, and any new developer can manage the code in his own way; for example, add a Config suffix to the configuration class, the service layer ends in Service, and the controller ends in Controller.

Closely related to the topic of consistency, server-side error handling deserves special emphasis. If you've ever had to deal with poorly written API exception responses, you probably know why-- parsing exceptions correctly can be painful, and determining why they happened in the first place is even more painful.

As an API developer, ideally you want to cover all user-facing endpoints and convert them to common error formats. This usually means having a common error code and description, rather than avoiding solving the problem: a) return a "500 Internal Server Error" message. B) return the stack information of the exception directly to the user. In fact, this should be avoided at all costs, because in addition to being difficult for the client to handle, it also exposes your internal information.

For example, a common error response format might look like this:

@ Valuepublic class ErrorResponse {private Integer errorCode; private String errorMessage;}

Similar things are often encountered in most popular API, and the results are often very good because they can be easily and systematically recorded. Converting an exception to this format can be done by providing @ ExceptionHandler annotations to the method (the annotation case can be found in Chapter 6).

5. Common mistake 5: improper multithreading

Whether it is desktop applications or Web applications, whether it is Spring or No Spring, multithreading is very difficult to crack. The problems caused by parallel execution are creepy and elusive, and are often difficult to debug-- in fact, because of the nature of the problem, once you realize that you are dealing with a parallel execution problem, you may have to abandon the debugger completely and "manually" check the code until you find the root cause of the error. Unfortunately, there is no one-size-fits-all solution to this kind of problem; assess the situation according to the specific situation, and then solve the problem from what you think is the best perspective.

Of course, ideally, you also want to avoid multithreading errors altogether. Again, there is no one-size-fits-all approach, but there are some practical considerations for debugging and preventing multithreaded errors:

5.1. Avoid global statu

First of all, keep in mind the "global state" problem. If you are creating a multithreaded application, you should pay close attention to anything that may be modified globally and, if possible, delete them all. If there is a reason why a global variable must remain modifiable, use synchronization carefully and track program performance to make sure that system performance is not degraded by the newly introduced wait time.

5.2. Avoid variability

This comes directly from functional programming and applies to OOP, declaring that class and state changes should be avoided. In short, this means abandoning the setter method and having private final fields on all model classes. The only time their values change is during construction. In this way, you can be sure that there will be no contention problems, and that accessing the object properties will always provide the correct values.

5.3. Record key data

Assess where exceptions may occur in your program and record all key data in advance. If an error occurs, you will be happy to get information about which requests have been received and to better understand why your application went wrong. Once again, it is important to note that logging introduces an additional file called Imando O, which may seriously affect the performance of the application, so please do not abuse the log.

5.4. Reuse existing implementations

Whenever you need to create your own threads (for example, making asynchronous requests to different services), reuse existing security implementations instead of creating your own solutions. This largely means using ExecutorServices and Java 8's concise functional [CompletableFutures] (http://www.nurkiewicz.com/2013/05/java-8-definition-guide-to-.html) to create threads. Spring also allows asynchronous request processing through the [DeferredResult] (http://docs.spring.io/springing-framework/docs/4.0.x/springing-frame-reference/html/mvc.html # mvc.ann-async) class.

6. Common mistake 6: do not use annotation-based validation

Suppose our previous TopTalent service needs an endpoint to add a new TopTalent. In addition, suppose that for some reason, each new noun needs to be 10 characters long. One way to do this might 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 method (except for poor construction) is not a really "clean" solution. We are checking for validity of more than one type (that is, TopTalentData must not be empty, TopTalentData.name must not be empty, and TopTalentData.name is 10 characters long), and throw an exception if the data is invalid.

By integrating [Hibernate validator] (http://hibernate nate.org/validator/) into Spring, data verification can be done more cleanly. Let's first ReFactor the addTopTalent method to support 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 point out what attributes we want to validate in the TopTalentData class:

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

Now, Spring will intercept the request and validate the parameters before invoking the method-- no additional manual testing is required.

Another way to achieve the same functionality is to create our own annotations. Although you usually use custom annotations only when you need to exceed Hibernate's built-in constraint set, in this case, we assume that @ Length does not exist. You can create two additional classes to validate the string length, one for validation and one for annotating attributes:

@ 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 [] payload () default {}; int value (); @ Componentpublic class MyAnnotationValidator implements ConstraintValidator {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 best practice for separation of concerns requires that the property be marked as valid when it is null (s = = null in the isValid method), and if this is an additional requirement of the property, use the @ NotNull annotation.

Public class TopTalentData {@ MyAnnotation (value = 10) @ NotNull private String name;} 7. Common mistake 7: (still) use xml-based configuration

Although previous versions of Spring required XML, most of today's configuration can be done through Java code or annotations; XML configuration is just additional unnecessary boilerplate code.

This article (and its accompanying GitHub repository) uses annotations to configure Spring,Spring to know which Bean to connect to, because the top-level package directory to be scanned has been declared in the @ SpringBootApplication compound annotation, as shown below:

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

Compound annotations (for more information can be found in [Spring documentation] (http://docs.spring.io/autorepo/docs/springing-boot/current/reference/html/using-boot-using-springbootapplication- annot.html)) simply prompt Spring which packages should be scanned 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 @ Configuration annotation classes, they will also check the Java-based configuration.

8. Common mistake 8: ignore profile

One of the problems often encountered in server-side development is to distinguish between different types of configuration, usually production configuration and development configuration. Instead of manually replacing various configuration items each time you switch from testing to deploying the application, a more efficient way is to use profile.

Consider a situation where you are using an in-memory database for local development while using a MySQL database in a production environment. In essence, this means that you need to use different URL and (hopefully) different credentials to access both. Let's see how we can do these two different profiles:

8.1. APPLICATION.YAML file # set default profile to 'dev'spring.profiles.active: dev# production database detailsspring.datasource.url:' jdbc:mysql://localhost:3306/toptal'spring.datasource.username: rootspring.datasource.password:8.2. APPLICATION-DEV.YAML file spring.datasource.url: 'jdbc:h3:mem:'spring.datasource.platform: H3

Suppose you don't want to accidentally take any action on the production database when you modify the code, so it makes sense to set the default configuration file to dev. Then, on the server, you can manually overwrite the configuration file by providing the-Dspring.profiles.active=prod parameter to JVM. In addition, the environment variable of the operating system can be set to the desired default profile.

9. Common error 9: unable to accept dependency injection

Using Spring's dependency injection correctly means allowing it to connect all objects together by scanning all the necessary configuration classes; this is useful for decoupling relationships and makes testing easier, rather than doing so through tight coupling between classes:

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

We asked Spring to make a connection for us:

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

Misko Hevery's Google talk provides an in-depth explanation of the "why" of dependency injection, so let's look at how it is used in practice. In the section on separation of concerns (common error # 3), we created a service and controller class. Suppose we want to test the controller on the premise that TopTalentService behaves correctly. We can insert a mock object instead of the actual service implementation by providing a separate configuration class:

@ Configurationpublic class SampleUnitTestConfig {@ Bean public TopTalentService topTalentService () {TopTalentService topTalentService = Mockito.mock (TopTalentService.class); Mockito.when (topTalentService.getTopTalent ()) .thenReturn (Stream.of ("Mary", "Joel") .map (TopTalentData::new) .targets (Collectors.toList ()); return topTalentService;}}

We can then inject the mock object by telling Spring to use SampleUnitTestConfig as its configuration class:

@ ContextConfiguration (classes = {SampleUnitTestConfig.class})

After that, we can inject Bean into the unit test using context configuration.

10. Common error 10: lack of testing, or improper testing

Although the concept of unit testing has been around for a long time, many developers seem to either "forget" to do it (especially if it is not "necessary") or just add it afterwards. This is obviously not desirable because tests should not only verify the correctness of the code, but also serve as documentation of how the program should behave in different scenarios.

When testing Web services, only "pure" unit tests are rarely done, because communicating over HTTP usually involves calling Spring's DispatcherServlet and seeing what happens when an actual HttpServletRequest is received (making it an "integration" test, handling validation, serialization, and so on). REST Assured, a Java DSL for simplifying testing of REST services, has been shown to provide a very elegant solution on top of MockMVC. 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; response.then () .body ("name", hasItems ("Mary", "Joel"));}}

The SampleUnitTestConfig class connects the mock implementation of TopTalentService to TopTalentController, while all other classes are standard configurations inferred by scanning the subordinate package directory of the package where the application class resides. RestAssuredMockMvc is simply used to set up a lightweight environment and send an GET request to the / toptal/get endpoint.

11. Become a master of Spring

Spring is a powerful framework that is easy to use, but takes some investment and time to fully master. In the long run, taking the time to familiarize yourself with the framework is sure to increase your productivity and eventually help you write cleaner code and become a better developer.

Looking for more resources, Spring In Action is an excellent practical book that covers many of the core topics of Spring.

Original text: https://www.toptal.com/spring/top-10-most-common-spring-framework-mistakes > > author: Toni Kukurin > > translator: ten thousand thoughts

This is the answer to the question about what are the ten mistakes often made in using Spring Framework. I hope the above content can be of some help to you. If you still have a lot of doubts to be solved, you can follow the industry information channel to learn more about it.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report