zuul网关


转载:
zuul初始化源码分析
zuul中的过滤器们
zuul的配置文件

ZUUL入口

在我们使用Spring Cloud Zuul通常是在启动类上添加@EnableZuulProxy注解或@EnableZuulServer。

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableZuulProxy   //这里这里
@EnableFeignClients
@Slf4j
@EnableHealth
public class ZuulServerApplication {

    public static void main(String[] args) {
       
        ApplicationContext applicationContext = SpringApplication.run(ZuulServerApplication.class, args);
        
    }
}

对比这两个注解的不同:

@EnableZuulProxy:

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {

}

@EnableZuulServer

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ZuulServerMarkerConfiguration.class)
public @interface EnableZuulServer {

}

对比结果:
1.@EnableZuulProxy是个复合注解,其引入了@EnableCircuitBreaker,也就是整合了Hystrix,而@EnableZuulServer并没有。
2.@EnableZuulProxy导入了 ZuulProxyMarkerConfiguration,而EnableZuulServer导入ZuulServerMarkerConfiguration。查看这两个不同的配置类的源码可以发现他们各自@link到ZuulProxyAutoConfiguration, ZuulServerAutoConfiguration

ZuulProxyMarkerConfiguration:

/**
 * Responsible for adding in a marker bean to trigger activation of
 * {@link ZuulProxyAutoConfiguration}.
 * 负责添加标记bean以触发ZuulProxyAutoConfiguration
 * @author Biju Kunjummen
 */

@Configuration(proxyBeanMethods = false)
public class ZuulProxyMarkerConfiguration {
	@Bean
	public Marker zuulProxyMarkerBean() {
		return new Marker();
	}
	class Marker {

	}
}

查看ZuulProxyAutoConfiguration源码可以看到类上面的@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)注解,意即:当容器中有这个(ZuulProxyMarkerConfiguration.Marker.class)bean才会触发ZuulProxyAutoConfiguration这个自动配置类,@Configuration作用和@Component差不多,就是将该类实例注入到容器

ZuulProxyAutoConfiguration:

@Configuration(proxyBeanMethods = false)
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
		RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
		RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
		HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {

}

ZuulProxyConfiguration.class又额外引入下面3个class,说明zuul支持以下3种方式作为底层http库:

  1. RestClientRibbonConfiguration.class,底层是Jersey client
  2. OkHttpRibbonConfiguration.class,底层是OkHttp,安卓上用的较多
  3. HttpClientRibbonConfiguration.class,底层是HttpClient,默认是这种

HttpCilent:相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅使客户端发送Http请求变得容易,而且也方便开发人员测试接口(基于Http协议的),提高了开发的效率。

使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。

  1. 创建HttpClient对象。
  2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
  3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HttpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
  4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
  5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
  6. 释放连接。无论执行方法是否成功,都必须释放连接

同时,从上面的源码还可以看出,ZuulProxyAutoConfiguration继承ZuulServerAutoConfiguration,可以说是对ZuulServerAutoConfiguration的一个扩展,新增了一些功能。

我们先从父类ZuulServerAutoConfiguration看看它都做了哪些事:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ZuulProperties.class })  
"触发zuulProperties.class,创建了这个zuulproperties的bean"
"这个类就是把application.yaml文件中的zuul前缀的配置绑定到每个属性上"
"参考https://www.cnblogs.com/tian874540961/p/12146467.html"
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
"@ConditionalOnClass表示只有在classpath下找到了zuulservlet.class这个类"
"才会创建ZuulServerAutoConfiguration这个bean"
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
public class ZuulServerAutoConfiguration {

	@Autowired
	protected ZuulProperties zuulProperties;  "application.yaml中前缀为zuul的属性"

	@Autowired
	protected ServerProperties server; "application.yaml中前缀为server的属性"

	@Autowired(required = false)
	private ErrorController errorController;

	private Map<String, CorsConfiguration> corsConfigurations;

	@Autowired(required = false)
	private List<WebMvcConfigurer> configurers = emptyList();

	@Bean
	public HasFeatures zuulFeature() {
		return HasFeatures.namedFeature("Zuul (Simple)",
				ZuulServerAutoConfiguration.class);
	}
	
	/** **/
	"============== RouteLocator接口 中文名:路由定位器=========="
	@Bean
	@Primary
	public CompositeRouteLocator primaryRouteLocator(
			Collection<RouteLocator> routeLocators) {
		return new CompositeRouteLocator(routeLocators);
	}
	"对于@Autowired声明的数组、集合类型,spring并不是根据beanName去找容器中对应的bean,"
	"而是把容器中所有类型与集合(数组)中元素类型相同的bean构造出一个对应集合,注入到目标bean中。"
	@Bean
	@ConditionalOnMissingBean(SimpleRouteLocator.class)
	public SimpleRouteLocator simpleRouteLocator() {
		return new SimpleRouteLocator(this.server.getServlet().getContextPath(),
				this.zuulProperties);
				/**server:
  					 port: 80
  					 servlet:
    					context-path: /wzxl/
    			这个context-path就是给这个项目的所有controller里的路径配一个统一的前缀
    			比如这里context-path配了一个/zuul/,而zuul里配了个路由规则是/api/test
    			那么,外部访问时就应该写/zuul/api/test
    			**/
	} 


	/** **/
	"====================================================================="
	"Zuul是用来处理Http请求的,其底层是基于Servlet和一系列的Filter,而这些事如何和spring mvc集成呢?如何让spring mvc发现zuul呢"
	"ZuulServerAutoConfiguration里定义了ZuulControler和ZuulHandlerMapping,用来和spring mcv集成。其源码如下"
	@Bean
	public ZuulController zuulController() {
		return new ZuulController();
	}
	/**
	查看ZuulController的源码可以发现其继承ServletWrappingController,
	ServletWrappingController的功能就是将Servlet保装成Controller。
	通过其源码发现最终的执行委托给了ZuulServlet,也就是说,看似请求zuulController,实际请求zuulServlet。
	**/

	@Bean
	public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,
			ZuulController zuulController) {
		ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController);
		mapping.setErrorController(this.errorController);
		mapping.setCorsConfigurations(getCorsConfigurations());
		return mapping;
	}

	protected final Map<String, CorsConfiguration> getCorsConfigurations() {
		if (this.corsConfigurations == null) {
			ZuulCorsRegistry registry = new ZuulCorsRegistry();
			this.configurers.forEach(configurer -> configurer.addCorsMappings(registry));
			this.corsConfigurations = registry.getCorsConfigurations();
		}
		return this.corsConfigurations;
	}

	@Bean
	public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
		return new ZuulRefreshListener();
	}
	"==================================zuulServlet=============================="
	@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;
	}
	"===============================ZuulServletFilter============================"
	@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;
	}
	
	"============================= prefilters=============================="

	@Bean   "这个Filter能做什么?标记处理Servlet的类型,order是-3,检测请求是用DispatcherServlet还是zuulServlet来处理"
	public ServletDetectionFilter servletDetectionFilter() {
		return new ServletDetectionFilter();
	}

	@Bean  "这个Filter能做什么?包装请求体,order是-1"
	@ConditionalOnMissingBean
	public FormBodyWrapperFilter formBodyWrapperFilter() {
		return new FormBodyWrapperFilter();
	}

	@Bean "这个Filter能做什么?标记调试标志,order是1"
	@ConditionalOnMissingBean
	public DebugFilter debugFilter() {
		return new DebugFilter();
	}

	@Bean "这个Filter能做什么?包装HttpServletRequest请求,order是-2"
	@ConditionalOnMissingBean
	public Servlet30WrapperFilter servlet30WrapperFilter() {
		return new Servlet30WrapperFilter();
	}

	" ================================post filters==========================="

	@Bean "这个Filter能做什么?处理正常返回来的响应,order是1000"
	public SendResponseFilter sendResponseFilter(ZuulProperties properties) {
		return new SendResponseFilter(zuulProperties);
	}

	@Bean "这个Filter能做什么?处理有错误的响应,order是0"
	public SendErrorFilter sendErrorFilter() {
		return new SendErrorFilter();
	}

	@Bean "这个Filter能做什么?处理forward请求转发的响应,order是500"
	public SendForwardFilter sendForwardFilter() {
		return new SendForwardFilter();
	}
	"============================ribbon.eager-load==============================="
	/**
	在服务消费方调用服务提供方接口的时候,第一次请求经常会超时,而之后的调用就没有问题了。
	造成第一次服务调用出现失败的原因主要是Ribbon进行客户端负载均衡的Client并不是在服务启动的时候就初始化好的,
	而是在调用的时候才会去创建相应的Client,所以第一次调用的耗时不仅仅包含发送HTTP请求的时间,还包含了创建RibbonClient的时间,
	这样一来如果创建时间速度较慢,同时设置的超时时间又比较短的话,很容易就会出现上面所描述的显现。
	通过将zuul.ribbon.eager-load.enabled属性配置为true,让它们提前创建,而不是在第一次调用的时候创建。可以解决此问题。
	**/
	@Bean
	@ConditionalOnProperty("zuul.ribbon.eager-load.enabled")
	public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer(
			SpringClientFactory springClientFactory) {
		return new ZuulRouteApplicationContextInitializer(springClientFactory,
				zuulProperties);
	}
	"==============================ZuulFilterConfiguration :=========================="
	/**
	 初始化filters,这个配置是将所有filter由ZuulFilterInitializer收集到FilterRegistry中。
	 FilterRegistry是一个简陋的存储filter的地方,持有一个HashMap,提供简单的get,put,getAll()方法
	 **/
	@Configuration(proxyBeanMethods = false)
	protected static class ZuulFilterConfiguration {

		@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中有一个@PostConstruct的方法:遍历this.filters里面所有的filter并put到filterRegistry的map里"
		"但这个this.filters是在哪个地方注入到IOC容器中的?难道是每个单独的filter bean可以被自动识别为Map<String,ZuulFilter>?"
		"好像真是这样的,参考https://blog.csdn.net/xiao297328/article/details/107668320"
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(MeterRegistry.class)
	protected static class ZuulCounterFactoryConfiguration {

		@Bean
		@ConditionalOnBean(MeterRegistry.class)
		@ConditionalOnMissingBean(CounterFactory.class)
		public CounterFactory counterFactory(MeterRegistry meterRegistry) {
			return new DefaultCounterFactory(meterRegistry);
		}

	}

	@Configuration(proxyBeanMethods = false)
	protected static class ZuulMetricsConfiguration {

		@Bean
		@ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry")
		@ConditionalOnMissingBean(CounterFactory.class)
		public CounterFactory counterFactory() {
			return new EmptyCounterFactory();
		}

		@ConditionalOnMissingBean(TracerFactory.class)
		@Bean
		public TracerFactory tracerFactory() {
			return new EmptyTracerFactory();
		}

	}
	"===============================ZuulRefreshListener=========================="
	/**
	Zuul的事件监听机制是动态路由的基石:zuul的动态路由功能依靠下面这个代码实现的
	从源码中可以看出Zuul会接收3种事件,
	1.RefreshScopeRefreshedEvent、
	2.RoutesRefreshedEvent、
	3.InstanceRegisteredEvent
	此三种事件会触发一个通知动作
	通知"实现了RefreshableRouteLocator接口的路由定位器"重新加载路由规则,
	CompositeRouteLocator和DiscoveryClientRouteLocator都实现了这个接口
	此外心跳续约监听器HeartbeatMointor也会触发此动作。
	**/
	private static class ZuulRefreshListener
			implements ApplicationListener<ApplicationEvent> {

		@Autowired
		private ZuulHandlerMapping zuulHandlerMapping;

		private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();

		@Override
		public void onApplicationEvent(ApplicationEvent event) {
			if (event instanceof ContextRefreshedEvent
					|| event instanceof RefreshScopeRefreshedEvent
					|| event instanceof RoutesRefreshedEvent
					|| event instanceof InstanceRegisteredEvent) {
				reset();
			}
			else if (event instanceof ParentHeartbeatEvent) {
				ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
				resetIfNeeded(e.getValue());
			}
			else if (event instanceof HeartbeatEvent) {
				HeartbeatEvent e = (HeartbeatEvent) event;
				resetIfNeeded(e.getValue());
			}
		}

		private void resetIfNeeded(Object value) {
			if (this.heartbeatMonitor.update(value)) {
				reset();
			}
		}

		private void reset() {
			this.zuulHandlerMapping.setDirty(true);
		}

	}

	private static class ZuulCorsRegistry extends CorsRegistry {

		@Override
		protected Map<String, CorsConfiguration> getCorsConfigurations() {
			return super.getCorsConfigurations();
		}

	}

}

以上是ZuulServerAutoConfiguration的主要相关内容,可以确认的是初始化了zuulProperties,各routeLocator,zuulController,zuulHandlerMapping,各zuulFilter。

ZuulProxyAutoConfiguration比ZuulServerAutoConfiguration主要新增了如下bean

1.DiscoveryClientRouteLocator
主要有两个功能,第一是从DiscoveryClient(如Eureka)发现路由信息,第二个是动态刷新路由信息

2.多了3个Filter
PreDecorationFilter: 为当前请求做一些预处理,比如:进行路由规则的匹配、在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据
RibbonRoutingFilter: 通过Ribbon和Hystrix来向服务实例发起请,它的order是10,类型是routeFilter。
SimpleHostRoutingFilter: 主要用来转发serviceId为空的,即直接使用httpclient来转发请求的

RouteLocator家族

RouteLocator是用来维护路由信息的,主要有以下三个方法,见名知意:

public interface RouteLocator {

	Collection<String> getIgnoredPaths(); //获取在yml中指定的被忽略的path集合
	
	List<Route> getRoutes();//获取路由列表

	Route getMatchingRoute(String path);//根据path获取路由信息

}

//todo 放个继承树的图

1.SimpleRouteLocatorr实现了RouteLocator及Ordered :主要负责维护配置文件中的路由配置,配置文件中的路由配置会被转化后存储在一个map中,其中key为配置的path

	@Bean
	@ConditionalOnMissingBean(SimpleRouteLocator.class)
	public SimpleRouteLocator simpleRouteLocator() {
		return new SimpleRouteLocator(this.server.getServlet().getContextPath(),
				this.zuulProperties);
	}

2.RefreshableRouteLocator接口继承自RouteLocator接口,额外提供了一个方法refresh( ),refresh方法会结合spring的ApplicationEvent,实现基于事件的路由刷新机制,具体可以参看ZuulDiscoveryRefreshListener源码

3.DiscoveryClientRouteLocator继承自SimpleRouteLocator,并且实现了RefreshableRouteLocator,从类定义上可以看出他具备了基本的路由功能及路由刷新功能。另外,该类会将配置文件中的路由配置(称为静态)以及服务发现(比如eureka)中的路由信息进行合并,并且会以服务发现中的路由信息为主
(去Eureka拿到各个服务名称,以“/服务名称/**”映射成路由信息)
(我仅在配置文件中配置了xxx的路由配置,但是实际访问时为什么yyy下的接口也可以被路由?原因就在于zuul会以服务发现中的路由信息为主,虽然自己在配置文件中没有配置,但是eureka中却已经存在了40+个服务路由)

4.CompositeRouteLocator,他虽然仅仅实现了RefreshableRouteLocator,但是却是能力最强的一个,因为他能够整合众多RouteLocator的功能于一身。它被标记为@Primary,作为主要的RouteLocator被使用,当CompositeRouteLocator被构造时被传入的routeLocators其实仅包括一个DiscoveryClientRouteLocator。

"在zuulServerAutoConfiguration中构造它:"

    @Bean
	@Primary
	public CompositeRouteLocator primaryRouteLocator(
			Collection<RouteLocator> routeLocators) {
		return new CompositeRouteLocator(routeLocators);
	}
"注意看CompositeRouteLocator的构造方法,它会将传入的routeLocators排序"
public class CompositeRouteLocator implements RefreshableRouteLocator {

	private final Collection<? extends RouteLocator> routeLocators;

	private ArrayList<RouteLocator> rl;

	public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {
		Assert.notNull(routeLocators, "'routeLocators' must not be null");
		rl = new ArrayList<>(routeLocators);
		AnnotationAwareOrderComparator.sort(rl);
		this.routeLocators = rl;
	}
	"其他几个方法就很好理解了,就是遍历排序后的routeLocators并调用相关的方法↓↓↓↓↓↓↓↓↓↓↓"
	@Override
	public Collection<String> getIgnoredPaths() {
		List<String> ignoredPaths = new ArrayList<>();
		for (RouteLocator locator : routeLocators) {
			ignoredPaths.addAll(locator.getIgnoredPaths());
		}
		return ignoredPaths;
	}

	@Override
	public List<Route> getRoutes() {
		List<Route> route = new ArrayList<>();
		for (RouteLocator locator : routeLocators) {
			route.addAll(locator.getRoutes());
		}
		return route;
	}

	@Override
	public Route getMatchingRoute(String path) {
		for (RouteLocator locator : routeLocators) {
			Route route = locator.getMatchingRoute(path);
			if (route != null) {
				return route;
			}
		}
		return null;
	}

	@Override
	public void refresh() {
		for (RouteLocator locator : routeLocators) {
			if (locator instanceof RefreshableRouteLocator) {
				((RefreshableRouteLocator) locator).refresh();
			}}}
}

ZuulHandlerMapping

在spring mvc的DispatcherServelet的initHandlerMapping方法会将容器中所有类型为HandlerMapping.class的bean注入到spring MVC持有的handlerMappings这个List中,也就包括ZuulHandlerMapping,因为从上面的代码ZuulServrAutoConfiguration中可以看到ZuulHandlerMapping被注入到容器中。

从源码中可以看到ZuulHandlerMapping中持有RouteLocator也就是路由信息:/zuul/api/*等,还持有zuulController,zuulController仅有一个。

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {

	private final RouteLocator routeLocator;

	private final ZuulController zuul;

	private ErrorController errorController;

	private PathMatcher pathMatcher = new AntPathMatcher();

	private volatile boolean dirty = true;

	public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul) {
		this.routeLocator = routeLocator;
		this.zuul = zuul;
		setOrder(-200);
	}

	@Override
	protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
			HandlerExecutionChain chain, CorsConfiguration config) {
		if (config == null) {
			// Allow CORS requests to go to the backend
			return chain;
		}
		return super.getCorsHandlerExecutionChain(request, chain, config);
	}

	public void setErrorController(ErrorController errorController) {
		this.errorController = errorController;
	}
	@Override
	protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
			return null;
		}
		if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
		RequestContext ctx = RequestContext.getCurrentContext();
		if (ctx.containsKey("forward.to")) {
			return null;
		}
		if (this.dirty) {
			synchronized (this) {
				if (this.dirty) {
					registerHandlers();
					this.dirty = false;
				}}}
		return super.lookupHandler(urlPath, request);
	}

	private boolean isIgnoredPath(String urlPath, Collection<String> ignored) {
		if (ignored != null) {
			for (String ignoredPath : ignored) {
				if (this.pathMatcher.match(ignoredPath, urlPath)) {
					return true;
				}}}
		return false;
	}

	private void registerHandlers() {
		Collection<Route> routes = this.routeLocator.getRoutes();
		if (routes.isEmpty()) {
			this.logger.warn("No routes found from RouteLocator");
		}
		else {
			for (Route route : routes) {
				registerHandler(route.getFullPath(), this.zuul);
			}}}
	"ZuulHandlerMapping在registerHandlers中获取了所有路由关系,"
	"然后把path跟ZuulController分别作为key和value写入到handlerMap"
	"注意的是所有的path都对应同一个zuulController"
	"这样会根据配置的path,找到相对应的zuulController处理请求。(和springMVC中其他handerler一样)"
	"有个疑问在于,为什么/api/test没有被zuul服务和真正的API服务争夺呢?"
	"因为我们对外暴露的是zuul服务的端口,我们在访问时访问的也是zuul的端口,因此springMVC也是zuul服务的springMVC,这里是没有注册api服务的handler的,是隔离了的"
}

zuul服务器来了一个请求,在学springMVC时分析过,doDispatch是关键方法,在doDispach中遍历HandlerMapping后找到合适的ZuulHandlerMapping,再调用了getHandler( )获取相应的Controller执行链,在getHandler()里面调用了getHandlerInternal( ),其里面又调用了lookUpHandler( ),进而调用了registerHandler( )(registerHandler( )中把path跟ZuulController分别作为key和value写入到它持有的handlerMap中,而path从哪儿来?从routeLocator.getRoutes( )里面遍历而来,这就是routeLocator的用处所在!!),然后一层层关闭栈返回到doDispatch( )层,然后在doDispatch真正处理时调用的是handle( ),把刚才get到的handler(即该path对应的controller:ZuulController)传入handle( )中。
在这里插入图片描述
如果该path在路由关系中不存在,则handler为null,通过/error调用相关的错误处理controller(BasicErrorController)返回404错误页面。

如果该path存在,就会执行ZuulController,而zuulController继承ServletWrappingController,并设置ServletClass为zuul的核心类zuulServlet,在实际调用中,相关逻辑委托给了zuulServlet处理,ZuulServlet.service( )方法就会处理这个请求。ZuulServlet接收到请求,初始化RequestContext,之后分别执行pre、route和post阶段的filter,最后销毁RequestContext。
顺序是:
1、pre->route->post,error不执行。
2、pre抛出异常,顺序是:pre->error->post。
3、route抛出异常,顺序是:pre->route->error->post。
4、post抛出异常,顺序是:pre->route->post->error。

public class ZuulServlet extends HttpServlet {
	
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        try {
	      
        	"跟踪init,发现它内部调用了RequestContext.getCurrentContext()获取了当前的context,并且将servletRequest和servletResponse都设置进了该context里"
            this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
            "第二句代码再次获取context,将zuulEngineRan设置进去"
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();
            try {
                this.preRoute();
            } catch (ZuulException var12) {
                this.error(var12);
                this.postRoute();
                return;
            }

            try {
                this.route();
            } catch (ZuulException var13) {
                this.error(var13);
                this.postRoute();
                return;
            }

            try {
                this.postRoute();
            } catch (ZuulException var11) {
                this.error(var11);
            }
        } catch (Throwable var14) {
            this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
        } finally {
        	"最后销毁RequestContext"
            RequestContext.getCurrentContext().unset();
        }
    }
    void postRoute() throws ZuulException {
        this.zuulRunner.postRoute();
    }

    void route() throws ZuulException {
        this.zuulRunner.route();
    }

    void preRoute() throws ZuulException {
        this.zuulRunner.preRoute();
    }

    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        this.zuulRunner.init(servletRequest, servletResponse);
    }

    void error(ZuulException e) {
        RequestContext.getCurrentContext().setThrowable(e);
        this.zuulRunner.error();
    }

这个service方法有以下几点要注意:
一:为啥要初始化context?首先要有一个共识:每一个新的请求都是由一个独立的线程处理(这个线程是Tomcat里面起的线程),换言之,请求的所有参数(Http报文信息解析出来的内容,如请求头、请求体等等)总是绑定在处理请求的线程中。 RequestContext的设计就是简单直接有效,它继承于ConcurrentHashMap<String, Object>,所以参数可以直接以key-value方式设置在RequestContext中

 public HttpServletRequest getRequest() {
        return (HttpServletRequest) get("request");
    }

    public void setRequest(HttpServletRequest request) {
        put("request", request);
    }

    public HttpServletResponse getResponse() {
        return (HttpServletResponse) get("response");
    }

    public void setResponse(HttpServletResponse response) {
        set("response", response);
    }
    ...

静态方法RequestContext.getCurrentContext()内容如下:

public static RequestContext getCurrentContext() {
			...
        	//当ThreadLocal的get()方法返回null的时候总是会调用initialValue()方法,
        	//所以这里是"无则新建RequestContext"的逻辑
       		 RequestContext context = threadLocal.get();
        	 return context;
    	}

二:在执行this.pre( ),this.route( ),this.post( )这些过滤器的时候,委托给了zuulRunner,通过看zuulRunner源码可以知道,它持有FilterProcessor,把pre( ),route( )和post( )的执行委托给了FilterProcessor,下面是FilterProcessor的代码

public void error() {
        try {
            this.runFilters("error");
        } catch (Throwable var2) {
            logger.error(var2.getMessage(), var2);
        }

    }

    public void route() throws ZuulException {
        try {
            this.runFilters("route");
        } catch (ZuulException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + var3.getClass().getName());
        }
    }

    public void preRoute() throws ZuulException {
        try {
            this.runFilters("pre");
        } catch (ZuulException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());
        }
    }

    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 = (ZuulFilter)list.get(i);
                Object result = this.processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= (Boolean)result;
                }
            }
        }

        return bResult;
    }

可以看到,这三种过滤器的执行最后都是调用runFilters( ),而runFilters( )内部调用了FilterLoader.getInstance().getFiltersByType( )获取了该类型的filter列表(四种类型:pre,route,post,error),挨个同一类型的filter,将filter传入this.processZuulFilter(zuulFilter)进行执行,在其内部调用了zuulFilter的runFilter( ):依次判断了isFilterDisabled() =>shouldFilter(),确定该filter需要执行,才会调用该filter的run( )。

ZuulFilter家族

过滤器order描述类型
ServletDetectionFilter-3检测请求是用 DispatcherServlet还是 ZuulServletpre
Servlet30WrapperFilter-2在Servlet 3.0 下,包装 requestspre
FormBodyWrapperFilter-1解析表单数据error
SendErrorFilter0如果中途出现错误pre
DebugFilter1设置请求过程是否开启debugpre
PreDecorationFilter5根据uri决定调用哪一个route过滤器pre
RibbonRoutingFilter10如果写配置的时候用ServiceId则用这个route过滤器,该过滤器可以用Ribbon 做负载均衡,用hystrix做熔断route
SimpleHostRoutingFilter100如果写配置的时候用url则用这个route过滤,查阅这个类的全部代码可知,该类创建了一个HttpClient作为请求类,并重构了url,请求到了具体的服务,得到的一个CloseableHttpResponse对象,并将CloseableHttpResponse对象的保存到RequestContext对象中route
SendForwardFilter500用RequestDispatcher请求转发route
SendResponseFilter1000用RequestDispatcher请求转发post

FilterLoader

我们写死在java代码中的zuulFilter过滤器是静态的,无法动态的修改。Zuul 支持过滤器"动态修改","动态加载"的功能,目前已经支持由Groovy编写的Filter文件,当Groovy编写的Filter类文件有变更zuul可以动态的重新编译。实现原理是监控存放Filter文件的目录,定期扫描这些目录,如果发现有新Filter源码文件或者Filter源码文件有改动,则对文件进行编译加载,源码是通过FilterFileManager实现此功能的。

FilterFileManager 功能如下:
1.FilterFileManager开启一个线程,开始轮询(startPoller)
2.每次轮询,处理目录内的所有*.groovy文件,即调用FilterLoader.getInstance().putFilter(file),这个方法内部其实调用的是FilterLRegister.put(key,value),把从*.groovy文件读取到的filter注册进去了。

public boolean putFilter(File file) throws Exception {
        String sName = file.getAbsolutePath() + file.getName();
        if (this.filterClassLastModified.get(sName) != null && file.lastModified() != (Long)this.filterClassLastModified.get(sName)) {
            LOG.debug("reloading filter " + sName);
            this.filterRegistry.remove(sName);
        }

        ZuulFilter filter = this.filterRegistry.get(sName);
        if (filter == null) {
            Class clazz = COMPILER.compile(file);
            if (!Modifier.isAbstract(clazz.getModifiers())) {
                filter = FILTER_FACTORY.newInstance(clazz);
                List<ZuulFilter> list = (List)this.hashFiltersByType.get(filter.filterType());
                if (list != null) {
                    this.hashFiltersByType.remove(filter.filterType());
                }

                this.filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
                this.filterClassLastModified.put(sName, file.lastModified());
                return true;
            }
        }

        return false;
    }

    public List<ZuulFilter> getFiltersByType(String filterType) {
        List<ZuulFilter> list = (List)this.hashFiltersByType.get(filterType);
        if (list != null) {
            return list;
        } else {
            List<ZuulFilter> list = new ArrayList();
            Collection<ZuulFilter> filters = this.filterRegistry.getAllFilters();
            Iterator iterator = filters.iterator();

            while(iterator.hasNext()) {
                ZuulFilter filter = (ZuulFilter)iterator.next();
                if (filter.filterType().equals(filterType)) {	"在此处根据filter的type进行过滤"
                    list.add(filter);"因为从this.filterRegistry.getAllFilters()获取的filter是混杂在同一个HashMap中的"
                }
            }

            Collections.sort(list);			"在此处根据filter的order进行了一个排序"
            this.hashFiltersByType.putIfAbsent(filterType, list);
            return list;
        }
    }

RibbonRoutingFilter:熔断降级的关键

两个route过滤器:
1.RibbonRoutingFilter:它的执行顺序为10,是route阶段的第一个执行的过滤器。该过滤器只对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。而该过滤器的执行逻辑就是面向服务路由的核心,它通过使用ribbon和hystrix来向服务实例发起请求,并将服务实例的请求结果返回。
在这里插入图片描述
2.SimpleHostRoutingFilter:它的执行顺序为100,是route阶段的第二个执行的过滤器。该过滤器只对请求上下文存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效。而该过滤器的执行逻辑就是直接向routeHost参数的物理地址发起请求,从源码中我们可以知道该请求是直接通过httpclient包实现的,而没有使用Hystrix命令进行包装,所以这类请求并没有线程隔离和断路器的保护。

在这里插入图片描述
RibbonRoutingFilter这个过滤器,使zuul拥有了负载均衡和熔断的功能。
1.order( )为10,

2.type( )为routeFilter,

3.shouldFilter( ):上下文包含serviceId,routeHost为空,且zuulResponse(决绝条件)为真时才会执行这个filter

4.在run方法中首先构造commandContext,也就是将请求上下文RequestContext包装成RibbonCommandContext,之后将包装好的上问下传入内部方法forward( )。

run方法:

@Override
public Object run() {
	RequestContext context = RequestContext.getCurrentContext();
	this.helper.addIgnoredHeaders();
	try {
	    "---------对将要发送的请求进行一个封装--------"
		RibbonCommandContext commandContext = buildCommandContext(context);
		"-----------------发送请求------------------"
		ClientHttpResponse response = forward(commandContext);
		"最后响应结果会被设置到RequestContext 上下文对象中"
		"会通过SendResponseFilter去处理把上下文对象中的响应结果写给客户端"
		setResponse(response);
		return response;
	}
	catch (ZuulException ex) {
		throw new ZuulRuntimeException(ex);
	}
	catch (Exception ex) {
		throw new ZuulRuntimeException(ex);
	}
}

forward方法:

protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
    Map<String, Object> info = this.helper.debug(context.getMethod(),
            context.getUri(), context.getHeaders(), context.getParams(),
            context.getRequestEntity());
	"通过RibbonCommandFactory创建RibbonCommand"
    RibbonCommand command = this.ribbonCommandFactory.create(context);
    try {
    	"在forward( )方法中最终调用RibbonCommand的execute()方法,将请求路由到下游"
        ClientHttpResponse response = command.execute();
        this.helper.appendDebug(info, response.getRawStatusCode(),
                response.getHeaders());
        return response;
    }
    catch (HystrixRuntimeException ex) {
        return handleException(info, ex);
    }
}

forward方法里面的RibbonCommandFactory和RibbonCommand都是接口,具体实现有三个:
在这里插入图片描述
1.HttpClientRibbonCommandFactory用于构建HttpClientRibbonCommand,
2.OkHttpRibbonCommandFactory用于构建OkHttpRibbonCommand,
3.RestClientRibbonCommandFactory用于构建RestClientRibbonCommand。

我们看一下 ribbonCommandFactory.create()

create方法:


	@Override
	public HttpClientRibbonCommand create(final RibbonCommandContext context) {
		"//获取服务对应的降级"
		FallbackProvider zuulFallbackProvider = getFallbackProvider(context.getServiceId());
		final String serviceId = context.getServiceId();
		"//获取Ribbon的负载均衡Http客户端"
		final RibbonLoadBalancingHttpClient client = this.clientFactory.getClient(
				serviceId, RibbonLoadBalancingHttpClient.class);
	
		"//获取 ILoadBalancer 负载均衡器,设置给 RibbonLoadBalancingHttpClient "
		client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
		"//创建一个 HttpClientRibbonCommand "
		return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties, zuulFallbackProvider,
				clientFactory.getClientConfig(serviceId));
	}

可见,create默认创建的是HttpClientRibbonCommand。create 方法主要是把服务的降级FallbackProvider,以及Ribbon的负载均衡Http客户端,以及ILoadBalancer 负载均衡器设置给 HttpClientRibbonCommand 对象并返回。

我们看一下 HttpClientRibbonCommand.execute()
execute( )是HystrixExecutable接口的方法,由HystrixCommand做了具体实现,继承树如下:
在这里插入图片描述
HystrixCommand.execute( )方法:

public R execute() {
        try {
            return this.queue().get();
        } catch (Exception var2) {
            throw Exceptions.sneakyThrow(this.decomposeException(var2));
        }
    }

进入HystrixCommand.queue( )方法(注意:Future接口用于执行异步阻塞方法,Future接口的get()方法可以当任务结束后返回一个结果,如果调用时,工作还没有结束,则会阻塞线程,直到任务执行完毕)。
queue( )里面采用了观察者设计模式,使用了异步阻塞方式去执行请求并等待响应,将响应get到之后返回到上一层。如果将要路由的微服务挂了,get时产生了异常,如果你没有为这个微服务写一个熔断处理器(实现FallbackProvider接口),那么客户端网页显示的就是ErrorPage(这是不希望看到的结果),如果你针对这个微服务自定义了熔断器,就会触发熔断,这个熔断处理器就会生效,改善了用户体验。

public Future<R> queue() {
        "// 这里采用了观察者设计模式,使用了异步阻塞方式去执行请求"
        final Future<R> delegate = toObservable().toBlocking().toFuture();
        final Future<R> f = new Future<R>() 
            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {
              	...省略...
                return res;
			}
            @Override
            public boolean isCancelled() {
                return delegate.isCancelled();
			}
            @Override
            public boolean isDone() {
                return delegate.isDone();
			}
            @Override
            public R get() throws InterruptedException, ExecutionException {
                return delegate.get();
            }
            @Override
            public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
                return delegate.get(timeout, unit);
            }
        };
        /* special handling of error states that throw immediately */
        if (f.isDone()) {
            try {
            	"//获取结果"
                f.get();
                return f;
            } catch (Exception e) {
            "在获取真正的响应结果过程中如果发生了错误就会执行下面的逻辑"
                Throwable t = decomposeException(e);
                if (t instanceof HystrixBadRequestException) {
                "=======如果是HystrixBadRequestException,不会触发熔断降级====="
                    return f;
                } else if (t instanceof HystrixRuntimeException) {
                "============如果是其他异常,就会触发熔断降级?============"
                    HystrixRuntimeException hre = (HystrixRuntimeException) t;
                    switch (hre.getFailureType()) {
					case COMMAND_EXCEPTION:
					case TIMEOUT:
						return f;
					default:
						throw hre;
					}
                } else {
                    throw Exceptions.sneakyThrow(t);
                }
            }
        }
        return f;
    }

queue( )方法最终会走到AbstractRibbonCommand的run( )方法中。

请关注这个类 AbstractRibbonCommand,在这个类里持RibbonCommandContext和ZuulFallbackProvider,具有ribbon和hystrix的功能。

AbstractRibbonCommand.run( ):

@Override
	protected ClientHttpResponse run() throws Exception {
		final RequestContext context = RequestContext.getCurrentContext();
		//创建请求,实现类是 RibbonApacheHttpRequest
		RQ request = createRequest();
		RS response;
		//可重试的客户端 
		boolean retryableClient = this.client instanceof AbstractLoadBalancingClient
				&& ((AbstractLoadBalancingClient)this.client).isClientRetryable((ContextAwareRequest)request);
		
		if (retryableClient) {
			"关键代码:如果是可重试的客户端,就走这里"
			response = this.client.execute(request, config);
		} else {
			//默认走这里使用负载均衡器执行
			response = this.client.executeWithLoadBalancer(request, config);
		}
		//把结果设置到RequestContext 上下文
		context.set("ribbonResponse", response);
		//响应超时,关闭response
		if (this.isResponseTimedOut()) {
			if (response != null) {
				response.close();
			}
		}
		//返回结果
		return new RibbonHttpResponse(response);
	}

后续流程可以查看
二十.SpringCloud源码剖析-Zuul使用Ribbon负载均衡-RibbonRoutingFilter

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值