老项目整合Feign,清爽的实现项目与项目之间的接口调用【简易】

说来惭愧,这是我敲代码七年来发布的第一篇帖子,至今认为,写帖子水平很差。还是直接来干的吧,或许对有缘人有帮助。
为什么要写这个呢?主要是用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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值