天天看點

Spring gateway 源碼解析之Route Predicate元件的類圖 06

1.Route兩種定義方式,都是等價的

routes:
      # =====================================
      # to run server
      # $ wscat --listen 9000
      # to run client
      # $ wscat --connect ws://localhost:8080/echo
      - id: websocket_test
        uri: ws://localhost:9000
        order: 9000
        predicates:
        - Path=/echo
        - Host=www.baidu.com
           
builder.routes()
				.route(r -> 
//形成一個連結清單
r.host("**.abc.org").and().path("/anything/png").or().path("abc").and().path("123")

					.filters(f ->
							f.prefixPath("/httpbin")
									.addResponseHeader("X-TestHeader", "foobar"))
					.uri(uri)
				)
           

2.需要實作的功能點:

1:支援同步或者異步的方式執行,并且同步和異步類型可以轉換。

2:需要支援不同方式來定義Route (yaml 或者 代碼)。

4:java 方式定義Route 的文法是 鍊式操作(也叫FluentAPI ),同時一個Route可以配置多個的斷言,比如(a&b&c|d)

     答:實作原理是通過GatewayFilterSpec,PredicateSpec, BooleanSpec這三個繼承了UriSpec的類實作的,然後外加BooleanSpec的 BooleanOpSpec繼承了PredicateSpec,進而支援鍊式文法操作

               比如 and(),or() 等方法傳回的是 BooleanOpSpec 就可以關聯PredicateSpec支援的方法after,before 等等,這樣就可以 path("/anything/png").or().path("abc").and().path("123") 這種寫法。

               yaml 配置檔案是通過RouteDefinitionRouteLocator::combinePredicates 來生成  AsyncPredicate對象的。AsyncPredicate 裡面是有 left ,right 屬性 用來儲存連結清單操作的關聯關系。

3:需要支援Route清單緩存。

      答:通過CacheRouteLocator來實作的。

5:  根據斷言規則比對到路由後會 執行gateway filter 和 gloable filter。

詳細類圖

Spring gateway 源碼解析之Route Predicate元件的類圖 06

自定義Predicate

public class RandomRoutePredicateFactory extends AbstractRoutePredicateFactory<RandomRoutePredicateFactory.Config> {
	public RandomRoutePredicateFactory() {
		super(RandomRoutePredicateFactory.Config.class);
	}

	@Override
	public ShortcutType shortcutType() {
		return ShortcutType.GATHER_LIST;
	}

	@Override
	public List<String> shortcutFieldOrder() {
		return Collections.singletonList("random");
	}

	@Override
	public Predicate<ServerWebExchange> apply(Config config) {

		return new GatewayPredicate() {
			@Override
			public boolean test(ServerWebExchange exchange) {
				String token = exchange.getRequest().getHeaders().getFirst("random");
				return null == token ? false : validate(token);
			}

			private boolean validate(String token) {
				// 可以替換成shiro、jwt、oauth或者cas/sso之類的,,其實這個場景更适合用filter
				// 這裡隻是示範一下predicate,傳遞的參數如果是以這個token開頭就運作,走指定路由
				return token.toLowerCase().startsWith(config.getRandom().toLowerCase());
			}

			@Override
			public String toString() {
				return String.format("random: %s", config.getRandom());
			}
		};

	}

	public static class Config {
		private String random;

		public String getRandom() {
			return random;
		}

		public void setRandom(String random) {
			this.random = random;
		}

		public RandomRoutePredicateFactory.Config setToken(String random) {
			this.random = random;
			return this;
		}
	}
}
           
routes:
      # =====================================
      # to run server
      # $ wscat --listen 9000
      # to run client
      # $ wscat --connect ws://localhost:8080/echo
      - id: websocket_test
        uri: ws://localhost:9000
        order: 9000
        predicates:
        - Path=/echo
        - Host=www.baidu.com
      # =====================================
#      - id: default_path_to_httpbin
#        uri: ${test.uri}
#        order: 10000
#        predicates:
#        - Path=/**
      - id: random-test
        uri: ${test.uri}
        order: 10000
        predicates:
          - Path=/**
          - name: Random
            args:
              random=123
          - Token=CC
           

備注:

  1:  這個類圖涉及了 Builder (https://www.runoob.com/w3cnote/builder-pattern.html)和 政策 設計模式,java的 predicate 和 function 接口.

public class TestFunction {

    public static void main(String[] args) {

        System.out.println(testFunction(i -> i * 2 + 1));
    }

    public static int testFunction( Function<Integer,Integer> function) {

        return function.apply(2);
    }
}
           
public class PredicateDemo {
    public static void main(String[] args) {
        //給list添加參數
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        System.out.println("輸出所有參數字:");
        eval(list, n -> true);
        System.out.println("\n輸出能被2整除的數字:");
        eval(list, n -> n%2==0);
        System.out.println("\n輸出大于3的數字:");
        eval(list, n-> n > 3 );
    }
    
    //自定義方法
    public static void eval(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list) {        
           if(predicate.test(n)) {
              //可以将滿足條件的參數傳回,這裡隻做輸出
              System.out.print(n + " ");
           }
        }
     }
}