简单搭建 Feign 框架
parent
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.17</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
server
@SpringBootApplication
public class AppServer {
public static void main(String[] args) {
SpringApplication.run(AppServer.class);
}
}
@RestController
public class FileController {
@GetMapping("download")
public ResponseEntity<byte[]> download() {
byte[] bytes = FileUtil.readBytes(new File("C:\\Users\\xxx\\Pictures\\企业微信截图_x-x-x-x.png"));
return ResponseEntity.ok(bytes);
}
}
client
@SpringBootApplication
@EnableFeignClients
public class AppClient {
public static void main(String[] args) {
SpringApplication.run(AppClient.class);
}
}
public class R<T> {
private String code;
private String message;
private T data;
}
@RestControllerAdvice
@Component
public class GlobalExceptionResolver {
@ExceptionHandler(Exception.class)
public R<?> exceptionResolver(Exception e) {
R<?> r = new R<>();
r.setMessage("系统错误");
return r;
}
}
@FeignClient(url = "http://127.0.0.1:9999/", name = "test")
public interface FileFeinClient {
@GetMapping("download")
ResponseEntity<byte[]> download();
}
@RestController
public class ClientFileController {
@Autowired
private FileFeinClient fileFeinClient;
@GetMapping("filedownload")
public ResponseEntity<byte[]> download(){
ResponseEntity<byte[]> download = fileFeinClient.download();
try {
System.out.println("Sleep Sleep Sleep Sleep ");
TimeUnit.SECONDS.sleep(5);
System.out.println("Sleep Complete");
} catch (InterruptedException e) {
}
return download;
}
}
操作
postman 调用 client,sleep 期间 cancel。
问题
然后 client 的 Controller 返回值会被 HttpEntityMethodProcessor
处理,在 HttpEntityMethodProcessor#handleReturnValue
写响应体(server 返回的,默认是 content-type: application/octet-stream,还未实际写入)。
咋这个方法的最后 writeWithMessageConverters 时,发现连接断开,于是报错,转而走全局异常处理,全局异常处理的 @RestControllerAdvice
是一个组合注解,含 @ResponseBody
,于是对于返回值的处理走 RequestResponseBodyMethodProcessor
,它处理返回值时写数据走(AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)),发现 ServletServerHttpResponse 已经写了 Content-Type,于是找 MessageConverter(这里就是 R -> application/octet-stream),于是没找到,最终抛出异常 HttpMessageNotWritableException。
isCommit
最终向 Response 写数据时,一旦写过(写缓存,即便没有通过网络发送),会设置标志位 committed 为 true,因此可以使用 HttpServletResponse.isCommitted 判断(除了这种情况,什么时候会 isCommited 且抛出异常)。