Spring gateway 源代码总结之ShortcutConfigurable详解 09

收获&总结

1:根据这2周的分析基本上搞懂了此框架的代码逻辑,包括gateway,route,filter,断言的关系,以及后面的负载均衡,熔断机制等等。

2:  了解框架的常见角色划分:比如Property, Definition,Composit, Factory, Configurable,为以后阅读其他框架打下了基础。

3:  代码的灵活解耦手段:设计模式(Factory,Composit,Builder,责任链,策略模式,Solid原则,事件消息机制) + java8(Function,Predicate,Consumer) +  泛型。

4:  Spring知识,比如自定义的Condition接口,SpringBoot stater,Ribbon机制。

5:体验到持续积累的神奇作用,打破了原来的自我设限,原来感觉很难的东西,只要慢慢积累其实也是可以做到的。

6:团队学习,学会提问,勤于思考,事半功倍。

 

接口关联总结

ShortcutConfigurable 简化配置接口详解

There are two ways to configure predicates and filters: shortcuts and fully expanded arguments. Most examples below use the shortcut way.

The name and argument names will be listed as code in the first sentance or two of the each section.

The arguments are typically listed in the order that would be needed for the shortcut configuration

Predicates&Filters 配置简化场景举例

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        predicates:
        - Path=/red/{segment}/
        - Host=**.somehost.org,**.anotherhost.org
        filters:
        - AddRequestHeader=X-Request-Red, Blue-{segment}

思考

1:  {segment} 参数化怎么处理?

2:如何把简化的写法恢复到正常形式?

3:简化写法划分了几个类型?

4:如何被调用的?

贴源代码


public interface ShortcutConfigurable {

	/**
	 *  程序先会生产自动生成的key: _genkey_0 -> X-Response-Default-Foo
	 * 从随机生成的 key,转换成 普通的key,比如 name 从shortcutFieldOrder()方法中拿到
	 * @param key _genkey_0
	 * @param entryIdx 下标
	 * @param argHints
	 * @param args 参数map集合
	 * @return
	 */
	static String normalizeKey(String key, int entryIdx, ShortcutConfigurable argHints,
			Map<String, String> args) {
		// RoutePredicateFactory has name hints and this has a fake key name
		// replace with the matching key hint
		if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX)
				&& !argHints.shortcutFieldOrder().isEmpty() && entryIdx < args.size()
				&& entryIdx < argHints.shortcutFieldOrder().size()) {
			key = argHints.shortcutFieldOrder().get(entryIdx);
		}
		return key;
	}

	/**
	 * Spring表达式语言解析器 把一些参数化的配置,转换成真实的value
	 *  routes:
	 *       - id: path_route
	 *         uri: https://example.org
	 *         predicates:
	 *         - Path=/red/{segment},/blue/{segment}
	 * @param parser
	 * @param beanFactory
	 * @param entryValue
	 * @return
	 */
	static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory,
			String entryValue) {
		Object value;
		String rawValue = entryValue;
		if (rawValue != null) {
			rawValue = rawValue.trim();
		}
		if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) {
			// assume it's spel
			StandardEvaluationContext context = new StandardEvaluationContext();
			context.setBeanResolver(new BeanFactoryResolver(beanFactory));
			Expression expression = parser.parseExpression(entryValue,
					new TemplateParserContext());
			value = expression.getValue(context);
		}
		else {
			value = entryValue;
		}
		return value;
	}

	/**
	 * 默认类型
	 * @return
	 */
	default ShortcutType shortcutType() {
		return ShortcutType.DEFAULT;
	}

	/**
	 * 因为要简写 a,b,c的形式所以需要定义属性的顺序
	 * Returns hints about the number of args and the order for shortcut parsing.
	 * @return the list of hints
	 */
	default List<String> shortcutFieldOrder() {
		return Collections.emptyList();
	}

	/**
	 * 简写的属性的prefix
	 * @return
	 */
	default String shortcutFieldPrefix() {
		return "";
	}
     //_genkey_0 -> X-Response-Default-Foo

	/**
	 * DEFAULT :按照shortcutFieldOrder顺序依次赋值
	 *          实现类:AfterRoutePredicateFactory
	 * GATHER_LIST:shortcutFiledOrder只能有一个值,如果参数有多个拼成一个集合
	 *              shortcutFieldOrder接口实现返回的list,必须是size=1
	 *              实现类:HostRoutePredicateFactory
	 * GATHER_LIST_TAIL_FLAG:shortcutFiledOrder只能有两个值,其中最后一个值为true或者false,其余的值变成一个集合付给第一个值
	 *                        自带的就一个实现类:PathRoutePredicateFactory  配置
	 *        - id: order
	 *         uri: lb://EUREKA-CLIENT
	 *         predicates:
	 *         - Path=/whoami/,false
	 * shortcutFieldOrder,这个值决定了Config中配置的属性,配置的参数都会被封装到该属性当中
	 */
	enum ShortcutType {
        //name -> X-Response-Default-Foo
		DEFAULT {
			@Override
			public Map<String, Object> normalize(Map<String, String> args,
					ShortcutConfigurable shortcutConf, SpelExpressionParser parser,
					BeanFactory beanFactory) {
				Map<String, Object> map = new HashMap<>();
				int entryIdx = 0;
				for (Map.Entry<String, String> entry : args.entrySet()) {
					String key = normalizeKey(entry.getKey(), entryIdx, shortcutConf,
							args);
					Object value = getValue(parser, beanFactory, entry.getValue());

					map.put(key, value);
					entryIdx++;
				}
				return map;
			}
		},
        //变成key是random value 是List [1,2,3]
		GATHER_LIST {
			@Override
			public Map<String, Object> normalize(Map<String, String> args,
					ShortcutConfigurable shortcutConf, SpelExpressionParser parser,
					BeanFactory beanFactory) {
				Map<String, Object> map = new HashMap<>();
				// field order should be of size 1
				List<String> fieldOrder = shortcutConf.shortcutFieldOrder();
				Assert.isTrue(fieldOrder != null && fieldOrder.size() == 1,
						"Shortcut Configuration Type GATHER_LIST must have shortcutFieldOrder of size 1");
				String fieldName = fieldOrder.get(0);
				map.put(fieldName,
						args.values().stream()
								.map(value -> getValue(parser, beanFactory, value))
								.collect(Collectors.toList()));
				return map;
			}
		},

		// list is all elements except last which is a boolean flag
		GATHER_LIST_TAIL_FLAG {
			@Override
			public Map<String, Object> normalize(Map<String, String> args,
					ShortcutConfigurable shortcutConf, SpelExpressionParser parser,
					BeanFactory beanFactory) {
				Map<String, Object> map = new HashMap<>();
				// field order should be of size 1
				List<String> fieldOrder = shortcutConf.shortcutFieldOrder();
				Assert.isTrue(fieldOrder != null && fieldOrder.size() == 2,
						"Shortcut Configuration Type GATHER_LIST_HEAD must have shortcutFieldOrder of size 2");
				List<String> values = new ArrayList<>(args.values());
				if (!values.isEmpty()) {
					// strip boolean flag if last entry is true or false
					int lastIdx = values.size() - 1;
					String lastValue = values.get(lastIdx);
					if (lastValue.equalsIgnoreCase("true")
							|| lastValue.equalsIgnoreCase("false")) {
						values = values.subList(0, lastIdx);
						map.put(fieldOrder.get(1),
								getValue(parser, beanFactory, lastValue));
					}
				}
				String fieldName = fieldOrder.get(0);
				map.put(fieldName,
						values.stream().map(value -> getValue(parser, beanFactory, value))
								.collect(Collectors.toList()));
				return map;
			}
		};

		public abstract Map<String, Object> normalize(Map<String, String> args,
				ShortcutConfigurable shortcutConf, SpelExpressionParser parser,
				BeanFactory beanFactory);

	}

}

原理解释

Shortcut 类型划分

  •   DEFAULT : 按照shortcutFieldOrder顺序依次赋值,如上文示例,test1->123;test2->abc, 比如 Header params,Query params,Cookie params 等等
  •   GATHER_LIST:shortcutFiledOrder只能有一个参数,如果值有多个拼成一个集合,如果将上文示例修改成该规则将报错,因为只能有一个参数。具体可以看org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType.GATHER_LIST, 比如:host, 白名单等等
  •   GATHER_LIST_TAIL_FLAG:shortcutFiledOrder只能有两个参数,其中最后一个值为true或者false,其余的值变成一个集合付给第一个参数, 比如:path 是否要匹配最后一个/ 的配置。
  •  

ShortcutConfigurable 如何如何被调用

1: RouteDefinitionRouteLocator::lookup ---> ShortcutType::normalize。开始调用normalize方法,最终的效果是把简化的配置转换成Config配置类。
2: ShortcutType::normalize--> normalizeKey--> shortcutFieldOrder。(让key按照规定的顺序以此替换)
3: shortcutFieldOrder--> getValue (参数化的参数值进行替换)

自定义使用的例子

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

    //根据这个配置来进行转换
	@Override
	public ShortcutType shortcutType() {
		return ShortcutType.GATHER_LIST;
	}
 
    //制定属性
	@Override
	public List<String> shortcutFieldOrder() {
		return Collections.singletonList("ips");
	}

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

		return new GatewayPredicate() {
			@Override
			public boolean test(ServerWebExchange exchange) {
				return false;
			}
		};
	}

    //转换成Config
	public static class Config {
		private List<String> ips;

	}
}

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值