说来惭愧,这是我敲代码七年来发布的第一篇帖子,至今认为,写帖子水平很差。还是直接来干的吧,或许对有缘人有帮助。
为什么要写这个呢?主要是用Feign维护起来清爽,干净,一目了然(可以不用加班)。(除此,就是考虑咱们现在不是很多项目都要对接蛮,新的要对接,老的也要对接,所以感觉这篇帖子还是很有用)
强烈推荐:https://github.com/OpenFeign/feign
<!-- <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId>
<version>11.0</version> </dependency> <dependency> <groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId> <version>11.0</version> </dependency>
<dependency> <groupId>io.github.lukehutch</groupId> <artifactId>fast-classpath-scanner</artifactId>
<version>2.18.1</version> </dependency> -->
<!-- <dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-core</artifactId>
<version>8.18.0</version> <scope>runtime</scope> </dependency> <dependency>
<groupId>com.netflix.feign</groupId> <artifactId>feign-jackson</artifactId>
<version>8.18.0</version> </dependency> <dependency> <groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId> <version>8.18.0</version> </dependency> -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>11.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>11.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.cdi</groupId>
<artifactId>feign-cdi</artifactId>
<version>0.1.6</version>
</dependency>
<!-- <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId>
<version>11.0</version> </dependency> -->
<!-- <dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency> -->
创建使用Feign的注解类:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignApi {
/**
* 调用的服务地址
* @return
*/
String serviceUrl();
}
创建FeiginClientRegister类:
import com.*.aap.utils.ConfigUtils;
import com.*.fy.znspgl.dme.t3c.annotation.FeignApi;
import com.*.fy.znspgl.dme.t3c.cons.Consts;
import feign.Feign;
import feign.Request;
import feign.RequestInterceptor;
import feign.Retryer;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.querymap.BeanQueryMapEncoder;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class FeignClientRegister implements BeanFactoryPostProcessor {
// 扫描的接口路径
private String scanPath = "com.**.fy.znspgl.dme.t3c.repository";
private static final String[] TRANS_HEADER_ARR = new String[] { "Authorization", "appToken","x-openapi-app-id" };
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
List<String> classes = scan(scanPath);
if (classes == null) {
return;
}
System.out.println(classes);
Feign.Builder builder = getFeignBuilder();
if (classes.size() > 0) {
for (String claz : classes) {
Class<?> targetClass = null;
try {
targetClass = Class.forName(claz);
String url = targetClass.getAnnotation(FeignApi.class).serviceUrl();
url = getRealUrl(url);
if (url.indexOf("http://") != 0) {
url = "http://" + url;
}
Object target = builder.target(targetClass, url);
beanFactory.registerSingleton(targetClass.getName(), target);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
}
public String getRealUrl(String value) {
//String corp = ZnspglSessionUtils.getCurrentCorpId();
String corp = System.getProperty("corp");
if (StringUtils.isBlank(corp)) {
corp = System.getenv("corp");
}
String url = null;
Pattern p = Pattern.compile(Consts.SPRING_EL_EXPRESSION);
// 匹配】
Matcher matcher = p.matcher(value);
// 处理匹配到的值
while (matcher.find()) {
System.out.println(StringUtils.substringBetween(matcher.group(0), Consts.SPRING_EL_EXPRESSION_START,
Consts.SPRING_EL_EXPRESSION_END));
value = ConfigUtils.getSysConfig(StringUtils.substringBetween(matcher.group(0),
Consts.SPRING_EL_EXPRESSION_START, Consts.SPRING_EL_EXPRESSION_END), corp);
}
return value;
}
public Feign.Builder getFeignBuilder() {
Feign.Builder builder = Feign.builder().encoder(new JacksonEncoder()).queryMapEncoder(new BeanQueryMapEncoder())
/* .encoder(new FormEncoder(new JacksonEncoder())) */
.client(new ApacheHttpClient()).decoder(new JacksonDecoder()).contract(new SpringContract())
//.requestInterceptor(headerInterceptor())
.options(new Request.Options(10000, 35000))
.retryer(new Retryer.Default(5000, 5000, 3));
return builder;
}
public RequestInterceptor headerInterceptor() {
//复用最高
return requestTemplate -> {
String corp = System.getProperty("corp");
if (StringUtils.isBlank(corp)) {
corp = System.getenv("corp");
}
requestTemplate.header("x-openapi-app-id", ConfigUtils.getSysConfig("t3c.openapi.app.id",corp));
requestTemplate.header("appToken", ConfigUtils.getSysConfig("t3c.apptoken",corp));
};
}
public List<String> scan(String path) {
ScanResult result = new FastClasspathScanner(path)
.matchClassesWithAnnotation(FeignApi.class, (Class<?> aClass) -> {
}).scan();
if (result != null) {
return result.getNamesOfAllInterfaceClasses();
}
return null;
}
}
其实到这一步咱们就可以正常使用了,但是由于更深层的兼容,你可能还需要下面这些东西:
下面是为了兼容requestbody标签和不支持getmapping等标签而做的,根据项目实际情况注解,这玩意还是很爽,阅读源码有助于提高你对一些请求的实现的理解:SpringContract
/**
* Copyright 2012-2020 The Feign Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import com.**.fy.znspgl.dme.t3c.annotation.SpringQueryMap;
import feign.DeclarativeContract;
import feign.MethodMetadata;
import feign.Request;
@Component
public class SpringContract extends DeclarativeContract {
static final String ACCEPT = "Accept";
static final String CONTENT_TYPE = "Content-Type";
public SpringContract() {
registerClassAnnotation(RequestMapping.class, (requestMapping, data) -> {
appendMappings(data, requestMapping.value());
if (requestMapping.method().length == 1)
data.template().method(Request.HttpMethod.valueOf(requestMapping.method()[0].name()));
handleProducesAnnotation(data, requestMapping.produces());
handleConsumesAnnotation(data, requestMapping.consumes());
});
registerMethodAnnotation(RequestMapping.class, (requestMapping, data) -> {
String[] mappings = requestMapping.value();
appendMappings(data, mappings);
if (requestMapping.method().length == 1)
data.template().method(Request.HttpMethod.valueOf(requestMapping.method()[0].name()));
});/*
* registerMethodAnnotation(GetMapping.class, (mapping, data) -> {
* appendMappings(data, mapping.value());
* data.template().method(Request.HttpMethod.GET);
* handleProducesAnnotation(data, mapping.produces());
* handleConsumesAnnotation(data, mapping.consumes()); });
*
* registerMethodAnnotation(PostMapping.class, (mapping, data) -> {
* appendMappings(data, mapping.value());
* data.template().method(Request.HttpMethod.POST);
* handleProducesAnnotation(data, mapping.produces());
* handleConsumesAnnotation(data, mapping.consumes()); });
*
* registerMethodAnnotation(PutMapping.class, (mapping, data) -> {
* appendMappings(data, mapping.value());
* data.template().method(Request.HttpMethod.PUT);
* handleProducesAnnotation(data, mapping.produces());
* handleConsumesAnnotation(data, mapping.consumes()); });
*
* registerMethodAnnotation(DeleteMapping.class, (mapping, data) -> {
* appendMappings(data, mapping.value());
* data.template().method(Request.HttpMethod.DELETE);
* handleProducesAnnotation(data, mapping.produces());
* handleConsumesAnnotation(data, mapping.consumes()); });
*
* registerMethodAnnotation(PatchMapping.class, (mapping, data) -> {
* appendMappings(data, mapping.value());
* data.template().method(Request.HttpMethod.PATCH);
* handleProducesAnnotation(data, mapping.produces());
* handleConsumesAnnotation(data, mapping.consumes()); });
*/
registerMethodAnnotation(ResponseBody.class, (body, data) -> {
handleConsumesAnnotation(data, "application/json");
});
registerMethodAnnotation(ExceptionHandler.class, (ann, data) -> {
data.ignoreMethod();
});
registerParameterAnnotation(PathVariable.class, (parameterAnnotation, data, paramIndex) -> {
String name = PathVariable.class.cast(parameterAnnotation).value();
nameParam(data, name, paramIndex);
});
registerParameterAnnotation(RequestBody.class, (body, data, paramIndex) -> {
handleProducesAnnotation(data, "application/json");
});
registerParameterAnnotation(RequestParam.class, (parameterAnnotation, data, paramIndex) -> {
String name = RequestParam.class.cast(parameterAnnotation).value();
Collection<String> query = addTemplatedParam(data.template().queries().get(name), name);
data.template().query(name, query);
nameParam(data, name, paramIndex);
});
registerParameterAnnotation(SpringQueryMap.class, (parameterAnnotation, data, paramIndex) -> {
MethodMetadata metadata = data;
if (metadata.queryMapIndex() == null) {
metadata.queryMapIndex(paramIndex);
metadata.queryMapEncoded(((SpringQueryMap)SpringQueryMap.class.cast(parameterAnnotation)).encoded());
}
});
}
private void appendMappings(MethodMetadata data, String[] mappings) {
for (int i = 0; i < mappings.length; i++) {
String methodAnnotationValue = mappings[i];
if (!methodAnnotationValue.startsWith("/") && !data.template().url().endsWith("/")) {
methodAnnotationValue = "/" + methodAnnotationValue;
}
if (data.template().url().endsWith("/") && methodAnnotationValue.startsWith("/")) {
methodAnnotationValue = methodAnnotationValue.substring(1);
}
data.template().uri(data.template().url() + methodAnnotationValue);
}}
private void handleProducesAnnotation(MethodMetadata data, String... produces) {
if (produces.length == 0)
return;
data.template().removeHeader(ACCEPT); // remove any previous produces
data.template().header(ACCEPT, produces[0]);
}
private void handleConsumesAnnotation(MethodMetadata data, String... consumes) {
if (consumes.length == 0)
return;
data.template().removeHeader(CONTENT_TYPE); // remove any previous consumes
data.template().header(CONTENT_TYPE, consumes[0]);
}
protected Collection<String> addTemplatedParam(Collection<String> possiblyNull, String name) {
if (possiblyNull == null) {
possiblyNull = new ArrayList<String>();
}
possiblyNull.add(String.format("{%s}", name));
return possiblyNull;
}
}
创建使用SpringQueryMap的注解类:这个注解和openfeign里面的SpringQueryMap实现的效果一样
//
//Source code recreated from a .class file by IntelliJ IDEA
//(powered by Fernflower decompiler)
//
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface SpringQueryMap {
@AliasFor("encoded")
boolean value() default false;
@AliasFor("value")
boolean encoded() default false;
}
下面是指定feigin指定用哪个框架来实现数据的收发ApacheHttpClient或者别的
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.Configurable;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import feign.Client;
import feign.Request;
import feign.Response;
import feign.Util;
import static feign.Util.UTF_8;
/**
* This module directs Feign's http requests to Apache's
* <a href="https://hc.apache.org/httpcomponents-client-ga/">HttpClient</a>. Ex.
*
* <pre>
* GitHub github = Feign.builder().client(new ApacheHttpClient()).target(GitHub.class,
* "https://api.github.com");
*/
/*
* Based on Square, Inc's Retrofit ApacheClient implementation
*/
public final class ApacheHttpClient implements Client {
private static final String ACCEPT_HEADER_NAME = "Accept";
private static final String URL_PARMA = "?";
private final HttpClient client;
public ApacheHttpClient() {
this(HttpClientBuilder.create().build());
}
public ApacheHttpClient(HttpClient client) {
this.client = client;
}
@Override
public Response execute(Request request, Request.Options options) throws IOException {
HttpUriRequest httpUriRequest;
try {
httpUriRequest = toHttpUriRequest(request, options);
} catch (URISyntaxException e) {
throw new IOException("URL '" + request.url() + "' couldn't be parsed into a URI", e);
}
HttpResponse httpResponse = client.execute(httpUriRequest);
return toFeignResponse(httpResponse, request);
}
HttpUriRequest toHttpUriRequest(Request request, Request.Options options)
throws URISyntaxException {
RequestBuilder requestBuilder = RequestBuilder.create(request.httpMethod().name());
// per request timeouts
RequestConfig requestConfig =
(client instanceof Configurable ? RequestConfig.copy(((Configurable) client).getConfig())
: RequestConfig.custom())
.setConnectTimeout(options.connectTimeoutMillis())
.setSocketTimeout(options.readTimeoutMillis())
.build();
requestBuilder.setConfig(requestConfig);
URI uri = new URIBuilder(request.url()).build();
requestBuilder.setUri(uri.getScheme() + "://" + uri.getAuthority() + uri.getRawPath());
// request query params
List<NameValuePair> queryParams =
URLEncodedUtils.parse(StringUtils.substringAfterLast(uri.toString(), URL_PARMA), requestBuilder.getCharset());
for (NameValuePair queryParam : queryParams) {
requestBuilder.addParameter(queryParam);
}
// request headers
boolean hasAcceptHeader = false;
for (Map.Entry<String, Collection<String>> headerEntry : request.headers().entrySet()) {
String headerName = headerEntry.getKey();
if (headerName.equalsIgnoreCase(ACCEPT_HEADER_NAME)) {
hasAcceptHeader = true;
}
if (headerName.equalsIgnoreCase(Util.CONTENT_LENGTH)) {
// The 'Content-Length' header is always set by the Apache client and it
// doesn't like us to set it as well.
continue;
}
for (String headerValue : headerEntry.getValue()) {
requestBuilder.addHeader(headerName, headerValue);
}
}
// some servers choke on the default accept string, so we'll set it to anything
if (!hasAcceptHeader) {
requestBuilder.addHeader(ACCEPT_HEADER_NAME, "*/*");
}
// request body
if (request.body() != null) {
HttpEntity entity = null;
if (request.charset() != null) {
ContentType contentType = getContentType(request);
String content = new String(request.body(), request.charset());
entity = new StringEntity(content, contentType);
} else {
entity = new ByteArrayEntity(request.body());
}
requestBuilder.setEntity(entity);
} else {
requestBuilder.setEntity(new ByteArrayEntity(new byte[0]));
}
return requestBuilder.build();
}
private ContentType getContentType(Request request) {
ContentType contentType = null;
for (Map.Entry<String, Collection<String>> entry : request.headers().entrySet())
if (entry.getKey().equalsIgnoreCase("Content-Type")) {
Collection<String> values = entry.getValue();
if (values != null && !values.isEmpty()) {
contentType = ContentType.parse(values.iterator().next());
if (contentType.getCharset() == null) {
contentType = contentType.withCharset(request.charset());
}
break;
}
}
return contentType;
}
Response toFeignResponse(HttpResponse httpResponse, Request request) throws IOException {
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
String reason = statusLine.getReasonPhrase();
Map<String, Collection<String>> headers = new HashMap<String, Collection<String>>();
for (Header header : httpResponse.getAllHeaders()) {
String name = header.getName();
String value = header.getValue();
Collection<String> headerValues = headers.get(name);
if (headerValues == null) {
headerValues = new ArrayList<String>();
headers.put(name, headerValues);
}
headerValues.add(value);
}
return Response.builder()
.status(statusCode)
.reason(reason)
.headers(headers)
.request(request)
.body(toFeignBody(httpResponse))
.build();
}
Response.Body toFeignBody(HttpResponse httpResponse) {
final HttpEntity entity = httpResponse.getEntity();
if (entity == null) {
return null;
}
return new Response.Body() {
@Override
public Integer length() {
return entity.getContentLength() >= 0 && entity.getContentLength() <= Integer.MAX_VALUE
? (int) entity.getContentLength()
: null;
}
@Override
public boolean isRepeatable() {
return entity.isRepeatable();
}
@Override
public InputStream asInputStream() throws IOException {
return entity.getContent();
}
@SuppressWarnings("deprecation")
@Override
public Reader asReader() throws IOException {
return new InputStreamReader(asInputStream(), UTF_8);
}
@Override
public Reader asReader(Charset charset) throws IOException {
Util.checkNotNull(charset, "charset should not be null");
return new InputStreamReader(asInputStream(), charset);
}
@Override
public void close() throws IOException {
EntityUtils.consume(entity);
}
};
}
}
一个使用的样例:
import java.util.List;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* T3cGlajRepository.
*
* @author zhujunhan
* @version 1.0
* @date 2020/11/19
*/
@FeignApi(serviceUrl = "${t3c.glaj.url}")
public interface T3cGlajRepository {
/**
* selectGlaj.
*
* @param ajbh 案件编号
* @param jbfy 经办法院
* @return List
*/
@RequestMapping(method = RequestMethod.GET,value="/api/v1/aj/{ajbh}/ajxx")
List<GlajDTO> selectGlaj(@PathVariable("ajbh") String ajbh, @RequestParam("jbfy") String jbfy);
}
使用起来还是很方便安逸的,结帖。如果想了解更多,再次强烈邀请你去读feign的源码https://github.com/OpenFeign/feign