动态路由需要达到可持久化配置,动态刷新的效果。如架构图所示,不仅要能满足从 spring 的配置文件 properties 加载路由信息,还需要从数据库加载我们的配置。另外一点是,路由信息在容器启动时就已经加载进入了内存,我们希望配置完成后,实施发布,动态刷新内存中的路由信息,达到不停机维护路由信息的效果。
//zuul 的控制器,负责处理链路调用 @Bean public ZuulController zuulController(){ returnnew ZuulController(); }
//MVC HandlerMapping that maps incoming request paths to remote services. @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes){ ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController); return mapping; }
// 注册了一个路由刷新监听器,默认实现是 ZuulRefreshListener.class,这个是我们动态路由的关键 @Bean public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener(){ returnnew ZuulRefreshListener(); }
@Bean @ConditionalOnMissingBean(name = "zuulServlet") public ServletRegistrationBean zuulServlet(){ ServletRegistrationBean 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; }
// pre filters
@Bean public ServletDetectionFilter servletDetectionFilter(){ returnnew ServletDetectionFilter(); }
@Bean public FormBodyWrapperFilter formBodyWrapperFilter(){ returnnew FormBodyWrapperFilter(); }
@Bean public DebugFilter debugFilter(){ returnnew DebugFilter(); }
@Bean public Servlet30WrapperFilter servlet30WrapperFilter(){ returnnew Servlet30WrapperFilter(); }
// post filters
@Bean public SendResponseFilter sendResponseFilter(){ returnnew SendResponseFilter(); }
@Bean public SendErrorFilter sendErrorFilter(){ returnnew SendErrorFilter(); }
@Bean public SendForwardFilter sendForwardFilter(){ returnnew SendForwardFilter(); }
// 具体就是在这儿定位路由信息的,我们之后从数据库加载路由信息,主要也是从这儿改写 /** * Compute a map of path pattern to route. The default is just a static map from the * {@link ZuulProperties}, but subclasses can add dynamic calculations. */ protected Map<String, ZuulRoute> locateRoutes(){ LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>(); for (ZuulRoute route : this.properties.getRoutes().values()) { routesMap.put(route.getPath(), route); } return routesMap; }
@Override protected Map<String, ZuulRoute> locateRoutes(){ LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>(); // 从 application.properties 中加载路由信息 routesMap.putAll(super.locateRoutes()); // 从 db 中加载路由信息 routesMap.putAll(locateRoutesFromDB()); // 优化一下配置 LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>(); for (Map.Entry<String, ZuulRoute> entry : routesMap.entrySet()) { String path = entry.getKey(); // Prepend with slash if not already present. if (!path.startsWith("/")) { path = "/" + path; } if (StringUtils.hasText(this.properties.getPrefix())) { path = this.properties.getPrefix() + path; if (!path.startsWith("/")) { path = "/" + path; } } values.put(path, entry.getValue()); } return values; }
private Map<String, ZuulRoute> locateRoutesFromDB(){ Map<String, ZuulRoute> routes = new LinkedHashMap<>(); List<ZuulRouteVO> results = jdbcTemplate.query("select * from gateway_api_define where enabled = true",new BeanPropertyRowMapper<>(ZuulRouteVO.class)); for (ZuulRouteVO result : results) { if(org.apache.commons.lang3.StringUtils.isBlank(result.getPath()) || org.apache.commons.lang3.StringUtils.isBlank(result.getUrl()) ){ continue; } ZuulRoute zuulRoute = new ZuulRoute(); try { org.springframework.beans.BeanUtils.copyProperties(result,zuulRoute); } catch (Exception e) { logger.error("=============load zuul route info from db with error==============",e); } routes.put(zuulRoute.getPath(),zuulRoute); } return routes; }
publicstaticclassZuulRouteVO{
/** * The ID of the route (the same as its map key by default). */ private String id;
/** * The path (pattern) for the route, e.g. /foo/**. */ private String path;
/** * The service ID (if any) to map to this route. You can specify a physical URL or * a service, but not both. */ private String serviceId;
/** * A full physical URL to map to the route. An alternative is to use a service ID * and service discovery to find the physical address. */ private String url;
/** * Flag to determine whether the prefix for this route (the path, minus pattern * patcher) should be stripped before forwarding. */ privateboolean stripPrefix = true;
/** * Flag to indicate that this route should be retryable (if supported). Generally * retry requires a service ID and ribbon. */ private Boolean retryable;