當前位置:
首頁 > 知識 > Spring Boot 學習(2)

Spring Boot 學習(2)

文 by / 林本托


Tips 做一個終身學習的人。

Spring Boot 學習(2)

源代碼:github下的/code01/ch2。

配置 Web 應用程序

在上一章中,我們學習了如何創建一個基本的應用程序模板,並添加了一些基本功能,並建立與資料庫的連接。 在本章中,我們將繼續增強BookPub應用程序,並提供 Web 支持。

在本章,主要包括以下內容:

  • 創建一個基本的 RESTful 風格的應用程序;
  • 創建一個 Spring Data REST 服務;
  • 配置一個自定義的 Servlet 的過濾器;
  • 配置一個自定義的攔截器;
  • 配置一個HttpMessageConverters的轉換器;
  • 配置一個自定義的PropertyEditors編輯器;
  • 配置一個自定義的類型格式化類。

一. 創建一個基本的 RESTful 風格的應用程序

雖然命令行應用程序確實有其用途,但今天的大多數應用程序開發都圍繞著 Web,REST 和數據服務。 我們開始增強 BookPub 應用程序,提供一個基於Web 的 API,以便訪問圖書目錄。

我們繼續使用前一章創建的應用程序框架,其中定義了實體對象和存儲庫服務,並配置了與資料庫的連接。

首先,第一件事情是我們需要在build.gradle 文件中添加新的依賴模塊spring-boot-starter-web,以便獲取基於 web 服務的所需的類庫。具體的代碼片段如下:

dependencies {
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("org.springframework.boot:spring-boot-starter-web")
runtime("com.h2database:h2")
testCompile("org.springframework.boot:spring-boot-
starter-test")
}

接下來,創建一個 Spring 控制器,用於處理我們應用程序中獲取目錄數據的 Web 請求。 新建一個包目錄來存放控制器相關的 Java 程序,以便我們的代碼按照適當的目的分組。 在 src/main/java/org/test/bookpub 目錄下創建一個名為 controllers的包目錄。

因為需要暴露圖書的數據,所以,在新建的包下,創建一個控制器類BookController

package org.test.bookpub.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.test.bookpub.entity.Book;
import org.test.bookpub.entity.Reviewer;
import org.test.bookpub.repository.BookRepository;

import java.util.Collections;
import java.util.List;

@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookRepository bookRepository;

@RequestMapping(value = "", method = RequestMethod.GET)
public Iterable getAllBooks {
return bookRepository.findAll;
}

@RequestMapping(value = "/{isbn}", method =
RequestMethod.GET)
public Book getBook(@PathVariable String isbn) {
return bookRepository.findBookByIsbn(isbn);
}
}

然後, 使用./gradlew clean bootRun.命令啟動應用程序。

最後,當應用程序程序啟動以後,在瀏覽器中輸入:http://localhost:8080/books, 然後會在頁面上顯示「」,表示目前沒有圖書的數據。

獲取暴露於Web請求的服務的關鍵是@RestController註解。這是一個元註解使用的例子,正如Spring文檔指出的那樣,我們在以前的代碼看到過。在@RestController中,定義了兩個註解:@Controller和@ResponseBody。 所以我們可以輕鬆給BookController類添加註解,如下所示:

@Controller
@ResponseBody
@RequestMapping("/books")
public class BookController {...}

@ResponseBody是一個Spring MVC的註解,指示來自Web請求映射方法的響應,構成HTTP響應主體有效負載的整個內容,這是RESTful應用程序比較典型的使用場景。

二. 創建一個 Spring Data REST 服務

在上一個例子中,我們使用REST控制器來展示我們的BookRepository,通過Web RESTful API訪問後台的數據。 雖然這是數據訪問的一種快捷方便的方式,但它要求我們手動創建一個控制器並定義所有所需操作的映射。 為了最小化代碼,Spring為我們提供了一種更方便的方法:spring-boot-starter-data-rest模塊。 這允許我們簡單地向存儲庫介面添加一個註解,而Spring將做剩下的事情用以將數據暴露給Web。

首先,我們需要在build.gradle文件中添加spring-boot-starter-data-rest模塊。

dependencies {
...
compile("org.springframework.boot:spring-boot-starter-data-rest")
...
}

第二步,在src/main/java/org/test/bookpub/repository目錄下新建AuthorRepository介面,代碼如下:

package org.test.bookpub.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Repository;
import org.test.bookpub.entity.Author;

@RepositoryRestResource
public interface AuthorRepository extends PagingAndSortingRepository {
}

接下來為剩下的實體模型創建對應的介面,

package org.test.bookpub.repository;

import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.test.bookpub.entity.Publisher;

@RepositoryRestResource
public interface PublisherRepository extends PagingAndSortingRepository {
}

package org.test.bookpub.repository;

import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.test.bookpub.entity.Reviewer;

@RepositoryRestResource
public interface ReviewerRepository extends PagingAndSortingRepository {
}

代碼完成後,執行./gradlew clean bootRun。當啟動成功以後,訪問http://localhost:8080/profile/authors,在 Chrome 瀏覽器下,顯示如下內容:

{
"_embedded" : {
"authors" :
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/authors{?page,size,sort}",
"templated" : true
},
"profile" : {
"href" : "http://localhost:8080/profile/authors"
}
},
"page" : {
"size" : 20,
"totalElements" : 0,
"totalPages" : 0,
"number" : 0
}
}

從瀏覽器里顯示的內容可以看出,我們將獲得比我們編寫BookController控制器更多的信息。 之所以顯示了更多的信息,由於我們沒有擴展CrudRepository介面,而是擴展了PagingAndSortingRepository,而它又是CrudRepository的擴展。 這樣做的原因是獲得PagingAndSortingRepository提供的額外好處。 這將添加額外的功能,使用分頁檢索實體,並能夠對它們進行排序。

@RepositoryRestResource註解是可選項,但可以讓我們更好地控制作為Web數據服務的存儲庫的暴露。 例如,如果我們要將URL路徑由「authers」改成「rel」,則可以此註解調整,如下所示:

@RepositoryRestResource(collectionResourceRel = "writers", path = "writers")

修改以後,之前的 url 現在改完「http://localhost:8080/reviewers」。

由於我們在構建依賴項中包含spring-boot-starter-data-rest模塊,我們還獲得spring-hateoas類庫的支持,此庫給我們提供了很好的ALPS元數據,比如_links對象。 這在構建API驅動的UI時可能非常有用,這可以從元數據推導出導航功能並以適合的方式呈現它們。


Tips 關於更多 ALPS 元數據的信息,請參考https://spring.io/blog/2014/07/14/spring-data-rest-now-comes-with-alps-metadata。

三. 配置一個自定義的 Servlet 的過濾器

在一個真實的Web應用程序中,我們幾乎總是需要為服務請求添加裝飾器或包裝器,用來記錄它們,過濾XSS(跨站腳本攻擊)的非法字元,執行認證等。Spring Boot自動添加OrderedCharacterEncodingFilter和HiddenHttpMethodFilter 兩個過濾器,除此之外,還有其他的過濾器。 讓我們看看Spring Boot如何幫助我們實現這個任務。

在Spring Boot,Spring Web,Spring MVC等的各種框架中,已經有各種不同的servlet過濾器可用,我們所要做的就是將它們作為一個bea定義到配置中。 假設應用程序將在負載平衡器代理之後運行,並且當我們的應用程序實例收到請求時,我們希望將用戶使用的實際請求IP轉換為代理的IP。 幸運的是,Tomcat 8已經為我們提供了一個實現:RemoteIpFilter。 我們需要做的就是將其添加到我們的過濾器鏈中。

根據功能的不同,便於管理和職責清晰,我們需要把不同的類放在不同的包下,我們創建一個WebConfiguration.java的文件,放在src/main/java/org/test/bookpub 目錄下。

package org.test.bookpub;

import org.apache.catalina.filters.RemoteIpFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
@Bean
public RemoteIpFilter remoteIpFilter {
return new RemoteIpFilter;
}
}

第二步,執行./gradlew clean bootRun命令,在啟動中,查看 log,會出現以下信息,表示過濾器已經添加:

o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: "remoteIpFilter" to: [/*]

這個功能背後的功能其實很簡單。 我們從單獨的配置類開始,並且將我們的方法用於過濾器bean的檢測。

我們看看主類BookPubApplication,這個類添加了@SpringBootApplication註解,此註解是是一個元註解,它聲明了@ComponentScan等其他註解。@ComponentScan的存在指示SpringBoot將WebConfiguration類檢測為@Configuration註解的標記類,並將其定義添加到應用程序上下文中。所以,我們將在WebConfiguration類中聲明的任何事情就好像我們將它放在BookPubApplication類中是一樣的。

@Bean public RemoteIpFilter remoteIpFilter{...}聲明為RemoteIpFilter類創建一個Spring bean。當Spring Boot 檢測到javax.servlet.Filter的所有bean時,它將自動將它們添加到過濾器鏈中。所以我們要做的就是,如果要添加更多的過濾器,那就是將它們聲明為@Bean配置。例如,對於更高級的過濾器配置,如果希望特定的過濾器僅適用於特定的URL模式,可以創建一個FilterBistrationBean類型的@Bean配置,並用來配置精確的設置。

四. 配置一個自定義的攔截器

Servlet過濾器是Servlet API的一部分,與Spring完全沒有任何關係,除了自動添加到過濾器鏈中,Spring MVC為我們提供了另一種方式來包裝Web請求:HandlerInterceptor攔截器。根據文檔,HandlerInterceptor就像一個Filter,但是,攔截器不是在嵌套鏈中包含請求,而是在處理請求、處理視圖或在頁面渲染之前,在不同的階段向我們提供了攔截點,對請求進行攔截。到最後,請求已經完成。它不改變有關請求的任何內容,但是如果攔截器邏輯返回false,它允許我們通過拋出異常來停止執行。

與過濾器的情況類似,Spring MVC附帶了一些預定義的HandlerInterceptor。常用的是LocaleChangeInterceptor和ThemeChangeInterceptor。接下來在應用程序中添加LocaleChangeInterceptor,看看它是如何完成的。

添加一個攔截器並不像剛才聲明一個bean那麼簡單。 實際上需要通過實現WebMvcConfigurer介面或重寫WebMvcConfigurationSupport來實現。

第一步,WebConfiguration類繼承WebMvcConfigurerAdapter類。

public class WebConfiguration extends WebMvcConfigurerAdapter {…}

接下來,為LocaleChangeInterceptor攔截器增加@Bean註解,

@Bean
public LocaleChangeInterceptor localeChangeInterceptor {
return new LocaleChangeInterceptor;
}

這實際上只會創建攔截 Spring bean,但不會將其添加到請求處理鏈中。 為了實現這一點,我們需要重寫addInterceptors方法,註冊攔截器。

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor);
}

整個代碼如下:

package org.test.bookpub;

import org.apache.catalina.filters.RemoteIpFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
@Bean
public RemoteIpFilter remoteIpFilter {
return new RemoteIpFilter;
}

@Bean
public LocaleChangeInterceptor localeChangeInterceptor {
return new LocaleChangeInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor);
}
}

執行./gradlew clean bootRun

應用程序啟動以後,在瀏覽器中輸入http://localhost:8080/books?locale=foo。

這時,瀏覽器報錯:

Spring Boot 學習(2)

後台的錯誤 log 如下:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution strategy

Tips 上面的錯誤不是因為我們輸入了無效的區域設置,而是因為默認語言環境解析策略不允許重置瀏覽器請求的語言環境。出現一個錯誤,實際上是證明了攔截器已經生效了。

當涉及到配置Spring MVC內部組件時,它並不像只是定義一堆bean那樣簡單—— 至少不總是這樣。 這是因為需要向請求提供更精細的MVC組件映射。 為了簡化難度,Spring為我們提供了WebMvcConfigurerAdapter適配器,它是WebMvcConfigurer介面實現,我們可以擴展和覆蓋我們需要的設置。

在配置攔截器的特定情況下,我們可以重寫addInterceptors(InterceptorRegistry registry)方法。 這是一個典型的回調方法,我們給予一個註冊類,以便根據需要註冊多個附加攔截器。 在MVC自動配置階段,Spring Boot就像過濾器一樣檢測到WebMvcConfigurer的實例,並依次調用所有這些回調方法。 這意味著如果需要其他邏輯分離,可以有多個WebMvcConfigurer類的實現。

五. 配置一個HttpMessageConverters轉換器

在構建RESTful Web服務時,我們定義了控制器,資源庫,並在其上面添加了註解,但是從Java實體bean到HTTP數據流輸出沒有任何類型的對象轉換。 實際上,Spring Boot自動配置了HttpMessageConverters轉換器將實體bean對象轉換為使用了Jackson類庫的JSON的格式,將生成的JSON數據寫入HTTP響應輸出流。 當多個轉換器可用時,根據消息對象類和請求的內容類型選擇最適用的轉換器。

HttpMessageConverters的目的是將各種對象類型轉換為相應的HTTP輸出格式。 轉換器可以支持一系列多種數據類型或多種輸出格式,或兩者的組合。 例如,MappingJackson2HttpMessageConverter類可以將任何Java對象轉換為application/json的格式,而ProtobufHttpMessageConverter類只能對com.google.protobuf.Message的實例進行操作,但可以將其作為application/json,application/xml,text/plain或application/xprotobuf格式。 HttpMessageConverters不僅支持寫入HTTP流,還支持將HTTP請求轉換為適當的Java對象。

我們可以通過多種方式配置轉換器。 這一切都取決於你喜歡哪一個,或者想要實現多少控制。

首先,我們在WebConfiguration類中增加ByteArrayHttpMessageConverter,並加上@Bean註解。

@Bean
public
ByteArrayHttpMessageConverter byteArrayHttpMessageConverter {
return new ByteArrayHttpMessageConverter;
}

另一種實現方式是重寫WebConfiguration類中的configureMessageConverters方法,首先需要繼承WebMvcConfigurerAdapter類,具體的代碼如下:

@Override
public void configureMessageConverters(List> converters) {
converters.add(new ByteArrayHttpMessageConverter);
}

如果想獲取更多的控制,還可以重寫extendMessageConverters方法。

@Override
public void
extendMessageConverters(List>
converters) {
converters.clear;
converters.add(new ByteArrayHttpMessageConverter);
}

如上所示,Spring給了我們多種方式來實現同樣的事情,這一切都取決於我們的偏好或具體的實現細節。

我們介紹了將HttpMessageConverter添加到應用程序中的三種不同的方法。 那有什麼區別呢?

將HttpMessageConverter聲明為@Bean是嚮應用程序添加自定義轉換器的最快捷,最簡單的方法。 它類似於我們在前面的例子中添加了Servlet過濾器。 如果Spring檢測到一個HttpMessageConverter類型的bean,它將自動將其添加到列表中。 如果WebConfiguration類沒有繼承WebMvcConfigurerAdapter父類,那麼這是首選方法。

當應用程序需要指定WebMvcConfigurerAdapter的擴展以配置其他的東西,如攔截器,那麼重寫configureMessageConverters方法並將我們的轉換器添加到列表將更為協調一致。可以從Spring Boot 的模塊中添加多個WebMvcConfigurers實例並自動配置,但是不能保證我們的方法可以以任何特定的順序被調用。

如果我們需要做一些更加具體的事情,比如從列表中刪除所有其他轉換器或清除重複的轉換器,這需要重寫extendMessageConverters方法的地方。 所有WebMvcConfigurer被調用到configureMessageConverter方法並且轉換器列表被完全填充後調用此方法。 當然,WebMvcConfigurer的其他一些實例完全可以重寫extendMessageConverters, 但是這樣做的機會並不多。

六. 配置一個自定義的PropertyEditors編輯器

在前面的例子中,我們學習了如何為HTTP請求和響應數據配置轉換器。 還有其他類型的轉換,特別是在將參數動態轉換為各種對象時,例如String類型轉換為Date或Integer。

當我們在控制器中聲明一個映射方法時,Spring使用確切的對象類型來自由定義方法簽名。 這個方式是通過使用PropertyEditor實現的。 PropertyEditor是一個默認概念,定義為JDK的一部分,旨在允許將文本值轉換為給定類型。 它最初用於構建Java Swing / AWT GUI,後來被證明是適合Spring需要將Web參數轉換為方法參數類型的需要。

Spring MVC已經為很多常見類型(如布爾型,貨幣型和類)提供了大量的PropertyEditor實現。 假設我們要創建一個Isbn類對象,並在我們的控制器中使用它,而不是一個純粹的String類型。

首先,我們需要在WebConfiguration類中移除extendMessageConverters方法,因為調用converters.clear這段代碼會中斷渲染,因為刪除了所有支持的類型轉換器。

然後,定義Isbn類,和對應的IsbnEditor屬性編輯器,以及重寫initBinde方法給我們的BookController類,使用以下內容配置IsbnEditor

public class Isbn {
private String isbn;

public Isbn(String isbn) {
this.isbn = isbn;
}

public String getIsbn {
return isbn;
}
}

public class IsbnEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text)) {
setValue(new Isbn(text.trim));
}
else {
setValue(null);
}
}

@Override
public String getAsText {
Isbn isbn = (Isbn) getValue;
if (isbn != null) {
return isbn.getIsbn;
}
else {
return "";
}
}
}

@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Isbn.class, new IsbnEditor);
}

第三步,在BookController類中修改getBook方法,以便可以接受Isbn類型的對象,

@RequestMapping(value = "/{isbn}", method = RequestMethod.GET)
public Book getBook(@PathVariable Isbn isbn) {
return bookRepository.findBookByIsbn(isbn.getIsbn);
}

第四步,啟動./gradlew clean bootRun,啟動成功以後,在瀏覽器中輸入http://localhost:8080/books/978-1-78528-415-1。

雖然我們不會觀察到任何可見的更改,但IsbnEditor確實在工作,從{isbn}參數中創建Isbn類對象實例。我們列印了傳遞過來的Isbn實例,重寫了toString方法。

Spring自動配置大量的默認編輯器,但是對於自定義類型,我們必須明確地為每個Web請求實例化新的編輯器。 這是在控制器中使用@InitBinder註解的方法完成的。 掃描此註解,所有檢測到的方法應具有接受WebDataBinder作為參數的簽名。 除此之外,WebDataBinder還為我們提供了註冊儘可能多的自定義編輯器的能力,要求控制器的方法被正確綁定。


Tips

PropertyEditor不是線程安全的!

因此,我們必須為每個Web請求創建一個新的自定義編輯器實例,並將其註冊到WebDataBinder。

如果需要新的PropertyEditor,最好通過擴展PropertyEditorSupport類並自定義重寫所需的方法來創建。

七. 配置一個自定義的類型格式化類

PropertyEditor因為它的狀態和非線程安全,從版本3起,Spring添加了一個Formatter介面作為PropertyEditor的替代。 格式化類旨在提供類似的功能,但是以完全線程安全的方式,並專註於解析對象類型中的String並將對象轉換為其字元串表示形式的非常具體的任務。

對於我們的應用程序,希望有一個格式化程序可以使用一個字元串形式的書籍的ISBN號碼並將其轉換為一個Book實體對象。 這樣,當請求URL簽名僅包含ISBN號碼或資料庫ID時,就可以使用Book類型的參數定義控制器請求的方法。

首先,在src/main/java/org/test/bookpub目錄下創建一個新的包formatters,在此包下,創建BookFormatter類並實現Formatter介面,代碼示例如下:

public class BookFormatter implements Formatter {
private BookRepository repository;
public BookFormatter(BookRepository repository) {
this.repository = repository;
}
@Override
public Book parse(String bookIdentifier, Locale locale) throws ParseException {
Book book = repository.findBookByIsbn(bookIdentifier);
return book != null ? book : repository.findOne(Long.valueOf(bookIdentifier));
}
@Override
public String print(Book book, Locale locale) {
return book.getIsbn;
}
}

然後,在WebConfiguration類中,重寫addFormatters(FormatterRegistry registry)方法,並把BookFormatter類註冊進去。

@RequestMapping(value = "/{isbn}/reviewers", method = RequestMethod.GET)
public List getReviewers(@PathVariable("isbn") Book book) {
return book.getReviewers;
}

接下來,在BookController類中,新增一個請求方法,用來根據給定的圖書的 isbn來顯示評論者,

為了一些數據,現在手動添加一些測試數據填充資料庫,通過向StartupRunner類添加兩個自動裝配的資源庫:

@Autowired private AuthorRepository authorRepository;
@Autowired private PublisherRepository publisherRepository;

下面這些代碼添加到StartupRunner類的run(...)方法中:

Author author = new Author("Alex", "Antonov");
author = authorRepository.save(author);
Publisher publisher = new Publisher("Packt");
publisher = publisherRepository.save(publisher);
Book book = new Book("978-1-78528-415-1", "Spring Boot Recipes", author, publisher);
bookRepository.save(book);

輸入./gradlew clean bootRun,在控制台,啟動應用程序。

Spring Boot 學習(2)

格式化功能旨在提供與PropertyEditors類似的功能。 通過將FormatterRegistry註冊在重寫的addFormatter方法中,告訴Spring使用Formatter將Book的文本表示轉換為實體對象並返回。 由於格式化是無狀態的,因此我們無需在控制器中每次都要註冊; 我們只做一次就好,這確保Spring為每個Web請求使用它。


Tips

如果要定義一個常用類型的轉換(例如String或Boolean),就像我們在IsbnEditor示例中所做的那樣,最好是通過Controller的InitBinder方法中的PropertyEditors初始化來做,因為這樣的改變可能不是全局所期望的,只是針對特定的控制器的功能。

你可能已經注意到,我們還將BookRepository自動裝配到WebConfiguration類,因為這是創建BookFormatter所需的。 這是Spring的一個很酷的東西,它讓我們可以組合配置類,並使它們同時依賴於其他bean。 正如我們指出,為了創建一個WebConfiguration類,我們需要一個BookRepository,Spring確保在創建WebConfiguration類時首先創建BookRepository,然後自動注入作為依賴。 實例化WebConfiguration之後,將對其進行處理以進行配置說明。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 科技優家 的精彩文章:

每天一道Java題「10」
基於binlog來分析mysql的行記錄修改情況(python腳本分析)
ASP.NET Core MVC 模型綁定用法及原理
TensorFlowSharp入門使用C#編寫TensorFlow人工智慧應用

TAG:科技優家 |

您可能感興趣

Rodarte 2019 Spring Fashion Week
Spring中BeanFactory和ApplicationContext 的區別
Ulla Johnson Spring 2018 FashionShow
CBC 2018 Spring Summer(春夏新款)第1組登場:Striking Beautiful
Spring data MongoDB 之 MongoRepository
Spring Boot 基礎教程 ( 二 ) :快速構建 Spring Boot/Cloud 工程
Carolina Herrera Spring Summer 2018
Spring Boot與Kotlin使用Spring-data-jpa簡化數據訪問層
Spring Security 5.0 的 DelegatingPasswordEncoder 詳解
Spring Boot 入門學習
SpringBoot 系列一 : SpringBoot 入門
SpringBoot伺服器壓測對比(jetty、tomcat、undertow)
Spring Boot 基礎教程 ( 三 ) :使用 Cloud Studio 在線編寫、管理 Spring Boot 應用
Spring Boot使用MongoDB
Springboot2.X之切換使用Servlet容器Jetty、Tomcat、Undertow
Spring Boot與Kotlin 使用MongoDB資料庫
1017 ALYX 9SM Spring/Summer 2019 Collection Pre-order
乾貨——Spring-Security-Mybatis-Demo
帶著新人學springboot的應用04(springboot+mybatis+redis 完)
回顧La Perla』s spring/summer 2018 collections at The Venetian