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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM