Feign客户端依赖类似Eureka等配置中心,使用Feign客户端之前,首先要配置好Eureka配置中心。
1、引入Feign依赖的方式
Feign客户端的依赖,需要在RPG客户端和服务端两端的pom.xml文件中都要同时引入同样的依赖。
需要特别注意的就是,Feign客户端依赖Eureka等注册中心,需要配置好类似Eureka等注册中心,才能配置Feign客户端来使用。
Feign客户端的依赖如下:
<!-- Feign客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
引入这个依赖的同时,最好引入Spring Cloud依赖:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2、服务端实现
2.1、Controller类
服务端直接写Controller类,然后注入IOC容器中。
需要特别注意的就是,不论在Controller类的类上还是请求方法上,所有的请求路径注解配置,都不能用类似GetMapping、PostMapping等之类,只能使用RequestMapping。
举例如下:
@Log4j2
@RestController
@RequestMapping(value = "/ocpp/chargepoint")
public class ChargePointController {
@RequestMapping(value = "", method = RequestMethod.GET)
public JsonResponse test(){
JsonResponse jr = new JsonResponse();
jr.setMsg("Infos From ocpp-protocol-server");
log.info("ChargePointController test jr: " + JSON.toJSONString(jr));
return jr;
}
@ApiIgnore
@RequestMapping(value = "/page", method = RequestMethod.GET)
public JsonResponse getByPage(@RequestParam(value = "pageNum") int pageNum,
@RequestParam(value = "pageSize") int pageSize,
@RequestParam(value = "chargePointCode") String chargePointCode){
JsonResponse jr = new JsonResponse();
log.info("ChargePointController getByPage jr: " + jr);
return jr;
}
}
2.2、main启动方法
在Application上要添加两个注解:@EnableDiscoveryClient、@EnableFeignClients,举例如下:
@EnableDiscoveryClient
@EnableEurekaClient
@EnableCaching // 启用缓存功能
@EnableScheduling // 开启定时任务功能
@MapperScan(basePackages = "com.charge")
@EnableFeignClients
@SpringBootApplication(scanBasePackages = "com.charge", exclude = SecurityAutoConfiguration.class)
public class ChargeSericeApp {
@Bean
public OCPP1_6JSONServer ocpp1_6JSONServer(){
return new OCPP1_6JSONServer();
}
public static void main(String[] args) {
SpringApplication.run(ChargeSericeApp.class, args);
}
}
注意,服务端启动后,要经过至少1分钟后,客户端才能访问到!!!
2.3、yml中定义服务端的服务名称
在服务端的pom.xml文件中定义服务的名称,举例如下:
spring:
application:
name: ocpp-protocol-server
这里的“ocpp-protocol-server”就是服务端的服务名称
3、客户端实现
3.1、编写Feign客户端请求接口类
需要编写一个interface类型的接口,并且在接口上著名服务端的服务名称,如上文2.3的服务名称为yml配置中的spring.application.name的“ocpp-protocol-server”,这两个名称必须一致!!!。
需要特别注意的就是,不论在Feign接口类的类上还是请求方法上,都不能用类似GetMapping、PostMapping等之类,只能使用RequestMapping。
如下例子:
@FeignClient(name = "ocpp-protocol-server")
@RequestMapping(value = "/ocpp/chargepoint")
public interface IOCPPProtoclFeign {
@RequestMapping(value = "", method = RequestMethod.GET)
public Result test();
@ApiIgnore
@RequestMapping(value = "/page", method = RequestMethod.GET)
public abstract Result getByPage(@RequestParam(value = "pageNum") int pageNum,
@RequestParam(value = "pageSize") int pageSize,
@RequestParam(value = "chargePointCode") String chargePointCode);
}
注意,如果启动后一直没有在IOC容器中注入Feign客户端的bean,那么,就必须在“main启动方法”中的“@EnableFeignClients”指定扫码的包!!!
例如:@EnableFeignClients("com.cbest.bee.res.api.controllerApi")
3.2、main启动方法
在Application上要添加两个注解:@EnableDiscoveryClient、@EnableFeignClients,举例如下:
@EnableDiscoveryClient
@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
public class ManagerSystemApplication {
public static void main(String[] args) {
SpringApplication.run(ManagerSystemApplication.class, args);
}
}
3.3、yml中定义服务端的服务名称
客户端也需要定义好服务的名称,这样才能在注册中心找到彼此。如下所示:
spring:
application:
name: ocpp-manager-system
3.4、配置Feign超时时间
在客户端的pom.xml中,配置Feign超时时间,如下:
################### Feign客户端配置超时时间 ###################
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
3.5、客户端调用服务端
在客户端的Controller类中从IOC容器中获取到Feign客户端请求接口类的实现,然后用来和普通方法的使用一样来和Feign服务端进行RPC远程调用通信,举例如下:
@Log4j2
@RestController
@RequestMapping("/chargepoint")
public class ChargePointController {
// 从IOC容器中获取到Feign客户端的实现
@Autowired
private IOCPPProtoclFeign iocppProtoclFeign;
@GetMapping("/t2")
public Result t2(HttpServletResponse response) {
response.addHeader("Access-Allow-Control-Origin", "*");
// 使用Feign调用另一个服务的执行请求
Result result = iocppProtoclFeign.test();
log.info("TestController t2 result: " + JSON.toJSONString(result));
return result;
}
}
4、一个微服务提供多个Feign远程调用接口给其他微服务调用(服务端实现)
yml 配置(客户端、服务端都要配置):
spring:
application:
main:
allow-bean-definition-overriding: true ##允许统一微服务多个远程调用接口
IFeignArticleController.java 配置(第一个Feign文件,客户端配置):
// name 远程调用微服务名字; contextId: 一个微服务提供多个Feign接口时使用这个区别(相当于远程接口的id); path : 请求路径
@Component
@FeignClient(name = "article-server",contextId = "article-server",path = "/article")
public interface IFeignArticleController {
@ApiOperation("根据用户名获取用户的博客数据")
@GetMapping("/api/feign/userBlogInfo/{userId}")
List<Map<String, Object>> userBlogInfo(@PathVariable("userId") String userId);
}
注意:“path = "/article"”根据自己情况可以不要。
IFeignColumnController.java 配置(第二个Feign文件,客户端配置):
//警告: 在同一个微服务里面,无论有多少个Feign文件,@FeignClient注解里面的name、path两个属性的必须保持一致,contextId 不能重复,否则其他微服务调用的时候必然会出现问题
@Component
@FeignClient(name = "article-server",contextId = "column-server", path = "/article")
public interface IFeignColumnController {
@ApiOperation("根据分栏id查询分栏详情")
@GetMapping("api/feign/column/{columnId}")
Column view(@PathVariable("columnId") String columnId);
}
注意:“path = "/article"”根据自己情况可以不要。
具体方法实现
上面只是定义了两个接口而已,还没有实现具体的方法,接下来请看如何实现:
IFeignColumnController.java接口的实现
@Api(value = "Feign-被远程调用的分栏微服务接口", tags = "Feign-被远程调用的分栏微服务接口")
@RestController
public class FeignColumnController implements IFeignColumnController {
private final IColumnService columnService;
public FeignColumnController(IColumnService columnService) {
this.columnService = columnService;
}
@Override
public Column view(String columnId) {
return columnService.getById(columnId);
}
}
IFeignArticleController.java接口的实现
@Api(value = "Feign-被远程调用的文章微服务接口",tags = "Feign-被远程调用的文章微服务接口")
@RestController
public class FeignArticleController implements IFeignArticleController {
private final ILabelService labelService;
private final IArticleService articleService;
public FeignArticleController(ILabelService labelService, IArticleService articleService) {this.labelService = labelService;
this.articleService = articleService;
}
@Override
public List<Map<String, Object>> userBlogInfo(String userId) {
return articleService.userBlogInfo(userId);
}
}
经过上面的配置,就可以满足 一个微服务提供多个Feign接口 这个需求
客户端调用方式
IFeignArticleController.java、IFeignColumnController.java放在服务端微服务、客户端微服务都能共同引用的基础工程中,这样,在服务端能够让其Controller类实现,客户端也能直接注入这两个类来直接调用。
5、注意事项
(1)Feign的客户端和服务端启动以后,需要等待大概1分钟以后,执行请求才能成功。
(2)Feign不支持直接使用对象作为参数请求
接口中如果有多参数需要用实体接收,要么把参数一个一个摆开,要么在对象参数上加上@RequestBody注解,让其以json方式接收,如:
@PostMapping("/account/insert")ResultData<String> insert(@RequestBody AccountDTO accountDTO);
(3)消费者模块启动类上使用@EnableFeignClients注解后,建议最好要指明Feign接口所在的包路径
如:@EnableFeignClients(basePackages = "com.xxxxx.feign.*")
(4)@RequestParam的坑在Feign接口层使用@RequestParam注解要注意,一定要加上value属性,
如:ResultData delete(@RequestParam("accountCode") String accountCode);
否则你会看到类似如下的错误:
Caused by: java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0这个异常
(5)@PathVariable的坑在Feign接口层使用@PathVariable注解要注意,一定要跟上面一样加上value属性,
如:ResultData getByCode(@PathVariable(value ="accountCode") String accountCode);
否则你也会看到类似如下的错误:
Caused by: java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0这个异常
(6)在消费者、客户端配置文件中添加Feign超时时间配置
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
否则你会经常看到如下所示的错误:
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method) ~[?:1.8.0_112]
(7)feign客户端不要传递枚举类型的变量,最好将枚举类型变量转换成字符串或者序列号后,在传递;
6、问题:java.net.ProtocolException: Invalid HTTP method: PATCH
添加依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.2.0</version>
</dependency>
修改yml文件:
feign:
httpclient: enable=true
7、多个微服务的feign客户端调用同一个feign服务端
1、applicant.yml 配置(客户端服务端都要配置):
spring:
main:
allow-bean-definition-overriding: true ##允许统一微服务多个远程调用接口
2、在所有feign客户端配置:
import feign.Feign;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@Configuration
@ConditionalOnClass({Feign.class})
public class FeignConfig {
@Bean
public WebMvcRegistrations feignWebRegistrations() {
RequestMappingHandlerMapping handlerMapping = this.requestMappingHandlerMapping();
return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return handlerMapping;
}
};
}
/**
* 使SpringMVC只扫描带有@Controller、@RestController的@RequestMapping,而忽略掉带有@RequestMapping的FeignClient的接口,从而避免启动报Ambiguous mapping错误
*/
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
@Override
protected boolean isHandler(Class<?> beanType) {
return super.isHandler(beanType) && (AnnotationUtils.findAnnotation(beanType, Controller.class) != null) && (AnnotationUtils.findAnnotation(beanType, RestController.class) != null);
}
};
}
}
8、问题:java.lang.NoSuchMethodError: feign.Request.requestTemplate()
httpclient依赖版本的不匹配导致的报异常,需要feign-core和feign-httpclient版本对应即可。