文档章节

教你如何在SpringMVC项目中单独使用Feign组件(含源码分析)

傲娇字符
 傲娇字符
发布于 05/30 09:55
字数 2393
阅读 505
收藏 0

需求

在项目中,经常有基于Restful格式的接口需要调用,特别是远程调用。做法有多种,例如:自己手写http请求接口、使用Spring的RestTemplate进行远程调用等。得益于SpringCloud组件的Feign组件,有了一种易于上手,忽略请求细节的选择方案。

我们当前有很多应用是基于SpringMVC开发的,而目前线上大部分集成Feign的解决方案都是基于SpringCloud,至少基于SpringBoot的场景。(参考网上的案例,自己整理了可行方案)

源码分析

【源码】SynchronousMethodHandler、Client类(含解释)

每次通过业务代码调用接口的时候,都会调用:

/*
 * Copyright 2014 Netflix, Inc.
 *
 * 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.
 */
package feign;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;

import feign.InvocationHandlerFactory.MethodHandler;
import feign.Request.Options;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;

import static feign.FeignException.errorExecuting;
import static feign.FeignException.errorReading;
import static feign.Util.checkNotNull;
import static feign.Util.ensureClosed;

final class SynchronousMethodHandler implements MethodHandler {

  private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L;

  private final MethodMetadata metadata;
  private final Target<?> target;
  private final Client client;
  private final Retryer retryer;
  private final List<RequestInterceptor> requestInterceptors;
  private final Logger logger;
  private final Logger.Level logLevel;
  private final RequestTemplate.Factory buildTemplateFromArgs;
  private final Options options;
  private final Decoder decoder;
  private final ErrorDecoder errorDecoder;
  private final boolean decode404;

//创建实例,同时根据容器配置的参数进行加载,此处可以在Register(后面有代码)中个性化配置
  private SynchronousMethodHandler(Target<?> target, Client client, Retryer retryer,
                                   List<RequestInterceptor> requestInterceptors, Logger logger,
                                   Logger.Level logLevel, MethodMetadata metadata,
                                   RequestTemplate.Factory buildTemplateFromArgs, Options options,
                                   Decoder decoder, ErrorDecoder errorDecoder, boolean decode404) {
    this.target = checkNotNull(target, "target");
    this.client = checkNotNull(client, "client for %s", target);
    this.retryer = checkNotNull(retryer, "retryer for %s", target);
    this.requestInterceptors =
        checkNotNull(requestInterceptors, "requestInterceptors for %s", target);
    this.logger = checkNotNull(logger, "logger for %s", target);
    this.logLevel = checkNotNull(logLevel, "logLevel for %s", target);
    this.metadata = checkNotNull(metadata, "metadata for %s", target);
    this.buildTemplateFromArgs = checkNotNull(buildTemplateFromArgs, "metadata for %s", target);
    this.options = checkNotNull(options, "options for %s", target);
    this.errorDecoder = checkNotNull(errorDecoder, "errorDecoder for %s", target);
    this.decoder = checkNotNull(decoder, "decoder for %s", target);
    this.decode404 = decode404;
  }

//方法调用时执行的代码,其中executeAndDecode是最重要的,就是基于http的请求以及数据格式转码
  @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

//源码请求发送和接收处理类
  Object executeAndDecode(RequestTemplate template) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options);
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
                response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return Response.create(response.status(), response.reason(), response.headers(), bodyData);
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          return decode(response);
        }
      } else if (decode404 && response.status() == 404) {
        return decoder.decode(response, metadata.returnType());
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }

  long elapsedTime(long start) {
    return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
  }

  Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    return target.apply(new RequestTemplate(template));
  }

  Object decode(Response response) throws Throwable {
    try {
      return decoder.decode(response, metadata.returnType());
    } catch (FeignException e) {
      throw e;
    } catch (RuntimeException e) {
      throw new DecodeException(e.getMessage(), e);
    }
  }

  static class Factory {

    private final Client client;
    private final Retryer retryer;
    private final List<RequestInterceptor> requestInterceptors;
    private final Logger logger;
    private final Logger.Level logLevel;
    private final boolean decode404;

    Factory(Client client, Retryer retryer, List<RequestInterceptor> requestInterceptors,
            Logger logger, Logger.Level logLevel, boolean decode404) {
      this.client = checkNotNull(client, "client");
      this.retryer = checkNotNull(retryer, "retryer");
      this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors");
      this.logger = checkNotNull(logger, "logger");
      this.logLevel = checkNotNull(logLevel, "logLevel");
      this.decode404 = decode404;
    }

    public MethodHandler create(Target<?> target, MethodMetadata md,
                                RequestTemplate.Factory buildTemplateFromArgs,
                                Options options, Decoder decoder, ErrorDecoder errorDecoder) {
      return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
                                          logLevel, md, buildTemplateFromArgs, options, decoder,
                                          errorDecoder, decode404);
    }
  }
}

【源码】Client客户端请求类

/*
 * Copyright 2013 Netflix, Inc.
 *
 * 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.
 */
package feign;

import static java.lang.String.format;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;

import feign.Request.Options;

import static feign.Util.CONTENT_ENCODING;
import static feign.Util.CONTENT_LENGTH;
import static feign.Util.ENCODING_DEFLATE;
import static feign.Util.ENCODING_GZIP;

/**
 * Submits HTTP {@link Request requests}. Implementations are expected to be thread-safe.
 */
public interface Client {

  /**
   * Executes a request against its {@link Request#url() url} and returns a response.
   *
   * @param request safe to replay.
   * @param options options to apply to this request.
   * @return connected response, {@link Response.Body} is absent or unread.
   * @throws IOException on a network error connecting to {@link Request#url()}.
   */
  Response execute(Request request, Options options) throws IOException;

  public static class Default implements Client {

    private final SSLSocketFactory sslContextFactory;
    private final HostnameVerifier hostnameVerifier;

    /**
     * Null parameters imply platform defaults.
     */
    public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
      this.sslContextFactory = sslContextFactory;
      this.hostnameVerifier = hostnameVerifier;
    }

    @Override
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection);
    }

//此处封装与请求有关的参数信息
    HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
      final HttpURLConnection
          connection =
          (HttpURLConnection) new URL(request.url()).openConnection();
      if (connection instanceof HttpsURLConnection) {
        HttpsURLConnection sslCon = (HttpsURLConnection) connection;
        if (sslContextFactory != null) {
          sslCon.setSSLSocketFactory(sslContextFactory);
        }
        if (hostnameVerifier != null) {
          sslCon.setHostnameVerifier(hostnameVerifier);
        }
      }
      connection.setConnectTimeout(options.connectTimeoutMillis());
      connection.setReadTimeout(options.readTimeoutMillis());
      connection.setAllowUserInteraction(false);
      connection.setInstanceFollowRedirects(true);
      connection.setRequestMethod(request.method());

      Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
      boolean
          gzipEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
      boolean
          deflateEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);

      boolean hasAcceptHeader = false;
      Integer contentLength = null;
      for (String field : request.headers().keySet()) {
        if (field.equalsIgnoreCase("Accept")) {
          hasAcceptHeader = true;
        }
        for (String value : request.headers().get(field)) {
          if (field.equals(CONTENT_LENGTH)) {
            if (!gzipEncodedRequest && !deflateEncodedRequest) {
              contentLength = Integer.valueOf(value);
              connection.addRequestProperty(field, value);
            }
          } else {
            connection.addRequestProperty(field, value);
          }
        }
      }
      // Some servers choke on the default accept string.
      if (!hasAcceptHeader) {
        connection.addRequestProperty("Accept", "*/*");
      }

      if (request.body() != null) {
        if (contentLength != null) {
          connection.setFixedLengthStreamingMode(contentLength);
        } else {
          connection.setChunkedStreamingMode(8196);
        }
        connection.setDoOutput(true);
        OutputStream out = connection.getOutputStream();
        if (gzipEncodedRequest) {
          out = new GZIPOutputStream(out);
        } else if (deflateEncodedRequest) {
          out = new DeflaterOutputStream(out);
        }
        try {
          out.write(request.body());
        } finally {
          try {
            out.close();
          } catch (IOException suppressed) { // NOPMD
          }
        }
      }
      return connection;
    }

    Response convertResponse(HttpURLConnection connection) throws IOException {
      int status = connection.getResponseCode();
      String reason = connection.getResponseMessage();

      if (status < 0) {
        throw new IOException(format("Invalid status(%s) executing %s %s", status,
            connection.getRequestMethod(), connection.getURL()));
      }

      Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
      for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
        // response message
        if (field.getKey() != null) {
          headers.put(field.getKey(), field.getValue());
        }
      }

      Integer length = connection.getContentLength();
      if (length == -1) {
        length = null;
      }
      InputStream stream;
      if (status >= 400) {
        stream = connection.getErrorStream();
      } else {
        stream = connection.getInputStream();
      }
      return Response.create(status, reason, headers, stream, length);
    }
  }
}

如何使用

通过对Feign底层源码的分析,大致关键点如下:

  1. 容器启动的时候动态扫描class文件,然后动态创建bean,并且注入到Spring容器中;
  2. 容器启动的同时,初始化Feign.Builder中的参数配置,例如是否在每次请求添加前置过滤等(后面有代码示例);
  3. 每次发送请求的的时候,通过Builder对象找到接口配置信息,并使用invoke动态调用接口数据,Feign会先构建Request对象,其中包括URL组装,前置处理代码如何集成等;
  4. Feign底层实际上是使用的HttpConnection的方式进行远程调用;

添加包依赖

    compile 'com.netflix.feign:feign-core:8.18.0'
    compile 'com.netflix.feign:feign-jackson:8.18.0'
    compile 'io.github.lukehutch:fast-classpath-scanner:2.18.1'

添加注解类

package com.chz.apps.web.bean;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Feign接口标记注解
 * @Author gongstring(gongstring@foxmail.com)
 * @website http://www.gongstring.com
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignApi {

    /**
     * 调用的服务地址
     * @return
     */
    String serviceUrl();
}

添加接口类(示例)

package com.cxkh.apps.userinfo.client.apis;

import com.chz.apps.common.vo.DicVo;
import com.chz.apps.web.bean.FeignApi;
import com.chz.apps.web.bean.ReqBean;
import com.chz.apps.web.bean.RespBean;
import com.cxkh.apps.userinfo.client.vo.CorpBean;
import feign.Headers;
import feign.RequestLine;

import java.util.List;

@FeignApi(serviceUrl = "http://dev.xxxx.com/xxxx/remote")
public interface UserInfoApi {

    @Headers({"Content-Type: application/json","Accept: application/json"})
    @RequestLine("POST /dictinary/all")
    RespBean<List<DicVo>> getDics(ReqBean<List<String>> reqBean);
}


添加Spring Bean加载类

package com.chz.apps.web.plugin;

import com.chz.apps.common.cache.LocalCache;
import com.chz.apps.common.redisson.RedissionTools;
import com.chz.apps.common.tools.HttpConstant;
import com.chz.apps.web.bean.FeignApi;
import com.chz.apps.web.bean.FeignConstant;
import com.chz.component.basic.tools.PackageTools;
import feign.Feign;
import feign.Request;
import feign.Retryer;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * Feign容器初始化类,用于动态加载Bean
 * @Author gongstring(gongstring@foxmail.com)
 * @website http://www.gongstring.com
 */
@Component
public class FeignClientRegister implements BeanFactoryPostProcessor{

    //扫描的接口路径
    private String  scanPath="com.chz.apps.**.apis,com.cxkh.apps.**.apis";

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        Set<String> classes = PackageTools.findPackageClass(scanPath);
        if(classes==null){
            return ;
        }
        Feign.Builder builder = getFeignBuilder();
        //动态创建bean,并注入到Spring容器中
        if(classes.size()>0){
            for (String claz : classes) {
                Class<?> targetClass = null;
                try {
                    targetClass = Class.forName(claz);
                    String url=targetClass.getAnnotation(FeignApi.class).serviceUrl();
                    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 Feign.Builder getFeignBuilder(){
        //设置请求超时时间(从配置文件中读取,否则使用默认配置)
        Object feignConnectTimeoutMillis = LocalCache.getInstance().getValue(FeignConstant.OPTIONS_CONNECTTIMEOUTMILLIS,1000);
        Object feignReadTimeoutMillis = LocalCache.getInstance().getValue(FeignConstant.OPTIONS_READTIMEOUTMILLIS,3500);

        Feign.Builder builder = Feign.builder()
                //使用Jackson进行参数处理,如果有必要可以自行定义
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                //超时处理
                .options(new Request.Options(Integer.parseInt(feignConnectTimeoutMillis.toString()), Integer.parseInt(feignReadTimeoutMillis.toString())))
                .retryer(new Retryer.Default(5000, 5000, 3))
                //每次请求时,自定义内部请求头部信息,例如:权限相关的信息
                .requestInterceptor(template -> {
                    template.header(HttpConstant.REQUEST_HEADER_REMOTE_TOKEN_KEY, RedissionTools.getSingleTokenKey());
                  template.header(HttpConstant.REQUEST_HEADER_REMOTE_FLAG,HttpConstant.REQUEST_HEADER_REMOTE_VALUE);
                });
        return builder;
    }

}

在代码中调用Api

@Autowired
private UserInfoApi userInfoApi;

基于Spring底层源码的包扫描工具类

package com.chz.component.basic.tools;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;

public class PackageTools {
    private static final Log log = LogFactory.getLog(PackageTools.class);
    protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    public PackageTools() {
    }

    public static Set<Method> findClassAnnotationMethods(String scanPackages, Class<? extends Annotation> annotation) {
        Set<String> clazzSet = findPackageClass(scanPackages);
        Set<Method> methods = new HashSet();
        Iterator var4 = clazzSet.iterator();

        while(var4.hasNext()) {
            String clazz = (String)var4.next();

            try {
                Set<Method> ms = findAnnotationMethods(clazz, annotation);
                if (ms != null) {
                    methods.addAll(ms);
                }
            } catch (ClassNotFoundException var7) {
                ;
            }
        }

        return methods;
    }

    public static Set<String> findPackageClass(String scanPackages) {
        if (StringUtils.isBlank(scanPackages)) {
            return Collections.EMPTY_SET;
        } else {
            Set<String> packages = checkPackage(scanPackages);
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            Set<String> clazzSet = new HashSet();
            Iterator var5 = packages.iterator();

            while(true) {
                String basePackage;
                do {
                    if (!var5.hasNext()) {
                        return clazzSet;
                    }

                    basePackage = (String)var5.next();
                } while(StringUtils.isBlank(basePackage));

                String packageSearchPath = "classpath*:" + ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + "**/*.class";

                try {
                    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
                    Resource[] var9 = resources;
                    int var10 = resources.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        Resource resource = var9[var11];
                        String clazz = loadClassName(metadataReaderFactory, resource);
                        clazzSet.add(clazz);
                    }
                } catch (Exception var14) {
                    log.error("获取包下面的类信息失败,package:" + basePackage, var14);
                }
            }
        }
    }

    private static Set<String> checkPackage(String scanPackages) {
        if (StringUtils.isBlank(scanPackages)) {
            return Collections.EMPTY_SET;
        } else {
            Set<String> packages = new HashSet();
            Collections.addAll(packages, scanPackages.split(","));
            String[] var2 = (String[])packages.toArray(new String[packages.size()]);
            int var3 = var2.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                String pInArr = var2[var4];
                if (!StringUtils.isBlank(pInArr) && !pInArr.equals(".") && !pInArr.startsWith(".")) {
                    if (pInArr.endsWith(".")) {
                        pInArr = pInArr.substring(0, pInArr.length() - 1);
                    }

                    Iterator<String> packageIte = packages.iterator();
                    boolean needAdd = true;

                    while(packageIte.hasNext()) {
                        String pack = (String)packageIte.next();
                        if (pInArr.startsWith(pack + ".")) {
                            needAdd = false;
                        } else if (pack.startsWith(pInArr + ".")) {
                            packageIte.remove();
                        }
                    }

                    if (needAdd) {
                        packages.add(pInArr);
                    }
                }
            }

            return packages;
        }
    }

    private static String loadClassName(MetadataReaderFactory metadataReaderFactory, Resource resource) throws IOException {
        try {
            if (resource.isReadable()) {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                if (metadataReader != null) {
                    return metadataReader.getClassMetadata().getClassName();
                }
            }
        } catch (Exception var3) {
            log.error("根据resource获取类名称失败", var3);
        }

        return null;
    }

    public static Set<Method> findAnnotationMethods(String fullClassName, Class<? extends Annotation> anno) throws ClassNotFoundException {
        Set<Method> methodSet = new HashSet();
        Class<?> clz = Class.forName(fullClassName);
        Method[] methods = clz.getDeclaredMethods();
        Method[] var5 = methods;
        int var6 = methods.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method method = var5[var7];
            if (method.getModifiers() == 1) {
                Annotation annotation = method.getAnnotation(anno);
                if (annotation != null) {
                    methodSet.add(method);
                }
            }
        }

        return methodSet;
    }

    public static void main(String[] args) {
        String packages = "com.a,com.ab,com.c,com.as.t,com.as,com.as.ta,com.at.ja,com.at.jc,com.at.";
        System.out.println("检测前的package: " + packages);
        System.out.println("检测后的package: " + StringUtils.join(checkPackage(packages), ","));
    }
}


© 著作权归作者所有

共有 人打赏支持
傲娇字符
粉丝 5
博文 38
码字总数 14769
作品 0
武汉
架构师
Spring Cloud-Honghu Cloud分布式微服务云系统—技术点

鸿鹄Cloud是基于springcloud的,spring cloud本身提供的组件就很多,但我们需要按照企业的业务模式来定制企业所需要的通用架构,那我们现在需要考虑使用哪些技术呢? 下面我针对于spring cl...

itcloud
04/25
0
0
Controller类的方法上的RequestMapping一定要写在Controller类里吗?

使用Spring Cloud做项目的同学会使用Feign这个组件进行远程服务的调用,Feign这个组件采用模板的方式,有着优雅的代码书写规范。核心原理对Feign等相关注解进行解析,并提取信息,在Spring ...

forezp
04/24
0
0
疯狂Spring Cloud连载(14)Spring Cloud整合Feign

本文节选自《疯狂Spring Cloud微服务架构实战》 京东购买地址:https://item.jd.com/12256011.html 当当网购买地址:http://product.dangdang.com/25201393.html Spring Cloud教学视频:htt...

杨大仙的程序空间
2017/10/29
0
2
微服务开发架构——Spring Cloud常见问题与总结Turbine 聚合数据不完整

个人GitHub地址:https://github.com/leebingbin/ 在使用Spring Cloud的过程中,难免会遇到一些问题。所以对Spring Cloud的常用问题做一些总结。 三、Turbine 聚合数据不完整 在某些版本的S...

Mr_ET
2017/11/01
0
0
不使用SpringBoot如何将原生Feign集成到Spring中来简化http调用

在微服务架构中,如果使用得是SpringCloud,那么只需要集成SpringFeign就可以了,SpringFeign可以很友好的帮我们进行服务请求,对象解析等工作。 然而SpingCloud是依赖于SpringBoot的。在老的...

宇的季节
05/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

windows nvm 安装 node

nvm 是 node 的版本控制管理 下面是下载 nvm 的地址,选择 nvm-setup.zip 下载 https://github.com/coreybutler/nvm-windows/releases 就是下一步下一步,一键安装 基本命令有: nvm arch [32...

U_I_A_N
7分钟前
0
0
函数式组件完整例子

之前创建的组件是比较简单,没有管理或者监听任何传递给他的状态,也没有生命周期方法。它只是一个接收参数的函数。 在下面这个例子中,我们标记组件为 functional,这意味它是无状态 (没有响...

tianyawhl
22分钟前
0
0
linux shell大文件操作

查找字符串所在行 : grep -n “待查找字符串” “文件名” 显示指定行信息:sed -n 1p “指定文件” 表示显示指定文件第一行的信息 ----------------------------------------------------...

悲催的古灵武士
23分钟前
0
0
centos7安装nexus3

centos7安装nexus3 2018年05月24日 16:20:34 阅读数:257 1、下载nexus wget https://sonatype-download.global.ssl.fastly.net/repository/repositoryManager/3/nexus-3.12.0-01-unix.tar.......

linjin200
24分钟前
1
0
springboot整合cxf发布webservice以及调用

webservice性能不高,但是现在好多公司还是在用,恰好今天在开发的时候对接项目组需要使用到webservice下面来说下简单的案例应用 首先老规矩:引入jar包 <dependency><groupId>org.apach...

落叶清风
28分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部