Restlight
支持多种拦截器,适用于不同性能/功能场景
- RouteInterceptor
- MappingInterceptor
- HandlerInterceptor
- InterceptorFactory
Tip
实现对应的拦截器接口并注入Spring即可Note
在Interceptor#preHandle0()
和Interceptor#postHandle0()
中直接通过throw抛出的异常将不会被
异常处理器处理,如果需要被正常处理,请使用CompletableFuture.completeExceptionally()
封装异常
拦截器定位
面向Controller/Route
, 同时支持按需匹配的拦截器
- 面向
Controller/Route
: 拦截器一定能让用户根据需求选择匹配到哪个Controller
接口 - 按需匹配: 支持按照
RequestContext
条件让用户灵活决定拦截器的匹配规则
InternalInterceptor
核心拦截器实现, 封装了拦截器核心行为
-
CompletableFuture<Boolean> preHandle0(RequestContext, Object)
Controller
执行前执行。返回布尔值表示是否允许当前请求继续往下执行。 -
CompletableFuture<Void> postHandle0(RequestContext, Object)
Controller
刚执行完之后执行 -
CompletableFuture<Void> afterCompletion0(RequestContext, Object, Exception)
请求行完之后执行
-
int getOrder()
返回当前拦截器的优先级(默认为最低优先级),我们保证
getOrder
返回值决定拦截器的执行顺序,但是不支持使用@Order(int)
注解情况下的顺序(虽然有时候看似顺序和@Order
一致,但那只是巧合)。
Note
- 框架在真正执行拦截器调用的时候只会调用上述方法(而不是
preHandle(xxx)
,postHandle(xxx)
,afterCompletion(xxx)
) - 请勿直接使用此接口,此接口仅仅是拦截器的核心实现类
拦截器匹配
初始化阶段: 初始化阶段为每一个Controller
确定所有可能匹配到当前Controller
的拦截器列表(包含可能匹配以及一定会匹配到当前Controller
的拦截器)。
运行时阶段: 一个请求到来时通过将RequestContext
作为参数传递到拦截器做路由判定,决定是否匹配。
Tip
通过初始化与运行时两个阶段的匹配行为扩展满足用户多样性匹配需求, 用户既可以直接将拦截器绑定到固定的Controller
也可以让拦截器随心所欲的根据请求去选择匹配。
Affinity
亲和性
public interface Affinity {
/**
* Current component is always attaching with the target subject.
*/
int ATTACHED = 0;
/**
* Current component has a high affinity with the target subject this is the highest value.
*/
int HIGHEST = 1;
/**
* Current component has no affinity with the target subject.
*/
int DETACHED = -1;
/**
* Gets the affinity value.
*
* @return affinity.
*/
int affinity();
}
用于表达拦截器与Controller/Route
之间的亲和性
affinity()
小于0表示拦截器不可能与Controller
匹配affinity()
等于0表示拦截器一定会与Controller
匹配affinity()
大于0表示拦截器可能会与Controller
匹配, 并且值越小匹配可能性越高(因此1为最高可能性), 相反值越大匹配可能性越小, 同时匹配的开销越大(用于拦截器匹配性能优化)。
InterceptorPredicate
public interface InterceptorPredicate extends RequestPredicate {
InterceptorPredicate ALWAYS = context -> Boolean.TRUE;
}
public interface RequestPredicate extends Predicate<RequestContext> {
// ignore this
default boolean mayAmbiguousWith(RequestPredicate another) {
return false;
}
}
test(RequestContext)
方法用于对每个请求的匹配。可以满足根据RequestContext
运行时的任意条件的匹配(而不仅仅是局限于URL
匹配)
Interceptor
Interceptor
接口为同时拥有Affinity
(Controller/Route
匹配)以及InterceptorPredicate
(请求RequestContext
匹配)的接口
public interface Interceptor extends InternalInterceptor, Affinity {
/**
* Gets the predicate of current interceptor. determines whether current interceptor should be matched to a {@link
* RequestContext}.
*
* @return predicate, or {@code null} if {@link #affinity()} return's a negative value.
*/
InterceptorPredicate predicate();
/**
* Default to highest affinity.
* <p>
* Whether a {@link Interceptor} should be matched to a {@link Route} is depends on it.
*
* @return affinity
*/
@Override
default int affinity() {
return HIGHEST;
}
}
由于int affinity()
根据不同的Controller/Route
可能得出不同的结果, 因此需要使用InterceptorFactory
进行创建
eg.
实现一个拦截器, 拦截所有GET接口(仅包含GET)且Header中包含X-Foo
请求头的请求
@Bean
public InterceptorFactory interceptor() {
return (ctx, route) -> Optional.of(new Interceptor() {
@Override
public CompletionStage<Boolean> preHandle0(RequestContext context, Object handler) {
// biz logic
return CompletableFuture.completedFuture(true);
}
@Override
public InterceptorPredicate predicate() {
return context -> context.request().headers().contains("X-Foo");
}
@Override
public int affinity() {
HttpMethod[] method = route.mapping().method();
if (method.length == 1 && method[0] == HttpMethod.GET) {
return ATTACHED;
}
return DETACHED;
}
});
}
Note
运行时仅在context.request().headers().contains("X-Foo")
上做匹配, 性能损耗极低。
RouteInterceptor
只绑定到固定的Controller/Route
的拦截器
public interface RouteInterceptor extends InternalInterceptor {
/**
* Gets the affinity value between current interceptor and the given {@link Routing}.
*
* @param ctx context
* @param route route to match
*
* @return affinity value.
*/
boolean match(DeployContext ctx, Routing route);
}
Tip
运行时无性能损耗, 用于需要将拦截器固定的绑定到Controller
的场景, 这里的boolean match(xx)
方法返回的true
和false
实际上相当于Affinity
接口返回Affinity.ATTACHED
以及Affinity.DETACHED
(即只有一定匹配和一定不匹配)
eg. 实现一个拦截器, 拦截所有GET请求(仅包含GET)
@Bean
public RouteInterceptor interceptor() {
return new RouteInterceptor() {
@Override
public boolean match(DeployContext ctx, Routing route) {
HttpMethod[] method = route.mapping().method();
return method.length == 1 && method[0] == HttpMethod.GET;
}
@Override
public CompletionStage<Boolean> preHandle0(RequestContext context, Object handler) {
// biz logic
return CompletableFuture.completedFuture(true);
}};
}
MappingInterceptor
绑定到所有Controller/Route
, 并匹配请求的拦截器
public interface MappingInterceptor extends InternalInterceptor, InterceptorPredicate {
}
相当于affinity()
固定返回Affinity.ATTACHED
eg.
实现一个拦截器, 拦截所有Header中包含X-Foo
请求头的请求
@Bean
public MappingInterceptor interceptor() {
return new MappingInterceptor() {
@Override
public CompletionStage<Boolean> preHandle0(RequestContext context, Object handler) {
// biz logic
return CompletableFuture.completedFuture(true);
}
@Override
public boolean test(RequestContext context) {
return context.request().headers().contains("X-Foo");
}
};
}
HandlerInterceptor
支持基于URI
匹配的拦截器接口
includes()
: 指定拦截器作用范围的Path, 默认作用于所有请求。excludes()
: 指定拦截器排除的Path(优先级高于includes
)默认为空。
public interface HandlerInterceptor extends InternalInterceptor {
String PATTERN_FOR_ALL = "/**";
default String[] includes() {
return null;
}
default String[] excludes() {
return null;
}
}
Warning
涉及到正则匹配, 会有较大性能损失, 且仅支持URI匹配。eg.
实现一个拦截器, 拦截除/foo/bar
意外所有/foo/
开头的请求
@Bean
public HandlerInterceptor interceptor() {
return new HandlerInterceptor() {
@Override
public CompletionStage<Boolean> preHandle0(RequestContext context, Object handler) {
// biz logic
return CompletableFuture.completedFuture(true);
}
@Override
public String[] includes() {
return new String[] {"/foo/**"};
}
@Override
public String[] excludes() {
return new String[] {"/foo/bar"};
}
};
}
InterceptorFactory
拦截器工厂类, 用于为每一个Controller/Route
生成一个Interceptor
public interface InterceptorFactory {
/**
* Create an instance of {@link Interceptor} for given target handler before starting Restlight server..
*
* @param ctx deploy context
*
* @param route target route.
* @return interceptor
*/
Optional<Interceptor> create(DeployContext ctx, Routing route);
}