回顾
Zuul是通过
ZuulServletFilter
或者ZuulServlet
接管我们的请求-
Zuul整个流程如下:
ZuulServletFilter(ZuulServlet)
->ZuulRunner
->FilterProcessor
->ZuulFilter
目标
明确SpringMVC和Zuul框架是怎么配合的
引入Zuul的版本信息
Hoxton.RELEASE
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.cloud
spring-cloud-starter-netflix-zuul
Zuul功能启用及配置的加载
Zuul的启用 - @EnableZuulProxy
// 引入断路器功能
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
// 注入触发Zuul配置类的标记Bean
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
ZuulProxyAutoConfiguration - Zuul自动配置Bean
// 此配置类不会被代理
@Configuration(proxyBeanMethods = false)
// 引入Ribbon相关配置
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
// 省略部分代码。。。
// 加载pre filters bean
@Bean
@ConditionalOnMissingBean(PreDecorationFilter.class)
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator,
this.server.getServlet().getContextPath(), this.zuulProperties,
proxyRequestHelper);
}
// 加载route filters bean
@Bean
@ConditionalOnMissingBean(RibbonRoutingFilter.class)
public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
RibbonCommandFactory> ribbonCommandFactory) {
RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
this.requestCustomizers);
return filter;
}
// 加载route filters bean
@Bean
@ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,
CloseableHttpClient.class })
public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
ZuulProperties zuulProperties,
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
ApacheHttpClientFactory httpClientFactory) {
return new SimpleHostRoutingFilter(helper, zuulProperties,
connectionManagerFactory, httpClientFactory);
}
}
ZuulServerAutoConfiguration - Zuul自动配置Bean
@Configuration(proxyBeanMethods = false)
// 加载zuul的自定义properties配置
@EnableConfigurationProperties({ ZuulProperties.class })
// 加载前提:classpath下有类ZuulServlet和ZuulServletFilter
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
public class ZuulServerAutoConfiguration {
// 省略部分代码。。。
// ZuulController是Controller的一个实现,负责将拦截的请求交给ZuulServlet处理
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
// ZuulHandlerMapping负责路由匹配
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,
ZuulController zuulController) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController);
mapping.setErrorController(this.errorController);
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
// 默认加载ZuulServlet
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false",
matchIfMissing = true)
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean servlet = new ServletRegistrationBean<>(
new ZuulServlet(), this.zuulProperties.getServletPattern());
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
// 当配置zuul.use-filter=true,加载zuulServletFilter, 表示用filter来拦截请求
@Bean
@ConditionalOnMissingBean(name = "zuulServletFilter")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true",
matchIfMissing = false)
public FilterRegistrationBean zuulServletFilter() {
final FilterRegistrationBean filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setUrlPatterns(
Collections.singleton(this.zuulProperties.getServletPattern()));
filterRegistration.setFilter(new ZuulServletFilter());
filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
filterRegistration.addInitParameter("buffer-requests", "false");
return filterRegistration;
}
// 在Zuul各阶段filter处理过程中捕获异常,SendErrorFilter会forward "/error"
@Bean
public SendErrorFilter sendErrorFilter() {
return new SendErrorFilter();
}
@Configuration(proxyBeanMethods = false)
protected static class ZuulFilterConfiguration {
// 注入Spring容器中的ZuulFilter类型所有的实现类,包括内置和自定义的Filter,内置的有10个
@Autowired
private Map filters;
// 注册ZuulFilter到FilterRegistry中
@Bean
public ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory,
TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory,
filterLoader, filterRegistry);
}
}
}
以上两个类,加载了Zuul的相关配置类:
-
拦截请求:
- 和SpringMVC结合的Bean:
ZuulController
、ZuulHandlerMapping
- 通过Web Filter拦截请求Bean:
ZuulServletFilter
- 和SpringMVC结合的Bean:
-
Zuul流程需要的Bean:
自带的ZuulFilter,有10个下面会一一介绍
监控相关
ZuulFilter的容器:
FilterRegistry
默认的ZuulFilters
Pre Filter
-
ServletDetectionFilter
order = -3作用:判断请求是否是由
DispatcherServlet
orZuulServlet
传来的,并把判断结果以键值对的形式放在RequestContext
-
Servlet30WrapperFilter
order = -2作用:包装request,兼容servlet3.0
-
FormBodyWrapperFilter
order = -1作用:包装表单数据并为下游服务重新编码
-
DebugFilter
order = 1作用:如果debug请求,那么会在
RequestContext
中标记为debug请求和routing -
PreDecorationFilter
= 5作用:请求路由和zuul路由配置进行匹配,并设置与代理相关的头部信息
Route Filter
-
RibbonRoutingFilter
order = 10作用:使用Ribbon、Hytrix和可插拔的httpClient发送请求,serviceId、是否重试以及负载均衡策略在相关联的
RequestContext
获取 -
SimpleHostRoutingFilter
order = 100作用:用HttpClient发送请求到预定的URLs,URLs通过
RequestContext#getRouteHost()
获取 -
SendForwardFilter
order = 500作用:用
RequestDispatcher
forwards请求,转发的地址是RequestContext
的FilterConstants#FORWARD_TO_KEY
对应value
Post Filter
-
SendResponseFilter
order = 1000作用:写 代理的请求得到的响应 到 当前响应
Error Filter
-
SendErrorFilter
order = 0作用:如果
RequestContext#getThrowable()
不为空,默认将请求转发到 /error
SpringMVC怎么把请求转发给Zuul?
从配置类分析
从上述配置可以看下几个重要的配置类源码:
ZuulController
public class ZuulController extends ServletWrappingController {
public ZuulController() {
// 设置Servlet的类型
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
try {
// We don't care about the other features of the base class, just want to
// handle the request
return super.handleRequestInternal(request, response);
}
finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
RequestContext.getCurrentContext().unset();
}
}
}
ServletWrappingController
public class ServletWrappingController extends AbstractController implements BeanNameAware, InitializingBean, DisposableBean {
// 省略代码。。
@Override
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("'servletClass' is required");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
// 通过反射 初始化servlet
this.servletInstance = ReflectionUtils.accessibleConstructor(this.servletClass).newInstance();
this.servletInstance.init(new DelegatingServletConfig());
}
// 通过servlet实例处理请求
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
Assert.state(this.servletInstance != null, "No Servlet instance");
this.servletInstance.service(request, response);
return null;
}
}
ZuulHandlerMapping
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
private final ZuulController zuul;
private volatile boolean dirty = true;
// 根据寻找路由处理器
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
return null;
}
// 如果属于配置的忽视路由,则返回null
if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.containsKey("forward.to")) {
return null;
}
if (this.dirty) {
// dirty默认为true,第一次会触发注册处理器到Spring容器中
// 或者发送zuul路由刷新事件,设置dirty为true,见ZuulRefreshListener
synchronized (this) {
if (this.dirty) {
registerHandlers();
this.dirty = false;
}
}
}
// 交给Spring查找路由对应的handler
return super.lookupHandler(urlPath, request);
}
// 注册配置路由对应的处理器
private void registerHandlers() {
Collection routes = this.routeLocator.getRoutes();
if (routes.isEmpty()) {
this.logger.warn("No routes found from RouteLocator");
}
else {
for (Route route : routes) {
// 在Spring容器中注册zuul路由配置对应ZuulController处理器
registerHandler(route.getFullPath(), this.zuul);
}
}
}
}
以上配置类:
-
ZuulController
:它是ServletWrappingController
的 子类,其属性包含了ZuulServlet
, -
ZuulHandlerMapping
:它是AbstractUrlHandlerMapping
的子类 -
ZuulServlet
:由上一篇知道它是Zuul流程的入口之一
回顾SpingMVC对于请求的处理流程
- 客户端请求交给SpringMVC的
DispatcherServlet
统一处理- 通过已经注册的
HandlerMapping
, 根据请求路由找到处理器执行链HandlerExecutionChain
,包括请求各个拦截器HandlerInterceptor
和请求处理器handler
- 找到请求处理器对应的适配器
HandlerAdapter
- 执行已注册的各拦截器的
preHandle
方法- 调用处理器处理请求,返回模型数据以及视图
ModelAndView
- 执行已注册的各拦截器的
postHandle
方法- 根据给定的
ModelAndView
进行渲染- 响应客户端
结合SpingMVC对于请求的处理流程可以猜到,当请求给到SpringMVC的DispatcherServlet
后,如果该路由是需要Zuul拦截的请求,那么会匹配到ZuulHandlerMapping
,从而找到处理器ZuulController
,之后在处理的时候,会交给ZuulServlet
,后面的流程见上一篇文章。
Debug验证
zuul拦截配置:
# zuul
# 是否启用ZuulServletFilter
# zuul.use-filter=true
ribbon.ConnectTimeout = 30000
ribbon.ReadTimeout = 30000
ribbon.eureka.enabled = false
management.endpoints.web.exposure.include = *
zuul.routes.test.path = /test/**
zuul.routes.test.stripPrefix = false
test.ribbon.listOfServers = ${service.test}
service.test=http://127.0.0.1:8081/t/test
请求:curl -v http://127.0.0.1:8080/test
图示过程:
结果显示:猜想是正确的。
大致流程:DispatcherServlet
-> ZuulController
-> ZuulServlet
-> 执行各阶段ZuulFilters
ZuulServletFilter - 另一种拦截请求流程
配置
// 在类ZuulServerAutoConfiguration中加载
@Bean
@ConditionalOnMissingBean(name = "zuulServletFilter")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true",
matchIfMissing = false)
public FilterRegistrationBean zuulServletFilter() {
final FilterRegistrationBean filterRegistration = new FilterRegistrationBean<>();
// URL匹配规则: /zuul
filterRegistration.setUrlPatterns(
Collections.singleton(this.zuulProperties.getServletPattern()));
filterRegistration.setFilter(new ZuulServletFilter());
filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
filterRegistration.addInitParameter("buffer-requests", "false");
return filterRegistration;
}
ZuulServletFilter
的URL匹配规则是/zuul
, 而且如果要是使得ZuulServletFilter
Bean加载,必须在配置文件中,添加:zuul.use-filter=true
,如图:
# 是否启用filter拦截
zuul.use-filter=true
zuul.routes.test.path = /zuul/test/**
zuul.routes.test.stripPrefix = false
test.ribbon.listOfServers = ${service.test}
service.test=http://127.0.0.1:8081
ZuulServletFilter源码
public class ZuulServletFilter extends com.netflix.zuul.filters.ZuulServletFilter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
super.doFilter(servletRequest, servletResponse, filterChain);
}
}
源码很简单,在请求上下文添加了一个标志位zuulEngineRan
为true。并执行父类com.netflix.zuul.filters.ZuulServletFilter
的doFilter
方法,进而进入了Zuul的核心流程当中,后面的流程我们已经熟悉了。
其中要要注意下,com.netflix.zuul.filters.ZuulServletFilter
虽然是Filter,但是并没有在其doFilter
方法中调用FilterChain
的doFilter
方法,我们可以回想下,如果是我们自己写FIlter,一定会调用。之所以ZuulServletFilte
没有这么做,是因为它要接管请求,并不要Servlet来处理。
大致流程如图:
总结
Zuul和SpringMVC结合并接管请求的方式主要有两种:
- 在Spring容器中通过注册请求处理器
ZuulController
和路由处理器的映射ZuulHandlerMapping
,做到请求的拦截,并内置了一些ZuulFIlter保证请求的处理。 - 通过注册
ZuulServletFilter
,使用Filter方式接管请求,注意默认的路径匹配及生效配置