1. 程式人生 > 其它 >SpringCloud升級之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(2)

SpringCloud升級之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(2)

本系列程式碼地址:https://github.com/JoJoTec/spring-cloud-parent

在使用雲原生的很多微服務中,比較小規模的可能直接依靠雲服務中的負載均衡器進行內部域名與服務對映,通過健康檢查介面判斷例項健康狀態,然後直接使用 OpenFeign 生成對應域名的 Feign Client。Spring Cloud 生態中,對 OpenFeign 進行了封裝,其中的 Feign Client 的各個元件,也是做了一定的定製化,可以實現在 OpenFeign Client 中整合服務發現與負載均衡。在此基礎上,我們還結合了 Resilience4J 元件,實現了微服務例項級別的執行緒隔離,微服務方法級別的斷路器以及重試。

我們先來分析下 Spring Cloud OpenFeign

Spring Cloud OpenFeign 解析

HTTP 編碼解碼器,與 spring-boot 中的編碼解碼器相結合

Spring Cloud 中的任何元件,都是基於 Spring Boot 而實現的。由於 Spring Boot 中已經有了 HTTP 編碼解碼器,就可以不用單獨給 OpenFeign 單獨再實現 HTTP 編碼解碼器了,而是考慮將 OpenFeign 的編碼解碼器介面用 Spring Boot 的 HTTP 編碼解碼器實現。

在 FeignClientsConfiguration 中,提供了預設的實現:

//由於初始化順序以及 NamedContextFactory 的 Configuration 初始化的原因,這裡需要注入 ObjectFactory 而不是直接注入 HttpMessageConverters 防止找不到 Bean
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;

@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() { 
	return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}

@Bean
@ConditionalOnMissingBean
//我們這裡忽略 Pageable 類存在的情況
//針對 Spring Data 的分頁包裝 Pageable 的相容實現也比較簡單,這裡忽略
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider) {
	return springEncoder(formWriterProvider, encoderProperties);
}

private Encoder springEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider,
	FeignEncoderProperties encoderProperties) {
    AbstractFormWriter formWriter = formWriterProvider.getIfAvailable();
    
    if (formWriter != null) {
    	return new SpringEncoder(new SpringPojoFormEncoder(formWriter), this.messageConverters, encoderProperties);
    }
    else {
    	return new SpringEncoder(new SpringFormEncoder(), this.messageConverters, encoderProperties);
    }
}

基於 SpringDecoder 的解碼器

通過原始碼可以看出,預設的 Decoder 是經過幾層包裝的 Decoder,分別包括:

  • OptionalDecoder:用於處理 Java JDK 中的 Optional 封裝類的解碼器
  • ResponseEntityDecoder:用於處理 spring-web 中對於請求響應封裝類 HttpEntity 的解碼器
  • SpringDecoder:使用 Spring 的解碼器實現的 Feign 的 Decoder

傳入 SpringDecoder 的 HttpMessageConverters 物件,是 spring-web 的所有 HttpMessageConverter 集合。HttpMessageConverter 是 spring-web 中對於 HTTP 請求和響應的 body 進行編碼解碼的工具。其介面結構是:

public interface HttpMessageConverter<T> {
    //判斷 clazz 型別是否可以被當前 HttpMessageConverter 所讀取
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    //判斷 clazz 型別是否可以被當前 HttpMessageConverter 寫
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    //獲取所有支援的 MediaType
	List<MediaType> getSupportedMediaTypes();
    //通過 clazz 型別獲取該 HttpMessageConverter 支援的 MediaType
    //預設實現是,如果該型別可以被當前 HttpMessageConverter 讀或者寫,那麼返回 getSupportedMediaTypes 所有支援的 MediaType
	default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
		return (canRead(clazz, null) || canWrite(clazz, null) ?
				getSupportedMediaTypes() : Collections.emptyList());
	}

    //從 inputMessage 中讀取並解析出 clazz 型別的物件,當請求的 Content-Type 為支援的 MediaType 時
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

    //將物件 t 序列化寫入 HttpOutputMessage,當請求的 accept 為支援的 MediaType 時
	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

spring boot 內建了很多 HttpMessageConverter,我們也可以實現自己的 HttpMessageConverter,去實現我們自定義 MediaType,例如我們這裡定義一個 :

public class CustomizedHttpMessageConverter implements HttpMessageConverter<Student> {
	@Override
	public boolean canRead(Class<?> clazz, MediaType mediaType) {
		return clazz.equals(Student.class);
	}

	@Override
	public boolean canWrite(Class<?> clazz, MediaType mediaType) {
		return clazz.equals(Student.class);
	}
	
	public static class StudentMediaType extends MediaType {
		public StudentMediaType() {
			super("application", "student", StandardCharsets.UTF_8);
		}
	}

	@Override
	public List<MediaType> getSupportedMediaTypes() {
		return List.of(new StudentMediaType());
	}

	@Override
	public Student read(Class<? extends Student> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
		String temp = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
		String[] split = temp.split(",");
		return new Student(
				Long.parseLong(split[0]),
				split[1]);
	}

	@Override
	public void write(Student student, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
		outputMessage.getBody().write((student.getId() + "," + student.getName()).getBytes(StandardCharsets.UTF_8));
	}
}

之後,與前面類似,將其配置到 spring boot 相容 MVC 配置中:

@Configuration(proxyBeanMethods = false)
public class TestConfig implements WebMvcConfigurer {
	@Override
	public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
		converters.add(new CustomizedHttpMessageConverter());
	}
}

編寫 Controller,測試:

@RestController
@RequestMapping("/test")
public class TestController {
	@PostMapping("/post-to-student")
	public Student postToStudent(@RequestBody Student student) {
		return student;
	}
}

使用 postman 類似的工具,指定 HTTP 請求頭:

Content-Type:application/student
Accept:application/student

Body 是:

1,zhx

請求後,就會走到 CustomizedHttpMessageConverter 的 read 解析成 Student 物件,之後響應的 student 也會被 CustomizedHttpMessageConverter 的 write 寫入響應 Body

由此可見,由於 SpringEncoder 的存在,我們可以複用 Spring 內建的 HttpMessageConverter,同時也能擴充套件自定義我們自己的 HttpMessageConverter,非常方便

ResponseEntityDecoder 的程式碼比較簡單,實現的效果就是解碼的時候,忽略 HttpEntity 這個 spring-web 對於 HTTP 響應的包裝類:

@Override
public Object decode(final Response response, Type type) throws IOException, FeignException {
    //是否是帶有形參的 HttpEntity<?> 
	if (isParameterizeHttpEntity(type)) {
	    //將形參型別取出
		type = ((ParameterizedType) type).getActualTypeArguments()[0];
		//使用形參型別,解析 response
		Object decodedObject = this.decoder.decode(response, type);
        //填充 HttpEntity 其中的返回物件,狀態碼等資訊
		return createResponse(decodedObject, response);
	}
	else if (isHttpEntity(type)) {
	    //空形參,代表沒有 body 或者忽略 body,僅填充 HttpEntity 狀態碼等資訊
		return createResponse(null, response);
	}
	else {
	    //如果不是 HttpEntity,直接解碼
		return this.decoder.decode(response, type);
	}
}

這個其實為了和 RestTemplate 的響應相容,RestTemplate 可以返回 HttpEntity,但是底層 HTTP 請求返回的 body 其實並沒有包裝這個型別。

同理,JDK 中的 Optional 包裝類,也需要做同樣的事情,這個就是通過 OptionalDecoder 實現的。

基於 SpringEncoder 的編碼器

SpringEncoder 編碼器也非常簡單,也是基於 spring 中的 HttpMessageConverter。這裡我們就不再贅述。

微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer