liwenbocsu 2019-06-06
1,自定义zuulfilter 纳入bean中管理即可
2,自定义路由ZuulProxyAutoConfiguration中RouteLocator实例化为自定义即可
3,zuul的控制层也是用servlet-controller,这调用连会调用过滤器(先),定义好的路由(后)----调用过程
通过继承ZuulFilter
我们可以定义一个新的过滤器,如下
public class IpAddressFilter extends ZuulFilter { @Autowired private IGatewayService iGatewayService; @Override public String filterType() { // pre类型的过滤器 return PRE_TYPE; } @Override public int filterOrder() { // 排序 return 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); String ip = ctx.getRequest().getRemoteAddr(); Set<String> blackList = ConcurrentCache.getBlackSet(); Set<String> whiteList = ConcurrentCache.getWhiteSet(); blackList.removeAll(whiteList); // 在黑名单中禁用 if (StringUtils.isNotBlank(ip)&& blackList.contains(ip)) { ctx.setSendZuulResponse(false); ctx.setResponseBody("Suspected flooding attack, IP blocked"); ctx.setResponseStatusCode(HttpStatus.FORBIDDEN.value()); ctx.addZuulResponseHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE); return null; } return null; } }
ZuulFilter
中实现了compareTo()
方法,根据它的值决定同类型的filter的执行顺序。compareTo()
方法如下:
public int compareTo(ZuulFilter filter) { return Integer.compare(this.filterOrder(), filter.filterOrder()); }
ZuulFilter
可以通过@Component
,也可以通过@Bean
实例化来纳入spring的生命周期中。
@Configuration public class FilterConfig { @Bean public IpAddressFilter addIpAddressFilter() { return new IpAddressFilter(); } }
ZuulServerAutoConfiguration
中自动装配了filter
,被spring实例化出来的所有的ZuulFilter
都会被自动装配到Map中。
@Configuration protected static class ZuulFilterConfiguration { // 根据类型,自动装配ZuulFilter到Map对象中 @Autowired private Map<String, ZuulFilter> filters; @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); } }
上面的代码会调用ZuulFilterInitializer
的构造方法。
ZuulFilterInitializer
中的contextInitialized()
开启了@PostConstruct
注解,在构造方法完成时,容器会调用contextInitialized()
方法(注意:ZuulFilterInitializer对象要由spring管理才会调用到@PostConstruct),将所有的filter保存到filterRegistry
中,filterRegistry
是一个单例对象。
contextInitialized()
方法如下:
@PostConstruct public void contextInitialized() { log.info("Starting filter initializer"); TracerFactory.initialize(tracerFactory); CounterFactory.initialize(counterFactory); for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) { // 保存filter filterRegistry.put(entry.getKey(), entry.getValue()); } }
ZuulProxyAutoConfiguration
类中注册了RouteLocator
的bean
,@Bean
会按照类型,自动注入RouteLocator的实现类。
@Bean public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) { return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), this.zuulProperties, proxyRequestHelper); }
RouteLocator
实例化
@Configuration public class AppConfig{ //.....省略.... @Bean(value = "discoveryRouteLocator") public DiscoveryClientRouteLocator discoveryClientRouteLocator(ServerProperties server, DiscoveryClient discovery, ZuulProperties properties,ServiceInstance localInstance) { return new CustomRouteLocator(server.getServletPath(), discovery,properties,localInstance); } }
CustomRouteLocator
实现自定义路由的功能,类如下。
public class CustomRouteLocator extends DiscoveryClientRouteLocator { // ....省略.... @Override // 重写 public Route getMatchingRoute(String path) { // ....省略.... //可以从数据库中读取路由规则,并进行处理 } // 重写 @Override protected LinkedHashMap<String, ZuulRoute> locateRoutes() { // ....省略.... } }
/
进行过滤。下面说明servlet的初始化内容。ZuulServerAutoConfiguration
类中定义了ZuulController
@Bean public ZuulController zuulController() { return new ZuulController(); }
ZuulController
继承了ServletWrappingController
类
public class ZuulController extends ServletWrappingController { public ZuulController() { // 设置类为ZuulServlet setServletClass(ZuulServlet.class); setServletName("zuul"); setSupportedMethods((String[]) null); } @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { try { return super.handleRequestInternal(request, response); } finally { RequestContext.getCurrentContext().unset(); } } }
ServletWrappingController
对ZuulServlet
进行实例化
@Override public void afterPropertiesSet() throws Exception { if (this.servletClass == null) { throw new IllegalArgumentException("'servletClass' is required"); } if (this.servletName == null) { this.servletName = this.beanName; } // 实例化 this.servletInstance = this.servletClass.newInstance(); // 调用servlet的init方法 this.servletInstance.init(new DelegatingServletConfig()); }
ZuulController
中,执行handleRequest()
方法。@Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { try { // 调用父类的handleRequestInternal方法 return super.handleRequestInternal(request, response); } finally { RequestContext.getCurrentContext().unset(); } }
handleRequestInternal()
方法如下:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { this.servletInstance.service(request, response); return null; }
servletInstance
即ZuulServlet
的实例,上面的方法最终调用ZuulServlet
中的service()
方法。
service()
方法如下:
@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 { // pre过滤器 preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; } try { // route过滤器 route(); } catch (ZuulException e) { error(e); postRoute(); return; } try { // post过滤器 postRoute(); } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
preRoute()
方法为例,对路由的处理过程进行说明。preRoute()
方法如下:
void preRoute() throws ZuulException { zuulRunner.preRoute(); }
ZuulRunner
中的preRoute()
方法如下:
public void preRoute() throws ZuulException { FilterProcessor.getInstance().preRoute(); }
FilterProcessor
是一个单例模式,FilterProcessor
中的preRoute()
方法如下:
public void preRoute() throws ZuulException { try { // 运行pre过滤器 runFilters("pre"); } catch (ZuulException e) { throw e; } catch (Throwable e) { throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName()); } }
执行过滤器,runFilters()
方法如下:
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; }
根据类型获取过滤器列表,getFiltersByType()
方法如下:
public List<ZuulFilter> getFiltersByType(String filterType) { List<ZuulFilter> list = hashFiltersByType.get(filterType); if (list != null) return list; list = new ArrayList<ZuulFilter>(); // 获取所有的过滤器 Collection<ZuulFilter> filters = filterRegistry.getAllFilters(); for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) { ZuulFilter filter = iterator.next(); // 取得filterType的类型列表 if (filter.filterType().equals(filterType)) { list.add(filter); } } // 对filter进行排序 Collections.sort(list); // sort by priority // 保存列表 hashFiltersByType.putIfAbsent(filterType, list); return list; }
filterRegistry
类是一个单例模式,getAllFilters()
方法如下
public class FilterRegistry { private static final FilterRegistry INSTANCE = new FilterRegistry(); // ....省略.... public Collection<ZuulFilter> getAllFilters() { return this.filters.values(); } }
过滤器的处理方法processZuulFilter()
如下:
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(); // ....省略.... // 运行filter ZuulFilterResult result = filter.runFilter(); ExecutionStatus s = result.getStatus(); execTime = System.currentTimeMillis() - ltime; // .....省略.... usageNotifier.notify(filter, s); return o; } catch (Throwable e) { // .....省略..... } }
runFilter()
方法如下,:
public ZuulFilterResult runFilter() { ZuulFilterResult zr = new ZuulFilterResult(); if (!isFilterDisabled()) { // 判断过滤器是否需要执行 if (shouldFilter()) { Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName()); try { // 调用filter的run方法。 Object res = run(); zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS); } catch (Throwable e) { // ....省略.... } finally { t.stopAndLog(); } } else { zr = new ZuulFilterResult(ExecutionStatus.SKIPPED); } } return zr; }
最终调用到各ZuulFilter
中的run()
方法。
pre
类型的PreDecorationFilter
过滤器,用来进行路由规则的匹配
如下:
执行后,上下文内容中的内容如下,加入了requestURI
根据下图可以知道,真正访问服务的是route阶段。如下:
对于正常的服务,比如:/xxx/service_name
是通过RibbonRoutingFilter
实现对服务的负载均衡访问,它的run()
方法如下:
public Object run() { RequestContext context = RequestContext.getCurrentContext(); this.helper.addIgnoredHeaders(); try { RibbonCommandContext commandContext = buildCommandContext(context); ClientHttpResponse response = forward(commandContext); setResponse(response); return response; } catch (ZuulException ex) { throw new ZuulRuntimeException(ex); } catch (Exception ex) { throw new ZuulRuntimeException(ex); } }
如果是固定的url链接,如:http://www.abc.com/xxx/service_name
这种,则是通过SendForwardFilter
过滤器实现转发。它的run()
方法如下:
public Object run() { try { RequestContext ctx = RequestContext.getCurrentContext(); String path = (String) ctx.get(FORWARD_TO_KEY); RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path); if (dispatcher != null) { ctx.set(SEND_FORWARD_FILTER_RAN, true); if (!ctx.getResponse().isCommitted()) { // url转发 dispatcher.forward(ctx.getRequest(), ctx.getResponse()); ctx.getResponse().flushBuffer(); } } } catch (Exception ex) { ReflectionUtils.rethrowRuntimeException(ex); } return null; }
2、架构图zuul 像其他微服务一样作为一个微服务向eureka server注册,并且能够通过注册列表获取所有的可用服务,其内部默认实现了ribbon,可达到负载均衡的目的,此时的微服务架构图如下: