最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
怎么在springcloud中使用Zuul實(shí)現(xiàn)動態(tài)路由

怎么在springcloud中使用Zuul實(shí)現(xiàn)動態(tài)路由?很多新手對此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

為察哈爾右翼后等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及察哈爾右翼后網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站建設(shè)、成都網(wǎng)站制作、察哈爾右翼后網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!

傳統(tǒng)互聯(lián)網(wǎng)架構(gòu)圖

怎么在springcloud中使用Zuul實(shí)現(xiàn)動態(tài)路由 

上圖是沒有網(wǎng)關(guān)參與的一個(gè)最典型的互聯(lián)網(wǎng)架構(gòu)(本文中統(tǒng)一使用book代表應(yīng)用實(shí)例,即真正提供服務(wù)的一個(gè)業(yè)務(wù)系統(tǒng))

加入eureka的架構(gòu)圖

怎么在springcloud中使用Zuul實(shí)現(xiàn)動態(tài)路由 

book注冊到eureka注冊中心中,zuul本身也連接著同一個(gè)eureka,可以拉取book眾多實(shí)例的列表。服務(wù)中心的注冊發(fā)現(xiàn)一直是值得推崇的一種方式,但是不適用與網(wǎng)關(guān)產(chǎn)品。因?yàn)槲覀兊木W(wǎng)關(guān)是面向眾多的其他部門的已有或是異構(gòu)架構(gòu)的系統(tǒng),不應(yīng)該強(qiáng)求其他系統(tǒng)都使用eureka,這樣是有侵入性的設(shè)計(jì)。

最終架構(gòu)圖

怎么在springcloud中使用Zuul實(shí)現(xiàn)動態(tài)路由 

要強(qiáng)調(diào)的一點(diǎn)是,gateway最終也會部署多個(gè)實(shí)例,達(dá)到分布式的效果,在架構(gòu)圖中沒有畫出,請大家自行腦補(bǔ)。

本博客的示例使用最后一章架構(gòu)圖為例,帶來動態(tài)路由的實(shí)現(xiàn)方式,會有具體的代碼。

動態(tài)路由

動態(tài)路由需要達(dá)到可持久化配置,動態(tài)刷新的效果。如架構(gòu)圖所示,不僅要能滿足從spring的配置文件properties加載路由信息,還需要從數(shù)據(jù)庫加載我們的配置。另外一點(diǎn)是,路由信息在容器啟動時(shí)就已經(jīng)加載進(jìn)入了內(nèi)存,我們希望配置完成后,實(shí)施發(fā)布,動態(tài)刷新內(nèi)存中的路由信息,達(dá)到不停機(jī)維護(hù)路由信息的效果。

zuul–HelloWorldDemo

項(xiàng)目結(jié)構(gòu)

 com.sinosoft
 zuul-gateway-demo
 pom
 1.0

 
  org.springframework.boot
  spring-boot-starter-parent
  1.5.2.RELEASE
 

 
  gateway
  book
 

 
  
   
    org.springframework.cloud
    spring-cloud-dependencies
    Camden.SR6
    pom
    import
   
  
 

tip:springboot-1.5.2對應(yīng)的springcloud的版本需要使用Camden.SR6,一開始想專門寫這個(gè)demo時(shí),只替換了springboot的版本1.4.0->1.5.2,結(jié)果啟動就報(bào)錯(cuò)了,最后發(fā)現(xiàn)是版本不兼容的鍋。

gateway項(xiàng)目:

啟動類:GatewayApplication.java

@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {

 public static void main(String[] args) {
  SpringApplication.run(GatewayApplication.class, args);
 }

}

配置:application.properties

#配置在配置文件中的路由信息
zuul.routes.books.url=http://localhost:8090
zuul.routes.books.path=/books/**
#不使用注冊中心,會帶來侵入性
ribbon.eureka.enabled=false
#網(wǎng)關(guān)端口
server.port=8080

book項(xiàng)目:

啟動類:BookApplication.java

@RestController
@SpringBootApplication
public class BookApplication {

 @RequestMapping(value = "/available")
 public String available() {
  System.out.println("Spring in Action");
  return "Spring in Action";
 }

 @RequestMapping(value = "/checked-out")
 public String checkedOut() {
  return "Spring Boot in Action";
 }

 public static void main(String[] args) {
  SpringApplication.run(BookApplication.class, args);
 }
}

配置類:application.properties

server.port=8090

測試訪問:http://localhost:8080/books/available

上述demo是一個(gè)簡單的靜態(tài)路由,簡單看下源碼,zuul是怎么做到轉(zhuǎn)發(fā),路由的。

@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulConfiguration {

 @Autowired
 //zuul的配置文件,對應(yīng)了application.properties中的配置信息
 protected ZuulProperties zuulProperties;

 @Autowired
 protected ServerProperties server;

 @Autowired(required = false)
 private ErrorController errorController;

 @Bean
 public HasFeatures zuulFeature() {
  return HasFeatures.namedFeature("Zuul (Simple)", ZuulConfiguration.class);
 }

 //核心類,路由定位器,最最重要
 @Bean
 @ConditionalOnMissingBean(RouteLocator.class)
 public RouteLocator routeLocator() {
  //默認(rèn)配置的實(shí)現(xiàn)是SimpleRouteLocator.class
  return new SimpleRouteLocator(this.server.getServletPrefix(),
    this.zuulProperties);
 }

 //zuul的控制器,負(fù)責(zé)處理鏈路調(diào)用
 @Bean
 public ZuulController zuulController() {
  return new 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;
 }

 //注冊了一個(gè)路由刷新監(jiān)聽器,默認(rèn)實(shí)現(xiàn)是ZuulRefreshListener.class,這個(gè)是我們動態(tài)路由的關(guān)鍵
 @Bean
 public ApplicationListener zuulRefreshRoutesListener() {
  return new 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() {
  return new ServletDetectionFilter();
 }

 @Bean
 public FormBodyWrapperFilter formBodyWrapperFilter() {
  return new FormBodyWrapperFilter();
 }

 @Bean
 public DebugFilter debugFilter() {
  return new DebugFilter();
 }

 @Bean
 public Servlet30WrapperFilter servlet30WrapperFilter() {
  return new Servlet30WrapperFilter();
 }

 // post filters

 @Bean
 public SendResponseFilter sendResponseFilter() {
  return new SendResponseFilter();
 }

 @Bean
 public SendErrorFilter sendErrorFilter() {
  return new SendErrorFilter();
 }

 @Bean
 public SendForwardFilter sendForwardFilter() {
  return new SendForwardFilter();
 }

 @Configuration
 protected static class ZuulFilterConfiguration {

  @Autowired
  private Map filters;

  @Bean
  public ZuulFilterInitializer zuulFilterInitializer() {
   return new ZuulFilterInitializer(this.filters);
  }

 }

 //上面提到的路由刷新監(jiān)聽器
 private static class ZuulRefreshListener
   implements ApplicationListener {

  @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) {
    //設(shè)置為臟,下一次匹配到路徑時(shí),如果發(fā)現(xiàn)為臟,則會去刷新路由信息
    this.zuulHandlerMapping.setDirty(true);
   }
   else if (event instanceof HeartbeatEvent) {
    if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) {
     this.zuulHandlerMapping.setDirty(true);
    }
   }
  }

 }

}

我們要解決動態(tài)路由的難題,第一步就得理解路由定位器的作用。

怎么在springcloud中使用Zuul實(shí)現(xiàn)動態(tài)路由 

很失望,因?yàn)閺慕涌陉P(guān)系來看,spring考慮到了路由刷新的需求,但是默認(rèn)實(shí)現(xiàn)的SimpleRouteLocator沒有實(shí)現(xiàn)RefreshableRouteLocator接口,看來我們只能借鑒DiscoveryClientRouteLocator去改造SimpleRouteLocator使其具備刷新能力。

public interface RefreshableRouteLocator extends RouteLocator {
 void refresh();
}

DiscoveryClientRouteLocator比SimpleRouteLocator多了兩個(gè)功能,第一是從DiscoveryClient(如Eureka)發(fā)現(xiàn)路由信息,之前的架構(gòu)圖已經(jīng)給大家解釋清楚了,我們不想使用eureka這種侵入式的網(wǎng)關(guān)模塊,所以忽略它,第二是實(shí)現(xiàn)了RefreshableRouteLocator接口,能夠?qū)崿F(xiàn)動態(tài)刷新。

對SimpleRouteLocator.class的源碼加一些注釋,方便大家閱讀:

public class SimpleRouteLocator implements RouteLocator {

 //配置文件中的路由信息配置
 private ZuulProperties properties;
 //路徑正則配置器,即作用于path:/books/**
 private PathMatcher pathMatcher = new AntPathMatcher();

 private String dispatcherServletPath = "/";
 private String zuulServletPath;

 private AtomicReference> routes = new AtomicReference<>();

 public SimpleRouteLocator(String servletPath, ZuulProperties properties) {
  this.properties = properties;
  if (servletPath != null && StringUtils.hasText(servletPath)) {
   this.dispatcherServletPath = servletPath;
  }

  this.zuulServletPath = properties.getServletPath();
 }

 //路由定位器和其他組件的交互,是最終把定位的Routes以list的方式提供出去,核心實(shí)現(xiàn)
 @Override
 public List getRoutes() {
  if (this.routes.get() == null) {
   this.routes.set(locateRoutes());
  }
  List values = new ArrayList<>();
  for (String url : this.routes.get().keySet()) {
   ZuulRoute route = this.routes.get().get(url);
   String path = route.getPath();
   values.add(getRoute(route, path));
  }
  return values;
 }

 @Override
 public Collection getIgnoredPaths() {
  return this.properties.getIgnoredPatterns();
 }

 //這個(gè)方法在網(wǎng)關(guān)產(chǎn)品中也很重要,可以根據(jù)實(shí)際路徑匹配到Route來進(jìn)行業(yè)務(wù)邏輯的操作,進(jìn)行一些加工
 @Override
 public Route getMatchingRoute(final String path) {

  if (log.isDebugEnabled()) {
   log.debug("Finding route for path: " + path);
  }

  if (this.routes.get() == null) {
   this.routes.set(locateRoutes());
  }

  if (log.isDebugEnabled()) {
   log.debug("servletPath=" + this.dispatcherServletPath);
   log.debug("zuulServletPath=" + this.zuulServletPath);
   log.debug("RequestUtils.isDispatcherServletRequest()="
     + RequestUtils.isDispatcherServletRequest());
   log.debug("RequestUtils.isZuulServletRequest()="
     + RequestUtils.isZuulServletRequest());
  }

  String adjustedPath = adjustPath(path);

  ZuulRoute route = null;
  if (!matchesIgnoredPatterns(adjustedPath)) {
   for (Entry entry : this.routes.get().entrySet()) {
    String pattern = entry.getKey();
    log.debug("Matching pattern:" + pattern);
    if (this.pathMatcher.match(pattern, adjustedPath)) {
     route = entry.getValue();
     break;
    }
   }
  }
  if (log.isDebugEnabled()) {
   log.debug("route matched=" + route);
  }

  return getRoute(route, adjustedPath);

 }

 private Route getRoute(ZuulRoute route, String path) {
  if (route == null) {
   return null;
  }
  String targetPath = path;
  String prefix = this.properties.getPrefix();
  if (path.startsWith(prefix) && this.properties.isStripPrefix()) {
   targetPath = path.substring(prefix.length());
  }
  if (route.isStripPrefix()) {
   int index = route.getPath().indexOf("*") - 1;
   if (index > 0) {
    String routePrefix = route.getPath().substring(0, index);
    targetPath = targetPath.replaceFirst(routePrefix, "");
    prefix = prefix + routePrefix;
   }
  }
  Boolean retryable = this.properties.getRetryable();
  if (route.getRetryable() != null) {
   retryable = route.getRetryable();
  }
  return new Route(route.getId(), targetPath, route.getLocation(), prefix,
    retryable,
    route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null);
 }

 //注意這個(gè)類并沒有實(shí)現(xiàn)refresh接口,但是卻提供了一個(gè)protected級別的方法,旨在讓子類不需要重復(fù)維護(hù)一個(gè)private AtomicReference> routes = new AtomicReference<>();也可以達(dá)到刷新的效果
 protected void doRefresh() {
  this.routes.set(locateRoutes());
 }


 //具體就是在這兒定位路由信息的,我們之后從數(shù)據(jù)庫加載路由信息,主要也是從這兒改寫
 /**
  * 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 locateRoutes() {
  LinkedHashMap routesMap = new LinkedHashMap();
  for (ZuulRoute route : this.properties.getRoutes().values()) {
   routesMap.put(route.getPath(), route);
  }
  return routesMap;
 }

 protected boolean matchesIgnoredPatterns(String path) {
  for (String pattern : this.properties.getIgnoredPatterns()) {
   log.debug("Matching ignored pattern:" + pattern);
   if (this.pathMatcher.match(pattern, path)) {
    log.debug("Path " + path + " matches ignored pattern " + pattern);
    return true;
   }
  }
  return false;
 }

 private String adjustPath(final String path) {
  String adjustedPath = path;

  if (RequestUtils.isDispatcherServletRequest()
    && StringUtils.hasText(this.dispatcherServletPath)) {
   if (!this.dispatcherServletPath.equals("/")) {
    adjustedPath = path.substring(this.dispatcherServletPath.length());
    log.debug("Stripped dispatcherServletPath");
   }
  }
  else if (RequestUtils.isZuulServletRequest()) {
   if (StringUtils.hasText(this.zuulServletPath)
     && !this.zuulServletPath.equals("/")) {
    adjustedPath = path.substring(this.zuulServletPath.length());
    log.debug("Stripped zuulServletPath");
   }
  }
  else {
   // do nothing
  }

  log.debug("adjustedPath=" + path);
  return adjustedPath;
 }

}

重寫過后的自定義路由定位器如下:

public class CustomRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator{

 public final static Logger logger = LoggerFactory.getLogger(CustomRouteLocator.class);

 private JdbcTemplate jdbcTemplate;

 private ZuulProperties properties;

 public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
  this.jdbcTemplate = jdbcTemplate;
 }

 public CustomRouteLocator(String servletPath, ZuulProperties properties) {
  super(servletPath, properties);
  this.properties = properties;
  logger.info("servletPath:{}",servletPath);
 }

 //父類已經(jīng)提供了這個(gè)方法,這里寫出來只是為了說明這一個(gè)方法很重要?。?!
// @Override
// protected void doRefresh() {
//  super.doRefresh();
// }


 @Override
 public void refresh() {
  doRefresh();
 }

 @Override
 protected Map locateRoutes() {
  LinkedHashMap routesMap = new LinkedHashMap();
  //從application.properties中加載路由信息
  routesMap.putAll(super.locateRoutes());
  //從db中加載路由信息
  routesMap.putAll(locateRoutesFromDB());
  //優(yōu)化一下配置
  LinkedHashMap values = new LinkedHashMap<>();
  for (Map.Entry 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 locateRoutesFromDB(){
  Map routes = new LinkedHashMap<>();
  List 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;
 }

 public static class ZuulRouteVO {

  /**
   * 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.
   */
  private boolean stripPrefix = true;

  /**
   * Flag to indicate that this route should be retryable (if supported). Generally
   * retry requires a service ID and ribbon.
   */
  private Boolean retryable;

  private Boolean enabled;

  public String getId() {
   return id;
  }

  public void setId(String id) {
   this.id = id;
  }

  public String getPath() {
   return path;
  }

  public void setPath(String path) {
   this.path = path;
  }

  public String getServiceId() {
   return serviceId;
  }

  public void setServiceId(String serviceId) {
   this.serviceId = serviceId;
  }

  public String getUrl() {
   return url;
  }

  public void setUrl(String url) {
   this.url = url;
  }

  public boolean isStripPrefix() {
   return stripPrefix;
  }

  public void setStripPrefix(boolean stripPrefix) {
   this.stripPrefix = stripPrefix;
  }

  public Boolean getRetryable() {
   return retryable;
  }

  public void setRetryable(Boolean retryable) {
   this.retryable = retryable;
  }

  public Boolean getEnabled() {
   return enabled;
  }

  public void setEnabled(Boolean enabled) {
   this.enabled = enabled;
  }
 }
}

配置這個(gè)自定義的路由定位器:

@Configuration
public class CustomZuulConfig {

 @Autowired
 ZuulProperties zuulProperties;
 @Autowired
 ServerProperties server;
 @Autowired
 JdbcTemplate jdbcTemplate;

 @Bean
 public CustomRouteLocator routeLocator() {
  CustomRouteLocator routeLocator = new CustomRouteLocator(this.server.getServletPrefix(), this.zuulProperties);
  routeLocator.setJdbcTemplate(jdbcTemplate);
  return routeLocator;
 }

}

現(xiàn)在容器啟動時(shí),就可以從數(shù)據(jù)庫和配置文件中一起加載路由信息了,離動態(tài)路由還差最后一步,就是實(shí)時(shí)刷新,前面已經(jīng)說過了,默認(rèn)的ZuulConfigure已經(jīng)配置了事件監(jiān)聽器,我們只需要發(fā)送一個(gè)事件就可以實(shí)現(xiàn)刷新了。

public class RefreshRouteService {

 @Autowired
 ApplicationEventPublisher publisher;

 @Autowired
 RouteLocator routeLocator;

 public void refreshRoute() {
  RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
  publisher.publishEvent(routesRefreshedEvent);
 }

}

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對創(chuàng)新互聯(lián)的支持。


新聞名稱:怎么在springcloud中使用Zuul實(shí)現(xiàn)動態(tài)路由
本文路徑:http://fisionsoft.com.cn/article/iedoih.html