Spring ten common mistakes, 78 percent of those old programmers have stepped pit!

First we look at, Spring common errors are those

  1. Too much attention to the bottom
  2. Internal structure "leaked"
  3. The lack of separation of concerns
  4. The lack exception handling or mishandling
  5. Improper multithreading
  6. Do not use annotation-based authentication
  7. (Still) using xml-based configuration
  8. Ignore profile
  9. Unacceptable dependency injection
  10. Lack of testing, or improper testing
    Spring ten common mistakes, 78 percent of those old programmers have stepped pit!

Then he introduced one of these common mistakes
1. a mistake: too much attention to the bottom
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" what it was like:
As you can imagine, the above code is compiled as follows:
However, please note that if you intend to use the IDE in Lombok, it may need to install a plug-in can be found Intellij IDEA version of the plug-in here.

2. Error two: internal structure "leaked"
open your internal structure, 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 access TopTalentEntity data. Return TopTalentEntity instance may be tempting, but more flexible solution is to create a new class to represent TopTalentEntity data on the API endpoint.

@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, in TopTalentEntity add a "password" field to store the database user password Hash value - if there is no connector TopTalentData like, forget to change the front-end services, will be accidentally exposed to unnecessary confidential information.
Spring ten common mistakes, 78 percent of those old programmers have stepped pit!
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 TopTalentData from the database.

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

起初,这段代码似乎没什么特别的问题;它提供了一个从 TopTalentEntity 实例检索出来的TopTalentData 的 List。
然而,仔细观察下,我们可以看到 TopTalentController 实际上在此做了些事情;也就是说,它将请求映射到特定端点,从数据库检索数据,并将从 TopTalentRepository 接收的实体转换为另一种格式。一个“更干净” 的解决方案是将这些关注点分离到他们自己的类中。看起来可能是这个样子的:

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

这种层次结构的另一个优点是,它允许我们通过检查类名来确定将功能驻留在何处。此外,在测试期间,如果需要,我们可以很容易地用模拟实现来替换任何类。

4. 错误四:缺乏异常处理或处理不当
一致性的主题并非是 Spring(或 Java)所独有的,但仍然是处理 Spring 项目时需要考虑的一个重要方面。虽然编码风格可能存在争议(通常团队或整个公司内部已达成一致),但拥有一个共同的标准最终会极大地提高生产力。对多人团队尤为如此;一致性允许交流发生,而不需要花费很多资源在手把手交接上,也不需要就不同类的职责提供冗长的解释。
考虑一个包含各种配置文件、服务和控制器的 Spring 项目。在命名时保持语义上的一致性,可以创建一个易于搜索的结构,任何新的开发人员都可以按照自己的方式管理代码;例如,将 Config 后缀添加到配置类,服务层以 Service 结尾,以及控制器用 Controller 结尾。
与一致性主题密切相关,服务器端的错误处理值得特别强调。如果你曾经不得不处理编写很差的 API 的异常响应,那你可能知道原因 —— 正确解析异常会是一件痛苦的事情,而确定这些异常最初发生的原因则更为痛苦。
作为一名 API 开发者,理想情况下你希望覆盖所有面向用户的端点,并将他们转换为常见的错误格式。这通常意味着有一个通用的错误代码和描述,而不是逃避解决问题:a) 返回一个 “500 Internal Server Error”信息。b) 直接返回异常的堆栈信息给用户。(实际上,这些都应该不惜一切代价地去避免,因为除了客户端难以处理以外,它还暴露了你的内部信息)。
例如,常见错误响应格式可能长这样:

@Value
public class ErrorResponse {
    private Integer errorCode;
    private String errorMessage;
}

与此类似的事情在大多数流行的 API 中也经常遇到,由于可以容易且系统地记录,效果往往很不错。将异常转换为这种格式可以通过向方法提供 @ExceptionHandler 注解来完成(注解案例可见于第六章)。
Spring ten common mistakes, 78 percent of those old programmers have stepped pit!

5. 错误五:多线程处理不当
不管是桌面应用还是 Web 应用,无论是 Spring 还是 No Spring,多线程都是很难破解的。由并行执行程序所引起的问题是令人毛骨悚然且难以捉摸的,而且常常难以调试 —— 实际上,由于问题的本质,一旦你意识到你正在处理一个并行执行问题,你可能就不得不完全放弃调试器了,并 “手动” 检查代码,直到找到根本上的错误原因。
不幸的是,这类问题并没有千篇一律的解决方案;根据具体场景来评估情况,然后从你认为最好的角度来解决问题。
当然,理想情况下,你也希望完全避免多线程错误。同样,不存在那种一刀切的方法,但这有一些调试和防止多线程错误的实际考虑因素:
5.1. 避免全局状态
首先,牢记 “全局状态” 问题。如果你正创建一个多线程应用,那么应该密切关注任何可能全局修改的内容,如果可能的话,将他们全部删掉。如果某个全局变量有必须保持可修改的原因,请仔细使用 synchronization,并对程序性能进行跟踪,以确定没有因为新引入的等待时间而导致系统性能降低。
5.2. 避免可变性
这点直接来自于 函数式编程,并且适用于 OOP,声明应该避免类和状态的改变。简而言之,这意味着放弃 setter 方法,并在所有模型类上拥有私有的 final 字段。它们的值唯一发生变化的时间是在构造期间。这样,你可以确定不会出现争用问题,且访问对象属性将始终提供正确的值。
5.3. 记录关键数据
评估你的程序可能会在何处发生异常,并预先记录所有关键数据。如果发生错误,你将很高兴可以得到信息说明收到了哪些请求,并可更好地了解你的应用程序为什么会出现错误。需要再次注意的是,日志记录引入了额外的文件 I/O,可能会严重影响应用的性能,因此请不要滥用日志。
5.4. 复用现存实现
每当你需要创建自己的线程时(例如:向不同的服务发出异步请求),复用现有的安全实现来代替创建自己的解决方案。这在很大程度上意味着要使用 ExecutorServices 和 Java 8 简洁的函数式 CompletableFutures 来创建线程。Spring 还允许通过 DeferredResult 类来进行异步请求处理。

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) {
        // throw some exception
    }
    topTalentService.addTopTalent(topTalentData);
}

然而,上面的方法(除了构造很差以外)并不是一个真正 “干净” 的解决办法。我们正检查不止一种类型的有效性(即 TopTalentData 不得为空,TopTalentData.name 不得为空,且 TopTalentData.name 为 10 个字符长度),以及在数据无效时抛出异常。
通过在 Spring 中集成 Hibernate validator,数据校验可以更干净地进行。让我们首先重构 addTopTalent 方法来支持验证:

@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
}
// 此外,我们还必须指出我们想要在 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 时,将其标记为有效(isValid 方法中的 s == null),如果这是属性的附加要求,则使用 @NotNull 注解。

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

Spring ten common mistakes, 78 percent of those old programmers have stepped pit!

7. 错误七:(依旧)使用基于xml的配置
虽然之前版本的 Spring 需要 XML,但如今大部分配置均可通过 Java 代码或注解来完成;XML 配置只是作为附加的不必要的样板代码。
本文(及其附带的 GitHub 仓库)均使用注解来配置 Spring,Spring 知道应该连接哪些 Bean,因为待扫描的顶级包目录已在 @SpringBootApplication 复合注解中做了声明,如下所示:

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

复合注解(可通过 Spring 文档 了解更多信息)只是向 Spring 提示应该扫描哪些包来检索 Bean。在我们的案例中,这意味着这个顶级包 (co.kukurin)将用于检索:

@Component (TopTalentConverter, MyAnnotationValidator)
@RestController (TopTalentController)
@Repository (TopTalentRepository)
@Service (TopTalentService) 类

如果我们有任何额外的 @Configuration 注解类,它们也会检查基于 Java 的配置。

8. 错误八:忽略 profile
在服务端开发中,经常遇到的一个问题是区分不同的配置类型,通常是生产配置和开发配置。在每次从测试切换到部署应用程序时,不要手动替换各种配置项,更有效的方法是使用 profile。关注Java技术栈微信公众号,在后台回复关键字:boot,可以获取一份栈长整理的 Spring Boot 最新技术干货。
考虑这么一种情况:你正在使用内存数据库进行本地开发,而在生产环境中使用 MySQL 数据库。本质上,这意味着你需要使用不同的 URL 和 (希望如此) 不同的凭证来访问这两者。让我们看看可以如何做到这两个不同的配置文件:
8.1. 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:
8.2. APPLICATION-DEV.YAML 文件
spring.datasource.url: 'jdbc:h2:mem:'
spring.datasource.platform: h2

假设你不希望在修改代码时意外地对生产数据库进行任何操作,因此将默认配置文件设为 dev 是很有意义的。
然后,在服务器上,你可以通过提供 -Dspring.profiles.active=prod 参数给 JVM 来手动覆盖配置文件。另外,还可将操作系统的环境变量设置为所需的默认 profile。

9. 错误九:无法接受依赖项注入
正确使用 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;
    }
}

Misko Hevery 的 Google talk 深入解释了依赖注入的 “为什么”,所以,让我们看看它在实践中是如何使用的。在关注点分离(常见错误 #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 })

之后,我们就可以使用上下文配置将 Bean 注入到单元测试中。

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 but in hindsight it added. 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 the Spring DispatcherServlet, and see what happens when when you receive an actual HttpServletRequest via HTTP (making it an "integrated" test , process the 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"));
    }
}

SampleUnitTestConfig class TopTalentService connected to the 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 endpoint sends a GET request.

Welcome to share with everyone, like the point of a praise yo remember the article, thanks for the support!

Guess you like

Origin blog.51cto.com/14442094/2426068