文章目录
前言
上一篇文章我们学习了使用openfeign来调用本地接口访问远程服务,这篇文章我们来学习另一种方式retrofit,这个工具在安卓中用的比较多,不过在springboot中使用也是一样啦。一、retrofit是什么?
okhttp是一款由square公司开源的java版本http客户端工具。square公司还开源了基于okhttp进一步封装的retrofit工具,用来支持通过接口的方式发起http请求。 retrofit-spring-boot-starter实现了Retrofit与SpringBoot框架快速整合,并且支持了部分功能增强,从而极大的简化spring-boot项目下http接口调用开发。二、使用步骤
1.引入库
此实例为gradle版本,如果是maven请切换为maven的写法
api group: 'com.github.lianjiatech', name: 'retrofit-spring-boot-starter', version: "2.2.14"
2.编写远程测试接口
编写远程服务实例,以下实例包含了多种情况,请求方式包括GET、POST、DELETE、PUT,传递函数方式包括RequestParam、PathVariable、RequestBody、RequestHeader、文件上传、文件下载。
package com.iscas.biz.test.retrofit;
import com.iscas.base.biz.util.SpringUtils;
import com.iscas.common.web.tools.file.FileDownloadUtils;
import com.iscas.templet.common.BaseController;
import com.iscas.templet.common.ResponseEntity;
import lombok.Data;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Map;
/**
*
* 测试远程被调用的retrofit接口
* @author zhuquanwen
* @vesion 1.0
* @date 2021/08/09 9:26
* @since jdk1.8
*/
@RestController
@RequestMapping("/test/retrofit/remote")
public class RemoteRetrofitController extends BaseController {
@GetMapping("/t1")
public ResponseEntity t1(@RequestParam("name") String name) {
ResponseEntity response = getResponse();
response.setValue(name);
return response;
}
@DeleteMapping("/t2/{name}")
public ResponseEntity t2(@PathVariable("name") String name) {
ResponseEntity response = getResponse();
response.setValue(name);
return response;
}
@PostMapping("/t3")
public ResponseEntity t3(@RequestBody Map<String, Object> params) {
ResponseEntity response = getResponse();
response.setValue(params);
return response;
}
@PutMapping("/t4")
public ResponseEntity t4(@RequestBody Map<String, Object> params) {
ResponseEntity response = getResponse();
response.setValue(params);
return response;
}
@PostMapping("/t5")
public ResponseEntity t5(@RequestParam Map<String, Object> params) {
ResponseEntity response = getResponse();
response.setValue(params);
return response;
}
@PostMapping("/t6")
public ResponseEntity t6(RetrofitTestModel model) {
ResponseEntity response = getResponse();
response.setValue(model);
return response;
}
@PostMapping("/t7")
public ResponseEntity t7(String name) {
ResponseEntity response = getResponse();
response.setValue(name);
return response;
}
@PostMapping("/t8")
public ResponseEntity t8(@RequestHeader("name") String name) {
ResponseEntity response = getResponse();
response.setValue(name);
return response;
}
@PostMapping("/t9")
public ResponseEntity t9(@RequestHeader("name") String name, @RequestHeader("token") String token) {
ResponseEntity response = getResponse();
response.setValue(name + ";" + token);
return response;
}
@PostMapping("/t10")
public ResponseEntity t10(MultipartFile file1) {
ResponseEntity response = getResponse();
response.setValue(file1.toString());
return response;
}
@PostMapping("/t11")
public ResponseEntity t11() throws ServletException, IOException {
HttpServletRequest request = SpringUtils.getRequest();
Collection<Part> parts = request.getParts();
if (parts != null) {
for (Part part : parts) {
InputStream inputStream = part.getInputStream();
}
}
ResponseEntity response = getResponse();
response.setValue(parts.toString());
return response;
}
@PostMapping("/t12")
public void t12() throws Exception {
FileDownloadUtils.downFile(SpringUtils.getRequest(), SpringUtils.getResponse(),
"D:/test-sp/aaa.html", "aaa.html");
}
@Data
public static class RetrofitTestModel {
private String name;
}
}
3.编写本地接口和测试接口
3.1. retrofit的配置信息
在application.properties中添加retrofit的配置项
###############retrofit配置###############
#连接池相关配置
retrofit.pool.test.max-idle-connection=3
retrofit.pool.test.keep-alive-second=100
#日志打印拦截器配置,可以继承BaseLoggingInterceptor实现自己的日志记录方式
retrofit.logging-interceptor=com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
#异常格式化处理,可以继承BaseHttpExceptionMessageFormatter,实现自己的异常格式化
retrofit。http-exception-message-formatter=com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultHttpExceptionMessageFormatter
3.2. 本地和测试接口
新建接口,添加@RetrofitClient
注解,配置baseUrl和poolName
,poolName
对应上一步的配置
先忽略@Interceptor
和@Sign
注解,后面再说明。整体Api文件如下,对应了步骤2的远程测试接口,各种传参方式可见接口的注释
package com.iscas.biz.test.retrofit;
import com.github.lianjiatech.retrofit.spring.boot.annotation.Intercept;
import com.github.lianjiatech.retrofit.spring.boot.annotation.RetrofitClient;
import com.iscas.templet.common.ResponseEntity;
import okhttp3.MultipartBody;
import okhttp3.ResponseBody;
import org.springframework.web.bind.annotation.RequestMapping;
import retrofit2.Call;
import retrofit2.http.*;
import java.util.List;
import java.util.Map;
/**
* 参考 https://blog.csdn.net/why_still_confused/article/details
*
* /108041657
*
* @author zhuquanwen
* @vesion 1.0
* @date 2021/8/8 18:25
* @since jdk1.8
*/
@RetrofitClient(baseUrl = "http://localhost:7901/demo", poolName = "test")
@Intercept(handler = SimpleInterceptor.class, include = {"/demo/test/**"}) //添加拦截器
@Sign(accessKeyId = "xxxx", accessKeySecret = "yyyyy", exclude = {"/demo/test/xxxx"})
public interface RetrofitApi {
/**url中参数 ?name=xxx*/
@GET("test/retrofit/remote/t1")
ResponseEntity t1(@Query("name") String name);
/**路径中传参 /{}*/
@DELETE("test/retrofit/remote/t2/{name}")
ResponseEntity t2(@Path("name") String name);
/**使用请求体传送json*/
@POST("test/retrofit/remote/t3")
ResponseEntity t3(@Body Map<String, Object> params);
/**put请求使用请求体传送json*/
@PUT("test/retrofit/remote/t4")
ResponseEntity t4(@Body Map<String, Object> params);
/**多个url参数使用map传递*/
@POST("test/retrofit/remote/t5")
ResponseEntity t5(@QueryMap Map<String, Object> params);
/**form表单传送数据,多个参数使用map传递*/
@FormUrlEncoded
@POST("test/retrofit/remote/t6")
ResponseEntity t6(@FieldMap Map<String, Object> model);
/**form表单传送数据,多个参数一个一个传递*/
@FormUrlEncoded
@POST("test/retrofit/remote/t7")
ResponseEntity t7(@Field("name") String name);
/**header传送数据,多个参数一个一个传递*/
@POST("test/retrofit/remote/t8")
ResponseEntity t8(@Header("name") String name);
/**header传送数据,多个参数一起传递*/
@POST("test/retrofit/remote/t9")
@Headers({"name:quanwen", "token:wgwegwe"})
ResponseEntity t9();
/**单个文件上传*/
@POST("test/retrofit/remote/t10")
@Multipart
ResponseEntity t10(@Part MultipartBody.Part filePart);
/**多个文件上传*/
@POST("test/retrofit/remote/t11")
@Multipart
ResponseEntity t11(@Part List<MultipartBody.Part> parts);
// /**多个文件上传*/
// @POST("test/retrofit/remote/t11")
// @Multipart
// ResponseEntity t11_2(@PartMap Map<String, MultipartBody.Part> parts);
/**文件下载*/
@POST("test/retrofit/remote/t12")
@Streaming
Call<ResponseBody> t12();
}
3.3. 测试
这是测试还是使用了Http的接口
package com.iscas.biz.test.retrofit;
import com.iscas.templet.common.ResponseEntity;
import com.rabbitmq.client.GetResponse;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import retrofit2.Call;
import retrofit2.Response;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author zhuquanwen
* @vesion 1.0
* @date 2021/8/8 20:58
* @since jdk1.8
*/
@RestController
@RequestMapping("/test/retrofit")
public class RetrofitTestController {
@Autowired
private RetrofitApi retrofitApi;
@GetMapping("t1")
public ResponseEntity t1() {
return retrofitApi.t1("quanwen");
}
@GetMapping("t2")
public ResponseEntity t2() {
return retrofitApi.t2("quanwen");
}
@GetMapping("t3")
public ResponseEntity t3() {
Map<String, Object> map = new HashMap<String, Object>(){{
put("name", "quanwen");
}};
return retrofitApi.t3(map);
}
@GetMapping("t4")
public ResponseEntity t4() {
Map<String, Object> map = new HashMap<String, Object>(){{
put("name", "quanwen");
}};
return retrofitApi.t4(map);
}
@GetMapping("t5")
public ResponseEntity t5() {
Map<String, Object> map = new HashMap<String, Object>(){{
put("name", "quanwen");
}};
return retrofitApi.t5(map);
}
@GetMapping("t6")
public ResponseEntity t6() {
Map<String, Object> map = new HashMap<String, Object>(){{
put("name", "quanwen");
}};
return retrofitApi.t6(map);
}
@GetMapping("t7")
public ResponseEntity t7() {
return retrofitApi.t7("quanwen");
}
@GetMapping("t8")
public ResponseEntity t8() {
return retrofitApi.t8("quanwen");
}
@GetMapping("t9")
public ResponseEntity t9() {
return retrofitApi.t9();
}
@GetMapping("t10")
public ResponseEntity t10() {
File file = new File("D:/test-sp/aaa.html");
RequestBody requestBody = RequestBody.create(MediaType.parse("text/html"), file);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file1", file.getName(), requestBody);
return retrofitApi.t10(filePart);
}
@GetMapping("t11")
public ResponseEntity t11() {
File file1 = new File("D:/test-sp/aaa.html");
File file2 = new File("D:/test-sp/post_download_file.html");
RequestBody requestBody1 = RequestBody.create(MediaType.parse("text/html"), file1);
MultipartBody.Part filePart1 = MultipartBody.Part.createFormData("file1", file1.getName(), requestBody1);
RequestBody requestBody2 = RequestBody.create(MediaType.parse("text/html"), file2);
MultipartBody.Part filePart2 = MultipartBody.Part.createFormData("file2", file2.getName(), requestBody2);
return retrofitApi.t11(Arrays.asList(filePart1, filePart2));
}
// @GetMapping("t11_2")
// public ResponseEntity t11_2() {
// File file1 = new File("D:/test-sp/aaa.html");
// File file2 = new File("D:/test-sp/post_download_file.html");
// RequestBody requestBody1 = RequestBody.create(MediaType.parse("text/html"), file1);
// MultipartBody.Part filePart1 = MultipartBody.Part.createFormData("file1", file1.getName(), requestBody1);
// RequestBody requestBody2 = RequestBody.create(MediaType.parse("text/html"), file2);
// MultipartBody.Part filePart2 = MultipartBody.Part.createFormData("file2", file2.getName(), requestBody2);
//
// return retrofitApi.t11_2(new HashMap<String, MultipartBody.Part>(){{
// put("file1", filePart1);
// put("file2", filePart2);
// }
// });
// }
@GetMapping("t12")
public ResponseEntity t12() throws IOException {
ResponseEntity<Object> responseEntity = new ResponseEntity<>();
Call<ResponseBody> responseBodyCall = retrofitApi.t12();
Response<ResponseBody> res = responseBodyCall.execute();
ResponseBody body = res.body();
return responseEntity;
}
}
4.编写拦截器
可是通过编写拦截器来拦截retrofit的请求,在请求中添加一些统一的处理,拦截器需要继承BasePathMatchInterceptor
并重写doIntercept
方法,示例如下:
package com.iscas.biz.test.retrofit;
import com.github.lianjiatech.retrofit.spring.boot.interceptor.BasePathMatchInterceptor;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
*
* @author zhuquanwen
* @vesion 1.0
* @date 2021/8/9 11:20
* @since jdk1.8
*/
@Component
public class SimpleInterceptor extends BasePathMatchInterceptor {
@Override
protected Response doIntercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.url();
url = url.newBuilder().addQueryParameter("time", String.valueOf(System.currentTimeMillis()))
.build();
Headers headers = request.headers();
headers = headers.newBuilder().add("add-header", "lalala").build();
request = request.newBuilder().headers(headers)
.url(url).build();
return chain.proceed(request);
}
}
在接口类中添加@Interceptor
注解,handler
属性为上面定义的拦截器,include
属性为拦截的URL,exclude
为不拦截的URL
@Intercept(handler = SimpleInterceptor.class, include = {"/demo/test/**"}) //添加拦截器
5.自定义注解拦截器
注意必须添加@InterceptMark
package com.iscas.biz.test.retrofit;
import com.github.lianjiatech.retrofit.spring.boot.annotation.InterceptMark;
import com.github.lianjiatech.retrofit.spring.boot.interceptor.BasePathMatchInterceptor;
import java.lang.annotation.*;
/**
*
* @author zhuquanwen
* @vesion 1.0
* @date 2021/8/9 13:20
* @since jdk1.8
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@InterceptMark //必须加这个注解
public @interface Sign {
/**
* 密钥key
* 支持占位符形式配置。
*
* @return
*/
String accessKeyId();
/**
* 密钥
* 支持占位符形式配置。
*
* @return
*/
String accessKeySecret();
/**
* 拦截器匹配路径
*
* @return
*/
String[] include() default {"/**"};
/**
* 拦截器排除匹配,排除指定路径拦截
*
* @return
*/
String[] exclude() default {};
/**
* 处理该注解的拦截器类
* 优先从spring容器获取对应的Bean,如果获取不到,则使用反射创建一个!
*
* @return
*/
Class<? extends BasePathMatchInterceptor> handler() default SignInterceptor.class;
}
在接口类上添加@Sign注解
@Sign(accessKeyId = "xxxx", accessKeySecret = "yyyyy", exclude = {"/demo/test/xxxx"})