Feign客户端的使用

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版本对应即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值