• springcloud-feign报错:org.springframework.beans.factory.UnsatisfiedDependencyException


    在做springcloud项目时,出现了如下报错:

    关键异常信息为:

    1.简单看一下报错信息,找不到依赖,或者可以理解为依赖失败,看一下截取的部分报错信息

    org.springframework.beans.factory.UnsatisfiedDependencyException: 
    Error creating bean with name 'goshowController': 

    针对这个报错,网上找到了很多关于它可能的解决方案

    (1) 启动类没有创建(项目都启动了,没启动类,或者启动类注解引入错误,这个一般不会是此情况,除非自己手敲启动类):

    有人遇到的是这样的:测试包下的启动类不能和main的启动类 不能同名,所导致的问题;

    (2) 没有在配置文件中,配置启动自动扫描Service所在的包:截取网上的两端配置信息演示:

    <context:component-scan base-package="com.liglei.service"></context:component-scan>
        <!-- 启动自动扫描 -->  
        <context:component-scan base-package="com.ssm.blog.*">  
            <!-- 制定扫包规则 ,不扫描@Controller注解的JAVA类 -->  
            <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>  
        </context:component-scan>

    (3) service接口实现类上有没有加@Service注解,注解是不是引用的spring的类?不要导错包:

    或者干脆,接口有没有写实现类,实现类是实现的对应接口么?比如CategoryServiceImpl implementsCategoryDAO 一不小心根据自动提示,本来应该实现CategoryService,结果实现了CategoryDAO

    (4) 查看注入是否正确:比如有网友遇到:

    在mapper中使用的@Mapper注解的时候
    导了错误的包
    import org.mapstruct.Mapper;

    应该导入
    import org.apache.ibatis.annotations.Mapper;

    (5) 看看jar包是否下载完整,是否有这个jar包,或者依赖冲突:

    比如,加了thymeleaf的jar,加入的配置如下

    <properties>
            
        <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
            <!-- 布局功能的支持程序  thymeleaf3主程序  layout2以上版本 -->
            <!-- thymeleaf2   layout1-->
            <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
        </properties>
        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

    加入配置后,我检查我们maven的jar,问题就出来了,我们springboot自己下载的版本是3.0.9,然后我再指定一个3.0.9,就导致报错了,我们只需要删掉一个即可解决此问题

     去除方法如下图

    或者,看看下图红色方框内是否有包没导进去,有就按照下面的步骤完成:

    File–>Project Structure–>Artifacts–>右键demoPage–>Put into Output Root–>OK

     确保以上步骤没问题的,重启一下项目试试~

    ------------------------------------------------------------------------------------------

    但我遇到的问题,都不是以上情况:我的还有如下报错信息:

    2.

    nested exception is java.lang.IllegalStateException: 
    Method has too many Body parameters: 
    org.springframework.beans.factory.UnsatisfiedDependencyException: 
    Error creating bean with name 'goshowController': Unsatisfied dependency expressed through field 'goshowService'; 
    nested exception is org.springframework.beans.factory.BeanCreationException: 
    Error creating bean with name 'com.csair.lds.shell.openfeign.GoshowService': 
    FactoryBean threw exception on object creation; 
    nested exception is java.lang.IllegalStateException: 
    Method has too many Body parameters: 
    public abstract com.csair.lds.model.result.Results com.csair.lds.shell.openfeign.GoshowService.addGoshowPsg(com.csair.lds.shell.dto.PaxInfo,com.csair.lds.shell.dto.FlightInfo)

    网上给出如下解释:

    feign中,如果发送的是get请求去调用其它模块的方法,在接口处,形参需要添加 @RequestParam 注解。

    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dispathcherController': Unsatisfied dependency expressed through field 'userServiceFeign'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.offcn.webui.service.UserServiceFeign': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Method has too many Body parameters: public abstract com.offcn.common.response.AppResponse com.offcn.webui.service.UserServiceFeign.login(java.lang.String,java.lang.String)

    会先报创建bean异常
    org.springframework.beans.factory.UnsatisfiedDependencyException
    再报
    java.lang.IllegalStateException
    其内容为:Method has too many Body parameters

    将方法上添加注解 @RequestParam 后正常

        //调用user模块的方法
        @GetMapping("/user/login")
        public AppResponse<UserRespVo> login(@RequestParam String username,@RequestParam String password);

    原因为spring底层校验导致的。

     那么,很明显,我的报错信息如出一辙,我需要传入的参数为两个对象信息,但使用openFeign接口,有如下两个注解@ResquestParam和@RequestBody:

    openFeign接口方法传递多个参数时错误:nested exception is java.lang.IllegalStateException: Method has too many Body parameters: public abstract xxx
    这是因为feign客户端的方法参数没有用相关的注解

    @RequestParam

    1. 客户端@RequestParam注解的value属性必须指定值,要和服务端接口参数名保持一致
    2. 如果需要传递多个字符串参数,要使用多个@RequestParam注解与服务端接口参数保持对应
    @RequestBody

    1. @RequestBody 注解在服务端和客户端都需要使用
    2. 参数名和参数类型在服务端和客户端需要保持一致
    3. 一般用于传递对象
    例:

    远程Controller

    @RestController
    @RequestMapping("/storage")
    public class StorageController {
    
        @Autowired
        private StorageService storageService;
    
        @RequestMapping("/decrease")
        public CommonResult decrease(Long productId,Integer count){
            storageService.decrease(productId,count);
            return new CommonResult(200,"扣减库存成功");
        }
    }

    FeignClient

    @FeignClient("seata-storage")
    @Component
    public interface StorageService {
    
        @PostMapping("/storage/decrease")
        CommonResult decrease(@RequestParam(value = "productId") Long productId, 
                              @RequestParam(value = "count") Integer count);
    }

    注:
    openFeign默认使用@RequestBody,但只允许一个@RequestBody,所以:

    1. 只传递一个参数无需注解
    2. 传递多个参数时要用@RequestParam
    若远程Controlle方法参数有@PathVariable注解,则FeignClient传递参数也必须用@PathVariable注解,且指定@PathVariable的value对应Controller。

    很明显,这两个注解都不能满足我在项目中的要求,那么,我又开始搜索,feign如何传递两个对象参数。

    Springcloud-feign:

    在微服务盛行的时代,我们必不可少的使用到远程调用,可远程调用的组件和框架有很多,restTemplate和feign就是其中很经典的使用组件,由于feign通常会配合spring cloud使用,而restTemplate因其api复杂,分布式场景支持有限,feign必将会是远程调用的首选。在进行springboot拆分成springcloud项目的时候,我使用feign来进行微服务的调用

    了解Feign历史的朋友会知道,Feign本身是Netflix的产品,Spring Cloud Feign是在原生Feign的基础上进行了封装,引入了大量的SpringMVC注解支持,这一方面使得其更容易被广大的Spring使用者开箱即用,但也产生了不小的混淆作用。所以在使用Spring Cloud Feign之前,笔者先介绍一下SpringMVC的一个入参机制。预设一个RestController,在本地的8080端口启动一个应用,用于接收http请求

    SpringMVC的请求参数绑定机制:

    @RestController
    public class BookController {
        @RequestMapping(value = "/hello") // <1>
        public String hello(String name) { // <2>
            return "hello " + name;
        }
    }

    这个接口写起来非常简单,但实际springmvc做了非常多的兼容,使得这个接口可以接受多种请求方式。

    • RequestMapping代表映射的路径,使用GET,POST,PUT,DELETE方式都可以映射到该端点。

    • SpringMVC中常用的请求参数注解有(@RequestParam,@RequestBody,@PathVariable)等。name被默认当做@RequestParam。形参String name由框架使用字节码技术获取name这个名称,自动检测请求参数中key值为name的参数,也可以使用@RequestParam(“name”)覆盖变量本身的名称。当我们在url中携带name参数或者form表单中携带name参数时,会被获取到。

    POST /hello HTTP/1.1
    Host: localhost:8080
    Content-Type: application/x-www-form-urlencoded
    name=formParam

    或者:

    GET /hello?name=queryString HTTP/1.1
    Host: localhost:8080

    Feign的请求参数绑定机制

    上述的SpringMVC参数绑定机制,大家应该都是非常熟悉的,但这一切在Feign中有些许的不同。

    我们来看一个非常简单的,但是实际上错误的接口写法:

    //注意:错误的接口写法
    @FeignClient("book")
    public interface BookApi {
        @RequestMapping(value = "/hello",method = RequestMethod.GET)
        String hello(String name);
    }

    配置请求地址:

    ribbon:
      eureka:
       enabled: false
    book:
      ribbon:
        listOfServers: http://localhost:8080

    我们按照写SpringMVC的RestController的习惯写了一个FeignClient,按照我们的一开始的想法,由于指定了请求方式是GET,那么name应该会作为QueryString拼接到Url中吧?发出一个这样的GET请求:

    GET /hello?name=xxx HTTP/1.1
    Host: localhost:8080

    而实际上,RestController并没有接收到,我们在RestController一侧的应用中获得了一些提示:

    • 并没有按照期望使用GET方式发送请求,而是POST方式

    • name参数没有被封装,获得了一个null值

     查看文档发现,如果不加默认的注解,Feign则会对参数默认加上@RequestBody注解,而RequestBody一定是包含在请求体中的,GET方式无法包含。所以上述两个现象得到了解释。Feign在GET请求包含RequestBody时强制转成了POST请求,而不是报错。

    理解清楚了这个机制我们就可以在开发Feign接口避免很多坑。而解决上述这个问题也很简单

    • 在Feign接口中为name添加@RequestParam(“name”)注解,name必须指定,Feign的请求参数不会利用SpringMVC字节码的机制自动给定一个默认的名称。

    • 由于Feign默认使用@RequestBody,也可以改造RestController,使用@RequestBody接收。但是,请求参数通常是多个,推荐使用上述的@RequestParam,而@RequestBody一般只用于传递对象。

    Feign绑定复合参数

    指定请求参数的类型与请求方式,上述问题的出现实际上是由于在没有理清楚Feign内部机制的前提下想当然的和SpringMVC进行了类比。同样,在使用对象作为参数时,也需要注意这样的问题。

    对于这样的接口

    @FeignClient("book")
    public interface BookApi {
        @RequestMapping(value = "/book",method = RequestMethod.POST)
        Book book(@RequestBody Book book); // <1>
       
        @RequestMapping(value = "/book",method = RequestMethod.POST)
        Book book(@RequestParam("id") String id,@RequestParam("name") String name); // <2>
       
        @RequestMapping(value = "/book",method = RequestMethod.POST)
        Book book(@RequestParam Map map); // <3>
       
        //错误的写法
        @RequestMapping(value = "/book",method = RequestMethod.POST)
        Book book(@RequestParam Book book); // <4>
    }
    • 使用@RequestBody传递对象是最常用的方式。

    • 如果参数并不是很多,可以平铺开使用@RequestParam

    • 使用Map,这也是完全可以的,但不太符合面向对象的思想,不能从代码立刻看出该接口需要什么样的参数。

    • 错误的用法,Feign没有提供这样的机制自动转换实体为Map。

    Feign中使用@PathVariable与RESTFUL规范

    这涉及到一个如何设计RESTFUL接口的话题,我们知道在自从RESTFUL在2000年初被提出来之后,就不乏文章提到资源,契约规范,CRUD对应增删改查操作等等。下面笔者从两个实际的接口来聊聊自己的看法。

    根据id查找用户接口:

    @FeignClient("user")
    public interface UserApi {
        @RequestMapping(value = "/user/{userId}",method = RequestMethod.GET)
        String findById(@PathVariable("id") String userId);
    }

    这应该是没有争议的,注意前面强调的,@PathVariable(“id”)括号中的id不可以忘记。那如果是“根据邮箱查找用户呢”?很有可能下意识的写出这样的接口:

    @FeignClient("user")
    public interface UserApi {
       
        @RequestMapping(value = "/user/{email}",method = RequestMethod.GET)
        String findByEmail(@PathVariable("email") String email);
    }

    首先看看Feign的问题。email中通常包含’.‘这个特殊字符,如果在路径中包含,会出现意想不到的结果。我不想探讨如何去解决它(实际上可以使用{email:.+}的方式),因为我觉得这不符合设计。

    再谈谈规范的问题。这两个接口是否是相似的,email是否应该被放到path中?这就要聊到RESTFUL的初衷,为什么userId这个属性被普遍认为适合出现在RESTFUL路径中,因为id本身起到了资源定位的作用,他是资源的标记。而email不同,它可能是唯一的,但更多的,它是资源的属性,所以,笔者认为不应该在路径中出现非定位性的动态参数。而是把email作为@RequestParam参数。

    RESUFTL结构化查询

    笔者成功的从Feign的话题过度到了RESTFUL接口的设计问题,也导致了本文的篇幅变长了,不过也不打算再开一片文章谈了。

    再考虑一个接口设计,查询某一个月某个用户的订单,可能还会携带分页参数,这时候参数变得很多,按照传统的设计,这应该是一个查询操作,也就是与GET请求对应,那是不是意味着应当将这些参数拼接到url后呢?再思考Feign,正如本文的第二段所述,是不支持GET请求携带实体类的,这让我们设计陷入了两难的境地。而实际上参考一些DSL语言的设计如elasticSearch,也是使用POST JSON的方式来进行查询的,所以在实际项目中,笔者并不是特别青睐CRUD与四种请求方式对应的这种所谓的RESTFUL规范,如果说设计RESTFUL应该遵循什么规范,那大概是另一些名词,如契约规范和领域驱动设计。

    @FeignClient("order")
    public interface BookApi {
        @RequestMapping(value = "/order/history",method = RequestMethod.POST)
        Page<List<Orders>> queryOrderHistory(@RequestBody QueryVO queryVO);
    }

    RESTFUL行为限定

    在实际接口设计中,我遇到了这样的需求,用户模块的接口需要支持修改用户密码,修改用户邮箱,修改用户姓名,而笔者之前阅读过一篇文章,也是讲舍弃CRUD而是用领域驱动设计来规范RESTFUL接口的定义,与项目中我的想法不谋而合。看似这三个属性是同一个实体类的三个属性,完全可以如下设计:

    @FeignClient("user")
    public interface UserApi {
        @RequestMapping(value = "/user",method = RequestMethod.POST)
        User update(@RequestBody User user);
    }

    但实际上,如果再考虑多一层,就应该产生这样的思考:这三个功能所需要的权限一致吗?真的应该将他们放到一个接口中吗?实际上,笔者并不希望接口调用方传递一个实体,因为这样的行为是不可控的,完全不知道它到底是修改了什么属性,如果真的要限制行为,还需要在User中添加一个操作类型的字段,然后在接口实现方加以校验,这太麻烦了。而实际上,笔者觉得规范的设计应当如下:

    @FeignClient("user")
    public interface UserApi {
        @RequestMapping(value = "/user/{userId}/password/update",method = RequestMethod.POST)
        ResultBean<Boolean> updatePassword(@PathVariable("userId) String userId,@RequestParam("password") password);
         
        @RequestMapping(value = "/user/{userId}/email/update",method = RequestMethod.POST)
        ResultBean<Boolean> updateEmail(@PathVariable("userId) String userId,@RequestParam("email") String email);
         
        @RequestMapping(value = "/user/{userId}/username/update",method = RequestMethod.POST)
        ResultBean<Boolean> updateUsername(@PathVariable("userId) String userId,@RequestParam("username") String username);
    }
    • 一般意义上RESTFUL接口不应该出现动词,这里的update并不是一个动作,而是标记着操作的类型,因为针对某个属性可能出现的操作类型可能会有很多,所以我习惯加上一个update后缀,明确表达想要进行的操作,而不是仅仅依赖于GET,POST,PUT,DELETE。实际上,修改操作推荐使用的请求方式应当是PUT,这点笔者的理解是,已经使用update标记了行为,实际开发中不习惯使用PUT。

    • password,email,username都是user的属性,而userId是user的识别符号,所以userId以PathVariable的形式出现在url中,而三个属性出现在ReqeustParam中。

    顺带谈谈逻辑删除,如果一个需求是删除用户的常用地址,这个api的操作类型,我通常也不会设计为DELETE请求,而是同样使用delete来标记操作行为

    @RequestMapping(value = "/user/{userId}/address/{addressId}/delete",method = RequestMethod.POST)
    ResultBean<Boolean> updateEmail(@PathVariable("userId") String userId,@PathVariable("userId") String email);

    官方对feign有如下说明:https://github.com/OpenFeign/feign

    Feign makes writing java http clients easier

    Feign is a Java to HTTP client binder inspired by RetrofitJAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.

    Feign 是一个声明式 Web服务客户端,使用它创建一个接口并注解,使得编写 Web服务客户端变得更加容易。

    它支持可插拔注解,包括 Feign 注解和 JAX-RS 注解,还支持可插拔的编码、解码器。

    Cloud 增加了对 Spring MVC 注解的支持,默认使用 httpmessageconverter 的支持。

    Cloud 集成了 Ribbon 和 Eureka以及 BalanceLoad,使得在使用 Feign 时支持 Http 客户端的负载均衡。

    Feign 自定义的客户端配置不需要使用 @Configuration 配置注解。如果使用了,需要排除在 @ComponentScan 注解扫描之外。否则,它将成为 feign.Encoder、feign.Decoder、feign.Contract等组件的默认实现。如果指定了,可以在 @ComponentScan 中显示排除。

    参考文章:

    1. https://blog.csdn.net/qq_38152400/article/details/109597676

    2. https://blog.csdn.net/pc8650/article/details/108749590

    feign的介绍与使用:

    1.https://blog.csdn.net/xianghe_qqq/article/details/109693420

    2.https://blog.csdn.net/liu_ares/article/details/103599418

    3.https://blog.csdn.net/weixin_42794008/article/details/106881431

    feign搭建:

    1. https://blog.csdn.net/weixin_38312502/article/details/105920219

  • 相关阅读:
    剑指OFFER----面试题17- 打印从1到最大的n位数
    剑指OFFER----面试题16. 数值的整数次方
    剑指OFFER----面试题15. 二进制中1的个数
    剑指OFFER----面试题14- II. 剪绳子II
    07 多层if判断
    08 while循环
    06 if 流程控制
    03 身份运算符、逻辑运算符
    04 位运算符、运算符优先级
    02 赋值运算符、成员运算符
  • 原文地址:https://www.cnblogs.com/HarryVan/p/14618859.html
Copyright © 2020-2023  润新知