欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springcloud Zuul動態(tài)路由的實現(xiàn)

 更新時間:2018年11月04日 16:29:39   作者:下一秒升華  
這篇文章主要介紹了springcloud Zuul動態(tài)路由的實現(xiàn),詳細(xì)的介紹了什么是Zuu及其動態(tài)路由的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

前言

Zuul 是Netflix 提供的一個開源組件,致力于在云平臺上提供動態(tài)路由,監(jiān)控,彈性,安全等邊緣服務(wù)的框架。也有很多公司使用它來作為網(wǎng)關(guān)的重要組成部分,碰巧今年公司的架構(gòu)組決定自研一個網(wǎng)關(guān)產(chǎn)品,集動態(tài)路由,動態(tài)權(quán)限,限流配額等功能為一體,為其他部門的項目提供統(tǒng)一的外網(wǎng)調(diào)用管理,最終形成產(chǎn)品(這方面阿里其實已經(jīng)有成熟的網(wǎng)關(guān)產(chǎn)品了,但是不太適用于個性化的配置,也沒有集成權(quán)限和限流降級)。

不過這里并不想介紹整個網(wǎng)關(guān)的架構(gòu),而是想著重于討論其中的一個關(guān)鍵點,并且也是經(jīng)常在交流群中聽人說起的:動態(tài)路由怎么做?

再闡釋什么是動態(tài)路由之前,需要介紹一下架構(gòu)的設(shè)計。

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

 

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

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

 

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

最終架構(gòu)圖

 

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

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

動態(tài)路由

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

zuul–HelloWorldDemo

項目結(jié)構(gòu)

 <groupId>com.sinosoft</groupId>
 <artifactId>zuul-gateway-demo</artifactId>
 <packaging>pom</packaging>
 <version>1.0</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.2.RELEASE</version>
 </parent>

 <modules>
  <module>gateway</module>
  <module>book</module>
 </modules>

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Camden.SR6</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>

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

gateway項目:

啟動類: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項目:

啟動類: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是一個簡單的靜態(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)配置的實現(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;
 }

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

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

 }

 //上面提到的路由刷新監(jiān)聽器
 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) {
    //設(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)路由的難題,第一步就得理解路由定位器的作用。

 

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

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

DiscoveryClientRouteLocator比SimpleRouteLocator多了兩個功能,第一是從DiscoveryClient(如Eureka)發(fā)現(xiàn)路由信息,之前的架構(gòu)圖已經(jīng)給大家解釋清楚了,我們不想使用eureka這種侵入式的網(wǎng)關(guān)模塊,所以忽略它,第二是實現(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<Map<String, ZuulRoute>> 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的方式提供出去,核心實現(xiàn)
 @Override
 public List<Route> getRoutes() {
  if (this.routes.get() == null) {
   this.routes.set(locateRoutes());
  }
  List<Route> 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<String> getIgnoredPaths() {
  return this.properties.getIgnoredPatterns();
 }

 //這個方法在網(wǎng)關(guān)產(chǎn)品中也很重要,可以根據(jù)實際路徑匹配到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<String, ZuulRoute> 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);
 }

 //注意這個類并沒有實現(xiàn)refresh接口,但是卻提供了一個protected級別的方法,旨在讓子類不需要重復(fù)維護(hù)一個private AtomicReference<Map<String, ZuulRoute>> 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<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;
 }

 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)提供了這個方法,這里寫出來只是為了說明這一個方法很重要?。?!
// @Override
// protected void doRefresh() {
//  super.doRefresh();
// }


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

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

 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;
  }
 }
}

配置這個自定義的路由定位器:

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

public class RefreshRouteService {

 @Autowired
 ApplicationEventPublisher publisher;

 @Autowired
 RouteLocator routeLocator;

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

}

具體的刷新流程其實就是從數(shù)據(jù)庫重新加載了一遍,有人可能會問,為什么不自己是手動重新加載Locator.dorefresh?非要用事件去刷新。這牽扯到內(nèi)部的zuul內(nèi)部組件的工作流程,不僅僅是Locator本身的一個變量,具體想要了解的還得去看源碼。

到這兒我們就實現(xiàn)了動態(tài)路由了,所以的實例代碼和建表語句我會放到github上,下載的時候記得給我star QAQ ?。?!

鏈接:https://github.com/lexburner/zuul-gateway-demo

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringAOP中的注解配置詳解

    SpringAOP中的注解配置詳解

    這篇文章主要介紹了SpringAOP中的注解配置詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • Java swing讀取txt文件實現(xiàn)學(xué)生考試系統(tǒng)

    Java swing讀取txt文件實現(xiàn)學(xué)生考試系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了Java swing讀取txt文件實現(xiàn)學(xué)生考試系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • flink進(jìn)階富函數(shù)生命周期介紹

    flink進(jìn)階富函數(shù)生命周期介紹

    這篇文章主要為大家介紹了flink進(jìn)階富函數(shù)生命周期的舉例介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • Java中的MapStruct的使用方法代碼實例

    Java中的MapStruct的使用方法代碼實例

    這篇文章主要介紹了Java中的MapStruct的使用方法代碼實例,mapstruct是一種實體類映射框架,能夠通過Java注解將一個實體類的屬性安全地賦值給另一個實體類,有了mapstruct,只需要定義一個映射器接口,聲明需要映射的方法,需要的朋友可以參考下
    2023-10-10
  • 利用javadoc注釋自動生成Swagger注解

    利用javadoc注釋自動生成Swagger注解

    由于現(xiàn)在controller方法上面沒有swagger注解,只能拿到接口url地址,無法獲得接口功能描述,所以本文為大家介紹一下如何利用javadoc注釋自動生成Swagger注解,感興趣的可以了解下
    2023-08-08
  • java 靜態(tài)工廠代替多參構(gòu)造器的適用情況與優(yōu)劣

    java 靜態(tài)工廠代替多參構(gòu)造器的適用情況與優(yōu)劣

    這篇文章主要介紹了java 靜態(tài)工廠代替多參構(gòu)造器的優(yōu)劣,幫助大家更好的理解和使用靜態(tài)工廠方法,感興趣的朋友可以了解下
    2020-12-12
  • 完全解析Java編程中finally語句的執(zhí)行原理

    完全解析Java編程中finally語句的執(zhí)行原理

    這篇文章主要深度介紹了Java編程中finally語句的執(zhí)行原理,細(xì)致講解了finally在異常處理中的流程控制作用,需要的朋友可以參考下
    2015-11-11
  • Atomikos + MybatisPlus解決多數(shù)據(jù)源事務(wù)一致性問題解決

    Atomikos + MybatisPlus解決多數(shù)據(jù)源事務(wù)一致性問題解決

    在實際項目的開發(fā)過程中,我們經(jīng)常會遇到在同一個項目或微服務(wù)中牽涉到使用兩個或多個數(shù)據(jù)源的,本文主要介紹了Atomikos + MybatisPlus解決多數(shù)據(jù)源事務(wù)一致性問題解決,具有一定的參考價值,感興趣的可以了解一下
    2024-07-07
  • Spring聲明式事務(wù)注解之@EnableTransactionManagement解析

    Spring聲明式事務(wù)注解之@EnableTransactionManagement解析

    這篇文章主要介紹了Spring聲明式事務(wù)注解之@EnableTransactionManagement解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • java 查詢oracle數(shù)據(jù)庫所有表DatabaseMetaData的用法(詳解)

    java 查詢oracle數(shù)據(jù)庫所有表DatabaseMetaData的用法(詳解)

    下面小編就為大家?guī)硪黄猨ava 查詢oracle數(shù)據(jù)庫所有表DatabaseMetaData的用法(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-11-11

最新評論