学透Spring Boot — 009. Spring Boot的四种 Http 客户端

目录

常见的HttpClient

Spring 提供的HttpClient

RestTemplate

Spring 提供的模板类 XXXTemplate

RestTemplate的使用

RestTemplate的使用技巧

RestTemplate的问题

RestClient

 RestClinet的基本使用

RestClient的自动配置

RestClient 序列化对象

异常处理 onStatus

更精细的控制 exchange

HTTP消息转换 - Jackson Json 视图

RestClient真正使用的HTTP库

非异步调用

WebClient

简单使用

请求拦截器

深度配置WebClient

HTTP接口

配置和使用


常见的HttpClient

我们项目中通常要调用内部或者外部的HTTP接口,这时候就需要用到Http Client, 以前我们经常用第三方的组件比如:

  1. Apache HttpClient
  2. OkHttp
  3. Spring Cloud提供的Feign Http客户端

但是对于一般的项目,我们直接使用Spring 提供的HttpClient客户端就可以满足大部分的场景了。

所以严格上说,本文介绍的不是Spring Boot的特性,而是Spring提供的特性。

Spring 提供的HttpClient

Spring框架提供了几种调用REST API的方式:

  • RestTemplate:调用API的同步客户端
  • RestClient:异步客户端
  • WebClient:异步并且是响应式的客户端
  • Http Interface:基于动态代理的声明式接口

说明:

本文我们会用一个开源的HTTP REST API用来测试:

https://jsonplaceholder.typicode.com/users

为此,我们定义了Java类接收json响应

@Builder
@Data
public class TypiUser {private Integer id;private String name;private String username;private String email;private String phone;private String website;private TypiAddress address;
}@Builder
@Data
public class TypiAddress {private String street;private String suite;private String city;private String zipcode;
}

Tips:

使用builder构造对象时,报错:

java: cannot find symbolsymbol:   method builder()location: class lab.rest.TypiUser

后来打开target/classes 看编译后的类才发现TypiUser没有编译正确,所以执行 `mvn clean build` 后可以解决报错问题。

发现另一个地方设置也问题,应该不要勾选Processor Path

RestTemplate

Spring 提供的模板类 XXXTemplate

熟悉Spring框架的开发都知道,Spring提供了各种XXXTemplate,使用模板方法模式封装了复杂的底层操作,简化了我们对外部组件的操作和使用。

常见的有:

  • JdbcTemplate:封装了JDBC的底层操作,简化了数据库操作
  • JmsTemplate:封装了对JMS API的操作,简化了收发消息的操作
  • ElasticsearchRestTemplate:是Spring Data ES模块的一部分,简化了对ES的操作
  • RedisTemplate:是Spring Data Redis模块的一部分,简化了对Redis的操作
  • HibernateTemplate:简化了对Hibernate的CRUD操作
  • ……

扩展:模板方法模式

这些XXXTemplate用到的是模版方法模式

  • 在在抽象父类中定义了算法的骨架
  • 然后子类中实现某些步骤(抽象方法)

RestTemplate的使用

而本章节,我们要介绍的是RestTemplate,它是Spring用于进行HTTP 发送请求和接收响应的客户端工具。使得我们调用REST API变得非常的简单。

使用RestTemplate发送post请求

@Test
public void basicTest(){RestTemplate restTemplate = new RestTemplate();String url = "https://jsonplaceholder.typicode.com/users";TypiUser user = TypiUser.builder().name("Joe").username("joe").email("joe@gmail.com").phone("123456789").website("joe.com").address(TypiAddress.builder().street("street").suite("suite").city("city").zipcode("zipcode").build()).build();ResponseEntity<TypiUser> response = restTemplate.postForEntity(url, user, TypiUser.class);TypiUser body = response.getBody();System.out.printf("用户创建成功,id:%s, name:%s%n", body.getId(), body.getName());
}

可以看到使用RestTemplate 发送RestTemplate非常简单。

我们在Web开发中通常会通过配置类来配置RestTemplate实例

@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate restTemplate(RestTemplateBuilder builder) {return builder.setConnectTimeout(Duration.ofSeconds(10)) //设置连接超时时间.setReadTimeout(Duration.ofSeconds(10)) // 设置读取超时时间.build();}
}

然后注入RestTemplate直接使用

@Service
public class TypiUserService {private final RestTemplate restTemplate;public TypiUserService(RestTemplate restTemplate) {this.restTemplate = restTemplate;}public TypiUser getUser(Integer id) {return restTemplate.getForObject("https://jsonplaceholder.typicode.com/users/" + id,TypiUser.class);}
}

RestTemplate的使用技巧

一般场景,我们可以直接用getXXX方法发起get请求,通过postForEntity发起post请求,通过put()方法发起put请求,通过delete发起delete请求。

但某些时候我们想更精准的控制client的时候,就需要用到更原始的方法exchange(),它允许我们指定HTTP方法,处理请求头和请求体。

public TypiUser getUserByExchange(String id) {String url = "https://jsonplaceholder.typicode.com/users/" + id;ResponseEntity<TypiUser> response = restTemplate.exchange(url, HttpMethod.GET, null, TypiUser.class);TypiUser user = response.getBody();System.out.println(user);System.out.println(response.getStatusCode());System.out.println(response.getHeaders());return user;
}

异常处理

接口如果处理有问题,RestTemplate 也会抛出一些异常,尤其是在遇到错误响应时。常见的异常包括:

  1. HttpClientErrorException:用于 4xx 错误。
  2. HttpServerErrorException:用于 5xx 错误。
  3. ResourceAccessException:网络或连接问题时抛出。

我们需要捕获并处理这些异常。

一种方式是我们直接在service中捕获

public TypiUser getUser(Integer id) {try{return restTemplate.getForObject("https://jsonplaceholder.typicode.com/users/" + id,TypiUser.class);}catch (HttpClientErrorException e){throw new RuntimeException("客户端异常", e);}catch (HttpServerErrorException e){throw new RuntimeException("服务器端异常", e);}catch (RestClientException e) {throw new RuntimeException("Rest Client异常", e);}
}

但是一般没人这么干,因为如果我们有100个方法调用rest client,那不是得写100遍。所以我们一般会统一的处理异常,使用Spring提供的@ControllerAdvice 和 @ExceptionHandler全局捕获并处理异常。

@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(HttpClientErrorException.class)public ResponseEntity<ApiErrorResponse> handleHttpClientError(HttpClientErrorException e) {ApiErrorResponse errorResponse = new ApiErrorResponse(e.getStatusCode().value(),"Client Error",e.getResponseBodyAsString());return new ResponseEntity<>(errorResponse, HttpStatus.valueOf(e.getStatusCode().value()));}// 略
}

不仅仅是RestTemplate,其它的Client也需要这种全局的异常处理。

RestTemplate的问题

RestTemplate简单,但是也存在一些问题。

最重要的一点是它是同步的,它的每个请求都会阻塞直到收到响应。因为每个请求都占用一个线程,所以当大量请求同时发起时,导致系统的线程数会很快被耗尽,所以他的并发性能比较低。

RestClient

Spring引入了更现代化的HTTP客户端 RestClient.

它本身也是同步的,不支持异步。但是它支持流式的调用方式,相比RestTemplate提供一个方法发起请求,流式调用更容易使用和控制。

 RestClinet的基本使用

@Service
public class TypiRestClientService {private final RestClient.Builder builder;private RestClient restClient;public TypiRestClientService(RestClient.Builder builder) {this.builder = builder;}// 使用 @PostConstruct 注解在 Spring 完成构造器注入后再进行初始化@PostConstructpublic void init() {// 使用 builder 创建 RestClient 实例,进行初始化this.restClient = this.builder.baseUrl("https://jsonplaceholder.typicode.com").build();}public TypiUser getUser(Integer id) {return restClient.get().uri("/users/" + id).retrieve().body(TypiUser.class);}
}

RestClient的自动配置

我们都没有显示的配置RestClient.Builder,它是怎么自动注入的呢?

还是那一套,SpringBoot的自动配置。

在autoconfiguration包下面

可以看到,自动配置了RestClient和RestTemplate

这里自动配置了Builder Bean

@ConditionalOnClass(RestClient.class) 这个注解表示,RestClientAutoConfiguration 只会在类路径中存在 RestClient 类时才会被加载

@Conditional(NotReactiveWebApplicationCondition.class)如果是反应式应用,RestClientAutoConfiguration 就不会被加载。

@AutoConfiguration(after = { HttpClientAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,SslAutoConfiguration.class })
@ConditionalOnClass(RestClient.class)
@Conditional(NotReactiveWebApplicationCondition.class)
public class RestClientAutoConfiguration {@Bean@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)@ConditionalOnMissingBeanRestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {return restClientBuilderConfigurer.configure(RestClient.builder());}
}

直接注入RestClient行不行呢?

@Service
public class TypiRestClientService {private final RestClient restClient;public TypiRestClientService(RestClient restClient) {this.restClient = restClient;}

当然不行,因为自动配置类中并没有配置RestClient的Bean.

启动报错如下:

Action:

Consider defining a bean of type 'org.springframework.web.client.RestClient' in your configuration.

RestClient 序列化对象

requestbody和responseBody会自动转对象

    public TypiUser saveUser() {TypiUser user = TypiUser.builder().name("Joe").username("joe").email("joe@gmail.com").address(TypiAddress.builder().city("Beijing").build()).build();TypiUser result = restClient.post().uri("/users").body(user).retrieve().body(TypiUser.class);return result;}

retrieve方法之前的body()是request body, 后面的body()是 response body.

异常处理 onStatus

它提供了很优雅的异常处理

TypiUser result = restClient.post().uri("/users").body(user).retrieve().onStatus(HttpStatusCode::is4xxClientError, ((request, response) -> {throw new RuntimeException("status code: " + response.getStatusCode());})).body(TypiUser.class);

也是抛出异常,在全局类里面捕获并处理异常。

更精细的控制 exchange

如果想要更精准的控制请求和响应,我们可以利用更底层的方法来发起请求。

    TypiUser result = restClient.post().uri("/users").body(user).exchange((request, response) -> {if(response.getStatusCode().is2xxSuccessful()){return response.bodyTo(TypiUser.class);} else if(response.getStatusCode().is4xxClientError()){throw new RuntimeException("客户端错误,status code: " + response.getStatusCode());} else {throw new RuntimeException("其它错误,status code: " + response.getStatusCode());}});

虽然可以更精细操作,但是操作得都是底层的request和response对象,也变得更麻烦了。世界上没有绝对完美的事情。

HTTP消息转换 - Jackson Json 视图

发送请求时,我们不想把整个对象的所有字段都序列化成json后发送,而是序列化部分字段,可以做到的吗?

当然可以,最笨的方法是直接新建一个新的对象,只包含部分属性,然后用BeanUtils.copy复制属性。

因为我们用的是Jackson,所以可以更优雅

使用Jackson的json视图

MappingJacksonValue value = new MappingJacksonValue(user);
value.setSerializationView(TypiUser.TypiUserView.class);
TypiUser result = restClient.post().uri("/users").body(value).retrieve().onStatus(HttpStatusCode::is4xxClientError, ((request, response) -> {throw new RuntimeException("status code: " + response.getStatusCode());})).body(TypiUser.class);return result;

第一次听到这个技术,挺有意思。

RestClient真正使用的HTTP库

RestClient是对外提供的客户端,要执行HTTP请求,它底层还是得依赖其它HTTP库。

很像Slf4j, 它是个门面,底层是需要用log4j等日志框架的。

在 Spring 中,RestClient 通过适配不同的 HTTP 库来执行 HTTP 请求。这些库的适配是通过实现 ClientRequestFactory 接口来实现的。

具体实现有:

  • JdkClientHttpRequestFactory:使用的是 Java的 HttpClient
  • HttpComponentsClientHttpRequestFactory 使用的是Apache HTTP Components HttpClient
  • JettyClientHttpRequestFactory:使用Jetty的 HttpClient
  • ReactorNettyClientRequestFactory:使用Reactor Netty的 HttpClient
  • SimpleClientHttpRequestFactory:简单的实现

我们什么也没有配置,使用默认配置,看看是什么工厂类:

可以看到,使用的是JDK的HTTP Client。

非异步调用

RestClient也是同步HTTP客户端。但是对比RestTemplate,API是流式的更容易使用。如果没有异步的场景需求,我们可以使用RestClient。

WebClient

RestTemplate和RestClient都是同步的,WebClient是非阻塞 响应式的HTTP 客户端。

它是Spring5引入的,用来替代RestTemplate。

简单使用

WebClient是Spring WebFlux模块的功能,所以我们需要先引入依赖

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>

WebClient也是SpringBoot自动配置的,我们直接注入使用即可

@Service
public class TypiWebClientService {private final WebClient webClient;public TypiWebClientService(WebClient.Builder builder) {this.webClient = builder.baseUrl("https://jsonplaceholder.typicode.com").build();}
}

自动配置类

配置条件是classpath下找到了WebClient就自动配置WebClient.Builder这个Bean。所以我们注入的是WebClient.Builder这个Bean 而不是WebClient。

这块配置配置和RestClient一样!

@AutoConfiguration(after = { CodecsAutoConfiguration.class, ClientHttpConnectorAutoConfiguration.class })
@ConditionalOnClass(WebClient.class)
public class WebClientAutoConfiguration {@Bean@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)@ConditionalOnMissingBeanpublic WebClient.Builder webClientBuilder(ObjectProvider<WebClientCustomizer> customizerProvider) {WebClient.Builder builder = WebClient.builder();customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder));return builder;}

发起异步调用

    public TypiUser getUser(Integer id) {Mono<TypiUser> mono = webClient.get().uri("/users/{id}", id).retrieve().bodyToMono(TypiUser.class).doOnTerminate(() -> System.out.println("调用结束"));System.out.println("继续调用");TypiUser user = mono.block(); // 阻塞等待System.out.println("获取到用户:" + user);return user;}

继续调用
调用结束
获取到用户:TypiUser(id=1, name=Leanne Graham, username=Bret, email=Sincere@april.biz, phone=1-770-736-8031 x56442, website=hildegard.org, address=TypiAddress(street=Kulas Light, suite=Apt. 556, city=Gwenborough, zipcode=92998-3874))

可以看到,发起请求后,线程并没有blocked住,而是继续往下走,直到执行到block()方法时,线程才会阻塞。

以前看了这么多八股文,很难理解异步WebClient。其实只要写个最简单的例子,就非常容易理解了。Java八股害死人。

请求拦截器

我们可以拦截请求,做一些特殊的处理。

    public TypiWebClientService(WebClient.Builder builder) {this.webClient = builder.baseUrl("https://jsonplaceholder.typicode.com").filter((request, next) -> {System.out.println("开始调用,路径 " + request.url().toString());return next.exchange(request);}).build();}

这样所有的请求,都会执行这个操作

继续调用
开始调用,路径 https://jsonplaceholder.typicode.com/users/1
调用结束

深度配置WebClient

在配置类中配置WebClient Bean

配置连接池和选择底层使用的HTTP客户端。

@Configuration
public class WebClientConfig {@Beanpublic WebClient webClient() {ConnectionProvider provider = ConnectionProvider.builder("custom").maxConnections(50)  // 设置最大连接数.build();HttpClient httpClient = HttpClient.create(provider).responseTimeout(Duration.ofSeconds(5))  // 设置响应超时.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).baseUrl("https://jsonplaceholder.typicode.com").build();}
}

这里我们使用的HTTP Client是Netty的客户端。

并设置了连接池的大小还有HTTP的响应时间。

这样我们可以直接注入使用WebClient了

@Service
public class TypiWebClientService {private final WebClient webClient;public TypiWebClientService(WebClient webClient) {this.webClient = webClient;}

HTTP接口

Spring允许我们通过Java接口的方式调用HTTP服务。我们不需要写代码显示调用HTTP,而是通过注解声明即可。

底层是利用动态代理技术,简化了远程 HTTP 调用。

配置和使用

首先,我们定义一个接口,并用注解声明

public interface TypiUserRestService {@GetExchange("/users/{id}")TypiUser getUser(@PathVariable Integer id);
}

然后,我们需要创建一个代理,底层还是需要其它HTTP库的

当然我们也可以选用其它的库比如WebClient等

@Configuration
public class TypiUserRestServiceConfig {@Beanpublic TypiUserRestService config(){RestClient restClient = RestClient.builder().baseUrl("https://jsonplaceholder.typicode.com").build();RestClientAdapter adapter = RestClientAdapter.create(restClient);HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();return factory.createClient(TypiUserRestService.class);}
}

接下来,我们就可以在我们的Controller中注入直接使用了。

@RestController
@RequestMapping("/typi")
public class TypiUserController {private final TypiUserRestService typiUserRestService;@GetMapping("/v4/user/{id}")public TypiUser getUser4(@PathVariable Integer id){return typiUserRestService.getUser(id);}

这种声明式的调用,让我们的代码显得非常简洁!

很类似Spring Cloud Feign的调用方式。

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

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

相关文章

leetcode117 填充每个节点的下一个右侧节点指针2

LeetCode 116 和 117 都是关于填充二叉树节点的 next 指针的问题&#xff0c;但它们的区别在于 树的类型 不同&#xff0c;117与 116 题类似&#xff0c;但给定的树是 普通二叉树&#xff08;不一定完全填充&#xff09;&#xff0c;即某些节点可能缺少左或右子节点。 树的结构…

软考系统架构师 — 4 嵌入式软件

目录 4.1 考点分析 4.2 嵌入式微处理器 4.2.1嵌入式微处理器体系结构 5.2.2 嵌入式微处理器分类 4.2.3 多核处理器 4.3 嵌入式软件 4.4 嵌入式系统 4.4.1 嵌入式系统的组成 4.4.2 嵌入式系统分类 4.4.3 嵌入式数据库系统DBMS 4.4.4 嵌入式操作系统OS 4.4.5 嵌入式实…

RocketMQ 中的 ProducerManager 组件剖析

一、引言 在分布式系统的消息传递领域&#xff0c;RocketMQ 以其高性能、高可用性和强大的扩展性脱颖而出。ProducerManager 作为 RocketMQ 中的一个关键组件&#xff0c;在消息生产环节发挥着至关重要的作用。它负责管理消息生产者&#xff08;Producer&#xff09;的生命周期…

k8s进阶之路:本地集群环境搭建

概述 文章将带领大家搭建一个 master 节点&#xff0c;两个 node 节点的 k8s 集群&#xff0c;容器基于 docker&#xff0c;k8s 版本 v1.32。 一、系统安装 安装之前请大家使用虚拟机将 ubuntu24.04 系统安装完毕&#xff0c;我是基于 mac m1 的系统进行安装的&#xff0c;所…

深度学习数据集划分比例多少合适

在机器学习和深度学习中&#xff0c;测试集的划分比例需要根据数据量、任务类型和领域需求灵活调整。 1. 常规划分比例 通用场景 训练集 : 验证集 : 测试集 60% : 20% : 20% 适用于大多数中等规模数据集&#xff08;如数万到数十万样本&#xff09;&#xff0c;平衡了训练数…

【TS学习】(15)分布式条件特性

在 TypeScript 中&#xff0c;分布式条件类型&#xff08;Distributive Conditional Types&#xff09; 是一种特殊的行为&#xff0c;发生在条件类型作用于裸类型参数&#xff08;Naked Type Parameter&#xff09; 时。这种特性使得条件类型可以“分布”到联合类型的每个成员…

NSSCTF [HGAME 2023 week1]simple_shellcode

3488.[HGAME 2023 week1]simple_shellcode 手写read函数shellcode和orw [HGAME 2023 week1]simple_shellcode (1) motalymotaly-VMware-Virtual-Platform:~/桌面$ file vuln vuln: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpret…

PostgreSQL的扩展(extensions)-常用的扩展-pg_dirtyread

PostgreSQL的扩展&#xff08;extensions&#xff09;-常用的扩展-pg_dirtyread pg_dirtyread 是 PostgreSQL 的一个特殊扩展&#xff0c;它允许读取已被删除但尚未被 VACUUM 清理的数据行&#xff0c;是数据恢复的重要工具。 原理&#xff1a; pg_dirtyread 通过直接访问表的…

linux3 mkdir rmdir rm cp touch ls -d /*/

Linux 系统的初始目录结构遵循 FHS&#xff08;Filesystem Hierarchy Standard&#xff0c;文件系统层次标准&#xff09;&#xff0c;定义了每个目录的核心功能和存储内容。以下是 Linux 系统初始安装后的主要目录及其作用&#xff1a; 1. 核心系统目录 目录用途典型内容示例…

Bazel中的Symbol, Rule, Macro, Target, Provider, Aspect 等概念

学习Bazel &#xff0c;就要学习Bazel 的规则定义&#xff0c; 弄清各个概念是重要的一个步骤。 在 Bazel 规则定义中&#xff0c;Symbol、Rule 和 Macro 是常见的概念。除此之外&#xff0c;Bazel 还有 Target、Provider、Aspect Repository、Package、 Workspace、 Configura…

深入探究 Hive 中的 MAP 类型:特点、创建与应用

摘要 在大数据处理领域,Hive 作为一个基于 Hadoop 的数据仓库基础设施,提供了方便的数据存储和分析功能。Hive 中的 MAP 类型是一种强大的数据类型,它允许用户以键值对的形式存储和操作数据。本文将深入探讨 Hive 中 MAP 类型的特点,详细介绍如何创建含有 MAP 类型字段的表…

基于Java的区域化智慧养老系统(源码+lw+部署文档+讲解),源码可白嫖!

摘 要 时代在飞速进步&#xff0c;每个行业都在努力发展现在先进技术&#xff0c;通过这些先进的技术来提高自己的水平和优势&#xff0c;区域化智慧养老系统当然不能排除在外。区域化智慧养老系统是在实际应用和软件工程的开发原理之上&#xff0c;运用Java语言、JSP技术以及…

关于JVM和OS中的指令重排以及JIT优化

关于JVM和OS中的指令重排以及JIT优化 前言&#xff1a; 这东西应该很重要才对&#xff0c;可是大多数博客都是以讹传讹&#xff0c;全是错误&#xff0c;尤其是JVM会对字节码进行重排都出来了&#xff0c;明明自己测一测就出来的东西&#xff0c;写出来误人子弟… 研究了两天&…

VS2022远程调试Linux程序

一、 1、VS2022安装参考 VS Studio2022安装教程&#xff08;保姆级教程&#xff09;_visual studio 2022-CSDN博客 注意&#xff1a;勾选的时候&#xff0c;要勾选下方的选项&#xff0c;才能调试Linux环境下运行的程序&#xff01; 2、VS2022远程调试Linux程序测试 原文参…

WPF设计学习记录滴滴滴4

<Button x:Name"btn"Content"退出"Width" 100"Height"25"Click"btn_Click" IsDefault"True"/> <Button x:Name"btn" <!-- 控件标识&#xff1a;定义按钮的实例名称为"btn&…

JVM 有哪些垃圾回收器

垃圾收集算法 标记-复制算法(Copying): 将可用内存按容量划分为两个区域,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。 标记-清除算法(Mark-Sweep): 算法分为“标记” 和“清除”两个…

React DndKit 实现类似slack 类别、频道拖动调整位置功能

一周调试终于实现了类 slack 类别、频道拖动调整位置功能。 历经四个版本迭代。 实现了类似slack 类别、频道拖动调整功能 从vue->react &#xff1b;更喜欢React的生态及编程风格&#xff0c;新项目用React来重构了。 1.zustand全局状态 2.DndKit 拖动 功能视频&…

新浪财经股票每天10点自动爬取

老规矩还是先分好三步&#xff0c;获取数据&#xff0c;解析数据&#xff0c;存储数据 因为股票是实时的&#xff0c;所以要加个cookie值&#xff0c;最好分线程或者爬取数据时等待爬取&#xff0c;不然会封ip 废话不多数&#xff0c;直接上代码 import matplotlib import r…

使用Android 原生LocationManager获取经纬度

一、常用方案 1、使用LocationManager GPS和网络定位 缺点&#xff1a;个别设备,室内或者地下停车场获取不到gps定位,故需要和网络定位相结合使用 2、使用Google Play服务 这种方案需要Android手机中有安装谷歌服务,然后导入谷歌的第三方库&#xff1a; 例如&#xff1a;i…