前面博文搭建了一個Eureka+Ribbon+Hystrix的框架,雖然可以基本滿足服務(wù)之間的調(diào)用,但是代碼看起來實在丑陋,每次客戶端都要寫一個restTemplate,為了讓調(diào)用更美觀,可讀性更強,現(xiàn)在我們開始學(xué)習(xí)使用Feign。
Feign包含了Ribbon和Hystrix,這個在實戰(zhàn)中才慢慢體會到它的意義,所謂的包含并不是Feign的jar包包含有Ribbon和Hystrix的jar包這種物理上的包含,而是Feign的功能包含了其他兩者的功能這種邏輯上的包含。簡言之:Feign能干Ribbon和Hystrix的事情,但是要用Ribbon和Hystrix自帶的注解必須要引入相應(yīng)的jar包才可以。
案例一:
Eureka注冊中心:https://github.com/yejingtao/forblog/tree/master/demo-eureka-register
服務(wù)提供方:https://github.com/yejingtao/forblog/tree/master/demo-feign-freeservice
服務(wù)調(diào)用方:https://github.com/yejingtao/forblog/tree/master/demo-feign-freeconsumer
服務(wù)提供方就是個簡單的EurekaClient端+web應(yīng)用,提供以下方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
@RestController @RequestMapping ( "/feign-service" ) public class HelloServiceContorller { private Logger logger = LoggerFactory.getLogger( this .getClass()); private void sleep(String methodName) { int sleepMinTime = new Random().nextInt( 3000 ); logger.info( "helloService " +methodName+ " sleepMinTime: " +sleepMinTime); try { Thread.sleep(sleepMinTime); } catch (InterruptedException e) { e.printStackTrace(); } } @RequestMapping (value= "/serviceGet" ,method=RequestMethod.GET) public String helloService( @RequestParam String name) { sleep( "get" ); return "HelloServiceImpl name :" +name; } @RequestMapping (value= "/serviceHead" , method=RequestMethod.HEAD) public String helloService( @RequestHeader String name, @RequestHeader String password) { sleep( "header" ); return "helloServiceHead name :" +name + " password:" +password; } @RequestMapping (value= "/servicePost" , method=RequestMethod.POST) public String helloService( @RequestBody UserDemo userDemo) { sleep( "post" ); return userDemo.toString(); } } |
需要注意的以下注解不可以省略。
@RequestParam:Annotation which indicates that amethod parameter should be bound to a web request parameter
@RequestBody:Annotation indicating a methodparameter should be bound to the body of the web request.
@RequestHeader:Annotation which indicates that amethod parameter should be bound to a web request header.
如果缺少了以上注解,服務(wù)運行起來以后雖然不會報錯,但是獲取不到入?yún)ⅰ?/p>
服務(wù)調(diào)用方項目:
1
2
3
4
|
< dependency > < groupId >org.springframework.cloud</ groupId > < artifactId >spring-cloud-starter-feign</ artifactId > </ dependency > |
這里只依賴了Feign,沒有依賴Ribbon和Hystrix。
application.yml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
server: port: 9051 spring: application: name: demo-feign-freeconsumer eureka: client: serviceUrl: defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/ feign: hystrix: enabled: true #Ribbon 超時時間設(shè)置 #ribbon: # ConnectTimeout: 500 # ReadTimeout: 3000 |
hystrix這個配置坑了我好久我用的Spring Cloud是Dalston版本SR1,比網(wǎng)上其他材料的版本要新,因為在新版本中Feign對Hystrix的支持默認是關(guān)閉的,所以要通過配置手動打開feign.hystrix.enabled=true,這樣服務(wù)降級等功能才有效果。
Application啟動程序
1
2
3
4
5
6
7
8
|
@SpringBootApplication @EnableEurekaClient @EnableFeignClients public class DemoFeignApplication { public static void main(String[] args) { SpringApplication.run(DemoFeignApplication. class , args); } } |
注意這里還有個坑,我這里用的是@SpringBootApplication+@EnableEurekaClient,而不是用的@SpringCloudApplication,因為后者包含了@EnableCircuitBreaker,而@EnableCircuitBreaker又是屬于Hystrix包里的內(nèi)容,我的pom里并沒有引入Hystrix。所以這一點Spring Cloud做的還是有不足的地方,直接用@SpringCloudApplication編譯不會報錯,但是啟動不了。當(dāng)然這里的主角還是@EnableFeignClients這個注解。
核心客戶端代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@FeignClient (name= "demo-feign-freeservice" ,fallback=DemoFeignFallback. class ) public interface DemoFeignService{ @RequestMapping (value= "/feign-service/serviceGet" ,method=RequestMethod.GET) String helloService( @RequestParam ( "name" ) String name); @RequestMapping (value= "/feign-service/serviceHead" , method=RequestMethod.HEAD) String helloService( @RequestHeader ( "name" ) String name, @RequestHeader ( "password" ) String password); @RequestMapping (value= "/feign-service/servicePost" , method=RequestMethod.POST) String helloService( @RequestBody UserDemo userDemo); } |
@FeignClient注解定義了該接口是一個Feign客戶端,name指定了注冊到Eureka上的服務(wù)名,fallback是服務(wù)降級后的接口實現(xiàn)類。
@RequestMapping里指定了請求的相對url和http請求方式,與服務(wù)端一一對應(yīng)。入?yún)⒗锏腀RequestParam、
@RequestBody、@RequestHeader注解比起服務(wù)端多了value屬性,這里不能省略,需要顯式的告知Feign客戶端參數(shù)要如何對應(yīng)。
降級服務(wù)代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Component public class DemoFeignFallback implements DemoFeignService{ @Override public String helloService(String name) { return "get error" ; } @Override public String helloService(String name,String password) { return "head error" ; } @Override public String helloService(UserDemo userDemo) { return "post error" ; } } |
發(fā)現(xiàn)這里的入?yún)⒗镂夜室馊サ袅薂RequestParam、@RequestBody、@RequestHeader注解,因為這幾個注解本質(zhì)上的意義就在于Feign在做微服務(wù)調(diào)用的時候?qū)ttp傳遞參數(shù)用的,但服務(wù)降級根本不會做http請求了,所以此處可以省略。
Controller代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@RestController public class DemoFeignController { @Autowired private DemoFeignService demoFeignService; @RequestMapping (value= "/test" , method=RequestMethod.GET) public String demoServiceTest() { StringBuffer sb = new StringBuffer(); sb.append(demoFeignService.helloService( "yuanyuan" )); sb.append( "\n" ); sb.append(demoFeignService.helloService( "yjt" , "xixihaha" )); sb.append( "\n" ); sb.append(demoFeignService.helloService( new UserDemo( "yejingtao" , "123456" ))); return sb.toString(); } } |
我們來看效果:
我們服務(wù)都沒超時,3個方法全部正常,但是head請求沒有拿到返回值,這個是因為head方式http請求的特性決定的,head不返回response的body體,一般用來做連通性測試來用。
再看一組:
運氣不好head和post請求方法處理時間超過了2000ms,服務(wù)降級,實現(xiàn)被fallback處理類取代。
在案例一中我們總有種感覺,服務(wù)提供方和服務(wù)調(diào)用方存在重復(fù)的代碼,是否可以進行優(yōu)化?請看案例二。
案例二:
Eureka注冊中心:https://github.com/yejingtao/forblog/tree/master/demo-eureka-register
接口API:https://github.com/yejingtao/forblog/tree/master/demo-feign-serviceapi
服務(wù)提供方:https://github.com/yejingtao/forblog/tree/master/demo-feign-serviceimpl
服務(wù)調(diào)用方:https://github.com/yejingtao/forblog/tree/master/demo-feign-apiconsumer
案例二最大的變動是將服務(wù)能力單獨寫到一個API的project中,調(diào)用方和提供方pom都依賴這個API。
API:
1
2
3
4
5
6
7
8
9
10
11
|
public interface HelloService { @RequestMapping (value= "/feign-service/serviceGet" ,method=RequestMethod.GET) String helloService( @RequestParam ( "name" ) String name); @RequestMapping (value= "/feign-service/serviceHead" , method=RequestMethod.HEAD) String helloService( @RequestHeader ( "name" ) String name, @RequestHeader ( "password" ) String password); @RequestMapping (value= "/feign-service/servicePost" , method=RequestMethod.POST) String helloService( @RequestBody UserDemo userDemo); } |
服務(wù)提供方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
@RestController public class HelloServiceContorller implements HelloService{ private Logger logger = LoggerFactory.getLogger( this .getClass()); private void sleep(String methodName) { int sleepMinTime = new Random().nextInt( 3000 ); logger.info( "helloService " +methodName+ " sleepMinTime: " +sleepMinTime); try { Thread.sleep(sleepMinTime); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String helloService( @RequestParam ( "name" ) String name) { sleep( "get" ); return "HelloServiceImpl name :" +name; } @Override public String helloService( @RequestHeader ( "name" ) String name, @RequestHeader ( "password" ) String password) { sleep( "header" ); return "helloServiceHead name :" +name + " password:" +password; } @Override public String helloService( @RequestBody UserDemo userDemo) { sleep( "post" ); return userDemo.toString(); } } |
服務(wù)調(diào)用方:
1
2
3
|
@FeignClient (name= "demo-feign-serviceimpl" , fallback=FeignServiceFallback. class ) public interface FeignService extends HelloService{ } |
其它代碼基本不變,效果也一樣。
兩種風(fēng)格各有優(yōu)缺點:freestyle的更自由,服務(wù)端新增方法不會影響客戶端代碼,缺點是服務(wù)能力不同步服務(wù)能力的變動會引起異常;API格式服務(wù)端客戶端服務(wù)能力同步,但是接口的變動需要修改兩邊的代碼,需要構(gòu)建的時候就要考慮清楚。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://blog.csdn.net/yejingtao703/article/details/77748190