SpringCloud学习笔记(七、SpringCloud Netflix Zuul)

目录:

  • 什么是zuul
  • 为什么要有zuul、zuul能做什么
  • zuul的基础知识
  • springcloud整合eureka、config、zuul
  • zuul源码分析

什么是zuul

Zuul是Netflix开源的一款网关项目,说到这里我们就需要了解一下什么是网关了。

按照个人理解,网关就是网络层面的关卡,是流量的一扇门,流量是否能通过这扇门,以及通向什么地方由网关来控制;这里你可以把网关类比成海关,这样你可以能更加快速的理解网关的含义。

综上服务网关最基本的功能便是路由转发 + 过滤器

  • 路由转发:接收一切外界请求,转发到后端的微服务上去;这里我拿我们较为熟悉的海关来类比,当一个商品要流入我国时我国的海关是一定要把控商品的流向的,比如可以流入到什么地方,不能流入到什么地方。
  • 过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(路由转发也是通过过滤器实现的);这里我还是拿海关来类比,商品入流我国时我国的海关肯定要读商品做一系列的检查(是否合法,数量的控制以及后续的流向等等),比如毒品、违禁品、外来物种都是不能流入我国的。

为什么要有zuul、zuul能做什么

这里还是可以用海关还说明。

首先我们假设没有海关会发生什么,第一商品的安全我们肯定是没有办法全面保证的,因为每个商家的认知水平肯定是参差不齐的,并不是每个商家都能很好的区分商品的安全性;就比如白粉是毒品,但它看起来和面粉没啥区别,假设有恐怖分子向通过这种方式来残害某地人员,而商家又没有识别到,这就会有严重的问题发生;当然这只是一个假设,实际上恐怖分子也不会这么蠢。

正如上面所说,商家的认知水平是参差不齐的,所以商家要保证商品的安全性就一定要学习如何辨识商品的危险度,这对商家来说无疑提高了学习成本,故此个人认为这也是海关应运而生的一个条件。

同理,在微服务体系中每个服务也肯定是要保证自身服务的安全,防止外部的恶意攻击;因此由服务自身来实现安全校验无疑增加了开发成本,而且也并不能很好的保证服务的安全,所以网关的诞生也是如此。

综上:zuul网关可以帮助微服务体系下的业务应用专注与自身的业务逻辑,而不必为了API日志、服务安全、监控、负载等等业务外的功能增加开发成本。

——————————————————————————————————————————————————————————————————————

上面说到微服务体系中可以通过网关来完成个服务的安全校验,这无疑使正确的。当然避免个服务都要自我实现安全校验的办法肯定不止一种,你可以将类似的操作都写到一个公共服务中去,然后其它的服务来依赖这个公共的服务。

是的,这样也可以达到预期的效果,但是微服务体系的服务实现肯定是不止java这一种语言的,如果你才用这种方法实现还是有些弊端的。就算这些服务都是java的,但也会因为依赖了公共服务,导致服务自身jar包的大小增加,不利于docker镜像进行部署的场景以及后续的维护。

——————————————————————————————————————————————————————————————————————

Zuul能做的很多,常用的应用场景如下:

  • 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
  • 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
  • 动态路由:动态地将请求路由到不同的后端集群。
  • 压力测试:逐渐增加指向集群的流量,以了解性能。
  • 负载分配:为每一种负载类型分配对应容量,并启用超出限定值的请求。
  • 静态响应处理:在边缘位置直接建立部分相应,从而避免其转发到内部集群。

zuul的基础知识

zuul路由配置:

1、传统路由配置:

传统路由的配置不依赖与服务发现机制,通过定制每个路由表达式实例的映射关系来实现api网关对外部请求的路由。

 1 ### 单例配置 ###
 2 # 配置请求路由路径:zuul.routes.<route>.path=xxx
 3 zuul.routes.service-a.path=/service-a/**
 4 # 配置路由后转发的url:zuul.routes.<route>.url=xxx
 5 zuul.routes.service-a.url=http://localhost:8080/
 6 
 7 ### 多例配置 ###
 8 # 配置请求路由路径:zuul.routes.<route>.path=xxx
 9 zuul.routes.service-a.path=/service-a/**
10 # 配置服务id:zuul.routes.<route>.serviceId=xxx
11 zuul.routes.service-a.serviceId=service-a
12 # ribbon负载
13 ribbon.eureka.enabled=false
14 # 配置路由后转发的url:zuul.routes.<route>.url=xxx
15 service-a.ribbon.listOfServers=http://localhost:8080/,http://localhost:9090/

说到这里就有必要提一下路由路径配置的表达式了(也就是请求路径后面的**):

  • ?:单个字符匹配,如/service-a/a、/service-a/b这些。
  • *:匹配任意数量的字符:如/service-a/a、/service-a/aaa这些,像/service-a/a/b就匹配不到。
  • **:匹配多级目前的任意数量字符:如/service-a/a、/service-a/aaa、/service-a/a/b都可以匹配到。

2、服务路由配置(基于服务发现):

集成服务发现时不需要像传统路由那样为serviceId指定具体服务实例的地址,通过SpringCloud Zuul与SpringCloud Eureka的整合就可以实现对服务实例的自动化维护

1 zuul.routes.service-a.path=/service-a/**
2 zuul.routes.service-a.serviceId=service-a

3、路由路径配置:

  • zuul.ignoredPatterns:忽略表达式,参数用来设置不希望被api网关进行路由的url表达式。
  • zuul.routes.prefix=/xxx、zuul.routes.strip-prefix=false:路由前缀,为路由规则增加前缀;如你希望网关上的路由规则增加/api前缀就可以在prefix里配置;但需要之意的是你要吧zuul.routes.strip-prefix配置为true。
  • serviceId:forward:/path:路由跳转,zuul支持forward形式的服务端跳转配置。
  • zuul.sensitiveHeaders:Cookie与头信息,zuul支持过去调http请求头的一些敏感信息,防止传递到下游服务。

——————————————————————————————————————————————————————————————————————

zuul线程模型:

zuul有两种线程模型,一种同步阻塞、一种异步非阻塞。

1、同步阻塞:

2、异步非阻塞:

——————————————————————————————————————————————————————————————————————

zuul请求生命周期:

zuul分为4大过滤器,pre-前置过滤器、route-路由过滤器、post-后置过滤器、error-错误过滤器。

  • pre:在请求转发前处理请求,如日志、请求校验等。
  • route:将请求转发到具体的服务提供方。
  • post:在接收到服务提供方的返回结果后做的一些处理,如数据加工、内容转换等等(数据脱敏)。
  • error:当请求发生异常时启用error过滤器。

zuul过滤器:

1、zuul自带过滤器

2、自定义过滤器:

见:https://github.com/mrjdrs/springcloud-integration.git的com.jdr.maven.sc.integration.zuul.filter.LogFilter

springcloud整合eureka、config、zuul:

1、架构图

2、源码:https://github.com/mrjdrs/springcloud-integration.git

zuul源码分析:

阅读目的:

  • 为什么在启动类上加@EnableZuulProxy就可以让此服务变成微服务网关,这个注解做了什么,它是如何实现的。
  • zuul是动态路由 + 过滤器,这两个分别是如何实现的。
  • 过滤器有四种,前置、请求转发、后置、错误,分别是如何实现的。

源码分析:

在阅读zuul源码前首先我们要大致了解下如下几个类的作用:

  • ZuulProperties:zuul的相关配置。
  • ZuulController:zuul的控制器,当有请求转发时会经过由ZuulController来处理请求。
  • ZuulServlet:是zuul用于处理具体请求的一个servlet。
  • ZuulHandlerMapping:HandlerMapping会根据请求的url找到对应处理器(Handler),并返回一个执行链;ZuulHandlerMapping就是根据配置的zuul请求找到对应的处理器。

好,开始分析!

——————————————————————————————————————————————————————————————————————

在SpringCloud Netflix Zuul中,我们只需要在启动类加上@EnableZuulProxy注解便可以让当前服务成为一个微服务网关。那么它是怎样做到的呢,我们来看看这个注解。

1 @EnableCircuitBreaker
2 @Target(ElementType.TYPE)
3 @Retention(RetentionPolicy.RUNTIME)
4 @Import(ZuulProxyMarkerConfiguration.class)
5 public @interface EnableZuulProxy {
6 }

@Target、@Retention不必多说,是元注解。@EnableCircuitBreaker是启用断路器,不在本次分析的范围内,所以我们主要看下@Import(ZuulProxyMarkerConfiguration.class)

@Import(ZuulProxyMarkerConfiguration.class):将ZuulProxyMarkerConfiguration注册到Spring容器中;既然是注册的bean,那么肯定是要使用的,我们来看看它是在哪使用的。

通过IDEA查看后得知类在org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration调用了:

1 @Configuration
2 @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
3         RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
4         RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
5         HttpClientConfiguration.class })
6 @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
7 public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
8 }

结合ZuulProxyMarkerConfiguration的调用情况(@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class))和类名可以推测其只是一个标记类,无特殊逻辑。

通过对ZuulProxyAutoConfiguration的大致浏览你可以发下,这是对ZuulProxy的自动装配类,主要是自动的初始化了一些ZuulProxy相关的bean。

当然这些都不是本次的重点,这一小段都只是通过各种零碎的信息叫你如何看源码。

——————————————————————————————————————————————————————————————————————

重点来了。

我们可以看到ZuulProxyAutoConfiguration是一个zuul的代理类,所以肯定是增加的额外功能,其核心逻辑还是在父类ZuulServerAutoConfiguration中。

这一结论你也可以通过org.springframework.cloud.netflix.zuul.EnableZuulProxy上的注释来得到。

所以我们来看看ZuulServerAutoConfiguration到底定义且实现了一些什么东西,源码不是很多,但我任只保留了一些核心的东西。

 1 @Configuration
 2 @EnableConfigurationProperties({ ZuulProperties.class })
 3 @ConditionalOnClass(ZuulServlet.class)
 4 @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
 5 // Make sure to get the ServerProperties from the same place as a normal web app would
 6 @Import(ServerPropertiesAutoConfiguration.class)
 7 public class ZuulServerAutoConfiguration {
 8 
 9     @Autowired
10     protected ZuulProperties zuulProperties;
11 
12     @Bean
13     public ZuulController zuulController() {
14         return new ZuulController();
15     }
16 
17     @Bean
18     public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
19         ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
20         mapping.setErrorController(this.errorController);
21         return mapping;
22     }
23 
24 }

这里涉及到的几个类也是开篇所提到的那几个,ZuulProperties、ZuulController、ZuulServlet、ZuulHandlerMapping。

我们一个一个来分析。

1、ZuulProperties:zuul的相关配置都在这里。

稍微浏览一下,你可以很容易的发现我们较为熟悉的几个配置,如:

  • routes:private Map<String, ZuulRoute> routes = new LinkedHashMap<>();
  • ignoredPatterns:private Set<String> ignoredPatterns = new LinkedHashSet<>();

routes配置了路由转发相关的信息,如上面zuul基础知识提到的path、url、serviceId等等。

同样的我们把的核心代码贴在下面:

 1 public static class ZuulRoute {
 2     private String id;
 3     private String path;
 4     private String serviceId;
 5     private String url;
 6     private boolean stripPrefix = true;
 7     private Boolean retryable;
 8     private Set<String> sensitiveHeaders = new LinkedHashSet<>();
 9     private boolean customSensitiveHeaders = false;
10 
11     // 省略构造函数、getter、setter等等
12 }

2、ZuulController:zuul的控制器,当有请求转发时会经过由ZuulController来处理请求。

从ZuulController的继承关系来看,它是通过Spring提供了Wrapping功能来实现自身逻辑的,其核心类图如下。

最上层的接口Controller的定义是为了处理请求,定义了handleRequest方法。

1 public interface Controller {
2 
3     ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
4 
5 }

AbstractController是Controller的抽象实现,定义了基本的可复用实现(检查请求参数、准备相应等等),且定义了处理内部请求的抽象方法handleRequestInternal。

 1 public abstract class AbstractController extends WebContentGenerator implements Controller {
 2 
 3     @Override
 4     public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
 5             throws Exception {
 6 
 7         if (HttpMethod.OPTIONS.matches(request.getMethod())) {
 8             response.setHeader("Allow", getAllowHeader());
 9             return null;
10         }
11 
12         // Delegate to WebContentGenerator for checking and preparing.
13         checkRequest(request);
14         prepareResponse(response);
15 
16         // Execute handleRequestInternal in synchronized block if required.
17         if (this.synchronizeOnSession) {
18             HttpSession session = request.getSession(false);
19             if (session != null) {
20                 Object mutex = WebUtils.getSessionMutex(session);
21                 synchronized (mutex) {
22                     return handleRequestInternal(request, response);
23                 }
24             }
25         }
26 
27         return handleRequestInternal(request, response);
28     }
29 
30     protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
31             throws Exception;
32 
33 }

而ServletWrappingController则是对AbstractController功能的一个包装,模板化的提供了提现方式,但需要指定实现的servlet。

 1 public class ServletWrappingController extends AbstractController
 2         implements BeanNameAware, InitializingBean, DisposableBean {
 3 
 4     /** 具体实现的servlet类对象 */
 5     private Class<? extends Servlet> servletClass;
 6 
 7     private String servletName;
 8 
 9     private Properties initParameters = new Properties();
10 
11     private String beanName;
12 
13     /** servlet示例 */
14     private Servlet servletInstance;
15 
16     public void setServletClass(Class<? extends Servlet> servletClass) {
17         this.servletClass = servletClass;
18     }
19 
20     public void setServletName(String servletName) {
21         this.servletName = servletName;
22     }
23 
24     @Override
25     public void afterPropertiesSet() throws Exception {
26         if (this.servletClass == null) {
27             throw new IllegalArgumentException("'servletClass' is required");
28         }
29         if (this.servletName == null) {
30             this.servletName = this.beanName;
31         }
32         // 拿到servletClass实例初始化servletInstance
33         this.servletInstance = this.servletClass.newInstance();
34         this.servletInstance.init(new DelegatingServletConfig());
35     }
36 
37     @Override
38     protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
39             throws Exception {
40 
41         this.servletInstance.service(request, response);
42         return null;
43     }
44 
45 }

最后我们来看看ZuulController,这样就会变得简单很多了。

 1 public class ZuulController extends ServletWrappingController {
 2 
 3     public ZuulController() {
 4         // 定义servlet实现实例为ZuulServlet
 5         setServletClass(ZuulServlet.class);
 6         setServletName("zuul");
 7         setSupportedMethods((String[]) null); // Allow all
 8     }
 9 
10     @Override
11     public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
12         try {
13             // We don't care about the other features of the base class, just want to
14             // handle the request
15             return super.handleRequestInternal(request, response);
16         }
17         finally {
18             // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
19             RequestContext.getCurrentContext().unset();
20         }
21     }
22 
23 }

15行的super.handleRequestInternal也正是上面说到ServletWrappingController包装好的,由具体servlet实现。

1 @Override
2 protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
3         throws Exception {
4 
5     this.servletInstance.service(request, response);
6     return null;
7 }

所以这里的this.servletInstance.service(request, response);也会有ZuulServlet来完成,com.netflix.zuul.http.ZuulServlet#service

3、ZuulHandlerMapping:ZuulHandlerMapping就是根据配置的zuul请求找到对应的处理器。

在细说ZuulHandlerMapping源码前是有必要了解下SpringMVC的,因为ZuulHandlerMapping的父级接口为HandlerMapping,它是SpringMVC的重要组件,会根据请求的url找到对应处理器(Handler),并返回一个执行链;同样的我们还是来瞅瞅ZuulHandlerMapping的类图。

HandlerMapping:定义了doHandler方法,用于返回此请求的处理程序和所有拦截器

1 public interface HandlerMapping {
2 
3     HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
4     
5 }

AbstractHandlerMapping:对doHandler方法提供了默认实现,以及其他基础功能。

 1 @Override
 2 public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 3     // 获取内部执行处理器(有具体子类实现)
 4     Object handler = getHandlerInternal(request);
 5     if (handler == null) {
 6         // 当内部处理器为null时,取默认的处理器
 7         // 默认处理器初始值为null,可以通过AbstractHandlerMapping#setDefaultHandler来指定
 8         handler = getDefaultHandler();
 9     }
10     if (handler == null) {
11         return null;
12     }
13     // Bean name or resolved handler?
14     // 如果处理器是String类型(返回的是beanName),则直接去getBean
15     if (handler instanceof String) {
16         String handlerName = (String) handler;
17         handler = getApplicationContext().getBean(handlerName);
18     }
19 
20     // 拿到执行链并返回(不是本次重点,不细说,有兴趣可细读)
21     HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
22     if (CorsUtils.isCorsRequest(request)) {
23         CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
24         CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
25         CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
26         executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
27     }
28     return executionChain;
29 }

可见AbstractHandlerMapping#doHandler是先拿到由子类具体实现的内部执行器,再去获取执行链,这些操作都是包装好的,实现的子类只需要提供获取内部执行器的方法即可(当然也可以指定默认的处理器)。

AbstractUrlHandlerMapping:对url的处理映射。

 1 @Override
 2 protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
 3     // 获取除host外的访问路径
 4     // 如request http://localhost:9999/user-consumer/userConsumer/get/ZD
 5     // 得到/user-consumer/userConsumer/get/ZD
 6     String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
 7     // 查找handler
 8     Object handler = lookupHandler(lookupPath, request);
 9     if (handler == null) {
10         // We need to care for the default handler directly, since we need to
11         // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
12         Object rawHandler = null;
13         // 根目录处理器
14         if ("/".equals(lookupPath)) {
15             rawHandler = getRootHandler();
16         }
17         // 默认处理器
18         if (rawHandler == null) {
19             rawHandler = getDefaultHandler();
20         }
21         // rawHandler是beanName时getBean后再处理
22         if (rawHandler != null) {
23             // Bean name or resolved handler?
24             if (rawHandler instanceof String) {
25                 String handlerName = (String) rawHandler;
26                 rawHandler = getApplicationContext().getBean(handlerName);
27             }
28             validateHandler(rawHandler, request);
29             handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
30         }
31     }
32     if (handler != null && logger.isDebugEnabled()) {
33         logger.debug("Mapping [" + lookupPath + "] to " + handler);
34     }
35     else if (handler == null && logger.isTraceEnabled()) {
36         logger.trace("No handler mapping found for [" + lookupPath + "]");
37     }
38     return handler;
39 }
 1 protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
 2     // Direct match?
 3     Object handler = this.handlerMap.get(urlPath);
 4     if (handler != null) {
 5         // Bean name or resolved handler?
 6         if (handler instanceof String) {
 7             String handlerName = (String) handler;
 8             handler = getApplicationContext().getBean(handlerName);
 9         }
10         validateHandler(handler, request);
11         return buildPathExposingHandler(handler, urlPath, urlPath, null);
12     }
13 
14     // Pattern match?
15     List<String> matchingPatterns = new ArrayList<String>();
16     for (String registeredPattern : this.handlerMap.keySet()) {
17         if (getPathMatcher().match(registeredPattern, urlPath)) {
18             matchingPatterns.add(registeredPattern);
19         }
20         else if (useTrailingSlashMatch()) {
21             if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
22                 matchingPatterns.add(registeredPattern + "/");
23             }
24         }
25     }
26 
27     String bestMatch = null;
28     Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
29     if (!matchingPatterns.isEmpty()) {
30         Collections.sort(matchingPatterns, patternComparator);
31         if (logger.isDebugEnabled()) {
32             logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
33         }
34         bestMatch = matchingPatterns.get(0);
35     }
36     if (bestMatch != null) {
37         handler = this.handlerMap.get(bestMatch);
38         if (handler == null) {
39             if (bestMatch.endsWith("/")) {
40                 handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
41             }
42             if (handler == null) {
43                 throw new IllegalStateException(
44                         "Could not find handler for best pattern match [" + bestMatch + "]");
45             }
46         }
47         // Bean name or resolved handler?
48         if (handler instanceof String) {
49             String handlerName = (String) handler;
50             handler = getApplicationContext().getBean(handlerName);
51         }
52         validateHandler(handler, request);
53         String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
54 
55         // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
56         // for all of them
57         Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
58         for (String matchingPattern : matchingPatterns) {
59             if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
60                 Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
61                 Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
62                 uriTemplateVariables.putAll(decodedVars);
63             }
64         }
65         if (logger.isDebugEnabled()) {
66             logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
67         }
68         return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
69     }
70 
71     // No handler found...
72     return null;
73 }

lookupHandler分为两种处理,一种是精确匹配,一种是正则匹配;如/get/user这种则为精确匹配,正则则为这种/get/user/{id:\\d+}。

两种方式源码不难,不做赘述。但其中handler都是从handlerMap取值,所以handlerMap中的值是从哪来的我们还是要了解清楚。

1 private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();

首先handlerMap是private,所以put应该是在本类发生的,当然你可能会说通过getHandlerMap函数再在外部进行修改;这是不允许的,因为getHandlerMap定义成了unmodifiableMap。

1 public final Map<String, Object> getHandlerMap() {
2     return Collections.unmodifiableMap(this.handlerMap);
3 }

所以put是肯定只能在类的内部发生,根据这个思路,便可以找到AbstractUrlHandlerMapping#registerHandler(java.lang.String, java.lang.Object)。

 1 protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
 2     Assert.notNull(urlPath, "URL path must not be null");
 3     Assert.notNull(handler, "Handler object must not be null");
 4     Object resolvedHandler = handler;
 5 
 6     // Eagerly resolve handler if referencing singleton via name.
 7     if (!this.lazyInitHandlers && handler instanceof String) {
 8         String handlerName = (String) handler;
 9         if (getApplicationContext().isSingleton(handlerName)) {
10             resolvedHandler = getApplicationContext().getBean(handlerName);
11         }
12     }
13 
14     Object mappedHandler = this.handlerMap.get(urlPath);
15     if (mappedHandler != null) {
16         if (mappedHandler != resolvedHandler) {
17             throw new IllegalStateException(
18                     "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
19                     "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
20         }
21     }
22     else {
23         // 根目录匹配
24         if (urlPath.equals("/")) {
25             if (logger.isInfoEnabled()) {
26                 logger.info("Root mapping to " + getHandlerDescription(handler));
27             }
28             setRootHandler(resolvedHandler);
29         }
30         // 一级目录匹配
31         else if (urlPath.equals("/*")) {
32             if (logger.isInfoEnabled()) {
33                 logger.info("Default mapping to " + getHandlerDescription(handler));
34             }
35             setDefaultHandler(resolvedHandler);
36         }
37         // 其它情况(多级目录匹配)
38         else {
39             this.handlerMap.put(urlPath, resolvedHandler);
40             if (logger.isInfoEnabled()) {
41                 logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
42             }
43         }
44     }
45 }

之后我们根据registerHandler函数的调用,便可以得出ZuulHandlerMapping在ZuulHandlerMapping#lookupHandler初始化了用户配置的route。

1 zuul.routes.user-provider=/user-provider/**
2 zuul.routes.user-consumer=/user-consumer/**
 1 private void registerHandlers() {
 2     Collection<Route> routes = this.routeLocator.getRoutes();
 3     if (routes.isEmpty()) {
 4         this.logger.warn("No routes found from RouteLocator");
 5     }
 6     else {
 7         for (Route route : routes) {
 8             registerHandler(route.getFullPath(), this.zuul);
 9         }
10     }
11 }

ZuulMappingHandler:针对zuul的映射器。

分析完上述三个父类后,ZuulMappingHandler就显得格外简单了,它对lookupHandler进行了重写,可忽略一些指定的路径,以及热加载routes列表(低版本的有bug,不建议使用)。

 1 @Override
 2 protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
 3     if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
 4         return null;
 5     }
 6     // 忽略指定的路径(不走Zuul网关)
 7     if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
 8     RequestContext ctx = RequestContext.getCurrentContext();
 9     if (ctx.containsKey("forward.to")) {
10         return null;
11     }
12     // 文件改变时会通过Spring事件机制将dirty重新置为true
13     // 以完成热加载routes配置
14     if (this.dirty) {
15         synchronized (this) {
16             if (this.dirty) {
17                 registerHandlers();
18                 this.dirty = false;
19             }
20         }
21     }
22     return super.lookupHandler(urlPath, request);
23 }

在总结完ZuulHandlerMapping后,你肯定有一个疑问,为什么在开篇要提到SpringMVC呢;因为HandlerMapping是SpringMVC的重要组件,定义的doHandler都是在DispatcherServlet初始化调用的。

其逻辑在org.springframework.web.servlet.DispatcherServlet#getHandler

 1 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 2     for (HandlerMapping hm : this.handlerMappings) {
 3         if (logger.isTraceEnabled()) {
 4             logger.trace(
 5                     "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
 6         }
 7         HandlerExecutionChain handler = hm.getHandler(request);
 8         if (handler != null) {
 9             return handler;
10         }
11     }
12     return null;
13 }

这块需要你自己debug理解。

最后我们来总结下ZuulHandlerMapping的调用链路:

1 org.springframework.web.servlet.DispatcherServlet#getHandler
2   org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
3     org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal
4       org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping#lookupHandler
5         org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler

由DispatcherServlet开始获取所有Handler,并调用getHandler方法;在确认Handler为ZuulHandlerMapping后,依次调用AbstractHandlerMapping、AbstractUrlHandlerMapping、ZuulHandlerMapping。

4、ZuulServlet:是zuul用于处理具体请求的一个servlet。

在源码的最后,我们把zuul的其它几个核心组件,如ZuulProperties、ZuulController、ZuulHandlerMapping都简单的过了一遍,就只剩下具体的Servlet处理了。

你现在还记得他是如何调用的嘛,没错正式由ZuulController#handleRequest委托ServletWrappingController#handleRequestInternal调用的。

 1 @Override
 2 public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
 3     try {
 4         init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
 5 
 6         // Marks this request as having passed through the "Zuul engine", as opposed to servlets
 7         // explicitly bound in web.xml, for which requests will not have the same data attached
 8         RequestContext context = RequestContext.getCurrentContext();
 9         context.setZuulEngineRan();
10 
11         try {
12             // 路由前的处理
13             preRoute();
14         } catch (ZuulException e) {
15             error(e);
16             postRoute();
17             return;
18         }
19         try {
20             // 路由处理
21             route();
22         } catch (ZuulException e) {
23             error(e);
24             postRoute();
25             return;
26         }
27         try {
28             // 路由后的处理
29             postRoute();
30         } catch (ZuulException e) {
31             error(e);
32             return;
33         }
34 
35     } catch (Throwable e) {
36         // 发送异常时的处理
37         error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
38     } finally {
39         RequestContext.getCurrentContext().unset();
40     }
41 }

每个处理器都由ZuulRunner封装,由FilterProcessor具体调用。

 1 void postRoute() throws ZuulException {
 2     zuulRunner.postRoute();
 3 }
 4 
 5 void route() throws ZuulException {
 6     zuulRunner.route();
 7 }
 8 
 9 void preRoute() throws ZuulException {
10     zuulRunner.preRoute();
11 }
 1 public void postRoute() throws ZuulException {
 2     FilterProcessor.getInstance().postRoute();
 3 }
 4 
 5 public void route() throws ZuulException {
 6     FilterProcessor.getInstance().route();
 7 }
 8 
 9 public void preRoute() throws ZuulException {
10     FilterProcessor.getInstance().preRoute();
11 }

每个过滤器都会走向runFilters方法,拿到相应类型的过滤器列表并以此调用processZuulFilter方法:

 1 public Object runFilters(String sType) throws Throwable {
 2     if (RequestContext.getCurrentContext().debugRouting()) {
 3         Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
 4     }
 5     boolean bResult = false;
 6     // 首先根据传入的过滤器类型来拿到过滤器列表
 7     List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
 8     if (list != null) {
 9         for (int i = 0; i < list.size(); i++) {
10             ZuulFilter zuulFilter = list.get(i);
11             // 然后依次调用processZuulFilter方法
12             Object result = processZuulFilter(zuulFilter);
13             if (result != null && result instanceof Boolean) {
14                 bResult |= ((Boolean) result);
15             }
16         }
17     }
18     return bResult;
19 }

processZuulFilter方法也不难,主要逻辑是调用runFilter,而runFilter则是调用com.netflix.zuul.IZuulFilter#run。

这几段逻辑不难读懂,我把它简单列出来。

 1 public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
 2 
 3     RequestContext ctx = RequestContext.getCurrentContext();
 4     boolean bDebug = ctx.debugRouting();
 5     final String metricPrefix = "zuul.filter-";
 6     long execTime = 0;
 7     String filterName = "";
 8     try {
 9         long ltime = System.currentTimeMillis();
10         filterName = filter.getClass().getSimpleName();
11         
12         RequestContext copy = null;
13         Object o = null;
14         Throwable t = null;
15 
16         if (bDebug) {
17             Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
18             copy = ctx.copy();
19         }
20         
21         ZuulFilterResult result = filter.runFilter();
22         ExecutionStatus s = result.getStatus();
23         execTime = System.currentTimeMillis() - ltime;
24 
25         switch (s) {
26             case FAILED:
27                 t = result.getException();
28                 ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
29                 break;
30             case SUCCESS:
31                 o = result.getResult();
32                 ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
33                 if (bDebug) {
34                     Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
35                     Debug.compareContextState(filterName, copy);
36                 }
37                 break;
38             default:
39                 break;
40         }
41         
42         if (t != null) throw t;
43 
44         usageNotifier.notify(filter, s);
45         return o;
46 
47     } catch (Throwable e) {
48         if (bDebug) {
49             Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
50         }
51         usageNotifier.notify(filter, ExecutionStatus.FAILED);
52         if (e instanceof ZuulException) {
53             throw (ZuulException) e;
54         } else {
55             ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
56             ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
57             throw ex;
58         }
59     }
60 }
 1 public ZuulFilterResult runFilter() {
 2     ZuulFilterResult zr = new ZuulFilterResult();
 3     if (!isFilterDisabled()) {
 4         if (shouldFilter()) {
 5             Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
 6             try {
 7                 Object res = run();
 8                 zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
 9             } catch (Throwable e) {
10                 t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
11                 zr = new ZuulFilterResult(ExecutionStatus.FAILED);
12                 zr.setException(e);
13             } finally {
14                 t.stopAndLog();
15             }
16         } else {
17             zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
18         }
19     }
20     return zr;
21 }

综上:只要在zuul的上下文中有定义前置、路由、后置、错误这几种过滤器时,zuul就会在请求进来的时候顺序调用,那么是不是说我们想要自定义过滤器时只要继承ZuulFilter方法呢,是的就是这样;这一点我们之前也在zuul过滤器时说过,你可以再回去看看。

——————————————————————————————————————————————————————————————————————

至此zuul的基本运行原理已经说完了,我们再会到开头提到的几个问题,一一解答。

1、为什么在启动类上加@EnableZuulProxy就可以让此服务变成微服务网关,这个注解做了什么,它是如何实现的。

@EnableZuulProxy通过@Import注解导入了ZuulProxyMarkerConfiguration,此中完成了一些自动化的配置,这样在后续的请求中就可以顺利使用了。

而这些加载的ZuulProperties、ZuulController、ZuulHandlerMapping、ZuulServlet都是由SpringMVC在doDispatcher的getHandler调用。

2、zuul是动态路由 + 过滤器,这两个分别是如何实现的。

动态路由:基于AbstractUrlHandlerMapping中对精确匹配、正则表达匹配来判断是否需要zuul网关动态路由,拿到routes.path转发请求到routes.url。

过滤器:定义根接口IZuulFilter,在请求到达前依次经过前置、请求转发、后置过滤器,发送异常是走到错误过滤器。

3、过滤器有四种,前置、请求转发、后置、错误,分别是如何实现的。

同上。

posted @ 2019-10-20 10:11  被猪附身的人  阅读(659)  评论(0编辑  收藏  举报