SpringCloud對Netflix Zuul的封裝
Netflix zuul是提供的是作爲集羣服務前門的API網關服務,而SpringCloud 是以SpringBoot 爲基礎實現的,要在SpringCloud裏面封裝Netflix Zuul,則必須符合SpringBoot的理念——習慣優於配置。
@EnableZuulProxy
在SpringCloud中,幾乎所有組件的使用都是以 @EnableXXX 開始的,Zuul也不例外:
@EnableCircuitBreaker
@EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
關鍵的一個地方,就是 @Import(ZuulProxyMarkerConfiguration.class) ,然而這裏面除了一個類定義,並沒有其他東西:
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
但這個作用在什麼地方,稍後再說。
ZuulProxyAutoConfiguration
SpringBoot 的一個核心就是 autoConfiguration ,默認的配置啓動類在類路徑中的 /META-INF/spring.factories 有定義,找到如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration
這個纔是SpringBoot自動裝配的啓動類,查看定義:
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {
//省略其他代碼
}
其中 @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class) 表示,如果 ZuulProxyMarkerConfiguration.Marker.class 這個類被加載了,那麼 ZuulProxyAutoConfiguration 就也會被加載。再回過頭去看 @EnableZuulProxy 中導入的類,正是這個 ZuulProxyMarkerConfiguration.Marker.class ,由此可見,ZuulProxyMarkerConfiguration.Marker.class 的真正作用,只是爲 ZuulProxyAutoConfiguration 的加載(開啓Zuul)提供一個條件。
而 @ConditionalOnClass(ZuulServlet.class) 則表明當前類路徑下必須存在 ZuulServlet 類。
ZuulServlet的加載
Netflix Zuul 起作用是以ZuulServlet的啓動開始的,在 SpringCloud 中,要實現對Zuul的封裝,必須要實現對ZuulServlet的加載:
在 ZuulProxyAutoConfiguration 的基類 ZuulServerAutoConfiguration 中,有如下定義:
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
在 ZuulServerAutoConfiguration 中加載了 ZuulController 的Bean,ZuulController 定義如下:
public class ZuulController extends ServletWrappingController {
public ZuulController() {
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();
}
}
}
ZuulController 在構造時就加載了 ZuulServlet。
Routes 的加載
在 SpringCloud 中使用Zuul時,會配置相應的route,這些route的加載過程:
在 ZuulServerAutoConfiguration 中有如下定義:
@Autowired
protected ZuulProperties zuulProperties;
@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection<RouteLocator> routeLocators) {
return new CompositeRouteLocator(routeLocators);
}
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(this.server.getServletPrefix(),
this.zuulProperties);
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
Filter的加載
在 ZuulServerAutoConfiguration 中有如下定義:
@Configuration
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);
}
}
filters 是通過 @Autowired 自動注入的,所有繼承ZuulFilter的類都會被組裝到filters這個Map裏。當 FilterLoader 把所有的 filter 都加載完畢,FilterRegistry 負責把所有的 filter 都註冊到 Zuul 的 filterRegistry 裏面:
@PostConstruct
public void contextInitialized() {
log.info("Starting filter initializer");
TracerFactory.initialize(tracerFactory);
CounterFactory.initialize(counterFactory);
for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
filterRegistry.put(entry.getKey(), entry.getValue());
}
}
Request的路由
所有 request 的路由,都交由 過濾器實現,這才符合“Zuul 一切都有過濾器實現”的理念,所以,request的路由,肯定也是一個過濾器:
public class PreDecorationFilter extends ZuulFilter {
public PreDecorationFilter(RouteLocator routeLocator, String dispatcherServletPath, ZuulProperties properties,
ProxyRequestHelper proxyRequestHelper) {
this.routeLocator = routeLocator;
this.properties = properties;
this.urlPathHelper.setRemoveSemicolonContent(properties.isRemoveSemicolonContent());
this.dispatcherServletPath = dispatcherServletPath;
this.proxyRequestHelper = proxyRequestHelper;
}
@Override
public int filterOrder() {//省略部分代碼}
@Override
public String filterType() {//省略部分代碼}
@Overrid
public boolean shouldFilter() {//省略部分代碼}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
Route route = this.routeLocator.getMatchingRoute(requestURI);
//省略部分代碼
}
//省略部分代碼
}
在PreDecorationFilter類裏:把HrrpServletRequest裏的url path拿出來,換成了Route對象,這個對象裏指定了這個Path路由到哪個服務或地址。