OSGI架构结合低版本Spring如何引进Feign

本文介绍用不了spring cloud的低版本系统如何借鉴cloud openfeign的封装思想来封装Feign,也能在多个工程以同样的接口申明方式来调用远程接口。

OpenFeign源于Netflix的Feign,是http通信的客户端。屏蔽了网络通信的细节,直接面向接口的方式开发,让开发者感知不到网络通信细节。 所有远程调用,都像调用本地方法一样完成!

封装的关键是OpenFeign 这两个注解,我们后续就是围绕这两个注解来封装

@FeignClient

该注解用于通知 OpenFeign 组件对 @RequestMapping 注解下的接口进行解析,并通过动态代理的方式产生实现类,实现负载均衡和服务调用。

@EnableFeignClients

该注解用于开启 OpenFeign 功能(一般使用在SpringBoot启动类上),当 Spring Cloud 应用启动时,OpenFeign 会扫描标有 @FeignClient 注解的接口,生成代理并注册到 Spring 容器中。

注解声明,面向接口:

/**
* REST 声明式规范(即支持的注解)有以下几种:Feign、JAX-RS 1/2、Spring Web MVC 都需要进行适配。这几种声明式注解的适配接口是 feign.Contract
* 这里选的是JAX-RS 1/2
*/
@FeignClient(name ="InnerOperatorService", path = "business/inner-invoke/", url = "${rootServiceUrl}", primary = false, contextId = "jaxrs")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/operator")
public interface IInnerOperatorService {
    @POST
 @Path("/login/{code}/{password}")
    Response login(@PathParam("code") String code, @PathParam("password")String password);
}

启动类配置

@SpringBootApplication(scanBasePackages ={"com.liangmixian.selfserviceApp"})
@EnableCaching
@EnableFeignClients({"com.liangmixian.sms.business.api.service"})
@EnableConfigurationProperties({LoginMap.class, AuthSmsParamsConfig.class})
public class FsaApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(FsaApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(FsaApplication.class);
    }

}

替换默认的底层通信

默认远程访问协议为jdk自带的sun.net.www.protocol.http.HttpURLConnection效率低,可改为okHttp

application.properties 配置,关闭默认的sun.net.www.protocol.http.HttpURLConnection,开启okhttp

feign.httpclient.enable=false

feign.okhttp.enable=true

超时控制:

OpenFeign默认超时时间为1s,超过1s就会返回错误页面。如果我们的接口处理业务确实超过1s,就需要对接口进行超时配置,如下:

ribbon: #设置feign客户端连接所用的超时时间,适用于网络状况正常情况下,两端连接所用时间

ReadTimeout: 1000 #指的是建立连接所用时间

ConnectTimeout: 1000 #指建立连接后从服务读取到可用资源所用时间

工作原理分析(这是我们后续封装工作的核心)

SpringBoot 应用启动时, 由针对 @EnableFeignClient 这一注解的处理逻辑触发程序扫描 classPath中所有被@FeignClient 注解的类, 将这些类解析为 BeanDefinition 并生成bean注册到 Spring 容器中,当业务请求真实发生时,Spring 会尝试从容器中查找创建好的动态代理对象,最终的调用被统一转发到了由 Feign 框架实现的 InvocationHandler 中, InvocationHandler 负责将接口中的入参转换为 HTTP 的形式, 发到服务端, 最后再解析 HTTP 响应, 将结果转换为 Java 对象, 予以返回。

扫描@FeignClient注解的接口并注册到Spring容器:

class FeignClientsRegistrar
      implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

   // patterned after Spring Integration IntegrationComponentScanRegistrar
   // and RibbonClientsConfigurationRegistgrar

   private ResourceLoader resourceLoader;

   private Environment environment;

   FeignClientsRegistrar() {
   }

   static void validateFallback(final Class clazz) {
      Assert.isTrue(!clazz.isInterface(),
            "Fallback class must implement the interface annotated by @FeignClient");
   }

   static void validateFallbackFactory(final Class clazz) {
      Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances "
            + "of fallback classes that implement the interface annotated by @FeignClient");
   }

   static String getName(String name) {
      if (!StringUtils.hasText(name)) {
         return "";
      }

      String host = null;
      try {
         String url;
         if (!name.startsWith("http://") && !name.startsWith("https://")) {
            url = "http://" + name;
         }
         else {
            url = name;
         }
         host = new URI(url).getHost();

      }
      catch (URISyntaxException e) {
      }
      Assert.state(host != null, "Service id not legal hostname (" + name + ")");
      return name;
   }

   static String getUrl(String url) {
      if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
         if (!url.contains("://")) {
            url = "http://" + url;
         }
         try {
            new URL(url);
         }
         catch (MalformedURLException e) {
            throw new IllegalArgumentException(url + " is malformed", e);
         }
      }
      return url;
   }

   static String getPath(String path) {
      if (StringUtils.hasText(path)) {
         path = path.trim();
         if (!path.startsWith("/")) {
            path = "/" + path;
         }
         if (path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
         }
      }
      return path;
   }

   @Override
   public void setResourceLoader(ResourceLoader resourceLoader) {
      this.resourceLoader = resourceLoader;
   }

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata,
         BeanDefinitionRegistry registry) {
      registerDefaultConfiguration(metadata, registry);
      registerFeignClients(metadata, registry);
   }

   private void registerDefaultConfiguration(AnnotationMetadata metadata,
         BeanDefinitionRegistry registry) {
      Map defaultAttrs = metadata
            .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

      if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
         String name;
         if (metadata.hasEnclosingClass()) {
            name = "default." + metadata.getEnclosingClassName();
         }
         else {
            name = "default." + metadata.getClassName();
         }
         registerClientConfiguration(registry, name,
               defaultAttrs.get("defaultConfiguration"));
      }
   }

   public void registerFeignClients(AnnotationMetadata metadata,
         BeanDefinitionRegistry registry) {

      LinkedHashSet candidateComponents = new LinkedHashSet<>();
      Map attrs = metadata
            .getAnnotationAttributes(EnableFeignClients.class.getName());
      final Class[] clients = attrs == null ? null
            : (Class[]) attrs.get("clients");
      if (clients == null || clients.length == 0) {
         ClassPathScanningCandidateComponentProvider scanner = getScanner();
         scanner.setResourceLoader(this.resourceLoader);
         scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
         Set basePackages = getBasePackages(metadata);
         for (String basePackage : basePackages) {
            candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
         }
      }
      else {
         for (Class clazz : clients) {
            candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
         }
      }

      for (BeanDefinition candidateComponent : candidateComponents) {
         if (candidateComponent instanceof AnnotatedBeanDefinition) {
            // verify annotated class is an interface
            AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
            AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
            Assert.isTrue(annotationMetadata.isInterface(),
                  "@FeignClient can only be specified on an interface");

            Map attributes = annotationMetadata
                  .getAnnotationAttributes(FeignClient.class.getCanonicalName());

            String name = getClientName(attributes);
            registerClientConfiguration(registry, name,
                  attributes.get("configuration"));

            registerFeignClient(registry, annotationMetadata, attributes);
         }
      }
   }

   private void registerFeignClient(BeanDefinitionRegistry registry,
         AnnotationMetadata annotationMetadata, Map attributes) {
      String className = annotationMetadata.getClassName();
      Class clazz = ClassUtils.resolveClassName(className, null);
      ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
            ? (ConfigurableBeanFactory) registry : null;
      String contextId = getContextId(beanFactory, attributes);
      String name = getName(attributes);
      FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
      factoryBean.setBeanFactory(beanFactory);
      factoryBean.setName(name);
      factoryBean.setContextId(contextId);
      factoryBean.setType(clazz);
      BeanDefinitionBuilder definition = BeanDefinitionBuilder
            .genericBeanDefinition(clazz, () -> {
               factoryBean.setUrl(getUrl(beanFactory, attributes));
               factoryBean.setPath(getPath(beanFactory, attributes));
               factoryBean.setDecode404(Boolean
                     .parseBoolean(String.valueOf(attributes.get("decode404"))));
               Object fallback = attributes.get("fallback");
               if (fallback != null) {
                  factoryBean.setFallback(fallback instanceof Class
                        ? (Class) fallback
                        : ClassUtils.resolveClassName(fallback.toString(), null));
               }
               Object fallbackFactory = attributes.get("fallbackFactory");
               if (fallbackFactory != null) {
                  factoryBean.setFallbackFactory(fallbackFactory instanceof Class
                        ? (Class) fallbackFactory
                        : ClassUtils.resolveClassName(fallbackFactory.toString(),
                              null));
               }
               return factoryBean.getObject();
            });
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      definition.setLazyInit(true);
      validate(attributes);

      AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
      beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
      beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

      // has a default, won't be null
      boolean primary = (Boolean) attributes.get("primary");

      beanDefinition.setPrimary(primary);

      String[] qualifiers = getQualifiers(attributes);
      if (ObjectUtils.isEmpty(qualifiers)) {
         qualifiers = new String[] { contextId + "FeignClient" };
      }

      BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
            qualifiers);
      BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
   }

   private void validate(Map attributes) {
      AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
      // This blows up if an aliased property is overspecified
      // FIXME annotation.getAliasedString("name", FeignClient.class, null);
      validateFallback(annotation.getClass("fallback"));
      validateFallbackFactory(annotation.getClass("fallbackFactory"));
   }

   /* for testing */ String getName(Map attributes) {
      return getName(null, attributes);
   }

   String getName(ConfigurableBeanFactory beanFactory, Map attributes) {
      String name = (String) attributes.get("serviceId");
      if (!StringUtils.hasText(name)) {
         name = (String) attributes.get("name");
      }
      if (!StringUtils.hasText(name)) {
         name = (String) attributes.get("value");
      }
      name = resolve(beanFactory, name);
      return getName(name);
   }

   private String getContextId(ConfigurableBeanFactory beanFactory,
         Map attributes) {
      String contextId = (String) attributes.get("contextId");
      if (!StringUtils.hasText(contextId)) {
         return getName(attributes);
      }

      contextId = resolve(beanFactory, contextId);
      return getName(contextId);
   }

   private String resolve(ConfigurableBeanFactory beanFactory, String value) {
      if (StringUtils.hasText(value)) {
         if (beanFactory == null) {
            return this.environment.resolvePlaceholders(value);
         }
         BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver();
         String resolved = beanFactory.resolveEmbeddedValue(value);
         if (resolver == null) {
            return resolved;
         }
         return String.valueOf(resolver.evaluate(resolved,
               new BeanExpressionContext(beanFactory, null)));
      }
      return value;
   }

   private String getUrl(ConfigurableBeanFactory beanFactory,
         Map attributes) {
      String url = resolve(beanFactory, (String) attributes.get("url"));
      return getUrl(url);
   }

   private String getPath(ConfigurableBeanFactory beanFactory,
         Map attributes) {
      String path = resolve(beanFactory, (String) attributes.get("path"));
      return getPath(path);
   }

   protected ClassPathScanningCandidateComponentProvider getScanner() {
      return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
         @Override
         protected boolean isCandidateComponent(
               AnnotatedBeanDefinition beanDefinition) {
            boolean isCandidate = false;
            if (beanDefinition.getMetadata().isIndependent()) {
               if (!beanDefinition.getMetadata().isAnnotation()) {
                  isCandidate = true;
               }
            }
            return isCandidate;
         }
      };
   }

   protected Set getBasePackages(AnnotationMetadata importingClassMetadata) {
      Map attributes = importingClassMetadata
            .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

      Set basePackages = new HashSet<>();
      for (String pkg : (String[]) attributes.get("value")) {
         if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
         }
      }
      for (String pkg : (String[]) attributes.get("basePackages")) {
         if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
         }
      }
      for (Class clazz : (Class[]) attributes.get("basePackageClasses")) {
         basePackages.add(ClassUtils.getPackageName(clazz));
      }

      if (basePackages.isEmpty()) {
         basePackages.add(
               ClassUtils.getPackageName(importingClassMetadata.getClassName()));
      }
      return basePackages;
   }

   private String getQualifier(Map client) {
      if (client == null) {
         return null;
      }
      String qualifier = (String) client.get("qualifier");
      if (StringUtils.hasText(qualifier)) {
         return qualifier;
      }
      return null;
   }

   private String[] getQualifiers(Map client) {
      if (client == null) {
         return null;
      }
      List qualifierList = new ArrayList<>(
            Arrays.asList((String[]) client.get("qualifiers")));
      qualifierList.removeIf(qualifier -> !StringUtils.hasText(qualifier));
      if (qualifierList.isEmpty() && getQualifier(client) != null) {
         qualifierList = Collections.singletonList(getQualifier(client));
      }
      return !qualifierList.isEmpty() ? qualifierList.toArray(new String[0]) : null;
   }

   private String getClientName(Map client) {
      if (client == null) {
         return null;
      }
      String value = (String) client.get("contextId");
      if (!StringUtils.hasText(value)) {
         value = (String) client.get("value");
      }
      if (!StringUtils.hasText(value)) {
         value = (String) client.get("name");
      }
      if (!StringUtils.hasText(value)) {
         value = (String) client.get("serviceId");
      }
      if (StringUtils.hasText(value)) {
         return value;
      }

      throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
            + FeignClient.class.getSimpleName());
   }

   private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
         Object configuration) {
      BeanDefinitionBuilder builder = BeanDefinitionBuilder
            .genericBeanDefinition(FeignClientSpecification.class);
      builder.addConstructorArgValue(name);
      builder.addConstructorArgValue(configuration);
      registry.registerBeanDefinition(
            name + "." + FeignClientSpecification.class.getSimpleName(),
            builder.getBeanDefinition());
   }

   @Override
   public void setEnvironment(Environment environment) {
      this.environment = environment;
   }

}

生成代理对象的FactoryBean:

public class FeignClientFactoryBean implements FactoryBean, InitializingBean,
      ApplicationContextAware, BeanFactoryAware {

   /***********************************
    * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some
    * lifecycle race condition.
    ***********************************/

   private static Log LOG = LogFactory.getLog(FeignClientFactoryBean.class);

   private Class type;

   private String name;

   private String url;

   private String contextId;

   private String path;

   private boolean decode404;

   private boolean inheritParentContext = true;

   private ApplicationContext applicationContext;

   private BeanFactory beanFactory;

   private Class fallback = void.class;

   private Class fallbackFactory = void.class;

   private int readTimeoutMillis = new Request.Options().readTimeoutMillis();

   private int connectTimeoutMillis = new Request.Options().connectTimeoutMillis();

   private boolean followRedirects = new Request.Options().isFollowRedirects();

   private List additionalCustomizers = new ArrayList<>();

   @Override
   public void afterPropertiesSet() {
      Assert.hasText(contextId, "Context id must be set");
      Assert.hasText(name, "Name must be set");
   }

   protected Feign.Builder feign(FeignContext context) {
      FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
      Logger logger = loggerFactory.create(type);

      // @formatter:off
      Feign.Builder builder = get(context, Feign.Builder.class)
            // required values
            .logger(logger)
            .encoder(get(context, Encoder.class))
            .decoder(get(context, Decoder.class))
            .contract(get(context, Contract.class));
      // @formatter:on

      configureFeign(context, builder);
      applyBuildCustomizers(context, builder);

      return builder;
   }

   private void applyBuildCustomizers(FeignContext context, Feign.Builder builder) {
      Map customizerMap = context
            .getInstances(contextId, FeignBuilderCustomizer.class);

      if (customizerMap != null) {
         customizerMap.values().stream()
               .sorted(AnnotationAwareOrderComparator.INSTANCE)
               .forEach(feignBuilderCustomizer -> feignBuilderCustomizer
                     .customize(builder));
      }
      additionalCustomizers.forEach(customizer -> customizer.customize(builder));
   }

   protected void configureFeign(FeignContext context, Feign.Builder builder) {
      FeignClientProperties properties = beanFactory != null
            ? beanFactory.getBean(FeignClientProperties.class)
            : applicationContext.getBean(FeignClientProperties.class);

      FeignClientConfigurer feignClientConfigurer = getOptional(context,
            FeignClientConfigurer.class);
      setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());

      if (properties != null && inheritParentContext) {
         if (properties.isDefaultToProperties()) {
            configureUsingConfiguration(context, builder);
            configureUsingProperties(
                  properties.getConfig().get(properties.getDefaultConfig()),
                  builder);
            configureUsingProperties(properties.getConfig().get(contextId), builder);
         }
         else {
            configureUsingProperties(
                  properties.getConfig().get(properties.getDefaultConfig()),
                  builder);
            configureUsingProperties(properties.getConfig().get(contextId), builder);
            configureUsingConfiguration(context, builder);
         }
      }
      else {
         configureUsingConfiguration(context, builder);
      }
   }

   protected void configureUsingConfiguration(FeignContext context,
         Feign.Builder builder) {
      Logger.Level level = getInheritedAwareOptional(context, Logger.Level.class);
      if (level != null) {
         builder.logLevel(level);
      }
      Retryer retryer = getInheritedAwareOptional(context, Retryer.class);
      if (retryer != null) {
         builder.retryer(retryer);
      }
      ErrorDecoder errorDecoder = getInheritedAwareOptional(context,
            ErrorDecoder.class);
      if (errorDecoder != null) {
         builder.errorDecoder(errorDecoder);
      }
      else {
         FeignErrorDecoderFactory errorDecoderFactory = getOptional(context,
               FeignErrorDecoderFactory.class);
         if (errorDecoderFactory != null) {
            ErrorDecoder factoryErrorDecoder = errorDecoderFactory.create(type);
            builder.errorDecoder(factoryErrorDecoder);
         }
      }
      Request.Options options = getInheritedAwareOptional(context,
            Request.Options.class);
      if (options != null) {
         builder.options(options);
         readTimeoutMillis = options.readTimeoutMillis();
         connectTimeoutMillis = options.connectTimeoutMillis();
         followRedirects = options.isFollowRedirects();
      }
      Map requestInterceptors = getInheritedAwareInstances(
            context, RequestInterceptor.class);
      if (requestInterceptors != null) {
         List interceptors = new ArrayList<>(
               requestInterceptors.values());
         AnnotationAwareOrderComparator.sort(interceptors);
         builder.requestInterceptors(interceptors);
      }
      QueryMapEncoder queryMapEncoder = getInheritedAwareOptional(context,
            QueryMapEncoder.class);
      if (queryMapEncoder != null) {
         builder.queryMapEncoder(queryMapEncoder);
      }
      if (decode404) {
         builder.decode404();
      }
      ExceptionPropagationPolicy exceptionPropagationPolicy = getInheritedAwareOptional(
            context, ExceptionPropagationPolicy.class);
      if (exceptionPropagationPolicy != null) {
         builder.exceptionPropagationPolicy(exceptionPropagationPolicy);
      }
   }

   protected void configureUsingProperties(
         FeignClientProperties.FeignClientConfiguration config,
         Feign.Builder builder) {
      if (config == null) {
         return;
      }

      if (config.getLoggerLevel() != null) {
         builder.logLevel(config.getLoggerLevel());
      }

      connectTimeoutMillis = config.getConnectTimeout() != null
            ? config.getConnectTimeout() : connectTimeoutMillis;
      readTimeoutMillis = config.getReadTimeout() != null ? config.getReadTimeout()
            : readTimeoutMillis;
      followRedirects = config.isFollowRedirects() != null ? config.isFollowRedirects()
            : followRedirects;

      builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS,
            readTimeoutMillis, TimeUnit.MILLISECONDS, followRedirects));

      if (config.getRetryer() != null) {
         Retryer retryer = getOrInstantiate(config.getRetryer());
         builder.retryer(retryer);
      }

      if (config.getErrorDecoder() != null) {
         ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
         builder.errorDecoder(errorDecoder);
      }

      if (config.getRequestInterceptors() != null
            && !config.getRequestInterceptors().isEmpty()) {
         // this will add request interceptor to builder, not replace existing
         for (Class bean : config.getRequestInterceptors()) {
            RequestInterceptor interceptor = getOrInstantiate(bean);
            builder.requestInterceptor(interceptor);
         }
      }

      if (config.getDecode404() != null) {
         if (config.getDecode404()) {
            builder.decode404();
         }
      }

      if (Objects.nonNull(config.getEncoder())) {
         builder.encoder(getOrInstantiate(config.getEncoder()));
      }

      if (Objects.nonNull(config.getDefaultRequestHeaders())) {
         builder.requestInterceptor(requestTemplate -> requestTemplate
               .headers(config.getDefaultRequestHeaders()));
      }

      if (Objects.nonNull(config.getDefaultQueryParameters())) {
         builder.requestInterceptor(requestTemplate -> requestTemplate
               .queries(config.getDefaultQueryParameters()));
      }

      if (Objects.nonNull(config.getDecoder())) {
         builder.decoder(getOrInstantiate(config.getDecoder()));
      }

      if (Objects.nonNull(config.getContract())) {
         builder.contract(getOrInstantiate(config.getContract()));
      }

      if (Objects.nonNull(config.getExceptionPropagationPolicy())) {
         builder.exceptionPropagationPolicy(config.getExceptionPropagationPolicy());
      }
   }

   private  T getOrInstantiate(Class tClass) {
      try {
         return beanFactory != null ? beanFactory.getBean(tClass)
               : applicationContext.getBean(tClass);
      }
      catch (NoSuchBeanDefinitionException e) {
         return BeanUtils.instantiateClass(tClass);
      }
   }
   protected  Map getInheritedAwareInstances(FeignContext context,
         Class type) {
      if (inheritParentContext) {
         return context.getInstances(contextId, type);
      }
      else {
         return context.getInstancesWithoutAncestors(contextId, type);
      }
   }

   protected  T loadBalance(Feign.Builder builder, FeignContext context,
         HardCodedTarget target) {
      Client client = getOptional(context, Client.class);
      if (client != null) {
         builder.client(client);
         Targeter targeter = get(context, Targeter.class);
         return targeter.target(this, builder, context, target);
      }

      throw new IllegalStateException(
            "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon or spring-cloud-starter-loadbalancer?");
   }

   @Override
   public Object getObject() {
      return getTarget();
   }

   /**
    * @param  the target type of the Feign client
    * @return a {@link Feign} client created with the specified data and the context
    * information
    */
    T getTarget() {
      FeignContext context = beanFactory != null
            ? beanFactory.getBean(FeignContext.class)
            : applicationContext.getBean(FeignContext.class);
      Feign.Builder builder = feign(context);

      if (!StringUtils.hasText(url)) {

         if (LOG.isInfoEnabled()) {
            LOG.info("For '" + name
                  + "' URL not provided. Will try picking an instance via load-balancing.");
         }
         if (!name.startsWith("http")) {
            url = "http://" + name;
         }
         else {
            url = name;
         }
         url += cleanPath();
         return (T) loadBalance(builder, context,
               new HardCodedTarget<>(type, name, url));
      }
      if (StringUtils.hasText(url) && !url.startsWith("http")) {
         url = "http://" + url;
      }
      String url = this.url + cleanPath();
      Client client = getOptional(context, Client.class);
      if (client != null) {
         if (client instanceof LoadBalancerFeignClient) {
            // not load balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient) client).getDelegate();
         }
         if (client instanceof FeignBlockingLoadBalancerClient) {
            // not load balancing because we have a url,
            // but Spring Cloud LoadBalancer is on the classpath, so unwrap
            client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
         }
         if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
            // not load balancing because we have a url,
            // but Spring Cloud LoadBalancer is on the classpath, so unwrap
            client = ((RetryableFeignBlockingLoadBalancerClient) client)
                  .getDelegate();
         }
         builder.client(client);
      }
      Targeter targeter = get(context, Targeter.class);
      return (T) targeter.target(this, builder, context,
            new HardCodedTarget<>(type, name, url));
   }

   private String cleanPath() {
      String path = this.path.trim();
      if (StringUtils.hasLength(path)) {
         if (!path.startsWith("/")) {
            path = "/" + path;
         }
         if (path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
         }
      }
      return path;
   }




   @Override
   public void setApplicationContext(ApplicationContext context) throws BeansException {
      applicationContext = context;
      beanFactory = context;
   }
   @Override
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
      this.beanFactory = beanFactory;
   }
}

封装之前,先对比看下引入feign会在代码量上改观多少,再说引入会遇到什么问题且每个是怎么解决的,最后介绍使用注意事项。

对比原来封装的HttpUtils

HttpUtils调用方式参见如下代码,设置传参组装请求并接收返回值,其中每一步都靠手动组装,且每次调用都靠手动:

public interface ICenterProxyService {
     CenterResponse sendCode(String phone);
     CenterResponse checkSmsCode(String phone,String code);
}
 
public class CenterProxyService implements ICenterProxyService {
    
    @Override
    public CenterResponse sendCode(String phone){
        String smsUrl = getCenterUrl()+CenterProxyConstant.CENTER_PREFIX+CenterProxyConstant.SMS_URL;
        Map params = new HashMap();
        params.put("phone", phone);
        return doPostForm(params,smsUrl);
    }
    
    public CenterResponse checkSmsCode(String phone,String code){
        String smsUrl = getCenterUrl()+CenterProxyConstant.CENTER_PREFIX+CenterProxyConstant.CHECK_SMS_CODE_URL;
        Map params = new HashMap();
        params.put("phone", phone);
        params.put("code", code);
        return doPostForm(params,smsUrl);
    }
    
    
    private  String getCenterUrl() {
         SysParameter p = sysParameterDao.findSysParameter(CenterProxyConstant.PARAMCODE);
         return p.getValue() == null || "".equals(p.getValue()) ? p.getDefaultValue() : p.getValue();
    }
    
    private CenterResponse doPostForm(Map params,String url){
        logger.info("# url #"+url);
        logger.info("# params #"+params);
        CenterResponse centerResponse = new CenterResponse();
        String result = "";
        try {
            result = HttpUtils.postForm(url, params);
            logger.info("# doPost result #"+result);
            setResult(centerResponse,result);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e);
            centerResponse.setSuccess(false);
            centerResponse.setMsg(e.getMessage());
        }
        return centerResponse;
    }
    
    private CenterResponse doPost(String json,String url){
        logger.info("# url #"+url);
        logger.info("# json #"+json);
        CenterResponse centerResponse = new CenterResponse();
        String result = "";
        try {
            result = HttpUtils.postJson(url,json.getBytes(Charset.forName("UTF-8")));
            logger.info("# doPost result #"+result);
            setResult(centerResponse,result);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e);
            centerResponse.setSuccess(false);
            centerResponse.setMsg(e.getMessage());
        }
        return centerResponse;
    }
    
    private void setResult(CenterResponse centerResponse,String result){
        logger.info("# center.return.values #"+result);
        Map map= JsonConvertUtils.getObject(result, Map.class);
        if(map==null){
            return ;
        }
        Object success = map.get("success");
        Object message = map.get("msg");
        if ( null == success ||(Boolean)success.equals(Boolean.FALSE)) {
            centerResponse.setSuccess(false);
            centerResponse.setMsg(message+"");
            return;
        }
        Map jsonDataMap = (Map) map.get("jsonData");
        Object codeResult = jsonDataMap.get("code");
        Object msgResult = jsonDataMap.get("msg");
        if (CenterProxyConstant.SUCCESS_CODE_0.equals((String)codeResult) || CenterProxyConstant.SUCCESS_CODE_1000.equals((String)codeResult)) {
            centerResponse.setSuccess(true);
        }else {
            centerResponse.setSuccess(false);
            centerResponse.setMsg(msgResult+"");
        }
    }

    public ISysParameterDao getSysParameterDao() {
        return sysParameterDao;
    }
    public void setSysParameterDao(ISysParameterDao sysParameterDao) {
        this.sysParameterDao = sysParameterDao;
    }
 

HttpUtils最终会调用这个请求构建并发送的方法,可以看到我们之前封装的很漂亮但很辛苦:

  public static String sendRequest(String url, String method, Map headers, InputStream bodyStream, File saveToFile) throws Exception {
        assertUrlValid(url);

        HttpURLConnection conn = null;

        try {
            // 打开链接
            URL urlObj = new URL(url);
            conn = (HttpURLConnection) urlObj.openConnection();

            // 设置各种默认属性
            setDefaultProperties(conn);

            // 设置请求方法
            if (method != null && method.length() > 0) {
                conn.setRequestMethod(method);
            }

            // 添加请求头
            if (headers != null && headers.size() > 0) {
                for (Map.Entry entry : headers.entrySet()) {
                    conn.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            // 设置请求内容
            if (bodyStream != null) {
                conn.setDoOutput(true);
                copyStreamAndClose(bodyStream, conn.getOutputStream());
            }

            // 获取响应code
            int code = conn.getResponseCode();
            logger.info("# code #"+code);
            // 处理重定向
            if (code == HttpURLConnection.HTTP_MOVED_PERM || code == HttpURLConnection.HTTP_MOVED_TEMP) {
                String location = conn.getHeaderField("Location");
                if (location != null) {
                    closeStream(bodyStream);
                    // 重定向为 GET 请求
                    return sendRequest(location, "GET", headers, null, saveToFile);
                }
            }

            // 获取响应内容长度
//            long contentLength = conn.getContentLengthLong();
            long contentLength = conn.getContentLength();
            logger.info("# contentLength #"+contentLength);

            // 获取内容类型
            String contentType = conn.getContentType();
            
            logger.info("# contentType #"+contentType);

            // 获取响应内容输入流
            InputStream in = conn.getInputStream();

            // 没有响应成功, 均抛出异常
            if (code != HttpURLConnection.HTTP_OK) {
                throw new IOException("Http Error: " + code + "; Desc: " + handleResponseBodyToString(in, contentType));
            }

            // 如果文件参数不为null, 则把响应内容保存到文件
            if (saveToFile != null) {
                handleResponseBodyToFile(in, saveToFile);
                return saveToFile.getPath();
            }

            // 如果需要将响应内容解析为文本, 则限制最大长度
            if (contentLength > TEXT_REQUEST_MAX_LENGTH) {
                throw new IOException("Response content length too large: " + contentLength);
            }
            return handleResponseBodyToString(in, contentType);

        } finally {
            closeConnection(conn);
        }
    }

feign调用方式参见代码,设置传参组装请求并接收返回值,其中每一步都靠接口注解声明:

@liangmixianFeignClient(urlPara="center.ip.and.port",path = "/appIntf")
@Consumes(MediaType.APPLICATION_JSON)
@Path("/sms")
public interface IAuthCenterProxyService {
    @POST
    @Path("/sendCode")
    AuthCenterResponse sendCode(@QueryParam("phone")String phone);
     
    @POST
    @Path("/checkSmsCode")
    AuthCenterResponse checkSmsCode(@QueryParam("phone")String phone,@QueryParam("code")String code);
}

所以说单纯从工作量来看,引入feign肯定是划算多了,还不说feign封装能做性能改进这些优化工作

引入Feign到底遇到什么问题?

  1. 只在一个工程中简单用spring上下文扫描feign接口是扫不全的
    OSGI架构中不同bundle中的跨bundle扫描feign接口需要每个bundle上下文提前扫,类似BeanFactoryHolder,每个Bundle都要执行,才能收集到所有bundle中的feign接口,
    (每个bundle都有自己的spring 容器,所以说FeignClientsRegistrar简单加个@Component肯定不行)

  1. feign接口的扫描注册器放哪个工程合适呢?
    一开始想把注册器FeignClientsRegistrar放在core工程(这样可以访问dao_interface),其他工程bundle也想执行扫描是找不到这个类的,特别是接口机,那就试试放到common工程里,这样每个bundle都能找到,如果没依赖common依赖下就能用,比如innerintf_interface就没有依赖common

  1. 放common中还想读数据库中的系统参数,common又不能依赖dao_interface啊
    获取ISysParameterDao接口的实例并调用方法findSysParameter,将找不到ISysParameterDao和SysParameter,报ClassNotFoundException异常,dao_interface已经依赖了common,不能再反过来依赖,那就用反射调用方法,这样就避开依赖限制;

  1. 请求地址配置在系统参数中,那启动阶段生成feign代理对象时拿不到系统参数值的,因为DAO实例还没生成
    提前扫出来构建feign代理类需要通过系统参数查询url,但是启动阶段还调用不了DAO,那就想办法在运行阶段再查系统参数:JDK动态代理生成一个空的代理对象,在运行期调用invoke方法再生成可用的feign代理类并进行调用

  1. 调用方和服务方传参解析的问题
    UnrecognizedPropertyException: Unrecognized field "count" (class com….AuthCenterResponse), not marked as ignorable (3 known properties: "msg", "jsonData", "success"]),这是主应用调用中间层进行短信验证,如果成功时中间层不会返回"msg",异常时才返回,对方可能少属性,且"count"对于调用方是不会用也不该存在,也就是两边属性肯定不一致,那就这么配置下
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
    表示反序列化忽略不认识的属性,就像解决spring 的MappingJackson2HttpMessageConverter也可以这么设置,这样不用在每个DTO上增加注解@JsonIgnoreProperties(ignoreUnknown = true)

  1. OkHttp依赖包需要转化

有这么个依赖链条:feign okhttp===>okhttp===>okio===>animal-sniffer-annotations,

而okio只是简单的jar包,参见这里https://mvnrepository.com/artifact/com.squareup.okio/okio/1.6.0,每个版本都不是bundle类型,所以需要转成bundle,只需改下MENIFEST.MF文件即可,另外okio又依赖animal-sniffer-annotations,且也不是bundle包,那就把animal-sniffer-annotations的class打进okio-1.6.0.jar里边

  1. httpinvoker客户端不能直接引用并调用feign接口
    如果是后台任务、controller这些地方调用feign接口,解决完上述几个问题其实已经可以使用,最后剩个问题:client端通过OsgiHttpInvokerServiceWithHandlerExporter去找server_开头的服务对象实例,找到的却是JdkDynamicAopProxy对象,调proxy对象的sendCode方法就会报NoSuchMethodException异常,所以针对这个问题只能再增加一层服务专门处理httpinvoker调用,这层服务再调用feign接口即可

多加一层调用:

public class AuthCenterProxyService implements ICenterProxyService {
    private Log logger = LogFactory.getLog(getClass());

    @Override
    public CenterResponse sendCode(String phone) {
        AuthCenterResponse result = getAuthCenterProxyService().sendCode(phone);
        return getCenterResponse(result);
    }

    @Override
    public CenterResponse checkSmsCode(String phone, String code) {
        AuthCenterResponse result = getAuthCenterProxyService().checkSmsCode(phone, code);
        return getCenterResponse(result);
    }

    public IAuthCenterProxyService getAuthCenterProxyService() {
        return BeanFactoryHolder.get().getBean("authCenterProxyService",IAuthCenterProxyService.class);
    }

    private CenterResponse getCenterResponse(AuthCenterResponse result){
        CenterResponse centerResponse = new CenterResponse();
        logger.info("# center.return.values #"+result);
        if (!result.getSuccess()) {
            centerResponse.setSuccess(false);
            centerResponse.setMsg(result.getMsg());
            return centerResponse;
        }
        String codeResult = result.getJsonData().getCode();
        String msgResult = result.getJsonData().getMsg();
        if (CenterProxyConstant.SUCCESS_CODE_0.equals((String)codeResult) || CenterProxyConstant.SUCCESS_CODE_1000.equals((String)codeResult)) {
            centerResponse.setSuccess(true);
        }else {
            centerResponse.setSuccess(false);
            centerResponse.setMsg(msgResult);
        }
        return centerResponse;
    }
}

最后我们看下解决了上述几个问题的底层封装代码:

@FeignClient注解有没有很眼熟,就类似spring cloud实现的@FeignClient

/**
 * 标记feign接口,据此生成feign调用代理对象
 */
@Target({ ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignClient {
    //系统参数配置
    int SYS_PARAMETER = 0;
    //本地文件配置
    int LOCAL_PROPERTIES = 1;
    //直接指定,本身就是url地址
    int ITSELF = 2;
    
    /**
     * url根路径配置项
     */
    String urlPara();
    
    /**
     * url根路径之后首先拼接的路径,用于多个接口的请求根路径一样但各自子路径不一样的情况
     */
    String path() default "";
    /**
     * url参数配置类型,默认用系统参数配置
     */
    int urlParaType() default SYS_PARAMETER;
    
    /**
     * 针对urlParaType配置为1的情况,需要指定properties配置文件的路径
     */
    String propertiesFile() default "";
 
    /**
     * 连接超时时间,单位为秒
     * 默认60秒
     */
    int connectTimeout() default 60;
    
    /**
     * 读取超时时间,单位为秒
     * 默认60秒
     */
    int readTimeout() default 30;
}

FeignClientsRegistrar眼熟吧,叫法也跟spring cloud封装一样,只不过里边内容很恨很不一样

public class FeignClientsRegistrar implements BeanFactoryPostProcessor,ResourceLoaderAware, ApplicationContextAware, BundleContextAware, InitializingBean{
    private List basePackages = Arrays.asList("com.liangmixian.sms.**.feign");
    private static final String DEFAULT_RESOURCE_PATTERN = "*.class";
    private MetadataReaderFactory metadataReaderFactory;
//    private ResourcePatternResolver resourcePatternResolver;//在osgi架构中不能扫描到别的bundle,故不能用
    private ApplicationContext applicationContext;
    private List bundleApplicationContexts = new ArrayList();
    private BundleContext bundleContext;
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 获取指定包下面的类
        for (String basePackage : basePackages) {
            Set> beanClasses = scannerPackages(basePackage );
            for (Class beanClass : beanClasses) {
                //要求是feign client接口,且包含注解@EnableFeignClient
                if (!beanClass .isInterface() || !beanClass.isAnnotationPresent(FeignClient.class)) {
                    continue;
                }
                FeignClient enableFeignAnnotation = (FeignClient) beanClass.getAnnotation(FeignClient.class);
//                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClass);
                // 在这里,我们可以给该对象的属性注入对应的实例。类似mybatis,就在这里注入了dataSource和sqlSessionFactory,
                FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(beanClass);
                factoryBean.setEnableFeignClient(enableFeignAnnotation);
                try {
                    String simpleName = beanClass.getSimpleName();
                    if (simpleName.startsWith("I")) {
                        simpleName = simpleName.substring(1);
                    }
                    String beanName = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
                    beanFactory.registerSingleton(beanName, factoryBean.getObject());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 根据包路径获取包及子包下的所有类
     * 
     * @param basePackage
     *            basePackage
     * @return Set> Set>
     */
    private Set> scannerPackages(String basePackage) {
        Set> set = new LinkedHashSet>();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;
        for (ApplicationContext ac : bundleApplicationContexts) {
            try {
                Resource[] resources = ac.getResources(packageSearchPath);
                for (Resource resource : resources) {
                    if (resource.isReadable()) {
                        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                        String className = metadataReader.getClassMetadata().getClassName();
                        Class clazz;
                        try {
                            clazz = ac.getClassLoader().loadClass(className);
                            set.add(clazz);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return set;
    }

    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(applicationContext.getEnvironment().resolveRequiredPlaceholders(basePackage));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.applicationContext = applicationContext;
    }
     
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
//         this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
         this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }
    
    @Override
    public void setBundleContext(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        ServiceReference[] srs = null;
        try {
            srs = bundleContext.getAllServiceReferences("org.springframework.context.ApplicationContext", null);
        } catch (InvalidSyntaxException e) {
            throw new RuntimeException(e);
        }
        if (srs != null) {
            for (ServiceReference sr : srs) {
                bundleApplicationContexts.add((ApplicationContext) bundleContext.getService(sr));
            }
        }
    }
    
}

FeignClientFactoryBean类名也跟spring cloud封装的一样,不过不一样的是只生成Feig.Builder对象不生成feign接口的代理对象,为什么呢里边注释有说

/**
 * 接口实例工厂,这里主要是用于提供接口的实例对象
 */
public class FeignClientFactoryBean implements FactoryBean {
    private Class interfaceType;
    private FeignClient enableFeignAnnotation;
    
    public FeignClientFactoryBean(Class interfaceType) {
        this.interfaceType = interfaceType;
    }

    @Override
    public T getObject() throws Exception {
        // 这里主要是创建接口对应的实例,便于注入到spring容器中
        /*    这里直接生成feign接口的代理对象是不行的,因为启动阶段还调用不了系统参数获取其配置的url,spring cloud的openfeign在这一步就生成Feign.Builder那是获取的配置文件
        T proxy = getFeignBuilder().target(interfaceType, getUrl());
        return (T) proxy;*/
        InvocationHandler handler = new FeignClientInvocationHandler(interfaceType,enableFeignAnnotation, getFeignBuilder());
        return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(),
                  new Class[] {interfaceType},handler);
    }

    @Override
    public Class getObjectType() {
        return interfaceType;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
    
    public void setEnableFeignClient(FeignClient enableFeignAnnotation) {
        this.enableFeignAnnotation = enableFeignAnnotation;
    }
    
    private Builder getFeignBuilder(){
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
        return Feign.builder().client(new OkHttpClient())
                .contract(new JAXRSContract())
                .encoder(new JacksonEncoder(mapper))
                .decoder(new JacksonDecoder(mapper))
                .errorDecoder(new FeignExceptionDecoder())
                .logLevel(Level.BASIC)
                .options(new Request.Options(feignClientParas.connectTimeout() * 1000, feignClientParas.readTimeout() * 1000))
                .retryer(new Retryer.Default(50000, 5000, 3))
                .requestInterceptor(new RequestInterceptor() {
                    @Override
                    public void apply(RequestTemplate template) {
                        ApplicationSession applicationSession = ApplicationSessionHolder.getApplicationSession();
                        if (applicationSession != null) {
                            template.header("sessionId",applicationSession.getId());
                        }
                    }
                });
    }
}

FeignClientInvocationHandler才生成Feig接口的代理对象,在这一步才能读取数据库数据

public class FeignClientInvocationHandler implements InvocationHandler {
    private Class interfaceType;
    private FeignClient enableFeignAnnotation;
    private Builder feignBuilder;
    
    public FeignClientInvocationHandler(Class intefaceType,FeignClient enableFeignAnnotation,Builder feignBuilder) {
        this.interfaceType = intefaceType;
        this.enableFeignAnnotation = enableFeignAnnotation;
        this.feignBuilder = feignBuilder;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        T proxyObject = feignBuilder.target(interfaceType, getUrl());
        return method.invoke(proxyObject, args);
        // 这里可以得到参数数组和方法等,可以通过反射,注解等,进行结果集的处理
        // mybatis就是在这里获取参数和相关注解,然后根据返回值类型,进行结果集的转换
    }
    
    private String getUrl() throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        if (enableFeignAnnotation.urlParaType() == FeignClient.SYS_PARAMETER){
            Object sysParameterDao = BeanFactoryHolder.get().getBean("sysParameterDaoImp");
            //用反射解决跨bundle报错ClassNotFoundException: ISysParameterDao或SysParameter cannot be found by com.liangmixian.liangmixianibusiness.common_1.0.0
            Method method = sysParameterDao.getClass().getMethod("findSysParameter",String.class);
            Object sysParameter = method.invoke(sysParameterDao, enableFeignAnnotation.urlPara());
            Method getValueMethod = sysParameter.getClass().getMethod("getValue");
            String paraValue = (String)getValueMethod.invoke(sysParameter);
            String urlValue = paraValue;
            if (!StringUtils.isEmpty(urlValue)) {
                return urlValue + enableFeignAnnotation.path();
            }
            Method getDefaultValueMethod = sysParameter.getClass().getMethod("getDefaultValue");
            return (String)getDefaultValueMethod.invoke(sysParameter) + enableFeignAnnotation.path();
        } else if (enableFeignAnnotation.urlParaType() == FeignClient.LOCAL_PROPERTIES){
            //自己扩展
//            String url = new Properties("***/*.properties").getValue(enableFeignAnnotation.urlPara());
            return "";
        }
        return enableFeignAnnotation.urlPara() + enableFeignAnnotation.path();
    }
}

使用注意事项

  • 增加的feign接口一定要用注解@FeignClient,且所属包路径符合规则:"com.liangmixian.sms.**.feign"

  • 新增了feign接口的当前bundle一定要实例化FeignClientsRegistrar

"com.liangmixian.remote.http.feign.FeignClientsRegistrar" lazy-init="false" />

  • 第一次本地启动注意发布一下依赖的jar

附其他测试用例:

//-----------------------------------------------------------测试客户端-------------------------------------------------------------------
@liangmixianFeignClient(urlPara = "feign.test.url")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/test-fegin")
public interface FeignJaxrsTestService {
    
    @Path("/queryPeople")
    @GET
    Long queryPeopleIdById(@QueryParam("typeId") Long typeId);
    
    @Path("/queryPeopleByTypeId/{typeId}")
    @GET
    List queryPeopleByTypeId(@PathParam("typeId") Long typeId);
    
    @Path("/add")
    @POST
    People addPeople(People People);
    
    @Path("/queryHugePeople")
    @GET
    PeopleType queryHugePeopleType();
}
 
public class People implements Serializable {
    
    public People(){}
    private Long id;

    private String name;

    private PeopleType type;

    public PeopleType getType() {
        return type;
    }

    public void setTyepe(PeopleType type) {
        this.type = type;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}


public enum PeopleType {
    MAN,
    WOMAN;

    public static PeopleType of(Integer typeId) {
        if(MAN.ordinal()==typeId) {
            return MAN;
        } else {
            return WOMAN;
        }
    }
}
 
//-----------------------------------------------------------测试服务端-------------------------------------------------------------------
@Controller
@RequestMapping(value="/test-fegin")
public class TestFeignController {
   @ResponseBody
   @RequestMapping(value = "/queryPeople",method={RequestMethod.GET})
   Long queryPeopleIdById(@RequestParam(value = "typeId") Long id){
      return id + 1;
   }

   @ResponseBody
   @RequestMapping(value = "/queryPeopleByTypeId/{typeId}",method={RequestMethod.GET})
   List queryPeopleByTypeId(@PathVariable("typeId") Integer typeId){
      People p1 = new People();
      p1.setTyepe(PeopleType.of(typeId));
      p1.setId(1000L);
      p1.setName("董小姐");

      People p2 = new People();
      p2.setTyepe(PeopleType.of(typeId));
      p2.setId(2000L);
      p2.setName("蔡嫡妃");
      return Arrays.asList(p1,p2);
   }

   @ResponseBody
   @RequestMapping(value = "/add",method={RequestMethod.POST})
   People addPeople(@RequestBody People People){
      People.setId(2222L);
      return People;
   }

   @ResponseBody
   @RequestMapping(value = "/queryHugePeople",method={RequestMethod.GET})
   PeopleType queryHugePeople(){
      return PeopleType.MAN;
   }

}

你可能感兴趣的:(java,spring,架构,java)