使用Spring WebFlux进行操作

Spring Boot 2.0最近在GA上线了,所以我决定在相当长一段时间内写我的第一篇有关Spring的文章。 自发布以来,我已经看到越来越多的提到Spring WebFlux以​​及有关如何使用它的教程。 但是,在阅读完它们并尝试使它们自己工作之后,我发现很难从我所阅读的帖子和教程中包含的代码过渡到编写代码,该代码实际上比返回字符串更有趣从后端。 现在,我希望我不会因为说到您可能对本文中使用的代码有同样的批评而无视我的脚步,但这是我尝试给出一个实际上类似于Spring WebFlux的教程的尝试。您可能在野外使用的东西。

在继续之前,以及在提到WebFlux之后,实际上是什么? Spring WebFlux是Spring MVC的完全不阻塞的反应性替代方案。 它允许更好的垂直扩展,而无需增加硬件资源。 现在,它是反应性的,它利用反应性流来异步处理从调用返回到服务器的数据。 这意味着我们将看到更少的ListCollection或什至单个对象,而是它们的反应等效项,例如FluxMono (来自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.RELEASE2.0.0.RELEASE版本。 作为本教程的主题是有关WebFlux的,包括spring-boot-starter-webflux显然是一个好主意。 spring-boot-starter-data-cassandra-reactive也已包括在内,因为我们将其用作示例应用程序的数据库,因为它是(在撰写本文时)为数不多的具有响应支持的数据库之一。 通过一起使用这些依赖关系,我们的应用程序可以从前到后完全反应。

WebFlux引入了一种不同的方式来处理请求,而不是使用Spring MVC中使用的@Controller@RestController编程模型。 但是,它不能替代它。 相反,它已被更新以允许使用反应类型。 这使您可以保持与使用Spring编写时相同的格式,但是对返回类型进行一些更改,因此可以返回FluxMono 。 以下是一个非常人为的示例。

@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有我们的支持。 它提供了一种替代方法来路由和处理到我们服务器的请求,该方法仅使用lambda来编写路由器功能。 让我们看一个例子。

@Configuration
public class PersonRouter {@Beanpublic 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
}

该方法显示,它与HandlerFunction一起接收RequestPredicate并输出RouterFunction

RequestPredicate是我们用来指定路由行为的内容,例如处理程序函数的路径,请求的类型以及可接受的输入类型。 由于我使用静态导入来使所有内容读起来更加清晰,因此一些重要信息已对您隐藏了。 要创建RequestPredicate我们应该使用RequestPredicates (复数),这是一个静态帮助器类,为我们提供了所需的所有方法。 我个人确实建议静态导入RequestPredicates否则由于您可能需要使用RequestPredicates静态方法的次数而使代码混乱。 在上面的示例中, GETPOSTPUTDELETEacceptcontentType都是静态RequestPredicates方法。

下一个参数是HandlerFunction ,它是一个功能接口。 这里有3条重要信息,它具有<T extends ServerResponse>的通用类型,其handle方法返回Mono<T>并接受ServerRequest 。 使用这些,我们可以确定我们需要传递一个返回Mono<ServerResponse> (或其子类型之一)的函数。 显然,这对我们的处理程序函数返回的内容施加了严格的约束,因为它们必须满足此要求,否则将不适合以这种格式使用。

最后,输出是RouterFunction 。 然后可以将其返回,并将其用于路由到我们指定的任何函数。 但是通常我们希望将许多不同的请求立即路由到各种处理程序,WebFlux可以满足这些请求。 由于route返回RouterFunction和事实RouterFunction也有提供自己的路由方法, andRoute ,我们可以链通话在一起,不断增加的所有额外的路线,我们需要。

如果再回顾一下上面的PersonRouter示例,我们可以看到这些方法以REST动词(例如GETPOST命名,它们定义了处理程序将采用的请求的路径和类型。 例如,如果我们以第一个GET请求为例,它将使用路径变量名称id (由{id}表示的路径变量)路由到/people ,并且返回内容的类型,特别是APPLICATION_JSONMediaType静态字段)使用accept方法。 如果使用其他路径,则不会处理。 如果路径正确,但是Accept标头不是接受的类型之一,则请求将失败。

在继续之前,我想contentType一下acceptcontentType方法。 这两个设置的请求标头都accept对Accept标头的匹配,将contentType匹配到Content-Type。 Accept标头定义了响应可接受的媒体类型,因为我们返回的Person对象的JSON表示将其设置为APPLICATION_JSON (实际标头中为application/json )。 Content-Type具有相同的想法,但是描述的是发送的请求正文中的媒体类型。 这就是为什么只有POSTPUT动词包含contentType的原因,其他动词的主体中没有任何动词。 DELETE不包含acceptcontentType因此我们可以得出结论,既不希望返回任何内容,也不希望在其请求正文中包含任何内容。

现在我们知道了如何设置路由,让我们看一下编写处理传入请求的处理程序方法。 以下是处理来自先前示例中定义的路由的所有请求的代码。

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

值得注意的一件事是缺少注释。 PersonHandler @Component注释自动创建PersonHandler bean,没有其他Spring注释。

我试图将大多数存储库逻辑都排除在此类之外,并通过委派给它所包含的PersonRepositoryPersonManager来隐藏对实体对象的任何引用。 如果您对PersonManager中的代码感兴趣,那么可以在我的GitHub上看到它,本文将不再对此进行进一步的解释,因此我们可以专注于WebFlux本身。

好的,回到手头的代码。 让我们仔细看看getpost方法,以了解发生了什么。

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请求的路径中包含一个路径变量。 在传递给该方法的ServerRequest上使用pathVariable方法,我们可以通过提供变量名称(在本例中为id来提取其值。 然后将ID转换为UUID ,如果字符串的格式不正确,则将引发异常,我决定忽略此问题,以使示例代码不会更混乱。

获得ID后,我们可以查询数据库中是否存在匹配记录。 返回一个Mono<Person> ,它包含映射到Person的现有记录,或者保留为空的Mono

使用返回的Mono我们可以根据它的存在输出不同的响应。 这意味着我们可以将有用的状态代码与主体内容一起返回给客户端。 如果记录存在,则flatMap返回状态为OKServerResponse 。 除了此状态外,我们还希望输出记录,为此,我们指定主体的内容类型(在本例中为APPLICATION_JSON ,然后将记录添加到其中。 fromPublisher将我们的Mono<Person> (它是Publisher )与Person类一起使用,因此它知道它映射到主体中的内容。 fromPublisherBodyInserters类中的静态方法。

如果该记录不存在,那么该流程将移至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状态,该方法采用URI来确定插入记录的路径。 然后,通过使用fromPublisher方法将新记录添加到响应的正文中,从而遵循与get方法类似的设置。 构成Publisher的代码略有不同,但是输出仍然是Mono<Person> ,这很重要。 只是为了进一步说明如何完成插入,使用我们生成的UUID将请求中传入的Person映射到新的Person ,然后通过调用flatMap save其传递到save 。 通过创建一个新的Person我们仅将值插入我们允许的Cassandra中,在这种情况下,我们不希望UUID从请求主体传入。

因此,关于处理程序,就是这样。 显然,还有其他一些我们没有经历过的方法。 它们的工作方式不同,但是都遵循相同的概念,即返回ServerResponse ,该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() {// POSTfinal 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()));// GETclient.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));// ALLclient.get().uri("/people").accept(APPLICATION_JSON).exchange().flatMapMany(response -> response.bodyToFlux(Person.class)).subscribe(person -> System.out.println("ALL: " + person));// PUTfinal 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()));// DELETEclient.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()));

我将此内容写下的略有不同,因此您可以看到从发送请求返回了Mono<ClientResponse>exchange方法将HTTP请求发送到服务器。 然后,无论何时到达,都会处理该响应。

当然,使用WebClient我们指定我们要使用post方法发送POST请求。 的URI ,然后用所添加的uri的方法(重载的方法,这一个发生在一个String ,但另一个接受一个URI )。 我不敢说此方法执行了该方法所要求的操作,然后将正文内容与Accept标头一起添加。 最后,我们通过调用exchange发送请求。

请注意, APPLICATION_JSON的媒体类型与POST路由器功能中定义的类型匹配。 如果我们要发送其他类型,请说TEXT_PLAIN ,则将收到404错误,因为不存在与请求返回的请求相匹配的处理程序。

使用通过调用exchange返回的Mono<ClientResponse> ,我们可以将其内容映射到所需的输出。 在上面的示例中,状态代码将打印到控制台。 如果我们回想一下PersonHandlerpost方法,请记住它只能返回“已创建”状态,但是如果发送的请求与之不正确匹配,则将打印出“未找到”。

让我们看一下其他请求之一。

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} ,并且正文保留为空。 响应的处理方式也不同。 在此示例中,它提取响应的正文并将其映射到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动词的方法,并在响应中返回了正确的数据和状态代码。 最后,我们研究了两种向后端发出请求的方法,一种是使用WebClient直接在客户端上处理输出,另一种是通过cURL查看返回的JSON的外观。

如果您有兴趣查看我用来创建本文示例应用程序的其余代码,可以在我的GitHub上找到它。

与往常一样,如果您发现此帖子有帮助,请分享它;如果您想了解我的最新帖子,则可以通过Twitter @LankyDanDev关注我。

翻译自: https://www.javacodegeeks.com/2018/03/doing-stuff-with-spring-webflux.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/348175.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【强化学习】Policy Gradient原理

1.Policy Gradient和DQN系列强化算法最大的区别在于&#xff1a; DQN系列基于Value&#xff0c;也就是说执行完所有的动作并保存所得到的价值&#xff0c;根据这些价值计算出最优价值函数&#xff0c;并以此选择动作&#xff0c;最终获得一个特定的策略。 Policy Gradient基于策…

ajax的url怎么将后缀补上_蜂蜜杏仁怎么做?杏仁和蜂蜜腌制方法

蜂蜜杏仁怎么做?杏仁和蜂蜜腌制方法蜂蜜杏仁是一种非常好吃的小零食&#xff0c;很多小可爱都喜欢吃蜂蜜杏仁。不过有些时候忘记补货&#xff0c;就会断粮&#xff0c;于是大家都想要在家里自制蜂蜜杏仁。不过你知道蜂蜜杏仁应该怎么做吗?蜂蜜杏仁的做法其实并不难&#xff0…

【强化学习】Policy Gradients代码注释版本

import gym # import tensorflow as tf import numpy as np# Hyper Parameters GAMMA 0.95 # discount factor 折扣因子 LEARNING_RATE 0.01 # 学习率class Policy_Gradient():# 咱们来搞一下大头&#xff01;def __init__(self, env): # 初始化# 先初始化一些参量sel…

c语言构建栈_选择技术栈构建通用平台

c语言构建栈Java社区中有许多关于Spring vs Java EE的话题。 一群人会争辩说您应该使用一个而不是其他。等等。当我看到这一点时&#xff0c;我不禁要想为什么我们不能同时使用它们呢&#xff1f; 实际上&#xff0c;我认为有效地使用它们将为建立基础架构创建一个伟大的技术堆…

【强化学习】Actor Critic原理

PG算法是一种只基于policy的一种方法&#xff0c;存在的问题就是该算法需要完整的状态序列&#xff0c;且单独对策略函数进行迭代更新&#xff0c;不太容易收敛。 Actor-critic方法呢是一种将 策略(Policy Based)和价值(Value Based)相结合的方法。下面继续来理一下AC方法的思路…

Java 9:对Process API的增强

Java 9对Process API进行了各种改进&#xff0c;用于控制和管理操作系统进程。 获取有关流程的信息 有一个新的ProcessHandle类&#xff0c;提供了进程的pid&#xff0c;父级和后代&#xff0c;以及有关开始时间和累积CPU时间的信息。 jshell> Process p new ProcessBui…

【强化学习】AC注释版本

## 强化学习 Actor-critic # 和PG比起来主要的变化&#xff1a; # 评估点由状态价值变成了TD_error,网络形式变了 # learn函数长得不一样 # action有一个优化函数&#xff0c;优化的是价值函数&#xff0c;希望最大化期望的reward&#xff0c;Critic网络也有一个reward&#xf…

python中sorted的用法append_Python中高阶函数sorted()用法

在Python中&#xff0c;有内置的排序方法&#xff1a;sorted(iterable, key, reverse)。Sorted()函数也是一个高阶函数&#xff0c;它还可以接收一个key函数来实现自定义的排序。key指定的函数将作用于list的每一个元素上&#xff0c;并根据key函数返回的结果进行排序。print(s…

【强化学习】A3C原理

先解释一下什么叫异步、什么叫并发&#xff1a; **异步&#xff1a;**和同步相对&#xff0c;同步是顺序执行&#xff0c;而异步是彼此独立&#xff0c;在等待某个事件的过程中继续做自己的事&#xff0c;不要等待这一事件完成后再工作。线程是实现异步的一个方式&#xff0c;异…

天玑机器人颈椎_天玑骨科手术机器人亮相机器人大会 误差不到1毫米

千龙网北京8月22日讯(记者 詹婷婷)8月23日-27日&#xff0c;年度科技盛典--2017年世界机器人大会将登陆北京亦创国际会展中心。100多家国内外机器人顶尖企业将携手机器人亮相。大会期间将秀出人工智能系统、特种机器人、服务机器人、工业机器人以及智慧家居机器人等先进产品。参…

jboss eap 7.0_创建委托登录模块(用于JBoss EAP 6.1)

jboss eap 7.0[如果只想查看代码&#xff0c;请向下滚动] 动机 在RHQ中&#xff0c;我们需要一个安全域&#xff0c;该域可用于通过容器管理的安全性来保护REST-api及其Web应用程序。 过去&#xff0c;我只是使用经典的DatabaseServerLoginModule对DatabaseServerLoginModule进…

cdn简单理解_简单地聊聊CDN原理作用及实现方法

释放双眼&#xff0c;带上耳机&#xff0c;听听看~&#xff01;相信只要会网上冲浪的都会接触到CDN吧&#xff0c;今天我们来聊聊对CDN的理解&#xff0c;给大家作为参考&#xff0c;本文将会以简单的描述来解释相关原理&#xff0c;供大家理解&#xff0c;希望相关专业同学进行…

JDK 10:FutureTask获取一个toString()

我已经有很长时间了&#xff0c;对于大多数具有独特属性的Java类&#xff0c;开发人员应该花时间重写Object.toString&#xff08;&#xff09; &#xff0c;即使它只是通过IDE生成的实现或使用诸如Apache之类的库类来实现Commons Lang的ToStringBuilder 。 如果要手动实现toSt…

【强化学习】A3C代码注释版本

########################################## # A3C做出的改进&#xff1a; # 解决AC难以收敛的问题 # 不一样的地方&#xff1a; #import threading # import tensorflow as tf import tensorflow.compat.v1 as tftf.compat.v1.disable_eager_execution() import numpy as np …

用python做人脸识别的程序怎么做_手把手教你用Python实现人脸识别

作者&#xff1a;Kangvcar简书专栏&#xff1a;http://www.jianshu.com/u/d9c480744afd环境要求&#xff1a;Ubuntu17.10Python 2.7.14环境搭建&#xff1a;1. 安装 Ubuntu17.10 > 安装步骤在这里2. 安装 Python2.7.14 (Ubuntu17.10 默认Python版本为2.7.14)3. 安装 git 、c…

【强化学习】PPO代码注释版本

# PPO主要通过限制新旧策略的比率&#xff0c;那些远离旧策略的改变不会发生# import tensorflow as tf import tensorflow.compat.v1 as tf tf.compat.v1.disable_eager_execution() import numpy as np import matplotlib.pyplot as plt import gym# 定义一些超级参量 EP_MAX…

【强化学习】可视化学习tensorboard

tensorflow定义了一个图结构&#xff1a; 代码&#xff1a; a tf.constant(3.0, name"a")b tf.constant(4.0, name"b")c tf.add(a, b, name"add")var tf.Variable(tf.random_normal([2, 3], mean0.0, stddev1.0), name"variable"…

面向初学者的JSF 2.0教程

1.什么是JSF&#xff1f; JSF是Java Server Faces的首字母缩写。 它是一种服务器端处理技术&#xff0c;它允许将服务器端代码嵌入到网页中。 由于可以将服务器端处理和呈现代码嵌入网页本身&#xff0c;因此使项目的整体编码更加简单。 减少总体数量以及文件大小。 JSF包含2个…

篡改referer_HTTP_REFERER的用法及伪造

引言在php中&#xff0c;可以使用$_SERVER[‘HTTP_REFERER’]来获取HTTP_REFERER信息&#xff0c;关于HTTP_REFERER&#xff0c;php文档中的描述如下&#xff1a;“引导用户代理到当前页的前一页的地址(如果存在)。由 user agent 设置决定。并不是所有的用户代理都会设置该项&a…

华为nova7保密柜_华为发布nova8系列新品 轻松拍出Vlog黄金脸占比

2020年12月23日&#xff0c;华为nova8系列手机正式发布。作为深受年轻人喜爱的手机品牌之一&#xff0c;华为nova8系列此次延续其强大的前置视觉实力、强悍的设计性能以及独特的设计美学&#xff0c;带来业内首款专为Vlog拍摄设计的5G手机。随着短视频和Vlog的崛起&#xff0c;…