SpringBoot——SpringBoot访问外部接口

这篇具有很好参考价值的文章主要介绍了SpringBoot——SpringBoot访问外部接口。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

摘要

在SpringBoot接口开发中,存在着本模块的代码需要访问外面模块接口或外部url链接的需求, 比如调用外部的地图API或者天气API。那么有哪些方式可以调用外部接口呢?本博文将介绍SpringBoot常见的访问外部接口方式。帮助大家更好的使用SpringBoot访问外部接口。

一、需要访问外部接口背景

调用其它模块的API,或者其它三方服务,比如调用外部的地图API或者天气API等。

二、访问外部接口的常见方案

2.1 采用原生的Http请求

在代码中采用原生的http请求,代码参考如下:

@RequestMapping("/doPostGetJson")
public String doPostGetJson() throws ParseException {
   //此处将要发送的数据转换为json格式字符串
   String jsonText = "{id: 1}";
   JSONObject json = (JSONObject) JSONObject.parse(jsonText);
   JSONObject sr = this.doPost(json);
   System.out.println("返回参数: " + sr);
   return sr.toString();
}

public static JSONObject doPost(JSONObject date) {
   HttpClient client = HttpClients.createDefault();
   // 要调用的接口方法
   String url = "http://192.168.1.101:8080/getJson";
   HttpPost post = new HttpPost(url);
   JSONObject jsonObject = null;
   try {
      StringEntity s = new StringEntity(date.toString());
      s.setContentEncoding("UTF-8");
      s.setContentType("application/json");
      post.setEntity(s);
      post.addHeader("content-type", "text/xml");
      HttpResponse res = client.execute(post);
      String response1 = EntityUtils.toString(res.getEntity());
      System.out.println(response1);
      if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
         String result = EntityUtils.toString(res.getEntity());// 返回json格式: 
         jsonObject = JSONObject.parseObject(result);
      }
   } catch (Exception e) {
      throw new RuntimeException(e);
   }
   return jsonObject;
}

2.2  采用Feign进行消费(PRC通信)

在maven项目中添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
    <version>1.2.2.RELEASE</version>
</dependency>

编写接口,放置在service层。这里的decisionEngine.url 是配置在properties中的 是ip地址和端口号 decisionEngine.url=http://10.2.1.148:3333/decision/person 是接口名字。

@FeignClient(url = "${decisionEngine.url}",name="engine")
public interface DecisionEngineService {
  @RequestMapping(value="/decision/person",method= RequestMethod.POST)
  public JSONObject getEngineMesasge(@RequestParam("uid") String uid,@RequestParam("productCode") String productCode);

}

在Java的启动类上加上@EnableFeignClients

@EnableFeignClients //参见此处
@EnableDiscoveryClient
@SpringBootApplication
@EnableResourceServer
public class Application   implements CommandLineRunner {
    private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
    @Autowired
    private AppMetricsExporter appMetricsExporter;

    @Autowired
    private AddMonitorUnitService addMonitorUnitService;

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }    
}

在代码中调用接口即可

@Autowired
private DecisionEngineService decisionEngineService ;
// ...
decisionEngineService.getEngineMesasge("uid" ,  "productCode");

2.3 采用RestTemplate方法

在Spring-Boot开发中,RestTemplate同样提供了对外访问的接口API,这里主要介绍Get和Post方法的使用。Get请求提供了两种方式的接口getForObject 和 getForEntity,getForEntity提供如下三种方法的实现。

2.3.1 Get请求之——getForEntity(Stringurl,Class responseType,Object…urlVariables)

该方法提供了三个参数,其中url为请求的地址,responseType为请求响应body的包装类型,urlVariables为url中的参数绑定,该方法的参考调用如下:

// http://USER-SERVICE/user?name={name)
RestTemplate restTemplate=new RestTemplate();
Map<String,String> params=new HashMap<>();
params.put("name","dada");  //
ResponseEntity<String> responseEntity=restTemplate.getForEntity("http://USERSERVICE/user?name={name}",String.class,params);

2.3.2 Get请求之——getForEntity(URI url,Class responseType)

该方法使用URI对象来替代之前的url和urlVariables参数来指定访问地址和参数绑定。URI是JDK java.net包下的一个类,表示一个统一资源标识符(Uniform Resource Identifier)引用。参考如下:

RestTemplate restTemplate=new RestTemplate();
UriComponents uriComponents=UriComponentsBuilder.fromUriString("http://USER-SERVICE/user?name={name}")
    .build()
    .expand("dodo")
    .encode();
URI uri=uriComponents.toUri();
ResponseEntity<String> responseEntity=restTemplate.getForEntity(uri,String.class).getBody();

2.3.3 Get请求之——getForObject

getForObject方法可以理解为对getForEntity的进一步封装,它通过HttpMessageConverterExtractor对HTTP的请求响应体body内容进行对象转换,实现请求直接返回包装好的对象内容。getForObject方法有如下:

getForObject(String url,Class responseType,Object...urlVariables)
getForObject(String url,Class responseType,Map urlVariables)
getForObject(URI url,Class responseType)

2.3.4 Post 请求

Post请求提供有三种方法,postForEntity、postForObject和postForLocation。其中每种方法都存在三种方法,postForEntity方法使用如下:

RestTemplate restTemplate=new RestTemplate();
User user=newUser("didi",30);
ResponseEntity<String> responseEntity=restTemplate.postForEntity("http://USER-SERVICE/user",user,String.class); //提交的body内容为user对象,请求的返回的body类型为String
String body=responseEntity.getBody();

postForEntity存在如下三种方法的重载

postForEntity(String url,Object request,Class responseType,Object... uriVariables)
postForEntity(String url,Object request,Class responseType,Map uriVariables)
postForEntity(URI url,Object request,Class responseType)

postForEntity中的其它参数和getForEntity的参数大体相同在此不做介绍。

2.4 在接口调用中需要注意什么?

需要注意两点:

  1. 需要设置超时时间
  2. 需要自行处理异常

三、SpringBoot保证接口幂等

3.1 什么是幂等

当我们讨论接口的幂等性时一般是在说:以相同的请求调用这个接口一次和调用这个接口多次,对系统产生的影响是相同的。如果一个接口满足这个特性,那么我们就说这个 接口是一个幂等接口。

  • 接口幂等和防止重复提交是一回事吗

严格来说,并不是。

  1. 幂等: 更多的是在重复请求已经发生,或是无法避免的情况下,采取一定的技术手段让这些重复请求不给系统带来副作用。
  2. 防止重复: 提交更多的是不让用户发起多次一样的请求。比如说用户在线购物下单时点了提交订单按钮,但是由于网络原因响应很慢,此时用户比较心急多次点击了订单提交按钮。 这种情况下就可能会造成多次下单。一般防止重复提交的方案有:将订单按钮置灰,跳转到结果页等。主要还是从客户端的角度来解决这个问题。
  • 哪些情况下客户端是防止不了重复提交的

虽然我们可在客户端做一些防止接口重复提交的事(比如将订单按钮置灰,跳转到结果页等), 但是如下情况依然客户端是很难控制接口重复提交到后台的,这也进一步表明了接口幂等和防止重复提交不是一回事以及后端接口保证接口幂等的必要性所在。

  1. 接口超时重试:接口可能会因为某些原因而调用失败,出于容错性考虑会加上失败重试的机制。如果接口调用一半,再次调用就会因为脏数据的存在而出现异常。
  2. 消息重复消费:在使用消息中间件来处理消息队列,且手动ack确认消息被正常消费时。如果消费者突然断开连接,那么已经执行了一半的消息会重新放回队列。被其他消费者重新消费时就会导致结果异常,如数据库重复数据,数据库数据冲突,资源重复等。
  3. 请求重发:网络抖动引发的nginx重发请求,造成重复调用;

3.2 什么是接口幂等?

在HTTP/1.1中,对幂等性进行了定义。它描述了一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外),即第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。

这里的副作用是不会对结果产生破坏或者产生不可预料的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

  • 对哪些类型的接口需要保证接口幂等

我们看下标准的restful请求,幂等情况是怎么样的:

  1. SELECT查询操作
    1. GET:只是获取资源,对资源本身没有任何副作用,天然的幂等性。
    2. HEAD:本质上和GET一样,获取头信息,主要是探活的作用,具有幂等性。
    3. OPTIONS:获取当前URL所支持的方法,因此也是具有幂等性的。
  2. DELETE删除操作
    1. 删除的操作,如果从删除的一次和删除多次的角度看,数据并不会变化,这个角度看它是幂等的
    2. 但是如果,从另外一个角度,删除数据一般是返回受影响的行数,删除一次和多次删除返回的受影响行数是不一样的,所以从这个角度它需要保证幂等。(折中而言DELETE操作通常也会被纳入保证接口幂等的要求)
  3. ADD/EDIT操作
    1. PUT:用于更新资源,有副作用,但是它应该满足幂等性,比如根据id更新数据,调用多次和N次的作用是相同的(根据业务需求而变)。
    2. POST:用于添加资源,多次提交很可能产生副作用,比如订单提交,多次提交很可能产生多笔订单。

3.3 保证幂等的方式

3.3.1 数据库层面

典型的数据库悲观锁:for update

select * from t_order where order_id = trade_no for update;

为什么加for update就可以?

  1. 当线程A执行for update,数据会对当前记录加锁,其他线程执行到此行代码的时候,会等待线程A释放锁之后,才可以获取锁,继续后续操作。
  2. 事物提交时,for update获取的锁会自动释放。

PS:这种方式很少被使用,因为如果业务处理比较耗时,并发情况下,后面线程会长期处于等待状态,占用了很多线程,让这些线程处于无效等待状态,我们的web服务中的线程数量一般都是有限的,如果大量线程由于获取for update锁处于等待状态,不利于系统并发操作。

3.3.2 唯一ID/索引

针对的是插入操作。数据库唯一主键的实现主要是利用数据库中主键唯一约束的特性,一般来说唯一主键比较适用于“插入”时的幂等性,其能保证一张表中只能存在一条带该唯一主键的记录。

使用数据库唯一主键完成幂等性时需要注意的是,该主键一般来说并不是使用数据库中自增主键,而是使用分布式 ID 充当主键,这样才能能保证在分布式环境下 ID 的全局唯一性。

  • 去重表

去重表本质上也是一种唯一索引方案。

这种方法适用于在业务中有唯一标的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和写入去去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。

3.3.3 乐观锁(基于版本号或者时间戳)

针对更新操作。

  • 使用版本号或者时间戳

这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等

boolean updateGoodsName(int id,String newName,int version);

在实现时可以如下

update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}
  • 状态机

本质上也是乐观锁,这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100。付款失败为99

在做状态机更新时,我们就这可以这样控制

update `order` set status=#{status} where id=#{id} and status<#{status}

3.3.4 分布式锁

分布式锁实现幂等性的逻辑是,在每次执行方法之前判断,是否可以获取到分布式锁,如果可以,则表示为第一次执行方法,否则直接舍弃请求即可。需要注意的是分布式锁的key必须为业务的唯一标识,通常用redis分布式锁或者zookeeper来实现分布式锁。文章来源地址https://www.toymoban.com/news/detail-733098.html

博文参考

到了这里,关于SpringBoot——SpringBoot访问外部接口的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【SpringBoot3】--03.数据访问、基础特性(外部化和内部外配置、整合JUnit)

    学习视频: 尚硅谷SpringBoot3视频 SpringBoot 整合 Spring 、 SpringMVC 、 MyBatis 进行 数据访问场景 开发 勾选之后会导入以下包 安装 MyBatisX 插件,帮我们生成Mapper接口的xml文件即可 在接口处 : Alt + 回车 必须把mapper的xml文件映射位置在配置文件定义! 而驼峰命名转换建议也开启 编

    2024年02月16日
    浏览(42)
  • Springboot之把外部依赖包纳入Spring容器管理的两种方式

    在Spring boot项目中,凡是标记有@Component、@Controller、@Service、@Configuration、@Bean等注解的类,Spring boot都会在容器启动的时候,自动创建bean并纳入到Spring容器中进行管理,这样就可以使用@Autowired等注解,在需要使用bean的业务类中进行注入。这里起到关键作用的就是@ComponentScan,

    2024年02月14日
    浏览(49)
  • SpringBoot访问静态资源和jar外部静态资源,部署前端打包后的vue项目放入静态资源里

    记录一下使用SpringBoot访问静态资源和SpringBoot打包之后的jar外部静态资源,在开发的时候,一般选择前后端分离的方式,前端使用vue 后端使用SpringBoot,通常情况下,部署都是前端通过http去请求后端资源,涉及到http请求,那么肯定需要资源的目标地址IP,一般云端部署给到I

    2024年02月09日
    浏览(75)
  • SpringBoot项目启动成功但接口访问404

    问题描述:SpringBoot项目启动成功,但使用postman调接口时报404 分析:找不到路径,controller未生效。 解决:将com.local.coupon.template.templateimpl改为com.local.coupon.template。SpringBoot会扫描到com.local.coupon.template包及其子包下面的内容,扫描路径也无需指定(即可以去掉@ComponentScan注解)

    2024年02月17日
    浏览(46)
  • Springboot项目启动后浏览器不能直接访问接口,而postman可以访问?

    在云服务器上部署springboot后端时,项目启动后浏览器不能直接访问接口,而postman可以访问。这是当时困扰了我大半天的小问题,在我打开防火墙和阿里云安全组之后还是没解决。然后在网上搜了很多很多资料,以为是浏览器访问权限或者是https什么证书问题,到头来通过简单

    2024年02月21日
    浏览(51)
  • Python中如何编写接口,以及如何请求外部接口

    Python是一种既简单又强大的编程语言,也是现代软件开发的重要工具之一。在开发过程中,我们通常需要编写接口,并且从外部接口获取数据。本文将介绍如何在Python中编写接口,以及如何请求外部接口。 在Python中,可以使用Flask和Django等框架来编写接口。本文以Flask为例,

    2024年02月03日
    浏览(32)
  • SpringBoot限制接口访问频率 - 这些错误千万不能犯

    最近在基于SpringBoot做一个面向普通用户的系统,为了保证系统的稳定性,防止被恶意攻击,我想控制用户访问每个接口的频率。为了实现这个功能,可以设计一个annotation,然后借助AOP在调用方法之前检查当前ip的访问频率,如果超过设定频率,直接返回错误信息。 在开始介

    2024年02月05日
    浏览(45)
  • SpringBoot 如何使用 Spring Data MongoDB 访问 MongoDB

    在现代应用程序开发中,许多应用都依赖于数据库来存储和检索数据。MongoDB 是一个流行的 NoSQL 数据库,而 Spring Boot 是一个广泛使用的 Java 开发框架。本文将介绍如何使用 Spring Boot 和 Spring Data MongoDB 来访问和操作 MongoDB 数据库。 MongoDB 是一个开源的 NoSQL 数据库,它以文档的

    2024年02月07日
    浏览(41)
  • 超详细!完整版!基于spring对外开放接口的签名认证方案(拦截器方式)

    由于项目需要开发第三方接口给多个供应商,为保证Api接口的安全性,遂采用Api接口签名验证。 请求发起时间得在限制范围内 请求的用户是否真实存在 是否存在重复请求 请求参数是否被篡改 1、服务端生成一对 accessKey/secretKey密钥对,将 accessKey公开给客户端,将 secretKey 保

    2024年01月22日
    浏览(57)
  • Spring Security的API Key实现SpringBoot 接口安全

    Spring Security 提供了各种机制来保护我们的 REST API。其中之一是 API 密钥。API 密钥是客户端在调用 API 调用时提供的令牌。 在本教程中,我们将讨论如何在Spring Security中实现基于API密钥的身份验证。 API Keys 一些REST API使用API密钥进行身份验证。API密钥是一个标记,用于向API客户

    2024年03月15日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包