RequestContext
Zuul
的上下文,继承 ConcurrentHashMap
。内置 ThreadLocal
变量。因此每个线程都会有自己的 RequestContext
变量
ZuulFilter
ZuulFilter
实现 IZuulFilter
Comparable
接口。
IZuulFilter
定义了 2 个方法
public interface IZuulFilter {
// 是否该拦截
boolean shouldFilter();
// 拦截之后做什么处理。注:如果返回值为非 boolean,不会做处理,这其实表示,该方法返回值 无意义
Object run() throws ZuulException;
}
来看 ZuulFilter
中 2 个抽象方法
abstract public String filterType();
abstract public int filterOrder();
Zuul
filterType
有以下几个值:pre
、route
、post
、error
- pre
执行前拦截
- route
执行远程服务
- post
执行后拦截
- error
发生错误时拦截
关于 filterOrder()
先看 compareTo()
方法
public abstract class ZuulFilter implements Comparable<ZuulFilter> {
// 升序
public int compareTo(ZuulFilter filter) {
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
}
ZuulFilter
实现了 Comparable
接口。表明,每个 ZuulFilter
之间是具备顺序的,filterOrder()
方法返回的值 越小越靠前
再来看 runFilter()
方法
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!isFilterDisabled()) {
if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(e);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
对于每个返回值,均被封装为 ZuulFiterResult
如果有异常,也不会被抛出,而是放入 ZuulFiterResult
中。
从 ZuulFilter 的实现原理来看,ZuulFilter 并非是基于传统的 Filter 实现。
ZuulProcessor
ZuulProcessor
为 Zuul
的处理器。先看 runFilters()
方法
public class FilterProcessor {
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
}
public class FilterProcessor {
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
boolean bDebug = ctx.debugRouting();
final String metricPrefix = "zuul.filter-";
long execTime = 0;
String filterName = "";
try {
long ltime = System.currentTimeMillis();
filterName = filter.getClass().getSimpleName();
RequestContext copy = null;
Object o = null;
Throwable t = null;
if (bDebug) {
Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
copy = ctx.copy();
}
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
execTime = System.currentTimeMillis() - ltime;
switch (s) {
case FAILED:
t = result.getException();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
break;
case SUCCESS:
o = result.getResult();
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
if (bDebug) {
Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
Debug.compareContextState(filterName, copy);
}
break;
default:
break;
}
if (t != null) throw t;
usageNotifier.notify(filter, s);
return o;
} catch (Throwable e) {
if (bDebug) {
Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
}
usageNotifier.notify(filter, ExecutionStatus.FAILED);
if (e instanceof ZuulException) {
throw (ZuulException) e;
} else {
ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
throw ex;
}
}
}
}
runFilters
方法遍历所有的 ZuulFilters
并执行 processZuulFilter
方法. processZuulFilter
方法主要是执行了 ZuulFilter.runFilter
方法。
可以看到,只有在当 ZuulFilter.runFilter
返回 Boolean
时,才会做处理
ZuulServlet、ZuulServletFilter
Zuul
为了适配 HttpServlet
,也因此有了 ZuulServlet
、ZuulServletFilter
.
默认情况下,启用的是 ZuulServlet
. 不过可以通过 zuul.use-filter
值修改启用类
public class ZuulServerAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false", matchIfMissing = true)
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean<ZuulServlet> 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;
}
@Bean
@ConditionalOnMissingBean(name = "zuulServletFilter")
@ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true", matchIfMissing = false)
public FilterRegistrationBean zuulServletFilter() {
final FilterRegistrationBean<ZuulServletFilter> 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;
}
}
ZuulServlet
、ZuulServletFilter
本质区别是:前者是基于 HttpServlet
实现,后者是基于 Filter
实现。从 j2ee
层面来说,Filter
的执行顺序在 HttpServlet
之前
因为 ZuulServlet
、ZuulServletFilter
核心实现差不多,这里用 ZuulServlet
来说明
public class ZuulServlet extends HttpServlet {
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
}
有几个细节处理一下:
- 因为
RequestConext
是基于 ThreadLocal 实现,因此在finally
中调用unset()
, 而unset()
方法,则是在内部调用了ThreadLocal.remove()
- 如果未发生异常,则正确的处理流程为:
pre
->route
->post
- pre 阶段发生异常
pre
-> error
-> post
- route 阶段发生异常
pre
-> route
-> error
-> post
- post 阶段发生异常
pre
-> route
-> post
-> error
- 在执行
pre
,route
,post
,error
时,内部借助zuulRunner
,zuulRunner
由借助FilterProcessor
调用对应的runFilters("pre" || "route" ...)
SpringCloud 动态路由
动态路由定义:不仅仅可以从配置文件中加载路由,还可以从别的地方加载,例如数据库。当有新的路由添加后,需要可以动态的刷新路由
先看下 RouteLocator
接口
public interface RouteLocator {
Collection<String> getIgnoredPaths();
List<Route> getRoutes();
Route getMatchingRoute(String path);
}
RouteLocator
的继承体系如下
其中 SimpleRouteLocator
不具备刷新功能,RefreshableRouteLocator
接口具备刷新功能。但是 SpringCloud 默认提供的实现类是 SimpleRouteLocator
这里再看,Zuul
启动后,如何加载路由
可以看到,是通过事件监听,从而刷新路由的。
动态路由食用 DEMO
public class MyRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
public MyRouteLocator(String servletPath, ZuulProperties properties) {
super(servletPath, properties);
}
@Override
public void refresh() {
super.doRefresh();
}
protected Map<String, ZuulRoute> locateRoutes() {
Map<String, ZuulRoute> routesMap = super.locateRoutes();
// TODO 实现你自己的加载逻辑
return routesMap;
}
}
@RestController
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
@Lazy
@Resource
MyRouteLocator myRouteLocator;
@Autowired
ApplicationContext applicationContext;
@GetMapping("/test")
public Object test() {
// 从源码流程看,我们需要发布刷新事件。因为发布刷新事件,dirty 会被设置为 true, 设置为 true 后,才会重新注册对应 URL 的 HandlerMapping
applicationContext.publishEvent(new RoutesRefreshedEvent(myRouteLocator));
return "12";
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。