springboot整合retrofit实现本地接口调用远程服务方式
一、简介
okhttp是一款由square公司开源的Java版本http客户端工具。
square公司还开源了基于okhttp进一步封装的retrofit工具,用来支持通过接口的方式发起http请求。
retrofit-spring-boot-starter实现了Retrofit与SpringBoot框架快速整合,并且支持了部分功能增强,从而极大的简化spring-boot项目下http接口调用开发
二、springboot整合retrofit
1.导入依赖
<dependency> <groupId>com.github.lianjiatech</groupId> <artifactId>retrofit-spring-boot-starter</artifactId> <version>2.2.22</version> </dependency>
2.编写远程测试接口
package com.hl.springbootmyBATis.controller; import cn.hutool.core.collection.ListUtil; import cn.hutool.json.JSONUtil; import lombok.Data; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Map; /** * 测试远程被调用的retrofit接口 */ @RestController @RequestMapping("/test/retrofit/remote") public class RemoteRetrofitController { @GetMapping("/t1") public List<String> t1(@RequestParam("name") String name) { return ListUtil.of(name); } @DeleteMapping("/t2/{name}") public List<String> t2(@PathVariable("name") String name) { return ListUtil.of(name); } @PostMapping("/t3") public Map<String, Object> t3(@RequestBody Map<String, Object> params) { return params; } @PutMapping("/t4") public Map<String, Object> t4(@RequestBody Map<String, Object> params) { return params; } @PostMapping("/t5") public Map<String, Object> t5(@RequestParam Map<String, Object> params) { return params; } @PostMapping("/t6") public String t6(RetrofitTestModel model) { return JSONUtil.toJsonStr(model); } @PostMapping("/t7") public String t7(String name) { return name; } @PostMapping("/t8") public String t8(@RequestHeader("name") String name) { return name; } @PostMapping("/t9") public String t9(@RequestHeader("name") String name, @RequestHeader("token") String token) { return name + ";" + token; } @GetMapping("/t10/{name}") public List<String> t10(@PathVariable("name") String name) { return ListUtil.of(name); } @Data public static class RetrofitTestModel { private String name; WDxuZvUx } }
3.编写本地测试接口
3.1 retrofit的配置信息
###############retrofit配置############### #连接池相关配置 retrofit.global-connect-timeout-ms= 5000 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
对应上一步的配置
package com.yolo.springbootretrofit.config; import com.github.lianjiatech.retrofit.spring.boot.annotation.RetrofitClient; import retrofit2.http.*; import java.util.List; import java.util.Map; @RetrofitClient(baseUrl = "http://localhost:9093/",poolName = "test") public interface HttpClient { @GET("test/retrofit/remote/t1") List<String> t1(@Query("name") String name); /**路径中传参 /{}*/ @DELETE("test/retrofit/remote/t2/{name}") String t2(@Path("name") String name); /**使用请求体传送json*/ @POST("test/retrofit/remote/t3") Map<String, Object> t3(@Body Map<String, Object>js; params); /**put请求使用请求体传送json*/ @PUT("test/retrofit/remote/t4") Map<String, Object> t4(@Body Map<String, Object> params); /**多个url参数使用map传递*/ @POST("test/retrofit/remote/t5") Map<String, Object> t5(@QueryMap Map<String, Object> params); /**form表单传送数据,多个参数使用map传递*/ @FormUrlEncoded @POST("test/retrofit/remote/t6") Map<String, Object> t6(@FieldMap Map<String, Object> model); /**form表单传送数据,多个参数一个一个传递*/ @FormUrlEncoded @POST("test/retrofit/remote/t7") String t7(@Field("name") String name); /**header传送数据,多个参数一个一个传递*/ @POST("test/retrofit/remote/t8") String t8(@Header("name") String name); /**header传送数据,多个参数一起传递*/ @POST("test/retrofit/remote/t9") @Headers({"name:zhangsan", "token:lisi"}) String t9(); /**自定义全路径,避免重新写一个HttpClient **/ @GET() List<String> t10(@Url String url) ; }
3.3 测试
package com.yolo.springbootretrofit.controller; import com.yolo.springbootretrofit.config.HttpClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; ihttp://www.devze.commport org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/test/retrofit") public class RetrofitTestController { @Resource private HttpClient httpClient; @GetMapping("/t1") public List<String> t1() { return httpClient.t1("zhangsan"); } @GetMapping("t2") public String t2() { return httpClient.t2("zhangsan"); } @GetMapping("t3") public Map<String, Object> t3() { Map<String, Object> map = new HashMap<>(); map.put("name", "zhangsan"); return httpClient.t3(map); } @GetMapping("t4") public Map<String, Object> t4() { Map<String, Object> map = new HashMap<>(); map.put("name", "zhangsan"); return httpClient.t4(map); } @GetMapping("t5") public Map<String, Object> t5() { Map<String, Object> map = new HashMap<>(); map.put("name", "zhangsan"); return httpClient.t5(map); } @GetMapping("t6") public Map<String, Object> t6() { Map<String, Object> map = new HashMap<>(); map.put("name", "zhangsan"); return httpClient.t6(map); } @GetMapping("t7") public String t7() { return httpClient.t7("zhangsan"); } @GetMapping("t8") public String t8() { return httpClient.t8("zhangsan"); } @GetMapping("t9") public String t9() { return httpClient.t9(); } @GetMapping("t10") public List<String> t10() { String s = "http://localhost:9093/test/retrofit/remote/t10/lisi"; return httpClient.t10(s); } }
4.编写拦截器
可是通过编写拦截器来拦截retrofit的请求,在请求中添加一些统一的处理,拦截器需要继承BasePathMatchInterceptor
并重写doIntercept
方法
接口上使用@Intercept
进行标注
package com.yolo.springbootretrofit.config; 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; @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.yolo.springbootretrofit.config; import com.github.lianjiatech.retrofit.spring.boot.annotation.InterceptMark; import com.github.lianjiatech.retrofit.spring.boot.interceptor.BasePathMatchInterceptor; import java.lang.annotation.*; @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; }
accessKeyId
和accessKeySecret
字段值会依据@Sign
注解的accessKeyId()
和accessKeySecret()
值自动注入,如果@Sign
指定的是占位符形式的字符串,则会取配置属性值进行注入。
另外,accessKeyId
和accessKeySecret
字段必须提供setter
方法
package com.yolo.springbootretrofit.config; import com.github.lianjiatech.retrofit.spring.boot.interceptor.BasePathMatchInterceptor; import okhttp3.Request; import okhttp3.Response; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class SignInterceptor extends BasePathMatchInterceptor { private String accessKeyId; private String accessKeySecret; public void setAccessKeyId(String accessKeyId) { this.accessKeyId = accessKeyId; } public void setAccessKeySecret(String accessKeySecret) { this.accessKeySecret = accessKeySecret; } @Override public Response doIntercept(Chain chain) throws IOException { Request request = chain.request(); Request newReq = request.newBuilder() .addHeader("accessKeyId", accessKeyId) .addHeader("accessKeySecret", accessKeySecret) .build(); return chain.proceed(newReq); } }
接口上使用@Sign
@RetrofitClient(baseUrl = "${test.baseUrl}") @Sign(accessKeyId = "${test.accessKeyId}", accessKeySecret = "${test.accessKeySecret}", exclude = {"/api/test/person"}) public interface HttpApi { @GET("person") Result<Person> getPerson(@Query("id") Long id); @POST("savePerson") Result<Person> savePerson(@Body Person person); }
三、连接池管理
默认情况下,所有通过Retrofit
发送的http请求都会使用max-idle-connections=5 keep-alive-second=300
的默认连接池。当然,我们也可以在配置文件中配置多个自定义的连接池,然后通过@RetrofitClient
的poolName
属性来指定使用。比如我们要让某个接口下的请求全部使用poolName=test1
的连接池
1.配置连接池
retrofit: # 连接池配置 pool: test1: max-idle-connections: 3 keep-alive-second: 100 test2: max-idle-connections: 5 keep-alive-second: 50
2.通过@RetrofitClient
的poolName
属性来指定使用的连接池
@RetrofitClient(baseUrl = "${test.baseUrl}", poolName="test1") public interface HttpApi { @GET("person") Result<Person> getPerson(@Query("id") Long id); javascript}
四、日志打印
很多情况下,我们希望将http请求日志记录下来。通过@RetrofitClient
的logLevel
和logStrategy
属性,您可以指定每个接口的日志打印级别以及日志打印策略。
retrofit-spring-boot-starter
支持了5种日志打印级别(ERROR
, WARN
, INFO
, DEBUG
, TRACE
),默认INFO
;支持了4种日志打印策略(NONE
, BASIC
, HEADERS
, BODY
),默认BASIC
。
4种日志打印策略含义如下:
NONE
:No logs.BASIC
:Logs request and response lines.HEADERS
:Logs request and response lines and their respective headers.BODY
:Logs request and response lines and their respective headers and bodies (if present)
retrofit-spring-boot-starter
默认使用了DefaultLoggingInterceptor
执行真正的日志打印功能,其底层就是okhttp
原生的HttpLoggingInterceptor
。
当然,你也可以自定义实现自己的日志打印拦截器,只需要继承BaseLoggingInterceptor
(具体可以参考DefaultLoggingInterceptor
的实现),然后在配置文件中进行相关配置即可。
retrofit: # 日志打印拦截器 logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
五、HTTP异常信息格式化器
当出现http请求异常时,原始的异常信息可能阅读起来并不友好,因此retrofit-spring-boot-starter
提供了HTTP异常信息格式化器,用来美化输出http请求参数,默认使用DefaultHttpExceptionMessageFormatter
进行请求数据格式化。
你也可以进行自定义,只需要继承BaseHttpExceptionMessageFormatter
,再进行相关配置即可
retrofit: # Http异常信息格式化器 http-exception-message-formatter: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultHttpExceptionMessageFormatterandroid
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
精彩评论