本文節選自《瘋狂Spring Cloud微服務架構實戰》html
京東購買地址:https://item.jd.com/12256011.htmljava
噹噹網購買地址:http://product.dangdang.com/25201393.htmlgit
Spring Cloud教學視頻:https://my.oschina.net/JavaLaw/blog/1552993web
Spring Cloud電子書:https://my.oschina.net/JavaLaw/blog/1570383spring
本文要點json
RestTemplate的負載均衡原理瀏覽器
RestTemplate本是spring-web項目中的一個REST客戶端,它遵循REST的設計原則,提供簡單的API讓咱們能夠調用HTTP服務。RestTemplate自己不具備負載均衡的功能,該類也與Spring Cloud沒有關係,但爲什麼加入@LoadBalanced註解後,一個RestTemplate實例就具備負載均衡的功能呢?實際上這要得益於RestTemplate的攔截器功能。架構
在Spring Cloud中,使用@LoadBalanced修飾的RestTemplate,在Spring容器啓動時,會爲這些被修飾過的RestTemplate添加攔截器,攔截器中使用了LoadBalancerClient來處理請求,LoadBalancerClient原本就是Spring封裝的負載均衡客戶端,經過這樣間接處理,使得RestTemplate就擁有了負載均衡的功能。app
本小節將模仿攔截器機制,帶領你們實現一個簡單的RestTemplate,以便讓你們更瞭解@LoadBalanced以及RestTemplate的原理。本小節的案例只依賴了spring-boot-starter-web模塊:負載均衡
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.4.RELEASE</version> </dependency>
先模仿@LoadBalanced註解,編寫一個自定義註解,請見代碼清單4-16。
代碼清單4-1:
codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\MyLoadBalanced.java
package org.crazyit.cloud; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Qualifier; @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface MyLoadBalanced { }
注意MyLoadBalanced註解中使用了@Qualifier限定註解,接下來編寫自定義的攔截器,請見代碼清單4-17。
代碼清單4-17:
codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\MyInterceptor.java
package org.crazyit.cloud; import java.io.IOException; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; /** * 自定義攔截器 * * @author 楊恩雄 * */ public class MyInterceptor implements ClientHttpRequestInterceptor { public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { System.out.println("============= 這是自定義攔截器實現"); System.out.println(" 原來的URI:" + request.getURI()); // 換成新的請求對象(更換URI) MyHttpRequest newRequest = new MyHttpRequest(request); System.out.println(" 攔截後新的URI:" + newRequest.getURI()); return execution.execute(newRequest, body); } }
在自定義攔截器MyInterceptor中,實現了intercept方法,該方法會將原來的HttpRequest對象,轉換爲咱們自定義的MyHttpRequest,MyHttpRequest是一個自定義的請求類,實現以下請見代碼清單4-18。
代碼清單4-18:
codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\MyHttpRequest.java
package org.crazyit.cloud; import java.net.URI; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; /** * 自定義的請求類,用於轉換URI * @author 楊恩雄 * */ public class MyHttpRequest implements HttpRequest { private HttpRequest sourceRequest; public MyHttpRequest(HttpRequest sourceRequest) { this.sourceRequest = sourceRequest; } public HttpHeaders getHeaders() { return sourceRequest.getHeaders(); } public HttpMethod getMethod() { return sourceRequest.getMethod(); } /** * 將URI轉換 */ public URI getURI() { try { URI newUri = new URI("http://localhost:8080/hello"); return newUri; } catch (Exception e) { e.printStackTrace(); } return sourceRequest.getURI(); } }
MyHttpRequest類中,會將原來請求的URI進行改寫,只要使用了這個對象,全部的請求都會被轉發到http://localhost:8080/hello這個地址。Spring Cloud在對RestTemplate進行攔截的時候,也作了一樣的事情,只不過並無像咱們這樣固定了URI,而是對「源請求」進行了更加靈活的處理。接下來使用自定義註解以及攔截器。
編寫一個Spring的配置類,在初始化的bean中爲容器中的RestTemplate實例設置自定義攔截器,本例的Spring自動配置類請見代碼清單4-19。
代碼清單4-19:
codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\MyAutoConfiguration.java
package org.crazyit.cloud; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; @Configuration public class MyAutoConfiguration { @Autowired(required=false) @MyLoadBalanced private List<RestTemplate> myTemplates = Collections.emptyList(); @Bean public SmartInitializingSingleton myLoadBalancedRestTemplateInitializer() { System.out.println("==== 這個Bean將在容器初始化時建立 ====="); return new SmartInitializingSingleton() { public void afterSingletonsInstantiated() { for(RestTemplate tpl : myTemplates) { // 建立一個自定義的攔截器實例 MyInterceptor mi = new MyInterceptor(); // 獲取RestTemplate原來的攔截器 List list = new ArrayList(tpl.getInterceptors()); // 添加到攔截器集合 list.add(mi); // 將新的攔截器集合設置到RestTemplate實例 tpl.setInterceptors(list); } } }; } }
配置類中,定義了RestTemplate實例的集合,而且使用了@MyLoadBalanced以及@Autowired註解進行修飾,@MyLoadBalanced中含有@Qualifier註解,簡單來講,就是Spring容器中,使用了@MyLoadBalanced修飾的RestTemplate實例,將會被加入到配置類的RestTemplate集合中。
在容器初始化時,會調用myLoadBalancedRestTemplateInitializer方法來建立Bean,該Bean在初始化完成後,會遍歷RestTemplate集合併爲它們設置「自定義攔截器」,請見代碼清單4-19中的粗體代碼。下面在控制器中使用@MyLoadBalanced來修飾調用者的RestTemplate。
控制器代碼請見代碼清單4-20。
代碼清單4-20:
codes\04\4.5\rest-template-test\src\main\java\org\crazyit\cloud\InvokerController.java
package org.crazyit.cloud; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @Configuration public class InvokerController { @Bean @MyLoadBalanced public RestTemplate getMyRestTemplate() { return new RestTemplate(); } /** * 瀏覽器訪問的請求 */ @RequestMapping(value = "/router", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public String router() { RestTemplate restTpl = getMyRestTemplate(); // 根據名稱來調用服務,這個URI會被攔截器所置換 String json = restTpl.getForObject("http://my-server/hello", String.class); return json; } /** * 最終的請求都會轉到這個服務 */ @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello() { return "Hello World"; } }
注意控制器的hello方法,前面實現的攔截器,會將所有請求都轉到這個服務中。控制器的RestTemplate,使用了@MyLoadBalanced註解進行修飾,熟悉前面使用RestTemplate的讀者可發現,咱們實現的註解,與Spring提供的@LoadBalanced註解使用方法一致。控制器的router方法中,使用這個被攔截過的RestTemplate發送請求。
打開瀏覽器,訪問http://localhost:8080/router,能夠看到實際上調用了hello服務,在訪問該地址時,控制檯輸出以下:
============= 這是自定義攔截器實現 原來的URI:http://my-server/hello 攔截後新的URI:http://localhost:8080/hello
Spring Cloud對RestTemplate的攔截實現更加複雜,而且在攔截器中,使用LoadBalancerClient來實現請求的負載均衡功能,咱們在實際環境中,並不須要實現自定義註解以及攔截器,用Spring提供的現成API便可,本小節目的是展現RestTemplate的原理。
本文節選自《瘋狂Spring Cloud微服務架構實戰》
Spring Cloud教學視頻:https://my.oschina.net/JavaLaw/blog/1552993
Spring Cloud電子書:https://my.oschina.net/JavaLaw/blog/1570383
本書代碼共享地址:https://gitee.com/yangenxiong/SpringCloud