<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          這10個Spring錯誤你一定中過招!

          共 11562字,需瀏覽 24分鐘

           ·

          2021-08-11 11:19

            Java大聯(lián)盟

            幫助萬千Java學(xué)習(xí)者持續(xù)成長

          關(guān)注


          者:Toni Kukurin
          toptal.com/spring/top-10-most-common-spring-framework-mistakes


          B 站搜索:楠哥教你學(xué)Java

          獲取更多優(yōu)質(zhì)視頻教程


          本文整理了研發(fā)人員使用 Spring framework 框架時經(jīng)常會出現(xiàn)的錯誤。

          錯誤一:太過關(guān)注底層

          我們正在解決這個常見錯誤,是因為 “非我所創(chuàng)” 綜合癥在軟件開發(fā)領(lǐng)域很是常見。癥狀包括經(jīng)常重寫一些常見的代碼,很多開發(fā)人員都有這種癥狀。
          雖然理解特定庫的內(nèi)部結(jié)構(gòu)及其實現(xiàn),在很大程度上是好的并且很有必要的(也可以是一個很好的學(xué)習(xí)過程),但作為軟件工程師,不斷地處理相同的底層實現(xiàn)細(xì)節(jié)對個人的開發(fā)生涯是有害的。
          像 Spring 這種抽象框架的存在是有原因的,它將你從重復(fù)地手工勞作中解放出來,并允許你專注于更高層次的細(xì)節(jié) —— 領(lǐng)域?qū)ο蠛蜆I(yè)務(wù)邏輯。
          因此,接受抽象。下次面對特定問題時,首先進(jìn)行快速搜索,確定解決該問題的庫是否已被集成到 Spring 中;現(xiàn)在,你可能找到一個合適的現(xiàn)成解決方案。
          比如,一個很有用的庫,在本文的其他部分,我將在示例中使用 Project Lombok 注解。Lombok 被用作樣板代碼生成器,希望懶惰的開發(fā)人員在熟悉這個庫時不會遇到問題。舉個例子,看看使用 Lombok 的 “標(biāo)準(zhǔn) Java Bean” 是什么樣子的:
          如你所想,上述代碼被編譯為:
          但是,請注意,如果你打算在 IDE 中使用 Lombok,很可能需要安裝一個插件,可在 此處 找到 Intellij IDEA 版本的插件。


          錯誤二:內(nèi)部結(jié)構(gòu) “泄露”

          公開你的內(nèi)部結(jié)構(gòu),從來都不是一個好主意,因為它在服務(wù)設(shè)計中造成了不靈活性,從而促進(jìn)了不好的編碼實踐。“泄露” 的內(nèi)部機(jī)制表現(xiàn)為使數(shù)據(jù)庫結(jié)構(gòu)可以從某些 API 端點訪問。例如,下面的POJO(“Plain Old Java Object”)類表示數(shù)據(jù)庫中的一個表:
          @Entity@NoArgsConstructor@Getterpublic class TopTalentEntity {
          @Id @GeneratedValue private Integer id;
          @Column private String name;
          public TopTalentEntity(String name) { this.name = name; }
          }
          假設(shè),存在一個端點,他需要訪問TopTalentEntity數(shù)據(jù)。返回TopTalentEntity實例可能很誘人,但更靈活的解決方案是創(chuàng)建一個新的類來表示 API 端點上的TopTalentEntity數(shù)據(jù)。
          @AllArgsConstructor@NoArgsConstructor@Getterpublic class TopTalentData {    private String name;}
          這樣,對數(shù)據(jù)庫后端進(jìn)行更改將不需要在服務(wù)層進(jìn)行任何額外的更改。考慮下,在TopTalentEntity中添加一個 “password” 字段來存儲數(shù)據(jù)庫中用戶密碼的 Hash 值 —— 如果沒有TopTalentData之類的連接器,忘記更改服務(wù)前端,將會意外地暴露一些不必要的秘密信息。

          錯誤三:缺乏關(guān)注點分離

          隨著程序規(guī)模的增長,逐漸地,代碼組織成為一個越來越重要的問題。諷刺的是,大多數(shù)好的軟件工程原則開始在規(guī)模上崩潰 —— 特別是在沒有太多考慮程序體系結(jié)構(gòu)設(shè)計的情況下。開發(fā)人員最常犯的一個錯誤就是混淆代碼關(guān)注點,這很容易做到!
          通常,打破 關(guān)注點分離 的是將新功能簡單地 “倒” 在現(xiàn)有類中。當(dāng)然,這是一個很好的短期解決方案(對于初學(xué)者來說,它需要更少的輸入),但它也不可避免地會在將來成為一個問題,無論是在測試期間、維護(hù)期間還是介于兩者之間。考慮下下面的控制器,它將從數(shù)據(jù)庫返回TopTalentData
          @RestControllerpublic 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。
          然而,仔細(xì)觀察下,我們可以看到TopTalentController實際上在此做了些事情;也就是說,它將請求映射到特定端點,從數(shù)據(jù)庫檢索數(shù)據(jù),并將從TopTalentRepository接收的實體轉(zhuǎn)換為另一種格式。一個“更干凈” 的解決方案是將這些關(guān)注點分離到他們自己的類中。看起來可能是這個樣子的:
          @RestController@RequestMapping("/toptal")@AllArgsConstructorpublic class TopTalentController {
          private final TopTalentService topTalentService;
          @RequestMapping("/get") public List<TopTalentData> getTopTalent() { return topTalentService.getTopTalent(); }}
          @AllArgsConstructor@Servicepublic class TopTalentService {
          private final TopTalentRepository topTalentRepository; private final TopTalentEntityConverter topTalentEntityConverter;
          public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(topTalentEntityConverter::toResponse) .collect(Collectors.toList()); }}
          @Componentpublic class TopTalentEntityConverter { public TopTalentData toResponse(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); }}
          這種層次結(jié)構(gòu)的另一個優(yōu)點是,它允許我們通過檢查類名來確定將功能駐留在何處。此外,在測試期間,如果需要,我們可以很容易地用模擬實現(xiàn)來替換任何類。


          錯誤四:缺乏異常處理或處理不當(dāng)

          一致性的主題并非是 Spring(或 Java)所獨有的,但仍然是處理 Spring 項目時需要考慮的一個重要方面。雖然編碼風(fēng)格可能存在爭議(通常團(tuán)隊或整個公司內(nèi)部已達(dá)成一致),但擁有一個共同的標(biāo)準(zhǔn)最終會極大地提高生產(chǎn)力。對多人團(tuán)隊尤為如此;一致性允許交流發(fā)生,而不需要花費很多資源在手把手交接上,也不需要就不同類的職責(zé)提供冗長的解釋。
          考慮一個包含各種配置文件、服務(wù)和控制器的 Spring 項目。在命名時保持語義上的一致性,可以創(chuàng)建一個易于搜索的結(jié)構(gòu),任何新的開發(fā)人員都可以按照自己的方式管理代碼;例如,將 Config 后綴添加到配置類,服務(wù)層以 Service 結(jié)尾,以及控制器用 Controller 結(jié)尾。
          與一致性主題密切相關(guān),服務(wù)器端的錯誤處理值得特別強(qiáng)調(diào)。如果你曾經(jīng)不得不處理編寫很差的 API 的異常響應(yīng),那你可能知道原因 —— 正確解析異常會是一件痛苦的事情,而確定這些異常最初發(fā)生的原因則更為痛苦。
          作為一名 API 開發(fā)者,理想情況下你希望覆蓋所有面向用戶的端點,并將他們轉(zhuǎn)換為常見的錯誤格式。這通常意味著有一個通用的錯誤代碼和描述,而不是逃避解決問題:a) 返回一個 “500 Internal Server Error”信息。b) 直接返回異常的堆棧信息給用戶。(實際上,這些都應(yīng)該不惜一切代價地去避免,因為除了客戶端難以處理以外,它還暴露了你的內(nèi)部信息)。
          例如,常見錯誤響應(yīng)格式可能長這樣:
          @Valuepublic class ErrorResponse {
          private Integer errorCode; private String errorMessage;
          }
          與此類似的事情在大多數(shù)流行的 API 中也經(jīng)常遇到,由于可以容易且系統(tǒng)地記錄,效果往往很不錯。將異常轉(zhuǎn)換為這種格式可以通過向方法提供@ExceptionHandler注解來完成(注解案例可見于第六章)。

          錯誤五:多線程處理不當(dāng)
          不管是桌面應(yīng)用還是 Web 應(yīng)用,無論是 Spring 還是 No Spring,多線程都是很難破解的。由并行執(zhí)行程序所引起的問題是令人毛骨悚然且難以捉摸的,而且常常難以調(diào)試 —— 實際上,由于問題的本質(zhì),一旦你意識到你正在處理一個并行執(zhí)行問題,你可能就不得不完全放棄調(diào)試器了,并 “手動” 檢查代碼,直到找到根本上的錯誤原因。
          不幸的是,這類問題并沒有千篇一律的解決方案;根據(jù)具體場景來評估情況,然后從你認(rèn)為最好的角度來解決問題。
          當(dāng)然,理想情況下,你也希望完全避免多線程錯誤。同樣,不存在那種一刀切的方法,但這有一些調(diào)試和防止多線程錯誤的實際考慮因素:

          避免全局狀態(tài)

          首先,牢記 “全局狀態(tài)” 問題。如果你正創(chuàng)建一個多線程應(yīng)用,那么應(yīng)該密切關(guān)注任何可能全局修改的內(nèi)容,如果可能的話,將他們?nèi)縿h掉。如果某個全局變量有必須保持可修改的原因,請仔細(xì)使用 synchronization,并對程序性能進(jìn)行跟蹤,以確定沒有因為新引入的等待時間而導(dǎo)致系統(tǒng)性能降低。

          避免可變性

          這點直接來自于 函數(shù)式編程,并且適用于 OOP,聲明應(yīng)該避免類和狀態(tài)的改變。簡而言之,這意味著放棄 setter 方法,并在所有模型類上擁有私有的 final 字段。它們的值唯一發(fā)生變化的時間是在構(gòu)造期間。這樣,你可以確定不會出現(xiàn)爭用問題,且訪問對象屬性將始終提供正確的值。

          記錄關(guān)鍵數(shù)據(jù)

          評估你的程序可能會在何處發(fā)生異常,并預(yù)先記錄所有關(guān)鍵數(shù)據(jù)。如果發(fā)生錯誤,你將很高興可以得到信息說明收到了哪些請求,并可更好地了解你的應(yīng)用程序為什么會出現(xiàn)錯誤。需要再次注意的是,日志記錄引入了額外的文件 I/O,可能會嚴(yán)重影響應(yīng)用的性能,因此請不要濫用日志。

          復(fù)用現(xiàn)存實現(xiàn)

          每當(dāng)你需要創(chuàng)建自己的線程時(例如:向不同的服務(wù)發(fā)出異步請求),復(fù)用現(xiàn)有的安全實現(xiàn)來代替創(chuàng)建自己的解決方案。這在很大程度上意味著要使用 ExecutorServices 和 Java 8 簡潔的函數(shù)式 CompletableFutures 來創(chuàng)建線程。Spring 還允許通過 DeferredResult 類來進(jìn)行異步請求處理。


          錯誤六:不使用基于注解的驗證

          假設(shè)我們之前的 TopTalent 服務(wù)需要一個端點來添加新的 TopTalent。此外,假設(shè)基于某些原因,每個新名詞都需要為 10 個字符長度。執(zhí)行此操作的一種方法可能如下:
          @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);}
          然而,上面的方法(除了構(gòu)造很差以外)并不是一個真正 “干凈” 的解決辦法。我們正檢查不止一種類型的有效性(即 TopTalentData 不得為空,TopTalentData.name 不得為空,且 TopTalentData.name 為 10 個字符長度),以及在數(shù)據(jù)無效時拋出異常。
          通過在Spring中集成 Hibernate validator,數(shù)據(jù)校驗可以更干凈地進(jìn)行。讓我們首先重構(gòu) 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;}
          現(xiàn)在,Spring 將在調(diào)用方法之前攔截其請求并對參數(shù)進(jìn)行驗證 —— 無需使用額外的手工測試。
          另一種實現(xiàn)相同功能的方法是創(chuàng)建我們自己的注解。雖然你通常只在需要超出 Hibernate的內(nèi)置約束集 時才使用自定義注解,本例中,我們假設(shè) @Length 不存在。你可以創(chuàng)建兩個額外的類來驗證字符串長度,一個用于驗證,一個用于對屬性進(jìn)行注解:
          @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();
          }
          @Componentpublic 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; }}
          請注意,這些情況下,關(guān)注點分離的最佳實踐要求在屬性為 null 時,將其標(biāo)記為有效(isValid方法中的s == null),如果這是屬性的附加要求,則使用@NotNull注解。
          public class TopTalentData {    @MyAnnotation(value = 10)    @NotNull    private String name;}

          錯誤七:(依舊)使用基于xml的配置

          雖然之前版本的 Spring 需要 XML,但如今大部分配置均可通過 Java 代碼或注解來完成;XML 配置只是作為附加的不必要的樣板代碼。
          本文(及其附帶的 GitHub 倉庫)均使用注解來配置 Spring,Spring 知道應(yīng)該連接哪些 Bean,因為待掃描的頂級包目錄已在@SpringBootApplication復(fù)合注解中做了聲明,如下所示:
          @SpringBootApplicationpublic class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }}
          復(fù)合注解(可通過 Spring 文檔 了解更多信息)只是向 Spring 提示應(yīng)該掃描哪些包來檢索 Bean。在我們的案例中,這意味著這個頂級包 (co.kukurin)將用于檢索:
          • @Component(TopTalentConverter,MyAnnotationValidator)
          • @RestController(TopTalentController)
          • @Repository(TopTalentRepository)
          • @Service(TopTalentService) 類
          如果我們有任何額外的@Configuration注解類,它們也會檢查基于 Java 的配置。


          錯誤八:忽略 profile

          在服務(wù)端開發(fā)中,經(jīng)常遇到的一個問題是區(qū)分不同的配置類型,通常是生產(chǎn)配置和開發(fā)配置。在每次從測試切換到部署應(yīng)用程序時,不要手動替換各種配置項,更有效的方法是使用 profile。
          考慮這么一種情況:你正在使用內(nèi)存數(shù)據(jù)庫進(jìn)行本地開發(fā),而在生產(chǎn)環(huán)境中使用 MySQL 數(shù)據(jù)庫。本質(zhì)上,這意味著你需要使用不同的 URL 和 (希望如此) 不同的憑證來訪問這兩者。讓我們看看可以如何做到這兩個不同的配置文件:

          APPLICATION.YAML 文件

          # set default profile to 'dev'spring.profiles.active: dev
          # production database details# 公眾號:程序員追風(fēng)spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal'spring.datasource.username: rootspring.datasource.password:8.2. APPLICATION-DEV.YAML 文件spring.datasource.url: 'jdbc:h2:mem:'spring.datasource.platform: h2
          假設(shè)你不希望在修改代碼時意外地對生產(chǎn)數(shù)據(jù)庫進(jìn)行任何操作,因此將默認(rèn)配置文件設(shè)為 dev 是很有意義的。
          然后,在服務(wù)器上,你可以通過提供-Dspring.profiles.active=prod參數(shù)給 JVM 來手動覆蓋配置文件。另外,還可將操作系統(tǒng)的環(huán)境變量設(shè)置為所需的默認(rèn) profile。


          錯誤九:無法接受依賴項注入

          正確使用 Spring 的依賴注入意味著允許其通過掃描所有必須的配置類來將所有對象連接在一起;這對于解耦關(guān)系非常有用,也使測試變得更為容易,而不是通過類之間的緊耦合來做這樣的事情:
          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 深入解釋了依賴注入的 “為什么”,所以,讓我們看看它在實踐中是如何使用的。在關(guān)注點分離(常見錯誤 #3)一節(jié)中,我們創(chuàng)建了一個服務(wù)和控制器類。
          假設(shè)我們想在TopTalentService行為正確的前提下測試控制器。我們可以通過提供一個單獨的配置類來插入一個模擬對象來代替實際的服務(wù)實現(xiàn):
          @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).collect(Collectors.toList()));        return topTalentService;    }}
          然后,我們可以通過告訴 Spring 使用SampleUnitTestConfig作為它的配置類來注入模擬對象:
          @ContextConfiguration(classes = { SampleUnitTestConfig.class })
          之后,我們就可以使用上下文配置將 Bean 注入到單元測試中。


          錯誤十:缺乏測試,或測試不當(dāng)

          盡管單元測試的概念已經(jīng)存在很長時間了,但很多開發(fā)人員似乎要么 “忘記” 做這件事(特別是如果它不是 “必需” 的時候),要么只是在事后把它添加進(jìn)來。這顯然是不可取的,因為測試不僅應(yīng)該驗證代碼的正確性,還應(yīng)該作為程序在不同場景下應(yīng)如何表現(xiàn)的文檔。
          在測試 Web 服務(wù)時,很少只進(jìn)行 “純” 單元測試,因為通過 HTTP 進(jìn)行通信通常需要調(diào)用 Spring 的DispatcherServlet,并查看當(dāng)收到一個實際的HttpServletRequest時會發(fā)生什么(使它成為一個 “集成” 測試,處理驗證、序列化等)。
          REST Assured,一個用于簡化測試REST服務(wù)的 Java DSL,在 MockMVC 之上,已經(jīng)被證明提供了一個非常優(yōu)雅的解決方案。考慮以下帶有依賴項注入的代碼片段:
          @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類將TopTalentService的模擬實現(xiàn)連接到TopTalentController中,而所有的其他類都是通過掃描應(yīng)用類所在包的下級包目錄來推斷出的標(biāo)準(zhǔn)配置。RestAssuredMockMvc只是用來設(shè)置一個輕量級環(huán)境,并向/toptal/get端點發(fā)送一個GET請求。

          推薦閱讀

          1、搞定數(shù)據(jù)庫索引,不怕面試官問了!

          2、Spring Boot+Vue項目實戰(zhàn)

          3、一文搞懂前后端分離

          4、快速上手Spring Boot+Vue前后端分離


          楠哥簡介

          資深 Java 工程師,微信號 southwindss

          《Java零基礎(chǔ)實戰(zhàn)》一書作者

          騰訊課程官方 Java 面試官今日頭條認(rèn)證大V

          GitChat認(rèn)證作者,B站認(rèn)證UP主(楠哥教你學(xué)Java)

          致力于幫助萬千 Java 學(xué)習(xí)者持續(xù)成長。




          有收獲,就在看 
          瀏覽 35
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩三级精品 | 日韩熟女色情视频一区二区三区 | 天天干天天日天天操天天干 | 天天操天天摸天天日天天爱 | 在线超碰成人 |