HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 Http 协议的客户端编程工具包,并且它支持 HTTP 协议最新版本和建议。
HttpClient 相比传统 JDK 自带的 URLConnection,提升了易用性和灵活性,使客户端发送 HTTP 请求变得容易,提高了开发的效率。
一个处理网络请求的开源项目,是安卓端最火的轻量级框架,由 Square 公司贡献,用于替代 HttpUrlConnection 和 Apache HttpClient。OkHttp 拥有简洁的 API、高效的性能,并支持多种协议(HTTP/2 和 SPDY)。
HttpURLConnection 是Java 的标准类,它继承自 URLConnection,可用于向指定网站发送 GET 请求、POST 请求。HttpURLConnection 使用比较复杂,不像 HttpClient 那样容易使用。
RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 HTTP 服务的方法,能够大大提高客户端的编写效率。
Feign 是一个声明式的 REST 客户端,它能让 REST 调用更加简单。Feign 供了 HTTP 请求的模板,通过编写简单的接口和插入注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。
而 Feign 则会完全代理 HTTP 请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。
Spring Cloud 对 Feign 进行了封装,使其支持 SpringMVC 标准注解和 HttpMessageConverters。Feign 可以与 Eureka 和 Ribbon 组合使用以支持负载均衡。
1 引入依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>3.1.0</version></dependency>
2 通过@EnableFeignClients开启 feign 功能
@EnableFeignClients@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[] args){SpringApplication.run(Application.class, args);}}
3 编写 feign接口进行远程调用
//${}可以应用application.yml内的配置,path会拼接到最后面@FeignClient(value="feignInterface",url="${test.url}/test",path="/service",configuration=GlobalFeignConfig.class)publicinterfaceFeignInterface{//请求 ${test.url}/test/service/hello 接口@GetMapping("/hello")publicStringgetNameByParam(@RequestParamString userName);}
@FeignClient内的configuration可以对 feign请求进行先关配置
publicclassGlobalFeignConfigimplementsRequestInterceptor{@BeanpublicOkHttpInterceptorokHttpInterceptor(){returnnewOkHttpInterceptor();}@Beanpublicokhttp3.OkHttpClientokHttpClient(){returnnewokhttp3.OkHttpClient.Builder().readTimeout(60,TimeUnit.SECONDS).connectTimeout(60,TimeUnit.SECONDS).writeTimeout(120,TimeUnit.SECONDS).connectionPool(newConnectionPool()).addInterceptor(okHttpInterceptor()).build();}@BeanLogger.LevelfeignLoggerLevel(){returnLogger.Level.FULL;}@BeanpublicDecoderfeignDecoder(){returnnewResponseEntityDecoder(newSpringDecoder(feignHttpMessageConverter()));}@BeanpublicEncoderfeignEncoder(){returnnewSpringEncoder(feignHttpMessageConverter());}@BeanpublicErrorDecodererrorDecoder(){returnnewErrorDecoder.Default();}publicObjectFactory<HttpMessageConverters>feignHttpMessageConverter(){finalHttpMessageConverters httpMessageConverters=newHttpMessageConverters(newPhpMappingJackson2HttpMessageConverter());return()-> httpMessageConverters;}classPhpMappingJackson2HttpMessageConverterextendsMappingJackson2HttpMessageConverter{PhpMappingJackson2HttpMessageConverter(){List<MediaType> mediaTypes=newArrayList<>(); mediaTypes.add(MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE+";charset=UTF-8"));//关键setSupportedMediaTypes(mediaTypes);ObjectMapper objectMapper=newObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false); objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); objectMapper.setDateFormat(newSimpleDateFormat("yyyy-MM-dd HH:mm:ss"));setObjectMapper(objectMapper);}}/** * 如果需要basic认证 */// @Bean// public BasicAuthenticationInterceptor basicAuthenticationInterceptor(){// return new BasicAuthenticationInterceptor();// }}
OkHttpInterceptor: 拦截器可以设置请求头等信息
@Slf4jpublicclassOkHttpInterceptorimplementsHandlerInterceptor,Interceptor{@OverridepublicResponseintercept(Chain chain)throwsIOException{ log.debug("进入okhttp拦截器");Request request= chain.request();Request.Builder builder= request.newBuilder();//加上租户标识if(StrUtil.isNotBlank(SessionValueHolder.getCompanyCode())){ builder.header(Constants.COMPANY_CODE,SessionValueHolder.getCompanyCode());}//加上tokenif(StrUtil.isNotBlank(SessionValueHolder.getAuthorization())){ builder.header(Constants.AUTHORIZATION_KEY,SessionValueHolder.getAuthorization());}//加上feign调用标识 builder.header(Constants.FEGIN_FLAG,"1");return chain.proceed(builder.build());}}
feign 配置文件相关配置
# 链接超时时间 feignName写自己feign接口的类名 feign.client.config.feignName.connectTimeout=5000 # 读取超时时间 feign.client.config.feignName.readTimeout=5000 # 日志等级 feign.client.config.feignName.loggerLevel=full # 重试 feign.client.config.feignName.retryer=com.example.SimpleRetryer # 拦截器 feign.client.config.feignName.requestInterceptors[0]=com.example.FooRequestInterceptor feign.client.config.feignName.requestInterceptors[1]=com.example.BarRequestInterceptor # 编码器 feign.client.config.feignName.encoder=com.example.SimpleEncoder # 解码器 feign.client.config.feignName.decoder=com.example.SimpleDecoder # 契约 feign.client.config.feignName.contract=com.example.SimpleContract #开启GZIP压缩,请求数据量很大的接口提高性能 #对请求数据进行压缩 feign.compression.request.enabled=true #对响应数据进行压缩 feign.compression.response.enabled=true #指定 请求方式 进行压缩 feign.compression.request.mime-types=text/xml,application/xml,application/json #设置最小压缩大小,只有超过了这个大小才会进行压缩 feign.compression.request.min-request-size=2048
通过Feign调用我们实现申明的接口进行测试:
Controller
@RestController@RequestMapping("service")publicclassServiceController{//请求参数@GetMapping("/param")publicStringgetNameByParam(String userName){return userName;}//请求体@PostMapping("/body")publicStringgetNameByBody(@RequestBodyString userName){return userName;}@GetMapping("/timeout/5000")publicStringtimeout(@RequestParam(required=false,defaultValue="5000")long millis)throwsException{Thread.sleep( millis);return"success";}//单文件@PostMapping(value="/file",consumes=MediaType.MULTIPART_FORM_DATA_VALUE)publicStringfile(@RequestPart("file2")MultipartFile multipartFile){if( multipartFile==null)returnnull;return multipartFile.getName();}//多文件@PostMapping("/files")publicIterable<String>files(MultipartRequest multipartRequest){if( multipartRequest==null)returnnull;List<String> fileNameArray=newArrayList();if( multipartRequestinstanceofStandardMultipartHttpServletRequest){StandardMultipartHttpServletRequest request=(StandardMultipartHttpServletRequest) multipartRequest;List<String> fileNameList= request.getMultiFileMap().values().stream().flatMap(o-> o.stream()).map(MultipartFile::getOriginalFilename).collect(Collectors.toList());return fileNameList;}Iterator<String> fileNames= multipartRequest.getFileNames();while(fileNames.hasNext()){String fileName= fileNames.next(); fileNameArray.add( multipartRequest.getFile(fileName).getOriginalFilename());}return fileNameArray;}}
@GetMapping("/param")publicStringgetNameByParam(@RequestParamString userName);
Post请求传递请求头
@PostMapping("/body")publicStringgetNameByBody(@RequestBodyString userName);
路径参数
@GetMapping("/timeout/{millis}")publicStringtimeout(@PathVariablelong millis);
文件上传
//必须设置MediaType.MULTIPART_FORM_DATA_VALUE 使用 @RequestPart接收form格式的文件数据@PostMapping(value="/file",consumes=MediaType.MULTIPART_FORM_DATA_VALUE)publicStringfile(@RequestPart("file2")MultipartFile multipartFile);
多文件上传
@PostMapping(value="/files",consumes=MediaType.MULTIPART_FORM_DATA_VALUE)publicIterable<String>files(@RequestPart("file2")Collection<MultipartFile> file);