feign 作为 springcloud 微服务 内部通信的组件 还是有很多坑的
坑1、
Load balancer does not have available server for client
这是因为 默认的eureka 启动时 相关的服务端还没有来得及往 eureka 服务端注册 或者 eureka server中没有注册 相关的服务
坑2、看了很多 教程 说 feign 类上的 @RequestMapping 不会被加入 接口映射 没错 想想也应该是这样 但是为了解决这个问题 我在接口方法上加入映射路径是否可行呢
可以做个 测试 我这里有 2个服务 MESSAGE-SERVICE 是服务提供者 SMS-SERVICE 是 服务消费者 feignware 单独模块 用来统一提供 相互调用的api 生产中建议feign集中配置 一个eureka server
MESSAGE-SERVICE 提供接口 :
@RestController
@RequestMapping("/msg")
public class UserController {
@PostMapping("/get")
public User getUser(@RequestBody User user) {
System.out.println("i am message-service <<<<<<<<<<<<<<<<<<<<");
System.out.println(user);
return user;
}
}
feignware : 这样写 直接加入 类一级的/msg
@FeignClient(value = "MEMBER-SERVICE")
public interface UserService {
@PostMapping("/msg/get")
User get(@RequestBody User user);
}
SMS-SERVICE 调用接口:
@RestController
@RequestMapping("/sms")
public class RemoteController {
@Resource
private UserService userService;
@GetMapping("/go")
public User test(User user){
System.out.println(">>>>>>>>>>>");
User result=userService.get(user);
System.out.println(result);
return result;
}
}
流程是这样的 :
执行结果: 请求成功
去掉了 feignware 中 /msg 一级后 测试 结果:
结果说明: feign 类上的 @RequestMapping 不会被加入 接口映射 是对的 如果 想加入正好上面是个解决方案
坑3、 feign 调用是 post 请求 还是上面的例子 测试看看 先说明一句 看到有的教程上说
feign只支持 @RequestMapping 这种说法是不对 上面已经有例子证明了这一点 起码上 @GetMapping @PostMapping 是支持的 其他 springcloud版本没有试过
我们先三者都采用@GetMapping 看看结果:
405 到底是哪里不支持 get呢 来几组测试:
sms get feignware get message get 结果 405
sms get feignware post message get 结果 405
sms post feignware post message get 结果 405
sms post feignware get message post 结果 200
sms post feignware post message post 结果 200
sms get feignware post message post 结果 200
sms get feignware get message post 结果 200
结果总结一下 就是 @RequestBody 调用提供方 一定要用post feign包 跟消费方 无所谓
坑4、 无法扫描到引用包的feign接口
在实际的生产中 我们服务模块是有很多的, 如果 A的接口 B要调用 我们声明一次feigin客户端 api 然后C也要调A的接口 我们还要声明一次feigin客户端api 如果还有更多 就会有很多重复代码 为了提高代码复用,我们往往单独声明一个包来引入feigin 的api 其他包如果调用的化引入到工程中就行了 但是 问题来了 引用包无法被直接扫描到 我们知道 一个微服务模块 如果需要开启feign调用功能 需要加上
@EnableFeignClients
源码:
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.feign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Scans for interfaces that declare they are feign clients (via {@link FeignClient
* <code>@FeignClient</code>}). Configures component scanning directives for use with
* {@link org.springframework.context.annotation.Configuration
* <code>@Configuration</code>} classes.
*
* @author Spencer Gibb
* @author Dave Syer
* @since 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
*
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
*
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
* @return
*/
Class<?>[] clients() default {};
}
其中basePackages 声明 扫描 feignclient 注解所在的包的包路径 声明后就能扫描到你在该包下@FeignClient 标记的 feign接口
坑5、 无法扫描到引入包的服务降级实现,大多数情况 我们要对feignClient接口 显式声明一个fallback 以便进行服务降级 但是如果你的feignclient 接口 不在 springboot 的启动类的子类 会无法启动 显示
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean
with name 'com.xxx.feign.api.UserService':
FactoryBean threw exception on object creation;
nested exception is java.lang.IllegalStateException:
No fallback instance of type class com.xxx.feign.api.UserServiceHystrix found for feign client MEMBER-SERVICE
也就是你feign 接口的实现类 无法被注入
先看一下 源码:
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.feign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Annotation for interfaces declaring that a REST client with that interface should be
* created (e.g. for autowiring into another component). If ribbon is available it will be
* used to load balance the backend requests, and the load balancer can be configured
* using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
*
* @author Spencer Gibb
* @author Venil Noronha
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
/**
* The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}.
*/
@AliasFor("name")
String value() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*
* @deprecated use {@link #name() name} instead
*/
@Deprecated
String serviceId() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*/
@AliasFor("value")
String name() default "";
/**
* Sets the <code>@Qualifier</code> value for the feign client.
*/
String qualifier() default "";
/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";
/**
* Whether 404s should be decoded instead of throwing FeignExceptions
*/
boolean decode404() default false;
/**
* A custom <code>@Configuration</code> for the feign client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] configuration() default {};
/**
* Fallback class for the specified Feign client interface. The fallback class must
* implement the interface annotated by this annotation and be a valid spring bean.
*/
Class<?> fallback() default void.class;
/**
* Define a fallback factory for the specified Feign client interface. The fallback
* factory must produce instances of fallback classes that implement the interface
* annotated by {@link FeignClient}. The fallback factory must be a valid spring
* bean.
*
* @see feign.hystrix.FallbackFactory for details.
*/
Class<?> fallbackFactory() default void.class;
/**
* Path prefix to be used by all method-level mappings. Can be used with or without
* <code>@RibbonClient</code>.
*/
String path() default "";
/**
* Whether to mark the feign proxy as a primary bean. Defaults to true.
*/
boolean primary() default true;
}
* Fallback class for the specified Feign client interface. The fallback class must
implement the interface annotated by this annotation and be a valid spring bean. *
fallback 上会有这样的注释 说的是 声明feign客户端接口 的降级类 而且 这个降级类必须实现 该feign 接口 并且必须是一个可用的spring bean
如果你仅仅在这个实现类上加入spring bean 声明注解 比如 @Component 你会发现依然 无法注入 来大致猜想一下流程 熟悉springboot 的 应该清楚 springboot 启动的时候 会扫描其main类 所在包的子包进行 bean 实例化 如果不在子包 默认是扫描不到的 那么如何扫描到呢 声明扫描的路径 也就是需要在main类上使用注解@ComponentScan 注解 但是 如果 我们仅仅声明了 feign 降级实现的路径 你会发现 main类的子包无法扫描到了 所以 此处应该
@ComponentScan(basePackages = {"main 所在的包","降级类所在的包"})
配置好后 我们写一个降级类:
@Component
public class UserServiceHystrix implements UserService {
@Override
public User get(User user) {
System.out.println("<><><><><><><><><><> MEMBER-SERVICE 挂了<><><><><><><><><><> ");
user.setAge(20017);
user.setGender("male");
user.setName("服务挂了");
return user;
}
}
然后我们测试一下 启动消费方 不启动 提供方MEMBER-SERVICE 发现熔断可用: