收获&总结
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;
}
}