隨著業(yè)務(wù)范圍越來越大阻星,我們的微服務(wù)項目越來越多,這么多微服務(wù)如何管理已添、服務(wù)之間的鑒權(quán)等等成為重中之重妥箕,所以隨著服務(wù)模塊的增加,我們就必須引入網(wǎng)關(guān)服務(wù)更舞,將流量統(tǒng)一轉(zhuǎn)發(fā)到網(wǎng)關(guān)服務(wù)畦幢,在網(wǎng)關(guān)服務(wù)中進(jìn)行鑒權(quán)操作、限流操作缆蝉、請求追蹤等等宇葱,所以本節(jié)我們重點分析基于Zuul框架的網(wǎng)關(guān)瘦真,Spring Cloud GateWay網(wǎng)關(guān)我們后續(xù)會進(jìn)行分析!
開局一張圖黍瞧,這里我們還是貼上Zuul的官方圖吗氏,這里的Zuul流程圖版本為1.x版本的,基于Servlet實現(xiàn)的雷逆,是BIO同步阻塞I/O模式,Zuul2.x版本則是基于Netty這種NIO同步非阻塞的I/O模型污尉,本節(jié)我們還是基于Zuul1.x版本分析膀哲,針對上圖,其實我們也可以簡單理解為Servlet Filter攔截器被碗,對請求前某宪、請求中、請求后執(zhí)行不同的攔截策略锐朴!
ZuulConfiguration配置類中主要包含一些基礎(chǔ)的配置兴喂,比如路由器RouteLocator、pre filter&post filter攔截器焚志、Application事件監(jiān)聽器衣迷、Url映射處理實現(xiàn)、Servlet攔截類酱酬。由于ZuulConfiguration比較長壶谒,所以只總結(jié)了重要的部分
ZuulConfiguration
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulConfiguration {
@Autowired
protected ZuulProperties zuulProperties;
@Autowired
protected ServerProperties server;
@Autowired(required = false)
private ErrorController errorController;
//注冊Zuul特性描述
@Bean
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Simple)", ZuulConfiguration.class);
}
//組合路由器,內(nèi)部其實只有DiscoveryClientRouteLocator這個具備路由刷新的路由器
@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection<RouteLocator> routeLocators) {
return new CompositeRouteLocator(routeLocators);
}
//簡單的路由器膳沽,不具備路由刷新功能汗菜,只有靜態(tài)路由功能
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(this.server.getServletPrefix(),
this.zuulProperties);
}
//Zuul的核心處理類
//讓ZuulServlet這個Servlet直接包裝為一個Controller(這個是servlet.mvc下面的,不是我們的經(jīng)常使用的@Controller注解挑社,不要搞混了)
//將請求交給ZuulServlet處理陨界,默認(rèn)處理的路徑為 "/" 這個根目錄,默認(rèn)由DispatcherServlet分發(fā)的請求
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
//注冊zuul.routes.*.path的路由處理類為ZuulController(內(nèi)部其實是ZuulServlet在處理邏輯)
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
//監(jiān)聽一些事件痛阻,處理路由刷新功能
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
//注冊一個ZuulServlet, 處理的路徑/zuul 開頭的路徑
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
this.zuulProperties.getServletPattern());
servlet.addInitParameter("buffer-requests", "false");
return servlet;
}
......
}
代碼片段中我們已經(jīng)基本注釋了每個Bean實例處理的邏輯菌瘪,上面有一個地方可能有些疑惑,那就是ZuulHandlerMapping內(nèi)部已經(jīng)把Zuul配置的路由Url信息全部都映射給了ZuulController(內(nèi)部也是ZuulServlet處理)录平,為何還需要單獨注冊一個ZuulServlet麻车?所以筆者這里也只是猜測可能單獨注冊這個也是為了更明確知道哪些Url路徑是可以直接使用ZuulServlet進(jìn)行處理的,明確知道這種Url信息就是需要Zuul轉(zhuǎn)發(fā)的,而不需要經(jīng)過DispatcherServlet層層處理斗这,總之為了提高效率吧动猬!
其中ZuulServlet可以說是Zuul的核心,外部訪問之后都會經(jīng)由ZuulServlet來做最終的轉(zhuǎn)發(fā)處理表箭,下面我們就單獨分析下代碼片段中非常重要的幾個Bean實例(CompositeRouteLocator赁咙、ZuulHandlerMapping、ZuulController)
CompositeRouteLocator組合路由器
public class CompositeRouteLocator implements RefreshableRouteLocator {
//此集合只有一個DiscoveryClientRouteLocator路由
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;
}
//獲取忽略的路徑url,也就是讓部分url對應(yīng)的Service不暴露給外面
@Override
public Collection<String> getIgnoredPaths() {
List<String> ignoredPaths = new ArrayList<>();
for (RouteLocator locator : routeLocators) {
ignoredPaths.addAll(locator.getIgnoredPaths());
}
return ignoredPaths;
}
//獲取路由映射集合,url等等信息
@Override
public List<Route> getRoutes() {
List<Route> route = new ArrayList<>();
for (RouteLocator locator : routeLocators) {
route.addAll(locator.getRoutes());
}
return route;
}
//根據(jù)實際匹配的路徑返回一個Route
@Override
public Route getMatchingRoute(String path) {......}
//刷新路由器
@Override
public void refresh() {......}
}
組合路由器主要就是用于分發(fā)作用,獲取每個RouteLocator對應(yīng)的操作彼水!
ZuulController
public class ZuulController extends ServletWrappingController {
public ZuulController() {
//設(shè)置ZuulServlet作為處理類
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // 支持所有方法
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
//調(diào)用ZuulServlet.service方法
return super.handleRequestInternal(request, response);
}
finally {
//釋放當(dāng)前請求上下文
RequestContext.getCurrentContext().unset();
}
}
}
ZuulController作用就是將ZuulServlet包裝為一個Servlet Controller崔拥,讓進(jìn)入到DispatcherServlet的請求通過分發(fā),最終到ZuulController的請求都交由ZuulServlet這個Servlet來處理凤覆。
ZuulHandlerMapping
public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
......
//查找urlPath對應(yīng)的處理實例
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
//urlPath與errorController匹配則忽略
if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
return null;
}
//若匹配到配置的忽略地址則忽略
String[] ignored = this.routeLocator.getIgnoredPaths().toArray(new String[0]);
if (PatternMatchUtils.simpleMatch(ignored, urlPath)) {
return null;
}
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.containsKey("forward.to")) {
return null;
}
//重點链瓦,注冊Zuul配置的路由url對應(yīng)的處理實例為ZuulController
if (this.dirty) {
synchronized (this) {
if (this.dirty) {
registerHandlers();
this.dirty = false;
}
}
}
//返回當(dāng)前urlPath對應(yīng)的處理實例
return super.lookupHandler(urlPath, request);
}
//注冊路由對應(yīng)的處理實例
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) {
//注冊zuul.routes.*.path的處理實例(ZuulController)
registerHandler(route.getFullPath(), this.zuul);
}
}
}
}
ZuulHandlerMapping繼承了AbstractUrlHandlerMapping之后通過重寫lookupHandler查找實例方法,注冊Zuul的路由處理實例為ZuulController盯桦,最后在返回當(dāng)前urlPath對應(yīng)的處理實例慈俯,請求下游服務(wù)的一次完整的請求流程(除開/zuul的路徑,這個路徑直接由ZuulServlet處理)大致為HttpServlet->FrameworkServlet->DispatcherServlet->DispatcherServlet#getHandler()->ZuulHandlerMapping->ZuulController->ZuulServlet->ZuulRunner-> FilterProcessor->ZuulFilter->RibbonRoutingFilter拥峦,DispatcherServlet#getHandler()這個步驟里面就會查找這個urlPath對應(yīng)的處理實例贴膘!
ZuulServlet
上面我們總結(jié)和分析了各種Bean實例的作用,其實都是在圍繞著ZuulServlet這個Servlet略号,ZuulServlet作為Zuul的一個非常核心的功能刑峡,作為Zuul的請求入口處理類,那都做了什么事情呢玄柠,我們接著往下看突梦!
public class ZuulServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
zuulRunner = new ZuulRunner(bufferReqs);
}
//執(zhí)行Zuul的filter階段
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
//設(shè)置zuul的RequestContext請求上下文參數(shù)
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
//執(zhí)行pre filters階段
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
//執(zhí)行routing filters階段
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
//執(zhí)行post filters階段
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
//釋放當(dāng)前請求上下文
RequestContext.getCurrentContext().unset();
}
}
//請求完后的攔截器
void postRoute() throws ZuulException {......}
//請求中的攔截器,請求下游服務(wù)在這個攔截器中
void route() throws ZuulException {......}
//請求前的攔截器
void preRoute() throws ZuulException {......}
//初始化階段,設(shè)置請求上下文參數(shù)
void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {......}
//失敗時候的攔截器
void error(ZuulException e) {......}
......
}
至此我們大致分析了ZuulConfiguration配置類中比較重要的一部分羽利,其中ZuulConfiguration#ZuulFilterConfiguration配置我們放到下一節(jié)一起分析和總結(jié)阳似,下一節(jié)我們將分析ZuulFilter(pre filters、routing filters铐伴、post filters...)的初始化以及調(diào)用過程撮奏,即調(diào)用鏈中的ZuulServlet->ZuulRunner-> FilterProcessor->ZuulFilter->RibbonRoutingFilter部分!