文档章节

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

傲娇字符
 傲娇字符
发布于 05/30 09:55
字数 2393
阅读 200
收藏 0
点赞 0
评论 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), ","));
    }
}


© 著作权归作者所有

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

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

itcloud ⋅ 04/25 ⋅ 0

Controller类的方法上的RequestMapping一定要写在Controller类里吗?

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

forezp ⋅ 04/24 ⋅ 0

不使用SpringBoot如何将原生Feign集成到Spring中来简化http调用

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

宇的季节 ⋅ 05/26 ⋅ 0

Feign使用Hystrix无效原因及解决方法

最近项目重构使用了Spring Boot和Spring Cloud。这两者结合确实给项目带来了方便,同时也遇到了一些问题。其中使用feign作为服务消费,但是断路器hystrix一直不起作用让人很费解。最终经过重...

嘻哈开发者 ⋅ 04/26 ⋅ 0

基于Spring Cloud的微服务落地

微服务架构模式的核心在于如何识别服务的边界,设计出合理的微服务。但如果要将微服务架构运用到生产项目上,并且能够发挥该架构模式的重要作用,则需要微服务框架的支持。 在Java生态圈,目...

烂猪皮 ⋅ 04/20 ⋅ 0

Spring Cloud--Honghu Cloud分布式微服务云系统—组件化

Spring Cloud集成项目有很多,下面我们列举一下和Spring Cloud相关的优秀项目,我们的企业架构中用到了很多的优秀项目,说白了,也是站在巨人的肩膀上去整合的。在学习Spring Cloud之前大家必...

itcloud ⋅ 04/26 ⋅ 0

springcloud(三):服务提供与调用

文章概述 上一篇文章我们介绍了eureka服务注册中心的搭建,这篇文章介绍一下如何使用eureka服务注册中心,搭建一个简单的服务端注册服务,客户端去调用服务使用的案例。 案例中有三个角色: ...

AHUSKY ⋅ 06/12 ⋅ 0

史上最简单的SpringCloud教程 | 第四篇:断路器(Hystrix)

在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。...

方宏春 ⋅ 04/14 ⋅ 0

【Spring Cloud】分布式必学springcloud(七)——声明式服务调用Feign

一、前言 在上篇博客中,小编带大家接触了断路器Hystrix,是不是很好玩。分布式服务之间有了Hystrix,可以很好的提高容错性能。 但是在实际开发中,项目中会有很多的服务间的调用,对于服务的...

kisscatforever ⋅ 04/23 ⋅ 0

《Spring Cloud Netflix官方文档》7.声明式 REST 客户端: Feign

原文链接 Feign 是一个声明式的web服务客户端。它使得编写web服务客户端更简单,创建一个接口并加上注解就能使用Feign了,它还支持JAX-RS类型的注解,可插入式的编码和解码,Spring cloud 为他...

floder ⋅ 2017/01/05 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

在java中读取文件(也支持读取jar中的文件)

getClass().getResourceAsStream("文件名.格式"); 这个方法是用于获取当前类所在目录下的文件;需要将文件放到和当前类同一个包下面 比如我有个类在 com.test这个包下, 要读取一个test.jpg的图...

太黑_thj ⋅ 41分钟前 ⋅ 0

CentOS 7 源码编译安装 MySQL 5.7记录

没事瞎折腾,本来可以yum安装,却偏偏去要编译源码。 1. 安装依赖包 1). 安装cmake等依赖 # yum install cmake ncurses ncurses-devel bison bison-devel 2). 安装boost 1.59.0 # wget htt...

admin_qing ⋅ 43分钟前 ⋅ 0

tcp/ip详解-链路层

简介 设计链路层的目的: 为IP模块发送和接收IP数据报 为ARP模块发送ARP请求和接收ARP应答 为RARP模块发送RARP请求和接收RARP应答 TCP/IP支持多种链路层协议,如以太网、令牌环往、FDDI、RS-...

loda0128 ⋅ 今天 ⋅ 0

spring.net aop代码例子

https://www.cnblogs.com/haogj/archive/2011/10/12/2207916.html

whoisliang ⋅ 今天 ⋅ 0

发送短信如何限制1小时内最多发送11条短信

发送短信如何限制1小时内最多发送11条短信 场景: 发送短信属于付费业务,有时为了防止短信攻击,需要限制发送短信的频率,例如在1个小时之内最多发送11条短信. 如何实现呢? 思路有两个 截至到当...

黄威 ⋅ 昨天 ⋅ 0

mysql5.7系列修改root默认密码

操作系统为centos7 64 1、修改 /etc/my.cnf,在 [mysqld] 小节下添加一行:skip-grant-tables=1 这一行配置让 mysqld 启动时不对密码进行验证 2、重启 mysqld 服务:systemctl restart mysql...

sskill ⋅ 昨天 ⋅ 0

Intellij IDEA神器常用技巧六-Debug详解

在调试代码的时候,你的项目得debug模式启动,也就是点那个绿色的甲虫启动服务器,然后,就可以在代码里面断点调试啦。下面不要在意,这个快捷键具体是啥,因为,这个keymap是可以自己配置的...

Mkeeper ⋅ 昨天 ⋅ 0

zip压缩工具、tar打包、打包并压缩

zip 支持压缩目录 1.在/tmp/目录下创建目录(study_zip)及文件 root@yolks1 study_zip]# !treetree 11└── 2 └── 3 └── test_zip.txt2 directories, 1 file 2.yum...

蛋黄Yolks ⋅ 昨天 ⋅ 0

聊聊HystrixThreadPool

序 本文主要研究一下HystrixThreadPool HystrixThreadPool hystrix-core-1.5.12-sources.jar!/com/netflix/hystrix/HystrixThreadPool.java /** * ThreadPool used to executed {@link Hys......

go4it ⋅ 昨天 ⋅ 0

容器之上传镜像到Docker hub

Docker hub在国内可以访问,首先要创建一个账号,这个后面会用到,我是用126邮箱注册的。 1. docker login List-1 Username不能使用你注册的邮箱,要用使用注册时用的username;要输入密码 ...

汉斯-冯-拉特 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部