通过stream去重_stream去重
引言Spring Boot 2.0最近去了GA,所以我決定寫我關于Spring的第一篇文章很長一段時間引言Spring Boot 2.0最近去了GA,所以我決定寫我關于Spring的第一篇文章很長一段時間。自發布以來,我一直在看到越來越多的Spring WebFlux以??及如何使用它的教程。但是在閱讀完它們并嘗試讓它自己工作之后,我發現從包含在我閱讀的文章和教程中的代碼跳轉到編寫實際上比返回字符串更有趣的事情從后端。現在,我希望我不會在自己的腳下說自己可能會對我在這篇文章中使用的代碼做出同樣的批評,但這里是我試圖給Spring WebFlux的教程,它實際上類似于你可能會在野外使用的東西。項目結構:?在我繼續之前,在提及WebFlux之后,究竟是什么呢?Spring WebFlux是Spring MVC的完全非阻塞反應式替代方案。它允許更好的垂直縮放而不增加硬件資源。被動反應它現在使用Reactive Streams來允許從調用返回到服務器的數據的異步處理。這意味著我們將看到更少的Lists,Collection甚至單個對象,而不是他們的反應等價物,例如Flux和Mono(來自Reactor)。我不會深入研究Reactive Streams是什么,誠實地說,在我嘗試向任何人解釋它之前,我需要更加深入地研究它。相反,讓我們回過頭來關注WebFlux。像往常一樣,我使用Spring Boot在本教程中編寫代碼。以下是我在這篇文章中使用的依賴關系。<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra-reactive</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
</dependencies>盡管我沒有將它包含在上面的依賴代碼片段中,但是它spring-boot-starter-parent被使用了,最終可以將其提升到版本2.0.0.RELEASE。本教程是關于WebFlux的,包括這spring-boot-starter-webflux顯然是一個好主意。spring-boot-starter-data-cassandra-reactive也被包括在內,因為我們將用它作為示例應用程序的數據庫,因為它是少數幾個有反應支持的數據庫之一(在編寫本文時)。通過一起使用這些依賴關系,我們的應用程序可以從前到后完全反應。WebFlux引入了一種不同的方式來處理請求,而不是使用Spring MVC?中使用的@Controller或@RestController編程模型。但是,它并沒有取代它。相反,它已被更新以允許使用被動類型。這使您可以保持與使用Spring編寫相同的格式,但對返回類型進行一些更改,以便返回Fluxs或Monos。下面是一個非常人為的例子。@RestController
public class PersonController {
private final PersonRepository personRepository;
public PersonController(PersonRepository personRepository) {
this.personRepository = personRepository;
}
@GetMapping("/people")
public Flux<Person> all() {
return personRepository.findAll();
}
@GetMapping("/people/{id}")
Mono<Person> findById(@PathVariable String id) {
return personRepository.findOne(id);
}
}對我來說,這看起來非常熟悉,并且從一眼就可以看出它與標準的Spring MVC控制器沒有任何區別,但通過閱讀方法后,我們可以看到不同的返回類型。在這個例子中PersonRepository必須是一個被動庫,因為我們已經能夠直接返回他們的搜索查詢的結果供參考,被動庫會返回一個Flux集合和一個Mono單一的實體。注釋方法不是我想在這篇文章中關注的內容。這對我們來說不夠酷,時髦。沒有足夠的lambda表達式來滿足我們以更有效的方式編寫Java的渴望。但Spring WebFlux有我們的支持。它提供了一種替代方法來路由和處理請求到我們的服務器,輕輕地使用lambdas編寫路由器功能。我們來看一個例子。@Configuration
public class PersonRouter {
@Bean
public RouterFunction<ServerResponse> route(PersonHandler personHandler) {
return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get)
.andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all)
.andRoute(POST("/people").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post)
.andRoute(PUT("/people/{id}").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put)
.andRoute(DELETE("/people/{id}"), personHandler::delete)
.andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry);
}
} 這些都是PersonHandler我們稍后會看到的方法的所有路線。我們創建了一個將處理我們路由的bean。為了設置路由功能,我們使用了名為的RouterFunctions類為我們提供了一個靜態方法,但現在我們只關心它的route方法。以下是該route方法的簽名。public static <T extends ServerResponse> RouterFunction<T> route(
RequestPredicate predicate, HandlerFunction<T> handlerFunction) {
// stuff
} ?該方法表明,它與a?RequestPredicate一起HandlerFunction并輸出a?RouterFunction。這RequestPredicate是我們用來指定路由的行為,比如我們處理函數的路徑,它是什么類型的請求以及它可以接受的輸入類型。由于我使用靜態導入將所有內容讀得更清晰,所以一些重要信息已經隱藏起來。要創建一個RequestPredicate我們應該使用RequestPredicates(復數),一個靜態幫助類為我們提供我們需要的所有方法。就個人而言,我建議靜態導入,RequestPredicates否則由于使用RequestPredicates靜態方法可能需要的次數,您的代碼將會一團糟。在上述例子中,GET,POST,PUT,DELETE,accept和contentType都是靜態RequestPredicates方法。下一個參數是a?HandlerFunction,它是一個功能接口。這里有三件重要的信息,它有一個泛型類型<T extends ServerResponse>,它的handle方法返回一個Mono<T>并且需要一個ServerRequest。使用這些我們可以確定我們需要傳遞一個返回一個Mono<ServerResponse>(或它的一個子類型)的函數。這顯然對我們的處理函數返回的內容有嚴格的約束,因為它們必須滿足這個要求,否則它們將不適合以這種格式使用。最后的結果是一個RouterFunction。這可以返回并用于路由到我們指定的任何函數。但通常情況下,我們希望一次將很多不同的請求發送給各種處理程序,這是WebFlux迎合的。由于route返回a?RouterFunction以及RouterFunction也有其自己的路由方法的事實andRoute,我們可以將這些調用鏈接在一起并繼續添加我們所需的所有額外路由。如果我們再回頭看一下PersonRouter上面的例子,我們可以看到這些方法是以REST動詞命名的,例如GET,POST它們定義了處理程序將要執行的請求的路徑和類型。GET例如,如果我們以第一個請求為例,它將/people使用路徑變量名稱id(path表示的路徑變量{id})和返回內容的類型(具體來說,使用該方法定義的APPLICATION_JSON靜態字段from?MediaType)進行路由accept。如果使用不同的路徑,則不會被處理。如果路徑正確但Accept頭不是可接受的類型之一,則請求將失敗。在我們繼續之前,我想了解一下accept和contentType方法。這兩個設置請求標頭都accept與Accept標頭和contentTypeContent-Type?匹配。Accept頭定義了響應可接受的媒體類型,因為我們返回的Person對象的JSON表示設置為APPLICATION_JSON(application/json在實際頭文件中)是有意義的。Content-Type具有相同的想法,但是卻描述了發送請求正文內的媒體類型。這就是為什么只有動詞POST和PUT動詞才contentType包括在內,因為其他人在他們的身體中沒有任何東西。DELETE不包括accept和contentType?所以我們可以得出這樣的結論:它既沒有期望返回任何東西,也沒有在其請求中包含任何東西。現在我們知道如何設置路由,讓我們看看如何編寫處理傳入請求的處理程序方法。以下是處理前面示例中定義的路由的所有請求的代碼。@Component
public class PersonHandler {
private final PersonManager personManager;
public PersonHandler(PersonManager personManager) {
this.personManager = personManager;
}
public Mono<ServerResponse> get(ServerRequest request) {
final UUID id = UUID.fromString(request.pathVariable("id"));
final Mono<Person> person = personManager.findById(id);
return person
.flatMap(p -> ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class)))
.switchIfEmpty(notFound().build());
}
public Mono<ServerResponse> all(ServerRequest request) {
return ok().contentType(APPLICATION_JSON)
.body(fromPublisher(personManager.findAll(), Person.class));
}
public Mono<ServerResponse> put(ServerRequest request) {
final UUID id = UUID.fromString(request.pathVariable("id"));
final Mono<Person> person = request.bodyToMono(Person.class);
return personManager
.findById(id)
.flatMap(
old ->
ok().contentType(APPLICATION_JSON)
.body(
fromPublisher(
person
.map(p -> new Person(p, id))
.flatMap(p -> personManager.update(old, p)),
Person.class)))
.switchIfEmpty(notFound().build());
}
public Mono<ServerResponse> post(ServerRequest request) {
final Mono<Person> person = request.bodyToMono(Person.class);
final UUID id = UUID.randomUUID();
return created(UriComponentsBuilder.fromPath("people/" + id).build().toUri())
.contentType(APPLICATION_JSON)
.body(
fromPublisher(
person.map(p -> new Person(p, id)).flatMap(personManager::save), Person.class));
}
public Mono<ServerResponse> delete(ServerRequest request) {
final UUID id = UUID.fromString(request.pathVariable("id"));
return personManager
.findById(id)
.flatMap(p -> noContent().build(personManager.delete(p)))
.switchIfEmpty(notFound().build());
}
public Mono<ServerResponse> getByCountry(ServerRequest serverRequest) {
final String country = serverRequest.pathVariable("country");
return ok().contentType(APPLICATION_JSON)
.body(fromPublisher(personManager.findAllByCountry(country), Person.class));
}
} ?有一點非常明顯,就是缺少注釋。酒吧的@Component注釋自動創建一個PersonHandler豆沒有其他Spring注解。我試圖將大部分存儲庫邏輯保留在這個類之外,并且通過經由它所包含的PersonManager代理來隱藏對實體對象的任何引用PersonRepository。如果你對代碼感興趣,PersonManager那么可以在我的GitHub上看到,關于它的進一步解釋將被排除在這篇文章之外,所以我們可以專注于WebFlux本身。好的,回到手頭的代碼。讓我們仔細看看get和post方法來弄清楚發生了什么。public Mono<ServerResponse> get(ServerRequest request) {
final UUID id = UUID.fromString(request.pathVariable("id"));
final Mono<Person> person = personManager.findById(id);
return person
.flatMap(p -> ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class)))
.switchIfEmpty(notFound().build());
}此方法用于從支持此示例應用程序的數據庫中檢索單個記錄。由于Cassandra是選擇的數據庫,我決定使用UUID每個記錄的主鍵,這使得測試示例更令人討厭的不幸效果,但沒有任何復制和粘貼無法解決的問題。請記住,此GET請求的路徑中包含路徑變量。使用pathVariable的方法ServerRequest傳遞到我們能夠提取它的價值通過提供變量的名稱,在這種情況下,方法id。然后將ID轉換成一個UUID,如果字符串格式不正確,它會拋出一個異常,我決定忽略這個問題,所以示例代碼不會變得混亂。一旦我們有了ID,我們就可以查詢數據庫中是否存在匹配的記錄。Mono<Person>返回的A?包含映射到a的現有記錄,Person或者它保留為空Mono。使用返回的,Mono我們可以根據它的存在輸出不同的響應。這意味著我們可以將有用的狀態代碼返回給客戶端以跟隨主體的內容。如果記錄存在,則flatMap返回一個ServerResponse與OK狀態。伴隨著這種狀態,我們希望輸出記錄,為此,我們在這種情況下指定正文的內容類型APPLICATION_JSON,并將記錄添加到記錄中。fromPublisher需要我們Mono<Person>(這是一個Publisher)與Person課程一起,因此它知道它映射到身體中的是什么。fromPublisher是類的靜態方法BodyInserters。如果記錄不存在,那么流程將移動到switchIfEmpty塊中并返回NOT FOUND狀態。因為沒有發現,身體可以留空,所以我們只是創建ServerResponse那里。現在到post處理程序。public Mono<ServerResponse> post(ServerRequest request) {
final Mono<Person> person = request.bodyToMono(Person.class);
final UUID id = UUID.randomUUID();
return created(UriComponentsBuilder.fromPath("people/" + id).build().toUri())
.contentType(APPLICATION_JSON)
.body(
fromPublisher(
person.map(p -> new Person(p, id)).flatMap(personManager::save), Person.class));
} ?即使只是從第一行開始,我們就可以看到,這種get方法的工作方式已經不同了。由于這是一個POST請求,它需要接受我們希望從請求主體持續存在的對象。由于我們試圖插入單個記錄,因此我們將使用請求的bodyToMono方法Person從正文中檢索。如果您正在處理多個記錄,則可能需要使用它們bodyToFlux。我們將CREATED使用created接受a?的方法返回狀態URI以確定插入記錄的路徑。然后,get通過使用該fromPublisher方法將新記錄添加到響應主體,然后采用與該方法類似的設置。形成該代碼的代碼Publisher稍有不同,但輸出仍然Mono<Person>是一個重要的內容。為了進一步解釋如何完成插入Person,從請求傳入的內容將被映射到Person使用UUID我們生成的新內容,然后通過save調用傳遞給新內容flatMap。通過創建一個新的Person我們只將值插入我們允許的Cassandra中,在這種情況下,我們不希望UUID從請求體傳入。所以說,這是關于處理程序。顯然還有其他方法,我們沒有經歷。它們的工作方式都不相同,但都遵循相同的概念,ServerResponse如果需要,它返回一個包含適當狀態代碼和記錄的體系。現在我們已經編寫了所有我們需要的代碼來獲得基本的Spring WebFlux后端運行。剩下的就是將所有配置綁定在一起,這對Spring Boot來說很簡單。@SpringBootApplication
public class Application {
public static void main(String args[]) {
SpringApplication.run(Application.class);
}
} ?我們應該研究如何真正使用代碼,而不是結束這篇文章。Spring提供了WebClient該類來處理請求而不會阻塞。我們現在可以利用這個來測試應用程序,盡管WebTestClient我們也可以在這里使用它。該WebClient是你可以使用,而不是阻止什么RestTemplate產生反應的應用程序時。下面是一些調用在PersonHandler。中定義的處理程序的代碼。public class Client {
private WebClient client = WebClient.create("http://localhost:8080");
public void doStuff() {
// POST
final Person record = new Person(UUID.randomUUID(), "John", "Doe", "UK", 50);
final Mono<ClientResponse> postResponse =
client
.post()
.uri("/people")
.body(Mono.just(record), Person.class)
.accept(APPLICATION_JSON)
.exchange();
postResponse
.map(ClientResponse::statusCode)
.subscribe(status -> System.out.println("POST: " + status.getReasonPhrase()));
// GET
client
.get()
.uri("/people/{id}", "a4f66fe5-7c1b-4bcf-89b4-93d8fcbc52a4")
.accept(APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class))
.subscribe(person -> System.out.println("GET: " + person));
// ALL
client
.get()
.uri("/people")
.accept(APPLICATION_JSON)
.exchange()
.flatMapMany(response -> response.bodyToFlux(Person.class))
.subscribe(person -> System.out.println("ALL: " + person));
// PUT
final Person updated = new Person(UUID.randomUUID(), "Peter", "Parker", "US", 18);
client
.put()
.uri("/people/{id}", "ec2212fc-669e-42ff-9c51-69782679c9fc")
.body(Mono.just(updated), Person.class)
.accept(APPLICATION_JSON)
.exchange()
.map(ClientResponse::statusCode)
.subscribe(response -> System.out.println("PUT: " + response.getReasonPhrase()));
// DELETE
client
.delete()
.uri("/people/{id}", "ec2212fc-669e-42ff-9c51-69782679c9fc")
.exchange()
.map(ClientResponse::statusCode)
.subscribe(status -> System.out.println("DELETE: " + status));
}
} 不要忘了在Client某個地方實例化,下面是一個很好的偷懶方式來做到這一點!@SpringBootApplication
public class Application {
public static void main(String args[]) {
SpringApplication.run(Application.class);
Client client = new Client();
client.doStuff();
}
} 首先我們創建一個WebClient。private final WebClient client = WebClient.create("http://localhost:8080");
一旦創建,我們就可以開始做它的東西,因此doStuff方法。我們來分解一下POST發送給后端的請求。final Mono<ClientResponse> postResponse =
client
.post()
.uri("/people")
.body(Mono.just(record), Person.class)
.accept(APPLICATION_JSON)
.exchange();
postResponse
.map(ClientResponse::statusCode)
.subscribe(status -> System.out.println("POST: " + status.getReasonPhrase()));我寫下這個稍有不同,所以你可以看到a?Mono<ClientResponse>是從發送請求返回的。該exchange方法將HTTP請求發送到服務器。然后,只要響應到達,就會處理響應,如果有的話。使用WebClient我們指定我們想要POST使用post當然的方法發送請求。在URI隨后與所添加的uri方法(重載的方法,這一個接受一個String但另一個接受URI)。我厭倦了說這個方法做了什么方法,所以,身體的內容隨后與Accept頭一起添加。最后我們通過電話發送請求exchange。請注意,媒體類型APPLICATION_JSON與POST路由器功能中定義的類型相匹配。如果我們要發送不同的類型,比如說TEXT_PLAIN我們會得到一個404錯誤,因為沒有處理程序存在與請求期望返回的內容相匹配的地方。使用Mono<ClientResponse>通過調用返回exchange,我們可以繪制它的內容給我們所需的輸出。在上面的例子中,狀態代碼被打印到控制臺。如果我們回想一下post方法PersonHandler,請記住它只能返回“創建”狀態,但如果發送的請求沒有正確匹配,則會打印出“未找到”。我們來看看其他請求之一。client
.get()
.uri("/people/{id}", "a4f66fe5-7c1b-4bcf-89b4-93d8fcbc52a4")
.accept(APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class))
.subscribe(person -> System.out.println("GET: " + person));這是我們的典型GET要求。它看起來與POST我們剛剛經歷的請求非常相似。主要區別在于uri,請求路徑和UUID(作為String在這種情況下)作為參數來取代路徑變量{id}并且主體留空。響應如何處理也是不同的。在這個例子中,它提取了響應的主體并將其映射到a?Mono<Person>并打印出來。這可以在前面的POST例子中完成,但是響應的狀態代碼對于它的情況更有用。對于略有不同的觀點,我們可以使用cURL發出請求并查看響應的樣子。CURL -H "Accept:application/json" -i localhost:8080/people
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json
[
{
"id": "13c403a2-6770-4174-8b76-7ba7b75ef73d",
"firstName": "John",
"lastName": "Doe",
"country": "UK",
"age": 50
},
{
"id": "fbd53e55-7313-4759-ad74-6fc1c5df0986",
"firstName": "Peter",
"lastName": "Parker",
"country": "US",
"age": 50
}
]
響應看起來像這樣,顯然它會根據您存儲的數據而有所不同。請注意響應標題。transfer-encoding: chunked
Content-Type: application/json????在transfer-encoding這里表示的是在可用于流式傳輸的數據塊傳輸的數據。這就是我們需要的,因此客戶可以對返回給它的數據采取反應態度。我認為這應該是一個停止的好地方。我們在這里已經涵蓋了相當多的材料,希望能夠幫助您更好地理解Spring WebFlux。還有一些其他的話題我想關注WebFlux,但是我會在單獨的帖子中做這些,因為我認為這個主題足夠長。????總之,在這篇文章中,我們非常簡要地討論了為什么你想在典型的Spring MVC后端中使用Spring WebFlux。然后我們看看如何設置路由和處理程序來處理傳入的請求。處理程序實現了可以處理大多數REST動詞的方法,并在響應中返回了正確的數據和狀態代碼。最后,我們研究了向后端發送請求的兩種方式,一種是使用a?WebClient直接在客戶端處理輸出,另一種使用cURL查看返回的JSON的外觀。注意源碼中使用的數據為:cassandra提示:項目源碼下載:demo-springboot-webflux-0401.zip
總結
以上是生活随笔為你收集整理的通过stream去重_stream去重的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Unity3D插件】Unity开发利器
- 下一篇: 兄弟7895dw粉盒清零_兄弟打印机22