SpringCloud feign 服务访问 404问题

#SpringCloud feign 服务访问 404问题 SpringCloud微服务分布式 311106863 ##一、问题 ###1.问题说明 feignClient默认只能在方法的RequestMapping 上加全部url路径,

类的RequestMapping,不能合并到方法上;

http://www.jianshu.com/p/191d45210d16 详见问题十 ###2.错误及代码示例 如:访问全路径url时/sys/user/login时,导致找不到服务,出现404错误。

api服务网关或消费者返回错误信息

Caused by: feign.FeignException: status 404 reading UserClient#login(String); content:
{"timestamp":1484022879514,"status":404,"error":"Not Found","message":"No message available","path":"/login"}
at feign.FeignException.errorStatus(FeignException.java:62)

示例代码

@FeignClient(value = "sys-service",configuration = FeignClientsConfiguration.class)
@RequestMapping("/sys/user")
public interface UserClient extends BaseFeignClient<User, Long> {
    @CustomScript
    /**
     * 根据用户名,查询用户信息
     */
    @RequestMapping(value = "/login")
    @RequiresPermissions(PermissionUtils.VIEW)
    @ResponseBody
    BaseResponse<User> login(@RequestParam("loginName")String loginName...);

}

##二、解决方案 ###1. 合并类和方法的url

SpringMvcContractCustom 拷贝自SpringMvcContract,增加类和方法url合并

###2. 增加自定义配置类

FeignClientsConfigurationCustom 拷贝自FeignClientsConfiguration
修改feignContract 方法  return new SpringMvcContractCustom

###3. feign客户端增加自定义配置类

@FeignClient(value = "sys-service",configuration = FeignClientsConfigurationCustom.class)

##三、修改代码示意 SpringMvcContractCustom 拷贝自SpringMvcContract

去掉类的url path

protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
//hongliangpan remove this line
                    //data.template().insert(0, pathValue);

合并方法和类的path

//note hongliangpan add merge  RequestMapping path and method path
    protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation,
                                             Method method) {

//note hongliangpan add merge class path and method path
                RequestMapping anno = method.getDeclaringClass().getAnnotation(RequestMapping.class);
                if (anno != null && anno.value().length > 0) {
                    String basePath = anno.value()[0];
                    if (!Strings.isNullOrEmpty(basePath) && !pathValue.startsWith(basePath)) {
                        pathValue = basePath + pathValue;
                    }
                }
                data.template().append(pathValue);

配置类FeignClientsConfigurationCustom

@Bean
	@ConditionalOnMissingBean
	public Contract feignContract(ConversionService feignConversionService) {
		return new SpringMvcContractCustom(this.parameterProcessors, feignConversionService);
	}

##四、完整代码

###1. FeignClientsConfigurationCustom

import java.util.ArrayList;
import java.util.List;

import com.glodon.cloud.feign.SpringMvcContractCustom;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor;
import org.springframework.cloud.netflix.feign.DefaultFeignLoggerFactory;
import org.springframework.cloud.netflix.feign.FeignFormatterRegistrar;
import org.springframework.cloud.netflix.feign.FeignLoggerFactory;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.cloud.netflix.feign.support.SpringDecoder;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.cloud.netflix.feign.support.SpringMvcContract;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;

import com.netflix.hystrix.HystrixCommand;

import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.hystrix.HystrixFeign;

/**
 * @author Dave Syer
 * @author Venil Noronha
 */
@Configuration
public class FeignClientsConfigurationCustom {

	@Autowired
	private ObjectFactory<HttpMessageConverters> messageConverters;

	@Autowired(required = false)
	private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();

	@Autowired(required = false)
	private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

	@Autowired(required = false)
	private Logger logger;

	@Bean
	@ConditionalOnMissingBean
	public Decoder feignDecoder() {
		return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
	}

	@Bean
	@ConditionalOnMissingBean
	public Encoder feignEncoder() {
		return new SpringEncoder(this.messageConverters);
	}

	@Bean
	@ConditionalOnMissingBean
	public Contract feignContract(ConversionService feignConversionService) {
		return new SpringMvcContractCustom(this.parameterProcessors, feignConversionService);
	}

	@Bean
	public FormattingConversionService feignConversionService() {
		FormattingConversionService conversionService = new DefaultFormattingConversionService();
		for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
			feignFormatterRegistrar.registerFormatters(conversionService);
		}
		return conversionService;
	}

	@Configuration
	@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
	protected static class HystrixFeignConfiguration {
		@Bean
		@Scope("prototype")
		@ConditionalOnMissingBean
		@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
		public Feign.Builder feignHystrixBuilder() {
			return HystrixFeign.builder();
		}
	}

	@Bean
	@Scope("prototype")
	@ConditionalOnMissingBean
	public Feign.Builder feignBuilder() {
		return Feign.builder();
	}

	@Bean
	@ConditionalOnMissingBean(FeignLoggerFactory.class)
	public FeignLoggerFactory feignLoggerFactory() {
		return new DefaultFeignLoggerFactory(logger);
	}

}

###2. SpringMvcContractCustom

import com.google.common.base.Strings;
import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
import feign.Param;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.PathVariableParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.RequestHeaderParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.RequestParamParameterProcessor;
import org.springframework.cloud.netflix.feign.support.SpringMvcContract;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;

import static feign.Util.checkState;
import static feign.Util.emptyToNull;
import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;

/**
 * @author Spencer Gibb
 */
//@Component
public class SpringMvcContractCustom extends Contract.BaseContract
        implements ResourceLoaderAware {

    private static final String ACCEPT = "Accept";

    private static final String CONTENT_TYPE = "Content-Type";

    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    private final Map<Class<? extends Annotation>, AnnotatedParameterProcessor> annotatedArgumentProcessors;
    private final Map<String, Method> processedMethods = new HashMap<>();

    private final ConversionService conversionService;
    private final Param.Expander expander;
    private ResourceLoader resourceLoader = new DefaultResourceLoader();

    public SpringMvcContractCustom() {
        this(Collections.<AnnotatedParameterProcessor>emptyList());
    }

    public SpringMvcContractCustom(
            List<AnnotatedParameterProcessor> annotatedParameterProcessors) {
        this(annotatedParameterProcessors, new DefaultConversionService());
    }

    public SpringMvcContractCustom(
            List<AnnotatedParameterProcessor> annotatedParameterProcessors,
            ConversionService conversionService) {
        Assert.notNull(annotatedParameterProcessors,
                "Parameter processors can not be null.");
        Assert.notNull(conversionService, "ConversionService can not be null.");

        List<AnnotatedParameterProcessor> processors;
        if (!annotatedParameterProcessors.isEmpty()) {
            processors = new ArrayList<>(annotatedParameterProcessors);
        } else {
            processors = getDefaultAnnotatedArgumentsProcessors();
        }
        this.annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
        this.conversionService = conversionService;
        this.expander = new SpringMvcContract.ConvertingExpander(conversionService);
    }

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

    @Override
    protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
        if (clz.getInterfaces().length == 0) {
            RequestMapping classAnnotation = findMergedAnnotation(clz,
                    RequestMapping.class);
            if (classAnnotation != null) {
                // Prepend path from class annotation if specified
                if (classAnnotation.value().length > 0) {
                    String pathValue = emptyToNull(classAnnotation.value()[0]);
                    pathValue = resolve(pathValue);
                    if (!pathValue.startsWith("/")) {
                        pathValue = "/" + pathValue;
                    }
                    //hongliangpan remove this line
                    //data.template().insert(0, pathValue);
                }
            }
        }
    }

    @Override
    public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        this.processedMethods.put(Feign.configKey(targetType, method), method);
        MethodMetadata md = super.parseAndValidateMetadata(targetType, method);

        RequestMapping classAnnotation = findMergedAnnotation(targetType,
                RequestMapping.class);
        if (classAnnotation != null) {
            //hongliangpan add
            //md = super.parseAndValidateMetadata(targetType, method);
            // produces - use from class annotation only if method has not specified this
            if (!md.template().headers().containsKey(ACCEPT)) {
                parseProduces(md, method, classAnnotation);
            }
            // consumes -- use from class annotation only if method has not specified this
            if (!md.template().headers().containsKey(CONTENT_TYPE)) {
                parseConsumes(md, method, classAnnotation);
            }

            // headers -- class annotation is inherited to methods, always write these if
            // present
            parseHeaders(md, method, classAnnotation);
        }
        return md;
    }

    @Override
    //note hongliangpan add merge  RequestMapping path and method path
    protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation,
                                             Method method) {
        if (!(methodAnnotation instanceof RequestMapping)) {
            return;
        }

        RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
        // HTTP Method
        RequestMethod[] methods = methodMapping.method();
        if (methods.length == 0) {
            methods = new RequestMethod[]{RequestMethod.GET};
        }
        checkOne(method, methods, "method");
        data.template().method(methods[0].name());

        // path
        checkAtMostOne(method, methodMapping.value(), "value");
        if (methodMapping.value().length > 0) {
            String pathValue = emptyToNull(methodMapping.value()[0]);
            if (pathValue != null) {
                pathValue = resolve(pathValue);
                // Append path from @RequestMapping if value is present on method
                if (!pathValue.startsWith("/")
                        && !data.template().toString().endsWith("/")) {
                    pathValue = "/" + pathValue;
                }
                //note hongliangpan add merge class path and method path
                RequestMapping anno = method.getDeclaringClass().getAnnotation(RequestMapping.class);
                if (anno != null && anno.value().length > 0) {
                    String basePath = anno.value()[0];
                    if (!Strings.isNullOrEmpty(basePath) && !pathValue.startsWith(basePath)) {
                        pathValue = basePath + pathValue;
                    }
                }
                data.template().append(pathValue);
            }
        }

        // produces
        parseProduces(data, method, methodMapping);

        // consumes
        parseConsumes(data, method, methodMapping);

        // headers
        parseHeaders(data, method, methodMapping);

        data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>());
    }

    private String resolve(String value) {
        if (StringUtils.hasText(value)
                && this.resourceLoader instanceof ConfigurableApplicationContext) {
            return ((ConfigurableApplicationContext) this.resourceLoader).getEnvironment()
                    .resolvePlaceholders(value);
        }
        return value;
    }

    private void checkAtMostOne(Method method, Object[] values, String fieldName) {
        checkState(values != null && (values.length == 0 || values.length == 1),
                "Method %s can only contain at most 1 %s field. Found: %s",
                method.getName(), fieldName,
                values == null ? null : Arrays.asList(values));
    }

    private void checkOne(Method method, Object[] values, String fieldName) {
        checkState(values != null && values.length == 1,
                "Method %s can only contain 1 %s field. Found: %s", method.getName(),
                fieldName, values == null ? null : Arrays.asList(values));
    }

    @Override
    protected boolean processAnnotationsOnParameter(MethodMetadata data,
                                                    Annotation[] annotations, int paramIndex) {
        boolean isHttpAnnotation = false;

        AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(
                data, paramIndex);
        Method method = this.processedMethods.get(data.configKey());
        for (Annotation parameterAnnotation : annotations) {
            AnnotatedParameterProcessor processor = this.annotatedArgumentProcessors
                    .get(parameterAnnotation.annotationType());
            if (processor != null) {
                Annotation processParameterAnnotation;
                // synthesize, handling @AliasFor, while falling back to parameter name on
                // missing String #value():
                processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(
                        parameterAnnotation, method, paramIndex);
                isHttpAnnotation |= processor.processArgument(context,
                        processParameterAnnotation);
            }
        }
        if (isHttpAnnotation && data.indexToExpander().get(paramIndex) == null
                && !isMultiValued(method.getParameterTypes()[paramIndex])
                && this.conversionService.canConvert(
                method.getParameterTypes()[paramIndex], String.class)) {
            data.indexToExpander().put(paramIndex, this.expander);
        }
        return isHttpAnnotation;
    }

    private boolean isMultiValued(Class<?> type) {
        // Feign will deal with each element in a collection individually (with no
        // expander as of 8.16.2, but we'd rather have no conversion than convert a
        // collection to a String (which ends up being a csv).
        return Collection.class.isAssignableFrom(type);
    }

    private void parseProduces(MethodMetadata md, Method method,
                               RequestMapping annotation) {
        checkAtMostOne(method, annotation.produces(), "produces");
        String[] serverProduces = annotation.produces();
        String clientAccepts = serverProduces.length == 0 ? null
                : emptyToNull(serverProduces[0]);
        if (clientAccepts != null) {
            md.template().header(ACCEPT, clientAccepts);
        }
    }

    private void parseConsumes(MethodMetadata md, Method method,
                               RequestMapping annotation) {
        checkAtMostOne(method, annotation.consumes(), "consumes");
        String[] serverConsumes = annotation.consumes();
        String clientProduces = serverConsumes.length == 0 ? null
                : emptyToNull(serverConsumes[0]);
        if (clientProduces != null) {
            md.template().header(CONTENT_TYPE, clientProduces);
        }
    }

    private void parseHeaders(MethodMetadata md, Method method,
                              RequestMapping annotation) {
        // TODO: only supports one header value per key
        if (annotation.headers() != null && annotation.headers().length > 0) {
            for (String header : annotation.headers()) {
                int index = header.indexOf('=');
                md.template().header(resolve(header.substring(0, index)),
                        resolve(header.substring(index + 1).trim()));
            }
        }
    }

    private Map<Class<? extends Annotation>, AnnotatedParameterProcessor> toAnnotatedArgumentProcessorMap(
            List<AnnotatedParameterProcessor> processors) {
        Map<Class<? extends Annotation>, AnnotatedParameterProcessor> result = new HashMap<>();
        for (AnnotatedParameterProcessor processor : processors) {
            result.put(processor.getAnnotationType(), processor);
        }
        return result;
    }

    private List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {

        List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();

        annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
        annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
        annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());

        return annotatedArgumentResolvers;
    }

    private Annotation synthesizeWithMethodParameterNameAsFallbackValue(
            Annotation parameterAnnotation, Method method, int parameterIndex) {
        Map<String, Object> annotationAttributes = AnnotationUtils
                .getAnnotationAttributes(parameterAnnotation);
        Object defaultValue = AnnotationUtils.getDefaultValue(parameterAnnotation);
        if (defaultValue instanceof String
                && defaultValue.equals(annotationAttributes.get(AnnotationUtils.VALUE))) {
            Type[] parameterTypes = method.getGenericParameterTypes();
            String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
            if (shouldAddParameterName(parameterIndex, parameterTypes, parameterNames)) {
                annotationAttributes.put(AnnotationUtils.VALUE,
                        parameterNames[parameterIndex]);
            }
        }
        return AnnotationUtils.synthesizeAnnotation(annotationAttributes,
                parameterAnnotation.annotationType(), null);
    }

    private boolean shouldAddParameterName(int parameterIndex, Type[] parameterTypes, String[] parameterNames) {
        // has a parameter name
        return parameterNames != null && parameterNames.length > parameterIndex
                // has a type
                && parameterTypes != null && parameterTypes.length > parameterIndex
                // and it is a simple property
                && BeanUtils.isSimpleProperty(parameterTypes[parameterIndex].getClass());
    }

    private class SimpleAnnotatedParameterContext
            implements AnnotatedParameterProcessor.AnnotatedParameterContext {

        private final MethodMetadata methodMetadata;

        private final int parameterIndex;

        public SimpleAnnotatedParameterContext(MethodMetadata methodMetadata,
                                               int parameterIndex) {
            this.methodMetadata = methodMetadata;
            this.parameterIndex = parameterIndex;
        }

        @Override
        public MethodMetadata getMethodMetadata() {
            return this.methodMetadata;
        }

        @Override
        public int getParameterIndex() {
            return this.parameterIndex;
        }

        @Override
        public void setParameterName(String name) {
            nameParam(this.methodMetadata, name, this.parameterIndex);
        }

        @Override
        public Collection<String> setTemplateParameter(String name,
                                                       Collection<String> rest) {
            return addTemplatedParam(rest, name);
        }
    }

    public static class ConvertingExpander implements Param.Expander {

        private final ConversionService conversionService;

        public ConvertingExpander(ConversionService conversionService) {
            this.conversionService = conversionService;
        }

        @Override
        public String expand(Object value) {
            return this.conversionService.convert(value, String.class);
        }

    }

}

转载于:https://my.oschina.net/itnms/blog/821962

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这里有一个简单的Spring Cloud Feign集成Protobuf的示例: 1. 首先,您需要在pom.xml文件中添加以下依赖项: ```xml <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty-shaded</artifactId> <version>1.37.0</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>1.37.0</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>1.37.0</version> </dependency> ``` 2. 创建Protobuf定义文件 在src/main/proto目录下创建一个名为`hello.proto`的文件,并添加以下内容: ```protobuf syntax = "proto3"; package io.github.xyz.spring.cloud.feign.protobuf.example.api; message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } service HelloService { rpc hello(HelloRequest) returns (HelloResponse); } ``` 3. 生成Java代码 在pom.xml文件中添加以下插件,用于生成Java代码: ```xml <build> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.5.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.12.4:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.37.0:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` 然后运行`mvn protobuf:compile`和`mvn protobuf:compile-custom`,将生成的Java代码放在`target/generated-sources/protobuf`目录下。 4. 创建Feign客户端 创建一个Feign客户端,用于调用HelloService服务。在使用Protobuf时,需要使用`@RequestLine`注解,以指定使用哪个HTTP方法,并使用`@Headers`注解,以指定使用哪种消息类型。 ```java @FeignClient(name = "hello-service", configuration = HelloClientConfiguration.class) public interface HelloClient { @RequestLine("POST /hello") @Headers({"Content-Type: application/x-protobuf", "Accept: application/x-protobuf"}) HelloResponse hello(HelloRequest request); } ``` 5. 创建Feign配置 创建一个Feign配置类,用于配置Feign客户端。在使用Protobuf时,需要将`Encoder`和`Decoder`设置为`ProtobufEncoder`和`ProtobufDecoder`,并将`Content-Type`和`Accept`设置为`application/x-protobuf`。 ```java @Configuration public class HelloClientConfiguration { @Bean public Encoder protobufEncoder() { return new ProtobufEncoder(); } @Bean public Decoder protobufDecoder() { return new ProtobufDecoder(); } @Bean public Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } @Bean public Request.Options options() { return new Request.Options(5000, 10000); } } ``` 6. 创建服务端 创建一个服务端,用于提供HelloService服务。在使用Protobuf时,需要使用`@RequestBody`和`@ResponseBody`注解,以指定使用哪种消息类型。 ```java @RestController public class HelloController { @PostMapping("/hello") public HelloResponse hello(@RequestBody HelloRequest request) { return HelloResponse.newBuilder() .setMessage("Hello, " + request.getName() + "!") .build(); } } ``` 7. 配置Swagger 使用Swagger来测试Feign客户端。在Spring Boot应用中,可以使用Springfox Swagger来配置Swagger。 ```java @Configuration @EnableSwagger2 public class SwaggerConfiguration { @Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); } } ``` 8. 启动应用 现在,您可以启动应用并访问http://localhost:8080/swagger-ui.html来测试Feign客户端了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值