시리즈를 척하기 (04) 계약의 소스 코드를 구문 분석
[TOC]
봄 클라우드 시리즈 카탈로그 ( https://www.cnblogs.com/binarylei/p/11563952.html#feign )
에서 이전 글 우리는 어떤 약 분석 Feign
작동하고, 그 Feign
결국은 행 HTTP 요청을 해결하기 위해 척하기, REST 선언 주석 JAX-RS 1/2, 방법의 매개 변수를 적용하는 방법, 요청 헤더, 요청입니다 몸은 무엇입니까? 여기에서 우리는 언급해야 Contract
이 인터페이스를.
1. 척하기 전체적인 흐름 부호화 파라미터
요약 : 처음 두 단계는 Feign
연기 생성 단계, 및 매개 변수 분석 방법은 메타 - 정보 주석. 위상, HTTP 요청의 데이터 포맷으로 부호화 방법 파라미터를 호출 세 단계 후.
public interface Contract {
List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}
요약 : 그 해결의 각 인터페이스의 계약 UserService 인터페이스 방법은 MethodMetadata을 언급, 다음 요청으로 인코딩 RequestTemplate 번호 요청을 사용합니다.
public final class RequestTemplate implements Serializable {
public Request request() {
if (!this.resolved) {
throw new IllegalStateException("template has not been resolved.");
}
return Request.create(this.method, this.url(), this.headers(), this.requestBody());
}
}
요약 : 당신은 클라이언트 # 실행 호출 할 수 있습니다 후 요청으로 인코딩 requestTemplate # 요청은 HTTP 요청을 보냅니다.
public interface Client {
Response execute(Request request, Options options) throws IOException;
}
요약 : 콘크리트 실현 클라이언트 등 HttpURLConnection의 아파치 HttpComponnets, OkHttp3, 인 Netty하고있다. 척하기 방법 메타 정보 분석 파라미터 인코딩 과정이 문서에서는 처음 세 단계에 초점을 맞춘다.
2. 계약 주석 메타 정보 분석 방법
하려면 Feign
기본적으로 Contract.Default
예를 들어 :
첫째, 볼 Feign
주석 (사용 @RequestLine @Headers @Body @Param @HeaderMap @QueryMap
) :
@Headers("Content-Type: application/json")
interface UserService {
@RequestLine("POST /user")
@Headers("Content-Type: application/json")
@Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
void user(@Param("user_name") String name, @Param("password") String password,
@QueryMap Map<String, Object> queryMap,
@HeaderMap Map<String, Object> headerMap, User user);
}
요약 : Contract.BaseContract#parseAndValidatateMetadata
MethodMetadata로 파싱하는 인터페이스 클래스, 메소드, 파라미터에있어서 특수 UserService 해결 방법의 각각을 통과한다.
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
MethodMetadata data = new MethodMetadata();
data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
data.configKey(Feign.configKey(targetType, method));
// 1. 解析类上的注解
if (targetType.getInterfaces().length == 1) {
processAnnotationOnClass(data, targetType.getInterfaces()[0]);
}
processAnnotationOnClass(data, targetType);
// 2. 解析方法上的注解
for (Annotation methodAnnotation : method.getAnnotations()) {
processAnnotationOnMethod(data, methodAnnotation, method);
}
Class<?>[] parameterTypes = method.getParameterTypes();
Type[] genericParameterTypes = method.getGenericParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
int count = parameterAnnotations.length;
for (int i = 0; i < count; i++) {
// isHttpAnnotation 表示参数上是否有注解存在
boolean isHttpAnnotation = false;
if (parameterAnnotations[i] != null) {
isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
}
// 方法参数上不存在注解
if (parameterTypes[i] == URI.class) {
data.urlIndex(i);
} else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
// 已经设置过 @FormParam JAX-RS规范
checkState(data.formParams().isEmpty(),
"Body parameters cannot be used with form parameters.");
// 已经设置过 bodyIndex,如 user(User user1, Person person) ×
checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
data.bodyIndex(i);
data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
}
}
return data;
}
이 방법은 또한 잘 이해되고, 이제 살펴 보자 @RequestLine @Headers @Body @Param @HeaderMap @QueryMap
구체적인 해결 과정이 주석을.
2.1 processAnnotationOnClass
@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> targetType) {
if (targetType.isAnnotationPresent(Headers.class)) {
String[] headersOnType = targetType.getAnnotation(Headers.class).value();
checkState(headersOnType.length > 0, "Headers annotation was empty on type %s.",
targetType.getName());
Map<String, Collection<String>> headers = toMap(headersOnType);
headers.putAll(data.template().headers());
data.template().headers(null); // to clear
data.template().headers(headers);
}
}
요약 : 클래스는 하나의 코멘트가 있습니다 :
- @Headers -.> data.template () 헤더
2.2 processAnnotationOnMethod
protected void processAnnotationOnMethod(
MethodMetadata data, Annotation methodAnnotation, Method method) {
Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
if (annotationType == RequestLine.class) {
String requestLine = RequestLine.class.cast(methodAnnotation).value();
checkState(emptyToNull(requestLine) != null,
"RequestLine annotation was empty on method %s.", method.getName());
Matcher requestLineMatcher = REQUEST_LINE_PATTERN.matcher(requestLine);
if (!requestLineMatcher.find()) {
throw new IllegalStateException(String.format(
"RequestLine annotation didn't start with an HTTP verb on method %s",
method.getName()));
} else {
data.template().method(HttpMethod.valueOf(requestLineMatcher.group(1)));
data.template().uri(requestLineMatcher.group(2));
}
data.template().decodeSlash(RequestLine.class.cast(methodAnnotation).decodeSlash());
data.template()
.collectionFormat(RequestLine.class.cast(methodAnnotation).collectionFormat());
} else if (annotationType == Body.class) {
String body = Body.class.cast(methodAnnotation).value();
checkState(emptyToNull(body) != null, "Body annotation was empty on method %s.",
method.getName());
if (body.indexOf('{') == -1) {
data.template().body(body);
} else {
data.template().bodyTemplate(body);
}
} else if (annotationType == Headers.class) {
String[] headersOnMethod = Headers.class.cast(methodAnnotation).value();
checkState(headersOnMethod.length > 0, "Headers annotation was empty on method %s.",
method.getName());
data.template().headers(toMap(headersOnMethod));
}
}
요약 : 세 가지 방법에이 주석 할 수있다 :
- @RequestLine -.> data.template () 메소드 + data.template () URI.
- @Body -.> data.template () 신체
- @Headers -.> data.template () 헤더
2.3 processAnnotationsOnParameter
protected boolean processAnnotationsOnParameter(
MethodMetadata data, Annotation[] annotations,int paramIndex) {
boolean isHttpAnnotation = false;
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType == Param.class) {
Param paramAnnotation = (Param) annotation;
String name = paramAnnotation.value();
checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.",
paramIndex);
nameParam(data, name, paramIndex);
Class<? extends Param.Expander> expander = paramAnnotation.expander();
if (expander != Param.ToStringExpander.class) {
data.indexToExpanderClass().put(paramIndex, expander);
}
data.indexToEncoded().put(paramIndex, paramAnnotation.encoded());
isHttpAnnotation = true;
// 即不是@Headers和@Body上的参数,只能是formParams了
if (!data.template().hasRequestVariable(name)) {
data.formParams().add(name);
}
} else if (annotationType == QueryMap.class) {
checkState(data.queryMapIndex() == null,
"QueryMap annotation was present on multiple parameters.");
data.queryMapIndex(paramIndex);
data.queryMapEncoded(QueryMap.class.cast(annotation).encoded());
isHttpAnnotation = true;
} else if (annotationType == HeaderMap.class) {
checkState(data.headerMapIndex() == null,
"HeaderMap annotation was present on multiple parameters.");
data.headerMapIndex(paramIndex);
isHttpAnnotation = true;
}
}
return isHttpAnnotation;
}
요약 : 세 개의 매개 변수에이 주석 할 수있다 :
@ Param-> data.indexToName
@ QueryMap-> data.queryMapIndex
@ HeaderMap-> data.headerMapIndex
표 1 : 주석 파싱에게 해당 값 꾀병 척하기 코멘트 MethodMetadata 분석 값 @Headers data.template (). 헤더 @RequestLine data.template (). 방법 + data.template (). URI @몸 data.template (). 본체 파라미터 : data.indexToName @QueryMap data.queryMapIndex @HeaderMap data.headerMapIndex
2.4 MethodMetadata
오랜 시간이 위의 설명을 잘, 그것은 목적은 보호하는 것입니다, 정보 분석 요소 방법입니다 Feign、JAX-RS 1/2、Spring Web MVC
사람들은 그 MethodMetadata 정보가 거기에 다른 차이는 결국, 선언적 주석을 REST?
private String configKey; // 方法签名,类全限名+方法全限名
private transient Type returnType; // 方法返回值类型
private Integer urlIndex; // 方法参数为url时,为 urlIndex
private Integer bodyIndex; // 方法参数没有任务注解,默认为 bodyIndex
private Integer headerMapIndex; // @HeaderMap
private Integer queryMapIndex; // @QueryMap
private boolean queryMapEncoded;
private transient Type bodyType;
private RequestTemplate template = new RequestTemplate(); // 核心
private List<String> formParams = new ArrayList<String>();
private Map<Integer, Collection<String>> indexToName =
new LinkedHashMap<Integer, Collection<String>>();
private Map<Integer, Class<? extends Expander>> indexToExpanderClass =
new LinkedHashMap<Integer, Class<? extends Expander>>();
private Map<Integer, Boolean> indexToEncoded = new LinkedHashMap<Integer, Boolean>();
private transient Map<Integer, Expander> indexToExpander;
요약 : 지금까지 매개 변수 방법 방법에 대한 메서드를 호출 할 MethodMetadata로 해석이는 위안 해상도를 argv를합니다 정보 MethodMetadata에 따라 요청 될 것입니다.
로 분석 3. 요청 매개 변수
예를 BuildTemplateByResolvingArgs합니다.
public RequestTemplate create(Object[] argv) {
RequestTemplate mutable = RequestTemplate.from(metadata.template());
// 1. 解析url参数
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null,
"URI parameter %s was null", urlIndex);
mutable.target(String.valueOf(argv[urlIndex]));
}
// 2. 解析参数argv成对应的对象
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
// 3. @Body中的参数占位符
RequestTemplate template = resolve(argv, mutable, varBuilder);
// 4. @QueryMap
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
Map<String, Object> queryMap = toQueryMap(value);
template = addQueryMapQueryParameters(queryMap, template);
}
// 5. @HeaderMap
if (metadata.headerMapIndex() != null) {
template =
addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
}
return template;
}
요약 : 이 방법 매개 변수가 간단한 후 RequestTemplate에 해결, 그냥 요청에 대한 최종 해결에 요청을 호출합니다. 당신은 요청이 모든 정보를 HTTP 요청을 포함 볼 수 있습니다. 이, 척하기를 분석 인수가 완료되었습니다.
public Request request() {
if (!this.resolved) {
throw new IllegalStateException("template has not been resolved.");
}
return Request.create(this.method, this.url(), this.headers(), this.requestBody());
}
4. 사고 : 척하기 방법 호환 JAX-RS 1/2, 스프링 웹 MVC
확실히 우리는 적응 작업을 완료하는 그들의 계약의 MethodMetadata로 파싱에 해당하는 주석 정보를 실현하기 위해, 추측했다.
jaxrs
기본적으로 지원 척하기, 구현 볼 수 관심이 :feign.jaxrs.JAXRSContract
Spring Web MVC
봄 클라우드 OpenFeign 지원을 제공합니다
매일 조금 기록의 의도. 아마도 내용이 중요하지 않지만 습관이 매우 중요합니다!