在用zuul的时候,不太清楚是如何完成路由的。为什么输入指定路径会跳转到某个服务。所以在后面有空看到源码的时候暂时记录一下。
在讲源码之前有部分关于springmvc的内容。之前这块也没有专门去看。但是有部分接口很重要。有必要先看一下。
重要的接口以及结构
HandlerInterceptor
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
这个类就是对于请求的一个拦截器。
结构如下,对执行前,执行后,操作完成后,做相关操作。
image.png
HandlerExecutionChain
HandlerExecutionChain其实就是一个处理器执行链。其中包括俩部分。
拦截器链,以及对应具体的handler(其实就是请求具体对应的执行方法)
public class HandlerExecutionChain {
private final Object handler;
@Nullable
private HandlerInterceptor[] interceptors;
@Nullable
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex = -1;
}
结构图
HandlerMapping
一个url的处理包括,拦截器的处理,以及自身的业务逻辑,HandlerExecutionChain恰好对此做了封装。
所以HandlerMapping其实就是url与执行器的映射。
public interface HandlerMapping {
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
那么如何通过url来找到对应的handler呢?
在DispatcherServlet中有一个很重要的方法,代码如下
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
@Nullable
private List<HandlerMapping> handlerMappings;
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
}
从上面代码来看还是比较简单的。DispatcherServlet有一个成员变量handlerMappings用于保存请求与处理器的映射。获取处理器也是通过HandlerMapping的getHandler方法。
zuul在运行的时候,有几个类,debug的结果如下。
HandlerMapping的debug过程
由于此处我们使用的是Zuul组件。所以需要关心怎么去找到映射的话,直接看ZuulHandlerMapping即可。
ZuulHandlerMapping
结构如下从结构上看,ZuulHandlerMapping是url与处理器的映射。
那么究竟如何找?
从结构上看,ZuulHandlerMapping的父类有3个。其中查询处理器的基本实现在父类中已经实现了。
父类中对于查询处理器的实现,代码如下
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
@Nullable
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
...基本的实现。
}
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
将请求解析成字符串路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
模板模式,如果子类不对子方法进行覆写,默认就是以下的执行流程。
lookupHandler方法就是查找处理器的核心逻辑。
Object handler = lookupHandler(lookupPath, request);
。。。省略部分代码
return handler;
}
}
其中ZuulHandlerMapping对lookupHandler进行了覆写。所以直接看ZuulHandlerMapping对于lookupHandler的实现即可。查找处理器的步骤分为两步。一开始得先注册处理器,然后再查找处理器。
对于处理器的注册
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
private volatile boolean dirty = true;
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request)
throws Exception {
...省略部分代码
默认为ture,由于volatile不能保证原子性,所以需要结合同步锁使用。
if (this.dirty) {
synchronized (this) {
if (this.dirty) {
registerHandlers();
this.dirty = false;
}
}
}
return super.lookupHandler(urlPath, request);
}
}
从上面的代码来看,我们并没有看到具体如何查找,但是有一个注册处理器的方法,实现如下。
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
private final ZuulController zuul;
private void registerHandlers() {
Collection<Route> routes = this.routeLocator.getRoutes();
if (routes.isEmpty()) {
this.logger.warn("No routes found from RouteLocator");
}
else {
其实就是将路由规则转成对应的路径,同时为路径设置对应的处理器。
处理器就是ZuulController.
for (Route route : routes) {
registerHandler(route.getFullPath(), this.zuul);
}
}
}
}
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
...省略代码
Object mappedHandler = this.handlerMap.get(urlPath);
一个请求路径只能有一个对应的处理器,否则会抛异常
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
。。。省略
}
else if (urlPath.equals("/*")) {
。。。省略
}
else {
将路径与处理器保存在成员变量中
this.handlerMap.put(urlPath, resolvedHandler);
}
}
}
}
再看看Debug的信息
debug信息
debug的信息里Route这个结构其实是我们配置文件里面写的一个路由规则,就是具体什么路径映射到什么服务,这个id就是注册中心里的application-name.
如何根据请求路径查找处理器?
在前面我们已经知道,ZuulHandlerMapping复写了lookupHandler方法。
会首先将请求与handler的映射进行保存。保存在AbstractUrlHandlerMapping的成员变量handlerMap 中。
url就是根据路由规则生成的全路径(/服务id/请求路径),而处理器就是ZuulController.那么相信大家也知道后面该如何找到处理器了。就是通过这个成员变量。
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
那么究竟是如何实现的?还是回到源码
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request)
throws Exception {
省略代码
if (this.dirty) {
synchronized (this) {
if (this.dirty) {
注册请求与处理器
registerHandlers();
this.dirty = false;
}
}
}
调用父类的lookupHandler方法
return super.lookupHandler(urlPath, request);
}
}
可以看到,在注册完处理器之后,又调用了父类的方法。具体代码如下
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
@Nullable
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
这里去获取处理器有俩种情况
Object handler = this.handlerMap.get(urlPath);
// 1.刚刚好路径是有对应的处理器的,比如在配置文件的路由规则中,写定的是具体的路由规则,而不是xx/**这种,确实是可以获取到对应处理器。
if (handler != null) {
。。。省略部分代码
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
//2.那如果路由规则是xx/**这种呢?
就会进行路径匹配,将handlerMap遍历一遍,看看有没有匹配的处理器。
List<String> matchingPatterns = new ArrayList<>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
matchingPatterns.sort(patternComparator);
if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
logger.trace("Matching patterns " + matchingPatterns);
}
bestMatch = matchingPatterns.get(0);
}
if (bestMatch != null) {
handler = this.handlerMap.get(bestMatch);
if (handler == null) {
if (bestMatch.endsWith("/")) {
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
}
if (handler == null) {
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
logger.trace("URI variables " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
没有匹配的处理器,则返回Null.
return null;
}
}
看看查询到的处理器。可以看到对应的handler就是ZuulController.
debug结果
查找的原理图
网友评论