SpringIntegration消息路由之Router的條件路由與過濾功能
引言
在企業(yè)集成架構(gòu)中,消息路由是一個(gè)至關(guān)重要的環(huán)節(jié),它負(fù)責(zé)根據(jù)預(yù)定的規(guī)則將消息分發(fā)到不同的目標(biāo)通道。Spring Integration作為企業(yè)集成模式的實(shí)現(xiàn)框架,提供了強(qiáng)大的Router組件來滿足各種復(fù)雜的路由需求。Router可以根據(jù)消息的內(nèi)容、消息頭或其它條件,智能地決定消息的流向,從而使系統(tǒng)的各個(gè)組件能夠?qū)W⒂谧约旱暮诵墓δ?,提高了系統(tǒng)的模塊化程度和可維護(hù)性。本文將深入探討Spring Integration中Router的實(shí)現(xiàn)方式和應(yīng)用場景,特別是條件路由和消息過濾的相關(guān)技術(shù),通過具體示例展示如何在實(shí)際項(xiàng)目中有效地使用這些功能。
一、Router基礎(chǔ)概念
Router是Spring Integration中的核心組件之一,其主要職責(zé)是根據(jù)特定的條件將輸入消息路由到一個(gè)或多個(gè)輸出通道。通過Router,可以構(gòu)建靈活的消息流,實(shí)現(xiàn)業(yè)務(wù)邏輯的動(dòng)態(tài)分支處理。Spring Integration提供了多種類型的Router實(shí)現(xiàn),包括PayloadTypeRouter、HeaderValueRouter、RecipientListRouter、ExpressionEvaluatingRouter等,開發(fā)人員可以根據(jù)具體需求選擇合適的Router類型。Router的工作原理是接收來自輸入通道的消息,根據(jù)配置的路由規(guī)則評估消息,然后決定將消息發(fā)送到哪個(gè)或哪些輸出通道。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.Router; import org.springframework.integration.channel.DirectChannel; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @Configuration public class BasicRouterConfig { // 定義通道 @Bean public MessageChannel inputChannel() { return new DirectChannel(); } @Bean public MessageChannel orderChannel() { return new DirectChannel(); } @Bean public MessageChannel inventoryChannel() { return new DirectChannel(); } @Bean public MessageChannel customerChannel() { return new DirectChannel(); } // 基礎(chǔ)路由器實(shí)現(xiàn) @Bean @Router(inputChannel = "inputChannel") public String route(Message<?> message) { // 根據(jù)消息的不同類型路由到不同的通道 Object payload = message.getPayload(); if (payload instanceof Order) { return "orderChannel"; } else if (payload instanceof InventoryItem) { return "inventoryChannel"; } else if (payload instanceof Customer) { return "customerChannel"; } else { throw new IllegalArgumentException("未知消息類型: " + payload.getClass().getName()); } } // 示例數(shù)據(jù)類 public static class Order { private String orderId; // 其他字段省略 } public static class InventoryItem { private String itemId; // 其他字段省略 } public static class Customer { private String customerId; // 其他字段省略 } }
二、條件路由實(shí)現(xiàn)
條件路由是指根據(jù)消息內(nèi)容或消息頭信息中的特定條件,將消息路由到不同的目標(biāo)通道。Spring Integration提供了多種方式來實(shí)現(xiàn)條件路由,包括使用SpEL表達(dá)式、Java DSL和基于注解的配置。ExpressionEvaluatingRouter允許使用SpEL表達(dá)式定義路由條件,使得復(fù)雜的路由邏輯可以通過簡潔的表達(dá)式實(shí)現(xiàn)。通過條件路由,系統(tǒng)可以根據(jù)業(yè)務(wù)規(guī)則動(dòng)態(tài)地決定消息的處理流程,例如根據(jù)訂單金額將訂單分為高優(yōu)先級和普通優(yōu)先級處理,或者根據(jù)客戶類型提供不同級別的服務(wù)。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.Router; import org.springframework.integration.router.ExpressionEvaluatingRouter; import org.springframework.messaging.MessageChannel; import java.util.HashMap; import java.util.Map; @Configuration public class ConditionalRouterConfig { // 使用SpEL表達(dá)式的條件路由器 @Bean @Router(inputChannel = "orderInputChannel") public ExpressionEvaluatingRouter orderRouter() { ExpressionEvaluatingRouter router = new ExpressionEvaluatingRouter("payload.amount > 1000 ? 'vipOrderChannel' : 'regularOrderChannel'"); router.setChannelMapping("true", "vipOrderChannel"); router.setChannelMapping("false", "regularOrderChannel"); return router; } // 使用Java DSL的方式配置條件路由 @Bean public org.springframework.integration.dsl.IntegrationFlow conditionRoutingFlow() { return org.springframework.integration.dsl.IntegrationFlows .from("paymentInputChannel") .<Payment, String>route( payment -> { if (payment.getAmount() < 100) { return "smallPaymentChannel"; } else if (payment.getAmount() < 1000) { return "mediumPaymentChannel"; } else { return "largePaymentChannel"; } }, mapping -> mapping .subFlowMapping("smallPaymentChannel", sf -> sf .handle(message -> { System.out.println("處理小額支付: " + message.getPayload()); })) .subFlowMapping("mediumPaymentChannel", sf -> sf .handle(message -> { System.out.println("處理中額支付: " + message.getPayload()); })) .subFlowMapping("largePaymentChannel", sf -> sf .handle(message -> { System.out.println("處理大額支付: " + message.getPayload()); })) ) .get(); } // 多條件路由示例 @Bean @Router(inputChannel = "customerInputChannel") public String routeCustomer(Customer customer) { // 根據(jù)客戶類型和信用評分路由 if (customer.getType().equals("VIP") && customer.getCreditScore() > 700) { return "premiumServiceChannel"; } else if (customer.getType().equals("VIP")) { return "vipServiceChannel"; } else if (customer.getCreditScore() > 700) { return "priorityServiceChannel"; } else { return "regularServiceChannel"; } } // 示例數(shù)據(jù)類 public static class Payment { private double amount; public double getAmount() { return amount; } } public static class Customer { private String type; private int creditScore; public String getType() { return type; } public int getCreditScore() { return creditScore; } } }
三、基于消息頭的路由
在企業(yè)集成場景中,消息頭通常包含了重要的元數(shù)據(jù),如消息類型、優(yōu)先級、來源系統(tǒng)等信息,這些信息對于路由決策十分有用。HeaderValueRouter專門用于根據(jù)消息頭的值進(jìn)行路由,簡化了基于消息頭的路由配置。通過消息頭路由,可以在不解析消息內(nèi)容的情況下快速做出路由決策,提高了系統(tǒng)性能,同時(shí)也使得路由邏輯與業(yè)務(wù)邏輯分離,增強(qiáng)了系統(tǒng)的模塊化程度。這種路由方式特別適合于處理來自不同系統(tǒng)的消息,或者需要根據(jù)消息的元數(shù)據(jù)進(jìn)行分類處理的場景。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.Router; import org.springframework.integration.router.HeaderValueRouter; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; @Configuration public class HeaderBasedRouterConfig { // 基于消息頭的路由器 @Bean @Router(inputChannel = "requestChannel") public HeaderValueRouter messageTypeRouter() { HeaderValueRouter router = new HeaderValueRouter("message-type"); router.setChannelMapping("ORDER", "orderProcessingChannel"); router.setChannelMapping("INVENTORY", "inventoryManagementChannel"); router.setChannelMapping("SHIPPING", "shippingChannel"); router.setChannelMapping("PAYMENT", "paymentProcessingChannel"); // 設(shè)置默認(rèn)通道,當(dāng)沒有匹配的消息頭值時(shí)使用 router.setDefaultOutputChannelName("unknownMessageChannel"); return router; } // 消息頭注入示例 @Bean public org.springframework.integration.transformer.HeaderEnricher headerEnricher() { Map<String, Object> headersToAdd = new HashMap<>(); headersToAdd.put("message-type", "ORDER"); headersToAdd.put("priority", "HIGH"); return new org.springframework.integration.transformer.HeaderEnricher(headersToAdd); } // 發(fā)送消息的示例方法 public void sendMessage() { // 創(chuàng)建包含消息頭的消息 Message<String> orderMessage = MessageBuilder .withPayload("訂單數(shù)據(jù)內(nèi)容") .setHeader("message-type", "ORDER") .setHeader("priority", "HIGH") .build(); Message<String> inventoryMessage = MessageBuilder .withPayload("庫存數(shù)據(jù)內(nèi)容") .setHeader("message-type", "INVENTORY") .setHeader("priority", "MEDIUM") .build(); // 將消息發(fā)送到requestChannel,路由器會(huì)根據(jù)message-type頭進(jìn)行路由 requestChannel().send(orderMessage); requestChannel().send(inventoryMessage); } @Bean public org.springframework.messaging.MessageChannel requestChannel() { return new org.springframework.integration.channel.DirectChannel(); } }
四、動(dòng)態(tài)路由與路由表
在某些復(fù)雜的集成場景中,路由規(guī)則可能需要根據(jù)運(yùn)行時(shí)的條件動(dòng)態(tài)變化,或者需要在配置文件中定義而不是硬編碼在代碼中。Spring Integration提供了動(dòng)態(tài)路由的能力,允許開發(fā)人員在運(yùn)行時(shí)修改路由規(guī)則或從外部配置中加載路由表。AbstractMappingMessageRouter是實(shí)現(xiàn)動(dòng)態(tài)路由的基礎(chǔ)類,它維護(hù)了一個(gè)通道映射表,可以在運(yùn)行時(shí)更新。這種方式使得系統(tǒng)能夠適應(yīng)業(yè)務(wù)規(guī)則的變化,而無需修改代碼和重新部署,提高了系統(tǒng)的靈活性和可維護(hù)性。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.router.AbstractMappingMessageRouter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.handler.annotation.Header; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Properties; @Configuration public class DynamicRouterConfig { @Autowired private RoutingRuleService routingRuleService; // 自定義動(dòng)態(tài)路由器 @Bean @ServiceActivator(inputChannel = "dynamicRoutingChannel") public AbstractMappingMessageRouter dynamicRouter() { return new AbstractMappingMessageRouter() { @Override protected Collection<MessageChannel> determineTargetChannels(Message<?> message) { // 從服務(wù)中獲取最新的路由規(guī)則 Map<String, String> routingRules = routingRuleService.getRoutingRules(); // 根據(jù)消息內(nèi)容或頭信息確定路由鍵 String routingKey = extractRoutingKey(message); // 根據(jù)路由鍵查找目標(biāo)通道名稱 String channelName = routingRules.getOrDefault(routingKey, "defaultChannel"); // 獲取目標(biāo)通道并返回 MessageChannel channel = getChannelResolver().resolveDestination(channelName); return Collections.singleton(channel); } private String extractRoutingKey(Message<?> message) { // 實(shí)現(xiàn)從消息中提取路由鍵的邏輯 // 這里簡化為從特定的消息頭中獲取 return (String) message.getHeaders().get("routing-key"); } }; } // 路由規(guī)則服務(wù),用于管理和提供路由規(guī)則 @Bean public RoutingRuleService routingRuleService() { return new RoutingRuleService(); } // 路由規(guī)則管理服務(wù) public static class RoutingRuleService { private Map<String, String> routingRules = new HashMap<>(); public RoutingRuleService() { // 初始化默認(rèn)路由規(guī)則 routingRules.put("ORDER", "orderChannel"); routingRules.put("INVENTORY", "inventoryChannel"); routingRules.put("CUSTOMER", "customerChannel"); } public Map<String, String> getRoutingRules() { return routingRules; } public void updateRoutingRule(String key, String channelName) { routingRules.put(key, channelName); } public void loadRoutingRules(Properties properties) { properties.forEach((k, v) -> routingRules.put(k.toString(), v.toString())); } } // 路由規(guī)則更新API @Bean @ServiceActivator(inputChannel = "routingRuleUpdateChannel") public void updateRoutingRule(Message<RoutingRuleUpdate> message) { RoutingRuleUpdate update = message.getPayload(); routingRuleService.updateRoutingRule(update.getKey(), update.getChannelName()); } // 路由規(guī)則更新請求 public static class RoutingRuleUpdate { private String key; private String channelName; // 省略getter和setter } }
五、消息過濾與選擇性路由
消息過濾是路由的一種特殊形式,它基于特定條件決定是否允許消息繼續(xù)流轉(zhuǎn)。Spring Integration的Filter組件用于實(shí)現(xiàn)這一功能,它可以根據(jù)消息的內(nèi)容或消息頭信息過濾掉不符合條件的消息。過濾器可以作為獨(dú)立的組件使用,也可以與路由器結(jié)合使用,實(shí)現(xiàn)更復(fù)雜的路由邏輯。例如,在處理訂單消息時(shí),可以過濾掉無效的訂單,或者將不同類型的訂單路由到不同的處理通道。這種選擇性路由機(jī)制使得系統(tǒng)能夠更有針對性地處理不同類型的消息,提高了處理效率和系統(tǒng)的可維護(hù)性。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.Filter; import org.springframework.integration.annotation.Router; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.core.MessageSelector; import org.springframework.integration.router.RecipientListRouter; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; @Configuration public class FilterAndSelectiveRoutingConfig { // 消息過濾器示例 @Bean @Filter(inputChannel = "unfilteredChannel", outputChannel = "validOrderChannel") public MessageSelector orderValidator() { return message -> { Order order = (Order) message.getPayload(); // 驗(yàn)證訂單是否有效 boolean isValid = order.getItems() != null && !order.getItems().isEmpty() && order.getCustomerId() != null && order.getTotalAmount() > 0; return isValid; }; } // 結(jié)合過濾和路由的示例 @Bean public org.springframework.integration.dsl.IntegrationFlow filterAndRouteFlow() { return org.springframework.integration.dsl.IntegrationFlows .from("inputOrderChannel") // 首先過濾無效訂單 .filter(message -> { Order order = (Order) message.getPayload(); return order.isValid(); }) // 然后根據(jù)訂單類型路由 .<Order, String>route( order -> order.getType(), mapping -> mapping .subFlowMapping("RETAIL", sf -> sf.channel("retailOrderChannel")) .subFlowMapping("WHOLESALE", sf -> sf.channel("wholesaleOrderChannel")) .subFlowMapping("ONLINE", sf -> sf.channel("onlineOrderChannel")) .defaultSubFlowMapping(sf -> sf.channel("unknownOrderChannel")) ) .get(); } // 使用RecipientListRouter實(shí)現(xiàn)有條件的多通道路由 @Bean @Router(inputChannel = "orderRoutingChannel") public RecipientListRouter orderRouter() { RecipientListRouter router = new RecipientListRouter(); // 添加基于SpEL表達(dá)式的路由條件 router.addRecipient("highValueOrderChannel", "payload.totalAmount > 1000"); router.addRecipient("priorityCustomerOrderChannel", "payload.customerType == 'VIP'"); router.addRecipient("internationalOrderChannel", "payload.shippingAddress.country != 'China'"); // 將訂單同時(shí)發(fā)送到審計(jì)通道 router.addRecipient("orderAuditChannel"); return router; } // 處理無效訂單的示例 @Bean @ServiceActivator(inputChannel = "invalidOrderChannel") public void handleInvalidOrder(Message<Order> message) { Order order = message.getPayload(); // 記錄無效訂單 System.out.println("無效訂單: " + order.getOrderId()); // 創(chuàng)建通知消息 Message<String> notification = MessageBuilder .withPayload("訂單 " + order.getOrderId() + " 驗(yàn)證失敗") .setHeader("notification-type", "ORDER_VALIDATION_FAILURE") .setHeader("order-id", order.getOrderId()) .build(); // 發(fā)送通知 notificationChannel().send(notification); } @Bean public org.springframework.messaging.MessageChannel notificationChannel() { return new org.springframework.integration.channel.DirectChannel(); } // 示例數(shù)據(jù)類 public static class Order { private String orderId; private String customerId; private String customerType; private String type; private List<OrderItem> items; private double totalAmount; private Address shippingAddress; // 省略getter和setter public boolean isValid() { return items != null && !items.isEmpty() && customerId != null && totalAmount > 0; } } public static class OrderItem { private String productId; private int quantity; private double price; // 省略getter和setter } public static class Address { private String street; private String city; private String state; private String zipCode; private String country; // 省略getter和setter } }
六、錯(cuò)誤處理與路由
在企業(yè)集成中,錯(cuò)誤處理是一個(gè)重要的考慮因素。Spring Integration提供了豐富的錯(cuò)誤處理機(jī)制,包括錯(cuò)誤通道、全局錯(cuò)誤處理器和特定組件的錯(cuò)誤處理配置。在路由過程中,可能會(huì)發(fā)生各種錯(cuò)誤,如無法找到匹配的通道、消息處理異常等。通過配置錯(cuò)誤通道和錯(cuò)誤處理器,可以在發(fā)生錯(cuò)誤時(shí)將消息路由到特定的錯(cuò)誤處理流程,從而實(shí)現(xiàn)錯(cuò)誤的集中處理和恢復(fù)。這種機(jī)制使得系統(tǒng)能夠更加健壯地應(yīng)對各種異常情況,提高了系統(tǒng)的可靠性和可用性。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.IntegrationComponentScan; import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.annotation.Router; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlows; import org.springframework.integration.handler.advice.ErrorMessageSendingRecoverer; import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessagingException; @Configuration @IntegrationComponentScan public class ErrorHandlingRouterConfig { // 定義錯(cuò)誤通道 @Bean public MessageChannel errorChannel() { return new DirectChannel(); } // 定義主路由器流程,包含錯(cuò)誤處理 @Bean public IntegrationFlow routerWithErrorHandling() { return IntegrationFlows .from("inputChannel") .<Message<?>, String>route( message -> { try { // 從消息中提取路由鍵 String type = (String) message.getHeaders().get("message-type"); if (type == null) { throw new IllegalArgumentException("消息類型不能為空"); } return type; } catch (Exception e) { // 將異常信息放入消息頭 throw new MessagingException(message, "路由錯(cuò)誤: " + e.getMessage(), e); } }, mapping -> mapping .subFlowMapping("ORDER", sf -> sf.channel("orderChannel")) .subFlowMapping("INVENTORY", sf -> sf.channel("inventoryChannel")) .defaultSubFlowMapping(sf -> sf.channel("unknownTypeChannel")) ) // 配置錯(cuò)誤通道 .errorChannel("errorChannel") .get(); } // 錯(cuò)誤處理服務(wù) @Bean @ServiceActivator(inputChannel = "errorChannel") public void handleError(Message<MessagingException> errorMessage) { MessagingException exception = errorMessage.getPayload(); Message<?> failedMessage = exception.getFailedMessage(); System.err.println("處理消息時(shí)發(fā)生錯(cuò)誤: " + exception.getMessage()); System.err.println("失敗的消息: " + failedMessage); // 根據(jù)異常類型執(zhí)行不同的錯(cuò)誤處理邏輯 if (exception.getCause() instanceof IllegalArgumentException) { // 發(fā)送到無效消息通道 invalidMessageChannel().send(MessageBuilder .withPayload(failedMessage.getPayload()) .copyHeaders(failedMessage.getHeaders()) .setHeader("error-message", exception.getMessage()) .build()); } else { // 發(fā)送到重試通道,嘗試重新處理 retryChannel().send(failedMessage); } } // 包含重試邏輯的路由器 @Bean public IntegrationFlow retryableRouterFlow() { return IntegrationFlows .from("retryChannel") .<Object, String>route( payload -> { if (payload instanceof Order) { return "orderChannel"; } else if (payload instanceof InventoryItem) { return "inventoryChannel"; } else { return "unknownTypeChannel"; } }, // 應(yīng)用重試通知 spec -> spec.advice(retryAdvice()) ) .get(); } // 重試通知配置 @Bean public RequestHandlerRetryAdvice retryAdvice() { RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); // 配置重試策略 org.springframework.retry.support.RetryTemplate retryTemplate = new org.springframework.retry.support.RetryTemplate(); // 設(shè)置重試策略:最多重試3次 retryTemplate.setRetryPolicy(new org.springframework.retry.policy.SimpleRetryPolicy(3)); // 設(shè)置退避策略:指數(shù)退避,初始1秒,最大30秒 org.springframework.retry.backoff.ExponentialBackOffPolicy backOffPolicy = new org.springframework.retry.backoff.ExponentialBackOffPolicy(); backOffPolicy.setInitialInterval(1000); backOffPolicy.setMaxInterval(30000); backOffPolicy.setMultiplier(2.0); retryTemplate.setBackOffPolicy(backOffPolicy); advice.setRetryTemplate(retryTemplate); // 設(shè)置恢復(fù)策略:發(fā)送到死信通道 ErrorMessageSendingRecoverer recoverer = new ErrorMessageSendingRecoverer(deadLetterChannel()); advice.setRecoveryCallback(recoverer); return advice; } // 定義死信通道 @Bean public MessageChannel deadLetterChannel() { return new DirectChannel(); } // 定義無效消息通道 @Bean public MessageChannel invalidMessageChannel() { return new DirectChannel(); } // 定義重試通道 @Bean public MessageChannel retryChannel() { return new DirectChannel(); } // 示例消息網(wǎng)關(guān) @MessagingGateway(defaultRequestChannel = "inputChannel") public interface MessageRoutingGateway { void send(Message<?> message); } }
總結(jié)
Spring Integration的Router組件為企業(yè)應(yīng)用集成提供了強(qiáng)大的消息路由能力,使得系統(tǒng)能夠根據(jù)不同的條件靈活地處理消息流。本文詳細(xì)介紹了Router的基礎(chǔ)概念、條件路由實(shí)現(xiàn)、基于消息頭的路由、動(dòng)態(tài)路由與路由表、消息過濾與選擇性路由以及錯(cuò)誤處理與路由等方面的內(nèi)容。這些技術(shù)為構(gòu)建復(fù)雜的企業(yè)集成解決方案提供了有力的支持,使得系統(tǒng)的各個(gè)組件能夠以松耦合的方式進(jìn)行協(xié)作,提高了系統(tǒng)的可維護(hù)性和可擴(kuò)展性。在實(shí)際應(yīng)用中,開發(fā)人員可以根據(jù)具體需求選擇合適的路由策略,通過組合使用多種路由機(jī)制,構(gòu)建靈活、健壯的消息處理流程。
到此這篇關(guān)于SpringIntegration消息路由之Router的條件路由與過濾的文章就介紹到這了,更多相關(guān)SpringIntegration消息路由內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于spring事務(wù)傳播行為非事務(wù)方式的理解
這篇文章主要介紹了對spring事務(wù)傳播行為非事務(wù)方式的全面理解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11快速解決springboot在yml配置了啟動(dòng)端口但啟動(dòng)還是8080問題
這篇文章主要介紹了快速解決springboot在yml配置了啟動(dòng)端口但啟動(dòng)還是8080問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03關(guān)于JSqlparser使用攻略(高效的SQL解析工具)
這篇文章主要介紹了關(guān)于JSqlparser使用攻略(高效的SQL解析工具),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11使用迭代器模式來進(jìn)行Java的設(shè)計(jì)模式編程
這篇文章主要介紹了使用迭代器模式來進(jìn)行Java的設(shè)計(jì)模式編程,文中對迭代器模式中的容器封裝方面的知識(shí)進(jìn)行了講解,需要的朋友可以參考下2016-02-02Project?Reactor源碼解析publishOn使用示例
這篇文章主要為大家介紹了Project?Reactor源碼解析publishOn使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08spring+maven實(shí)現(xiàn)發(fā)送郵件功能
這篇文章主要為大家詳細(xì)介紹了spring+maven實(shí)現(xiàn)發(fā)送郵件功能,利用spring提供的郵件工具來發(fā)送郵件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07