Repository: alibaba/spring-cloud-alibaba Branch: 2025.1.x Commit: b85e7efead6b Files: 748 Total size: 2.1 MB Directory structure: gitextract_av4r5gmy/ ├── .circleci/ │ └── config.yml ├── .codecov.yml ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ ├── feature_request.md │ │ └── question.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependbot.yml │ ├── labels.yml │ └── workflows/ │ ├── github-packages-release.yml │ ├── integration-test.yml │ ├── issue-command.yml │ ├── md-link-check.yml │ └── stale.yml ├── .gitignore ├── .licenscheckconfig.yaml ├── .mvn/ │ ├── jvm.config │ └── wrapper/ │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README-zh.md ├── README.md ├── Roadmap-zh.md ├── Roadmap.md ├── eclipse/ │ ├── checkstyle-suppressions.xml │ ├── eclipse-code-formatter.xml │ ├── org.eclipse.jdt.core.prefs │ └── org.eclipse.jdt.ui.prefs ├── mvnw ├── mvnw.cmd ├── pom.xml ├── spring-cloud-alibaba-coverage/ │ └── pom.xml ├── spring-cloud-alibaba-dependencies/ │ └── pom.xml ├── spring-cloud-alibaba-examples/ │ ├── integrated-example/ │ │ ├── config-init/ │ │ │ ├── config/ │ │ │ │ ├── datasource-config.yaml │ │ │ │ ├── integrated-account.yaml │ │ │ │ ├── integrated-consumer.yaml │ │ │ │ ├── integrated-gateway.yaml │ │ │ │ ├── integrated-order.yaml │ │ │ │ ├── integrated-provider.yaml │ │ │ │ └── integrated-storage.yaml │ │ │ ├── rocketmq/ │ │ │ │ └── broker.conf │ │ │ ├── scripts/ │ │ │ │ └── nacos-config-quick.sh │ │ │ └── sql/ │ │ │ └── init.sql │ │ ├── docker-compose/ │ │ │ ├── docker-compose-env.yml │ │ │ └── docker-compose-service.yml │ │ ├── docs/ │ │ │ ├── en/ │ │ │ │ ├── docker-compose-deployment.md │ │ │ │ ├── kubernetes-deployment.md │ │ │ │ ├── local-deployment.md │ │ │ │ └── readme.md │ │ │ └── zh/ │ │ │ ├── docker-compose-deploy-zh.md │ │ │ ├── kubernetes-deployment-zh.md │ │ │ ├── local-deployment-zh.md │ │ │ └── readme-zh.md │ │ ├── helm-chart/ │ │ │ ├── Chart.yaml │ │ │ ├── templates/ │ │ │ │ ├── integrated-account.yaml │ │ │ │ ├── integrated-frontend.yaml │ │ │ │ ├── integrated-gateway.yaml │ │ │ │ ├── integrated-mysql.yaml │ │ │ │ ├── integrated-nacos-mysql.yaml │ │ │ │ ├── integrated-nacos-stand.yaml │ │ │ │ ├── integrated-order.yaml │ │ │ │ ├── integrated-praise-consumer.yaml │ │ │ │ ├── integrated-praise-provider.yaml │ │ │ │ ├── integrated-rocketmq.yaml │ │ │ │ ├── integrated-seata.yaml │ │ │ │ └── integrated-storage.yaml │ │ │ └── values.yaml │ │ ├── integrated-account/ │ │ │ ├── Dockerfile │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── integration/ │ │ │ │ └── account/ │ │ │ │ ├── AccountServiceApplication.java │ │ │ │ ├── controller/ │ │ │ │ │ └── AccountController.java │ │ │ │ ├── dto/ │ │ │ │ │ └── AccountDTO.java │ │ │ │ ├── mapper/ │ │ │ │ │ └── AccountMapper.java │ │ │ │ └── service/ │ │ │ │ ├── AccountService.java │ │ │ │ └── impl/ │ │ │ │ └── AccountServiceImpl.java │ │ │ └── resources/ │ │ │ └── application.yaml │ │ ├── integrated-common/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── integration/ │ │ │ └── common/ │ │ │ ├── BusinessException.java │ │ │ ├── IResult.java │ │ │ ├── Result.java │ │ │ └── ResultEnum.java │ │ ├── integrated-frontend/ │ │ │ ├── Dockerfile │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── integration/ │ │ │ │ └── frontend/ │ │ │ │ ├── FrontendApplication.java │ │ │ │ └── controller/ │ │ │ │ └── IntegrationController.java │ │ │ └── resources/ │ │ │ └── templates/ │ │ │ ├── order.html │ │ │ ├── rocketmq.html │ │ │ └── sentinel.html │ │ ├── integrated-gateway/ │ │ │ ├── Dockerfile │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── integration/ │ │ │ │ └── gateway/ │ │ │ │ ├── GatewayApplication.java │ │ │ │ └── config/ │ │ │ │ └── GatewayConfig.java │ │ │ └── resources/ │ │ │ └── application.yaml │ │ ├── integrated-order/ │ │ │ ├── Dockerfile │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── integration/ │ │ │ │ └── order/ │ │ │ │ ├── OrderServiceApplication.java │ │ │ │ ├── controller/ │ │ │ │ │ └── OrderController.java │ │ │ │ ├── entity/ │ │ │ │ │ └── Order.java │ │ │ │ ├── feign/ │ │ │ │ │ ├── AccountServiceFeignClient.java │ │ │ │ │ ├── StorageServiceFeignClient.java │ │ │ │ │ └── dto/ │ │ │ │ │ ├── AccountDTO.java │ │ │ │ │ └── StorageDTO.java │ │ │ │ ├── mapper/ │ │ │ │ │ └── OrderMapper.java │ │ │ │ └── service/ │ │ │ │ ├── OrderService.java │ │ │ │ └── impl/ │ │ │ │ └── OrderServiceImpl.java │ │ │ └── resources/ │ │ │ └── application.yaml │ │ ├── integrated-praise-consumer/ │ │ │ ├── Dockerfile │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── integration/ │ │ │ │ └── consumer/ │ │ │ │ ├── PraiseConsumerApplication.java │ │ │ │ ├── controller/ │ │ │ │ │ └── PraiseController.java │ │ │ │ ├── listener/ │ │ │ │ │ └── ListenerAutoConfiguration.java │ │ │ │ ├── mapper/ │ │ │ │ │ └── PraiseMapper.java │ │ │ │ ├── message/ │ │ │ │ │ └── PraiseMessage.java │ │ │ │ └── service/ │ │ │ │ ├── PraiseService.java │ │ │ │ └── impl/ │ │ │ │ └── PraiseServiceImpl.java │ │ │ └── resources/ │ │ │ └── application.yaml │ │ ├── integrated-praise-provider/ │ │ │ ├── Dockerfile │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── integration/ │ │ │ │ └── provider/ │ │ │ │ ├── PraiseProviderApplication.java │ │ │ │ ├── controller/ │ │ │ │ │ └── PraiseController.java │ │ │ │ └── message/ │ │ │ │ └── PraiseMessage.java │ │ │ └── resources/ │ │ │ └── application.yaml │ │ └── integrated-storage/ │ │ ├── Dockerfile │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── integration/ │ │ │ └── storage/ │ │ │ ├── StorageServiceApplication.java │ │ │ ├── controller/ │ │ │ │ └── StorageController.java │ │ │ ├── dto/ │ │ │ │ └── StorageDTO.java │ │ │ ├── mapper/ │ │ │ │ └── StorageMapper.java │ │ │ └── service/ │ │ │ ├── StorageService.java │ │ │ └── impl/ │ │ │ └── StorageServiceImpl.java │ │ └── resources/ │ │ └── application.yaml │ ├── nacos-example/ │ │ ├── nacos-config-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ ├── NacosConfigApplication.java │ │ │ │ ├── example/ │ │ │ │ │ ├── BeanAutoRefreshConfigExample.java │ │ │ │ │ ├── ConfigListenerExample.java │ │ │ │ │ ├── DockingInterfaceExample.java │ │ │ │ │ └── ValueAnnotationExample.java │ │ │ │ └── model/ │ │ │ │ └── NacosConfigInfo.java │ │ │ └── resources/ │ │ │ └── application.yaml │ │ ├── nacos-discovery-example/ │ │ │ ├── nacos-discovery-consumer-example/ │ │ │ │ ├── pom.xml │ │ │ │ ├── scripts/ │ │ │ │ │ ├── error.sh │ │ │ │ │ ├── feign-defaultmethod-error.sh │ │ │ │ │ ├── feign-error.sh │ │ │ │ │ ├── index.sh │ │ │ │ │ └── sleep.sh │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ ├── ConsumerApplication.java │ │ │ │ │ ├── TestController.java │ │ │ │ │ ├── configuration/ │ │ │ │ │ │ ├── FeignConfiguration.java │ │ │ │ │ │ ├── RestTemplateConfiguration.java │ │ │ │ │ │ └── UrlCleaner.java │ │ │ │ │ └── feign/ │ │ │ │ │ ├── EchoClient.java │ │ │ │ │ └── EchoClientFallback.java │ │ │ │ └── resources/ │ │ │ │ ├── application.properties │ │ │ │ ├── degraderule.json │ │ │ │ └── flowrule.json │ │ │ ├── nacos-discovery-consumer-sclb-example/ │ │ │ │ ├── pom.xml │ │ │ │ ├── scripts/ │ │ │ │ │ ├── error.sh │ │ │ │ │ ├── feign-defaultmethod-error.sh │ │ │ │ │ ├── feign-error.sh │ │ │ │ │ ├── index.sh │ │ │ │ │ ├── resttemplate.sh │ │ │ │ │ └── sleep.sh │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ ├── ConsumerSCLBApplication.java │ │ │ │ │ ├── RandomLoadBalancer.java │ │ │ │ │ ├── TestController.java │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── FeignConfiguration.java │ │ │ │ │ │ ├── MyLoadBalancerConfiguration.java │ │ │ │ │ │ ├── MySCLBConfiguration.java │ │ │ │ │ │ ├── RestTemplateConfiguration.java │ │ │ │ │ │ └── UrlCleaner.java │ │ │ │ │ └── feign/ │ │ │ │ │ ├── EchoClient.java │ │ │ │ │ └── EchoClientFallback.java │ │ │ │ └── resources/ │ │ │ │ ├── application.properties │ │ │ │ ├── degraderule.json │ │ │ │ └── flowrule.json │ │ │ ├── nacos-discovery-provider-example/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ ├── EchoController.java │ │ │ │ │ └── ProviderApplication.java │ │ │ │ └── resources/ │ │ │ │ └── application.properties │ │ │ ├── nacos-discovery-spring-cloud-config-client-example/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ ├── GetConfigController.java │ │ │ │ │ └── SpringCloudConfigClientApplication.java │ │ │ │ └── resources/ │ │ │ │ └── application.yml │ │ │ ├── nacos-discovery-spring-cloud-config-server-example/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ └── SpringCloudConfigServerApplication.java │ │ │ │ └── resources/ │ │ │ │ └── application.yml │ │ │ ├── nacos-reactivediscovery-consumer-example/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ ├── ConsumerReactiveApplication.java │ │ │ │ │ ├── MyController.java │ │ │ │ │ └── WebClientConfiguration.java │ │ │ │ └── resources/ │ │ │ │ └── application.properties │ │ │ └── pom.xml │ │ ├── nacos-gateway-example/ │ │ │ ├── nacos-gateway-discovery-example/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ └── GatewayApplication.java │ │ │ │ └── resources/ │ │ │ │ └── application.properties │ │ │ ├── nacos-gateway-provider-example/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ ├── EchoController.java │ │ │ │ │ └── ProviderApplication.java │ │ │ │ └── resources/ │ │ │ │ └── application.properties │ │ │ └── pom.xml │ │ ├── readme-zh.md │ │ └── readme.md │ ├── pom.xml │ ├── rocketmq-example/ │ │ ├── readme-zh.md │ │ ├── readme.md │ │ ├── rocketmq-broadcast-example/ │ │ │ ├── rocketmq-broadcast-consumer1-example/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ └── broadcast/ │ │ │ │ │ └── RocketMQBroadcastConsumer1Application.java │ │ │ │ └── resources/ │ │ │ │ └── application.yml │ │ │ ├── rocketmq-broadcast-consumer2-example/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ └── broadcast/ │ │ │ │ │ └── RocketMQBroadcastConsumer2Application.java │ │ │ │ └── resources/ │ │ │ │ └── application.yml │ │ │ └── rocketmq-broadcast-producer-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ └── broadcast/ │ │ │ │ └── RocketMQBroadcastProducerApplication.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── rocketmq-comprehensive-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ ├── RocketMQComprehensiveApplication.java │ │ │ │ └── User.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── rocketmq-delay-consume-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ └── delay/ │ │ │ │ └── RocketMQDelayConsumeApplication.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── rocketmq-example-common/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ └── common/ │ │ │ │ └── SimpleMsg.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── rocketmq-orderly-consume-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ └── orderly/ │ │ │ │ ├── OrderlyMessageQueueSelector.java │ │ │ │ └── RocketMQOrderlyConsumeApplication.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── rocketmq-pollable-consume-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ └── pollable/ │ │ │ │ └── RocketMQPollableConsumeApplication.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── rocketmq-sql-consume-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ └── sql/ │ │ │ │ └── RocketMQSqlConsumeApplication.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── rocketmq-tx-example/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── examples/ │ │ │ └── tx/ │ │ │ ├── RocketMQTxApplication.java │ │ │ └── TransactionListenerImpl.java │ │ └── resources/ │ │ └── application.yml │ ├── seata-example/ │ │ ├── account-service/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ ├── AccountApplication.java │ │ │ │ ├── AccountController.java │ │ │ │ └── DatabaseConfiguration.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── all.sql │ │ ├── business-service/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ ├── BusinessApplication.java │ │ │ │ ├── HomeController.java │ │ │ │ └── Order.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── order-service/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ ├── DatabaseConfiguration.java │ │ │ │ ├── Order.java │ │ │ │ ├── OrderApplication.java │ │ │ │ └── OrderController.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── readme-zh.md │ │ ├── readme.md │ │ └── storage-service/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── examples/ │ │ │ ├── StorageApplication.java │ │ │ ├── config/ │ │ │ │ └── DatabaseConfiguration.java │ │ │ └── controller/ │ │ │ └── StorageController.java │ │ └── resources/ │ │ └── application.yml │ ├── sentinel-example/ │ │ ├── README-zh.md │ │ ├── README.md │ │ ├── sentinel-circuitbreaker-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ ├── FeignCircuitBreakerApplication.java │ │ │ │ ├── controller/ │ │ │ │ │ ├── ApiController.java │ │ │ │ │ └── TestController.java │ │ │ │ └── feign/ │ │ │ │ ├── OrderClient.java │ │ │ │ ├── OrderClientFallBack.java │ │ │ │ ├── UserClient.java │ │ │ │ └── UserClientFallBack.java │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ └── sentinel-circuitbreaker-rules.yml │ │ ├── sentinel-core-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ ├── ExceptionUtil.java │ │ │ │ ├── JsonFlowRuleListConverter.java │ │ │ │ ├── SentinelCoreApplication.java │ │ │ │ ├── TestController.java │ │ │ │ └── WebMvcConfiguration.java │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ ├── authority.json │ │ │ ├── degraderule.json │ │ │ ├── flowrule.json │ │ │ ├── flowrule.xml │ │ │ ├── param-flow.json │ │ │ ├── system.json │ │ │ └── templates/ │ │ │ └── errorPage.html │ │ ├── sentinel-openfeign-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ ├── OpenFeignApplication.java │ │ │ │ ├── configuration/ │ │ │ │ │ ├── EchoServiceFallbackFactory.java │ │ │ │ │ ├── HttpbinClient.java │ │ │ │ │ ├── HttpbinClientFallback.java │ │ │ │ │ └── SentinelRulesConfiguration.java │ │ │ │ └── controller/ │ │ │ │ └── TestController.java │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ ├── degraderule.json │ │ │ └── flowrule.json │ │ ├── sentinel-resttemplate-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── com/ │ │ │ │ │ └── alibaba/ │ │ │ │ │ └── cloud/ │ │ │ │ │ └── examples/ │ │ │ │ │ ├── RestTemplateApplication.java │ │ │ │ │ ├── configuration/ │ │ │ │ │ │ ├── RestTemplateConfiguration.java │ │ │ │ │ │ └── SentinelRulesConfiguration.java │ │ │ │ │ └── controller/ │ │ │ │ │ └── TestController.java │ │ │ │ └── resources/ │ │ │ │ ├── application.yml │ │ │ │ ├── degraderule.json │ │ │ │ └── flowrule.json │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── examples/ │ │ │ └── RestTemplateApplicationTest.java │ │ ├── sentinel-spring-cloud-gateway-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── examples/ │ │ │ │ ├── MySCGConfiguration.java │ │ │ │ ├── RulesWebFluxController.java │ │ │ │ └── SentinelSpringCloudGatewayApplication.java │ │ │ └── resources/ │ │ │ ├── api.json │ │ │ ├── application.yaml │ │ │ └── gateway.json │ │ └── sentinel-webflux-example/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── examples/ │ │ │ ├── MyConfiguration.java │ │ │ ├── SentinelWebFluxApplication.java │ │ │ └── SentinelWebFluxController.java │ │ └── resources/ │ │ ├── application.yml │ │ └── flowrule.json │ ├── spring-cloud-alibaba-sidecar-examples/ │ │ ├── node-service.js │ │ ├── readme-zh.md │ │ ├── readme.md │ │ ├── spring-cloud-alibaba-sidecar-consul-example/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── sidecar/ │ │ │ │ └── DemoApplication.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── spring-cloud-alibaba-sidecar-nacos-example/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── sidecar/ │ │ │ └── DemoApplication.java │ │ └── resources/ │ │ └── application.yml │ ├── spring-cloud-bus-rocketmq-example/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── examples/ │ │ │ └── rocketmq/ │ │ │ ├── RocketMQBusApplication.java │ │ │ ├── User.java │ │ │ └── UserRemoteApplicationEvent.java │ │ └── resources/ │ │ └── application.properties │ └── spring-cloud-scheduling-example/ │ ├── README-en.md │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cloud/ │ │ └── examples/ │ │ └── schedule/ │ │ ├── ScheduleApplication.java │ │ └── job/ │ │ └── SimpleJob.java │ └── resources/ │ ├── application-schedulerx.yaml │ ├── application-shedlock.yaml │ └── application.yaml ├── spring-cloud-alibaba-starters/ │ ├── pom.xml │ ├── spring-alibaba-nacos-config/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── nacos/ │ │ │ │ ├── NacosConfigAutoConfiguration.java │ │ │ │ ├── NacosConfigEnabledCondition.java │ │ │ │ ├── NacosConfigManager.java │ │ │ │ ├── NacosConfigProperties.java │ │ │ │ ├── NacosPropertiesPrefixProvider.java │ │ │ │ ├── NacosPropertiesPrefixer.java │ │ │ │ ├── NacosPropertySourceRepository.java │ │ │ │ ├── annotation/ │ │ │ │ │ ├── AbstractConfigChangeListener.java │ │ │ │ │ ├── CustomDateDeserializer.java │ │ │ │ │ ├── JsonUtils.java │ │ │ │ │ ├── NacosAnnotationProcessor.java │ │ │ │ │ ├── NacosConfig.java │ │ │ │ │ ├── NacosConfigKeysListener.java │ │ │ │ │ ├── NacosConfigListener.java │ │ │ │ │ ├── NacosConfigRefreshableListener.java │ │ │ │ │ ├── NacosPropertiesKeyListener.java │ │ │ │ │ ├── ObjectUtils.java │ │ │ │ │ ├── PropertiesUtils.java │ │ │ │ │ ├── ScaYamlConfigChangeParser.java │ │ │ │ │ └── TargetRefreshable.java │ │ │ │ ├── client/ │ │ │ │ │ ├── NacosPropertySource.java │ │ │ │ │ └── NacosPropertySourceBuilder.java │ │ │ │ ├── configdata/ │ │ │ │ │ ├── ConfigPreference.java │ │ │ │ │ ├── NacosConfigDataLoadProperties.java │ │ │ │ │ ├── NacosConfigDataLoader.java │ │ │ │ │ ├── NacosConfigDataLocationResolver.java │ │ │ │ │ └── NacosConfigDataResource.java │ │ │ │ ├── constants/ │ │ │ │ │ └── Constants.java │ │ │ │ ├── diagnostics/ │ │ │ │ │ └── analyzer/ │ │ │ │ │ ├── NacosConnectionFailureAnalyzer.java │ │ │ │ │ └── NacosConnectionFailureException.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── NacosConfigEndpoint.java │ │ │ │ │ ├── NacosConfigEndpointAutoConfiguration.java │ │ │ │ │ └── NacosConfigHealthIndicator.java │ │ │ │ ├── parser/ │ │ │ │ │ ├── AbstractPropertySourceLoader.java │ │ │ │ │ ├── NacosByteArrayResource.java │ │ │ │ │ ├── NacosDataParserHandler.java │ │ │ │ │ ├── NacosJsonPropertySourceLoader.java │ │ │ │ │ └── NacosXmlPropertySourceLoader.java │ │ │ │ ├── proxy/ │ │ │ │ │ └── druid/ │ │ │ │ │ ├── NacosDruidConfigFilter.java │ │ │ │ │ └── NacosDruidFilterConfiguration.java │ │ │ │ ├── refresh/ │ │ │ │ │ ├── NacosConfigRefreshEvent.java │ │ │ │ │ ├── NacosContextRefresher.java │ │ │ │ │ ├── NacosPropertySourceRefreshListener.java │ │ │ │ │ ├── NacosRefreshHistory.java │ │ │ │ │ └── NacosSnapshotConfigManager.java │ │ │ │ └── utils/ │ │ │ │ ├── NacosConfigUtils.java │ │ │ │ ├── PropertySourcesUtils.java │ │ │ │ └── StringUtils.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── native-image/ │ │ │ │ ├── reflect-config.json │ │ │ │ └── resource-config.json │ │ │ ├── services/ │ │ │ │ └── com.alibaba.nacos.api.config.listener.ConfigChangeParser │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ ├── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── nacos/ │ │ │ └── annotation/ │ │ │ └── NacosPropertiesKeyListenerTest.java │ │ └── com.alibaba.cloud.nacos/ │ │ ├── NacosConfigPropertiesTest.java │ │ ├── configdata/ │ │ │ └── NacosConfigDataLocationResolverTest.java │ │ └── endpoint/ │ │ └── NacosConfigEndpointTests.java │ ├── spring-cloud-alibaba-commons/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cloud/ │ │ └── commons/ │ │ ├── context/ │ │ │ └── support/ │ │ │ └── PropertySourcesUtils.java │ │ ├── io/ │ │ │ ├── Charsets.java │ │ │ ├── FileUtils.java │ │ │ ├── IOUtils.java │ │ │ └── StringBuilderWriter.java │ │ └── lang/ │ │ └── StringUtils.java │ ├── spring-cloud-alibaba-sentinel-datasource/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── sentinel/ │ │ │ │ └── datasource/ │ │ │ │ ├── RuleType.java │ │ │ │ ├── config/ │ │ │ │ │ ├── AbstractDataSourceProperties.java │ │ │ │ │ ├── ApolloDataSourceProperties.java │ │ │ │ │ ├── ConsulDataSourceProperties.java │ │ │ │ │ ├── DataSourcePropertiesConfiguration.java │ │ │ │ │ ├── FileDataSourceProperties.java │ │ │ │ │ ├── NacosDataSourceProperties.java │ │ │ │ │ ├── RedisDataSourceProperties.java │ │ │ │ │ └── ZookeeperDataSourceProperties.java │ │ │ │ ├── converter/ │ │ │ │ │ ├── JsonConverter.java │ │ │ │ │ ├── SentinelConverter.java │ │ │ │ │ └── XmlConverter.java │ │ │ │ └── factorybean/ │ │ │ │ ├── ApolloDataSourceFactoryBean.java │ │ │ │ ├── ConsulDataSourceFactoryBean.java │ │ │ │ ├── FileRefreshableDataSourceFactoryBean.java │ │ │ │ ├── NacosDataSourceFactoryBean.java │ │ │ │ ├── RedisDataSourceFactoryBean.java │ │ │ │ └── ZookeeperDataSourceFactoryBean.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── sentinel-datasource.properties │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── sentinel/ │ │ │ └── datasource/ │ │ │ ├── ApolloDataSourceFactoryBeanTests.java │ │ │ ├── DataSourcePropertiesConfigurationTests.java │ │ │ ├── DataSourcePropertiesTests.java │ │ │ ├── FileRefreshableDataSourceFactoryBeanTests.java │ │ │ ├── NacosDataSourceFactoryBeanTests.java │ │ │ ├── NacosDataSourcePropertiesTests.java │ │ │ ├── RuleTypeTests.java │ │ │ ├── SentinelConverterTests.java │ │ │ └── ZookeeperDataSourceFactoryBeanTests.java │ │ └── resources/ │ │ ├── flowrule-errorcontent.json │ │ ├── flowrule-errorformat.json │ │ ├── flowrule.json │ │ └── flowrule.xml │ ├── spring-cloud-alibaba-sentinel-gateway/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── sentinel/ │ │ │ │ └── gateway/ │ │ │ │ ├── ConfigConstants.java │ │ │ │ ├── FallbackProperties.java │ │ │ │ ├── GatewayEnvironmentPostProcessor.java │ │ │ │ ├── SentinelGatewayAutoConfiguration.java │ │ │ │ └── scg/ │ │ │ │ ├── SentinelGatewayProperties.java │ │ │ │ └── SentinelSCGAutoConfiguration.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── sentinel/ │ │ │ └── gateway/ │ │ │ ├── ConfigConstantsTest.java │ │ │ ├── FallbackPropertiesTest.java │ │ │ ├── GatewayEnvironmentPostProcessorTest.java │ │ │ ├── SentinelGatewayAutoConfigurationTest.java │ │ │ └── scg/ │ │ │ ├── SentinelGatewayPropertiesTest.java │ │ │ └── SentinelSCGAutoConfigurationTest.java │ │ └── resources/ │ │ ├── apidefinition.json │ │ ├── apidefinition.xml │ │ ├── gatewayflowrule.json │ │ └── gatewayflowrule.xml │ ├── spring-cloud-circuitbreaker-sentinel/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── circuitbreaker/ │ │ │ │ └── sentinel/ │ │ │ │ ├── ReactiveSentinelCircuitBreaker.java │ │ │ │ ├── ReactiveSentinelCircuitBreakerAutoConfiguration.java │ │ │ │ ├── ReactiveSentinelCircuitBreakerFactory.java │ │ │ │ ├── SentinelCircuitBreaker.java │ │ │ │ ├── SentinelCircuitBreakerAutoConfiguration.java │ │ │ │ ├── SentinelCircuitBreakerFactory.java │ │ │ │ ├── SentinelConfigBuilder.java │ │ │ │ └── feign/ │ │ │ │ ├── CircuitBreakerRuleChangeListener.java │ │ │ │ ├── FeignClientCircuitNameResolver.java │ │ │ │ ├── SentinelFeignClientAutoConfiguration.java │ │ │ │ └── SentinelFeignClientProperties.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cloud/ │ │ └── circuitbreaker/ │ │ └── sentinel/ │ │ ├── ReactiveSentinelCircuitBreakerIntegrationTest.java │ │ ├── ReactiveSentinelCircuitBreakerTest.java │ │ ├── SentinelCircuitBreakerIntegrationTest.java │ │ ├── SentinelCircuitBreakerTest.java │ │ └── feign/ │ │ └── FeignClientCircuitBreakerRuleIntegrationTest.java │ ├── spring-cloud-starter-alibaba-nacos-config/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── nacos/ │ │ │ │ ├── NacosConfigSpringCloudAutoConfiguration.java │ │ │ │ ├── SpringCloudNacosPropertiesPrefixProvider.java │ │ │ │ ├── client/ │ │ │ │ │ └── NacosPropertySourceLocator.java │ │ │ │ ├── configdata/ │ │ │ │ │ ├── NacosConfigDataMissingEnvironmentPostProcessor.java │ │ │ │ │ └── NacosConfigRefreshEventListener.java │ │ │ │ └── refresh/ │ │ │ │ ├── RefreshBehavior.java │ │ │ │ ├── SmartConfigurationPropertiesRebinder.java │ │ │ │ └── condition/ │ │ │ │ ├── ConditionalOnNonDefaultBehavior.java │ │ │ │ └── NonDefaultBehaviorCondition.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── services/ │ │ │ │ └── com.alibaba.cloud.nacos.NacosPropertiesPrefixProvider │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cloud/ │ │ └── nacos/ │ │ ├── SmartConfigurationPropertiesRebinderIntegrationTest.java │ │ └── configdata/ │ │ └── NacosConfigDataMissingEnvironmentPostProcessorTest.java │ ├── spring-cloud-starter-alibaba-nacos-discovery/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── nacos/ │ │ │ │ ├── ConditionalOnNacosDiscoveryEnabled.java │ │ │ │ ├── NacosDiscoveryProperties.java │ │ │ │ ├── NacosServiceAutoConfiguration.java │ │ │ │ ├── NacosServiceInstance.java │ │ │ │ ├── NacosServiceManager.java │ │ │ │ ├── balancer/ │ │ │ │ │ └── NacosBalancer.java │ │ │ │ ├── discovery/ │ │ │ │ │ ├── NacosDiscoveryAutoConfiguration.java │ │ │ │ │ ├── NacosDiscoveryClient.java │ │ │ │ │ ├── NacosDiscoveryClientConfiguration.java │ │ │ │ │ ├── NacosDiscoveryHeartBeatConfiguration.java │ │ │ │ │ ├── NacosDiscoveryHeartBeatPublisher.java │ │ │ │ │ ├── NacosServiceDiscovery.java │ │ │ │ │ ├── NacosWatch.java │ │ │ │ │ ├── ServiceCache.java │ │ │ │ │ ├── actuate/ │ │ │ │ │ │ └── health/ │ │ │ │ │ │ └── NacosDiscoveryHealthIndicator.java │ │ │ │ │ ├── configclient/ │ │ │ │ │ │ ├── NacosConfigServerAutoConfiguration.java │ │ │ │ │ │ └── NacosDiscoveryClientConfigServiceBootstrapConfiguration.java │ │ │ │ │ └── reactive/ │ │ │ │ │ ├── NacosReactiveDiscoveryClient.java │ │ │ │ │ └── NacosReactiveDiscoveryClientConfiguration.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── NacosDiscoveryEndpoint.java │ │ │ │ │ └── NacosDiscoveryEndpointAutoConfiguration.java │ │ │ │ ├── event/ │ │ │ │ │ └── NacosDiscoveryInfoChangedEvent.java │ │ │ │ ├── loadbalancer/ │ │ │ │ │ ├── ConditionalOnLoadBalancerNacos.java │ │ │ │ │ ├── DefaultLoadBalancerAlgorithm.java │ │ │ │ │ ├── LoadBalancerAlgorithm.java │ │ │ │ │ ├── LoadBalancerNacosAutoConfiguration.java │ │ │ │ │ ├── NacosLoadBalancer.java │ │ │ │ │ ├── NacosLoadBalancerClientConfiguration.java │ │ │ │ │ └── ServiceInstanceFilter.java │ │ │ │ ├── registry/ │ │ │ │ │ ├── NacosAutoServiceRegistration.java │ │ │ │ │ ├── NacosGracefulShutdownDelegate.java │ │ │ │ │ ├── NacosRegistration.java │ │ │ │ │ ├── NacosRegistrationCustomizer.java │ │ │ │ │ ├── NacosServiceRegistry.java │ │ │ │ │ └── NacosServiceRegistryAutoConfiguration.java │ │ │ │ └── util/ │ │ │ │ ├── InetIPv6Utils.java │ │ │ │ └── UtilIPv6AutoConfiguration.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── native-image/ │ │ │ │ ├── reflect-config.json │ │ │ │ └── resource-config.json │ │ │ ├── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cloud/ │ │ └── nacos/ │ │ ├── NacosDiscoveryClientTests.java │ │ ├── NacosDiscoveryPropertiesServerAddressBothLevelTests.java │ │ ├── NacosDiscoveryPropertiesServerAddressTopLevelTests.java │ │ ├── discovery/ │ │ │ ├── NacosDiscoveryAutoConfigurationTests.java │ │ │ ├── NacosDiscoveryClientConfigurationTest.java │ │ │ ├── NacosDiscoveryHeartBeatConfigurationTest.java │ │ │ ├── NacosDiscoveryLoadBalancerConfigurationTest.java │ │ │ ├── NacosServiceDiscoveryTest.java │ │ │ └── reactive/ │ │ │ ├── NacosReactiveDiscoveryClientConfigurationTests.java │ │ │ └── NacosReactiveDiscoveryClientTests.java │ │ ├── registry/ │ │ │ ├── MockNamingService.java │ │ │ ├── NacosAutoServiceRegistrationIpNetworkInterfaceTests.java │ │ │ ├── NacosAutoServiceRegistrationIpTests.java │ │ │ ├── NacosAutoServiceRegistrationManagementPortTests.java │ │ │ ├── NacosAutoServiceRegistrationPortTests.java │ │ │ ├── NacosAutoServiceRegistrationTests.java │ │ │ ├── NacosGracefulShutdownDelegateTests.java │ │ │ └── NacosRegistrationCustomizerTest.java │ │ └── test/ │ │ ├── CommonTestConfig.java │ │ └── NacosMockTest.java │ ├── spring-cloud-starter-alibaba-schedulerx/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── scheduling/ │ │ │ │ ├── SchedulingConstants.java │ │ │ │ ├── schedulerx/ │ │ │ │ │ ├── JobProperty.java │ │ │ │ │ ├── SchedulerxAutoConfigure.java │ │ │ │ │ ├── SchedulerxConfigurations.java │ │ │ │ │ ├── SchedulerxProperties.java │ │ │ │ │ ├── constants/ │ │ │ │ │ │ └── SchedulerxConstants.java │ │ │ │ │ ├── service/ │ │ │ │ │ │ ├── JobSyncService.java │ │ │ │ │ │ └── ScheduledJobSyncConfigurer.java │ │ │ │ │ └── util/ │ │ │ │ │ └── CronExpression.java │ │ │ │ └── shedlock/ │ │ │ │ └── ShedLockAutoConfigure.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ ├── spring/ │ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── spring.factories │ │ │ └── shedlock/ │ │ │ └── schema/ │ │ │ └── schema-mysql.sql │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cloud/ │ │ └── scheduling/ │ │ └── schedulerx/ │ │ └── util/ │ │ └── CronExpressionTest.java │ ├── spring-cloud-starter-alibaba-seata/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── seata/ │ │ │ ├── feign/ │ │ │ │ ├── SeataFeignBuilderBeanPostProcessor.java │ │ │ │ ├── SeataFeignClientAutoConfiguration.java │ │ │ │ └── SeataFeignRequestInterceptor.java │ │ │ ├── rest/ │ │ │ │ ├── SeataRestTemplateAutoConfiguration.java │ │ │ │ ├── SeataRestTemplateInterceptor.java │ │ │ │ └── SeataRestTemplateInterceptorAfterPropertiesSet.java │ │ │ ├── web/ │ │ │ │ ├── SeataHandlerInterceptor.java │ │ │ │ └── SeataHandlerInterceptorConfiguration.java │ │ │ ├── webclient/ │ │ │ │ ├── SeataWebClientAutoConfiguration.java │ │ │ │ ├── SeataWebClientBuilderCustomizer.java │ │ │ │ └── SeataWebClientFilter.java │ │ │ └── webflux/ │ │ │ ├── SeataWebFilter.java │ │ │ └── SeataWebfluxAutoConfiguration.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── native-image/ │ │ │ ├── com.alibaba.cloud/ │ │ │ │ └── spring-cloud-starter-alibaba-seata/ │ │ │ │ └── native-image.properties │ │ │ ├── proxy-config.json │ │ │ ├── reflect-config.json │ │ │ └── resource-config.json │ │ └── spring/ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ ├── spring-cloud-starter-alibaba-sentinel/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── sentinel/ │ │ │ │ ├── SentinelConstants.java │ │ │ │ ├── SentinelProperties.java │ │ │ │ ├── SentinelWebAutoConfiguration.java │ │ │ │ ├── SentinelWebFluxAutoConfiguration.java │ │ │ │ ├── SentinelWebMvcConfigurer.java │ │ │ │ ├── annotation/ │ │ │ │ │ └── SentinelRestTemplate.java │ │ │ │ ├── aot/ │ │ │ │ │ └── hint/ │ │ │ │ │ └── SentinelProtectInterceptorHints.java │ │ │ │ ├── custom/ │ │ │ │ │ ├── BlockClassRegistry.java │ │ │ │ │ ├── SentinelAutoConfiguration.java │ │ │ │ │ ├── SentinelBeanPostProcessor.java │ │ │ │ │ ├── SentinelDataSourceHandler.java │ │ │ │ │ ├── SentinelProtectInterceptor.java │ │ │ │ │ └── context/ │ │ │ │ │ └── SentinelApplicationContextInitializer.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── SentinelEndpoint.java │ │ │ │ │ ├── SentinelEndpointAutoConfiguration.java │ │ │ │ │ └── SentinelHealthIndicator.java │ │ │ │ ├── feign/ │ │ │ │ │ ├── SentinelContractHolder.java │ │ │ │ │ ├── SentinelFeign.java │ │ │ │ │ ├── SentinelFeignAutoConfiguration.java │ │ │ │ │ └── SentinelInvocationHandler.java │ │ │ │ └── rest/ │ │ │ │ └── SentinelClientHttpResponse.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ ├── native-image/ │ │ │ │ ├── reflect-config.json │ │ │ │ └── resource-config.json │ │ │ ├── spring/ │ │ │ │ ├── aot.factories │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── sentinel/ │ │ │ ├── ContextIdSentinelFeignTests.java │ │ │ ├── SentinelAutoConfigurationTests.java │ │ │ ├── SentinelBeanAutowiredTests.java │ │ │ ├── SentinelDataSourceTests.java │ │ │ ├── SentinelFallbackSupportFactoryBeanTests.java │ │ │ ├── SentinelFeignLazilyTests.java │ │ │ ├── SentinelFeignTests.java │ │ │ ├── SentinelRestTemplateTests.java │ │ │ ├── TestConverter.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ └── SentinelProtectInterceptorHintsTest.java │ │ │ ├── custom/ │ │ │ │ └── SentinelDataSourceHandlerTests.java │ │ │ └── endpoint/ │ │ │ └── SentinelHealthIndicatorTests.java │ │ └── resources/ │ │ ├── authority.json │ │ ├── degraderule.json │ │ ├── flowrule.json │ │ ├── param-flow.json │ │ └── system.json │ ├── spring-cloud-starter-alibaba-sidecar/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── sidecar/ │ │ │ ├── CustomHealthCheckHandler.java │ │ │ ├── SidecarAutoConfiguration.java │ │ │ ├── SidecarDiscoveryClient.java │ │ │ ├── SidecarHealthChecker.java │ │ │ ├── SidecarHealthIndicator.java │ │ │ ├── SidecarInstanceInfo.java │ │ │ ├── SidecarProperties.java │ │ │ ├── consul/ │ │ │ │ ├── SidecarConsulAutoConfiguration.java │ │ │ │ ├── SidecarConsulAutoRegistration.java │ │ │ │ └── SidecarConsulDiscoveryClient.java │ │ │ └── nacos/ │ │ │ ├── SidecarNacosAutoConfiguration.java │ │ │ ├── SidecarNacosDiscoveryClient.java │ │ │ └── SidecarNacosDiscoveryProperties.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ ├── spring-cloud-starter-bus-rocketmq/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── bus/ │ │ │ └── rocketmq/ │ │ │ ├── autoconfigurate/ │ │ │ │ └── RocketMQBusAutoConfiguration.java │ │ │ └── env/ │ │ │ └── RocketMQBusEnvironmentPostProcessor.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── spring.factories │ └── spring-cloud-starter-stream-rocketmq/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── stream/ │ │ │ └── binder/ │ │ │ └── rocketmq/ │ │ │ ├── RocketMQMessageChannelBinder.java │ │ │ ├── actuator/ │ │ │ │ └── RocketMQBinderHealthIndicator.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ ├── RocketMQConsumerPropertiesHints.java │ │ │ │ └── RocketMQSpecificPropertiesProviderHints.java │ │ │ ├── autoconfigurate/ │ │ │ │ ├── ExtendedBindingHandlerMappingsProviderConfiguration.java │ │ │ │ └── RocketMQBinderAutoConfiguration.java │ │ │ ├── constant/ │ │ │ │ └── RocketMQConst.java │ │ │ ├── convert/ │ │ │ │ └── RocketMQMessageConverter.java │ │ │ ├── custom/ │ │ │ │ ├── RocketMQBeanContainerCache.java │ │ │ │ └── RocketMQConfigBeanPostProcessor.java │ │ │ ├── extend/ │ │ │ │ └── ErrorAcknowledgeHandler.java │ │ │ ├── integration/ │ │ │ │ ├── inbound/ │ │ │ │ │ ├── RocketMQConsumerFactory.java │ │ │ │ │ ├── RocketMQInboundChannelAdapter.java │ │ │ │ │ └── pull/ │ │ │ │ │ ├── DefaultErrorAcknowledgeHandler.java │ │ │ │ │ ├── RocketMQAckCallback.java │ │ │ │ │ └── RocketMQMessageSource.java │ │ │ │ └── outbound/ │ │ │ │ ├── RocketMQProduceFactory.java │ │ │ │ └── RocketMQProducerMessageHandler.java │ │ │ ├── metrics/ │ │ │ │ ├── Instrumentation.java │ │ │ │ └── InstrumentationManager.java │ │ │ ├── properties/ │ │ │ │ ├── RocketMQBinderConfigurationProperties.java │ │ │ │ ├── RocketMQCommonProperties.java │ │ │ │ ├── RocketMQConsumerProperties.java │ │ │ │ ├── RocketMQExtendedBindingProperties.java │ │ │ │ ├── RocketMQProducerProperties.java │ │ │ │ └── RocketMQSpecificPropertiesProvider.java │ │ │ ├── provisioning/ │ │ │ │ ├── RocketMQTopicProvisioner.java │ │ │ │ └── selector/ │ │ │ │ └── PartitionMessageQueueSelector.java │ │ │ ├── support/ │ │ │ │ ├── AbstractRocketMQHeaderMapper.java │ │ │ │ ├── JacksonRocketMQHeaderMapper.java │ │ │ │ ├── RocketMQHeaderMapper.java │ │ │ │ └── RocketMQMessageConverterSupport.java │ │ │ └── utils/ │ │ │ └── RocketMQUtils.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── native-image/ │ │ │ ├── com.alibaba.cloud/ │ │ │ │ └── spring-cloud-starter-stream-rocketmq/ │ │ │ │ └── native-image.properties │ │ │ └── reflect-config.json │ │ ├── spring/ │ │ │ ├── aot.factories │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── spring.binders │ └── test/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── cloud/ │ └── stream/ │ └── binder/ │ └── rocketmq/ │ ├── RocketMQAutoConfigurationTests.java │ ├── RocketMQMessageChannelBinderTest.java │ ├── RocketMQMessageConverterSupportTest.java │ ├── TestConsumerDestination.java │ └── aot/ │ └── hint/ │ ├── RocketMQConsumerPropertiesHintsTests.java │ └── RocketMQSpecificPropertiesProviderHintsTests.java └── spring-cloud-alibaba-tests/ ├── nacos-tests/ │ ├── nacos-config-test/ │ │ └── src/ │ │ └── test/ │ │ └── resources/ │ │ └── docker/ │ │ └── nacos-compose-test.yml │ ├── nacos-discovery-test/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── tests/ │ │ │ └── nacos/ │ │ │ └── discovery/ │ │ │ └── NacosDiscoveryTestApp.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── tests/ │ │ │ └── nacos/ │ │ │ └── discovery/ │ │ │ └── NacosDiscoveryTest.java │ │ └── resources/ │ │ ├── application-service-1.yml │ │ ├── application-service-2.yml │ │ ├── docker/ │ │ │ └── nacos-compose-test.yml │ │ └── nacos/ │ │ └── nacos-config-refresh.yml │ └── pom.xml ├── pom.xml ├── rocketmq-tests/ │ ├── pom.xml │ └── rocketmq-stream-test/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cloud/ │ │ └── stream/ │ │ └── binder/ │ │ └── rocketmq/ │ │ └── RocketmqStreamApplication.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cloud/ │ │ └── stream/ │ │ └── binder/ │ │ └── rocketmq/ │ │ ├── RocketMQAutoConfigurationTests.java │ │ ├── RocketmqProduceAndConsumerTests.java │ │ └── fixture/ │ │ └── RocketmqBinderProcessor.java │ └── resources/ │ └── docker/ │ ├── data/ │ │ └── broker/ │ │ └── conf/ │ │ └── broker.conf │ ├── data1/ │ │ └── broker/ │ │ └── conf/ │ │ └── broker.conf │ └── rocket-compose-test.yml ├── sentinel-tests/ │ ├── pom.xml │ ├── sentinel-degrade-test/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cloud/ │ │ │ │ └── tests/ │ │ │ │ └── sentinel/ │ │ │ │ └── degrade/ │ │ │ │ └── SentinelDegradeTestApp.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cloud/ │ │ └── tests/ │ │ └── sentinel/ │ │ └── degrade/ │ │ └── SentinelDegradeTestAppTest.java │ └── sentinel-flowcontrol-test/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cloud/ │ │ │ └── tests/ │ │ │ └── sentinel/ │ │ │ └── degrade/ │ │ │ ├── SentinelFlowControlTestApp.java │ │ │ └── Util.java │ │ └── resources/ │ │ └── application.yml │ └── test/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── cloud/ │ └── tests/ │ └── sentinel/ │ └── degrade/ │ └── SentinelFlowControlTestAppTest.java └── spring-cloud-alibaba-test-support/ ├── pom.xml └── src/ └── main/ ├── java/ │ └── com/ │ └── alibaba/ │ └── cloud/ │ └── testsupport/ │ ├── Constant.java │ ├── ContainerStarter.java │ ├── Func.java │ ├── HasDockerAndItEnabled.java │ ├── HasDockerAndItEnabledCondition.java │ ├── InetUtil.java │ ├── SpringCloudAlibaba.java │ ├── SpringCloudAlibabaExtension.java │ ├── TestExtend.java │ ├── TestTimeoutExtension.java │ └── Tester.java └── resources/ └── rocketmq/ └── conf/ └── broker.conf ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ version: 2 jobs: build: docker: - image: springcloud/pipeline-base user: appuser environment: _JAVA_OPTIONS: "-Xms1024m -Xmx2048m" TERM: dumb branches: ignore: - gh-pages # list of branches to ignore resource_class: medium steps: - checkout - restore_cache: key: sc-alibaba-{{ .Branch }} - run: name: "Download dependencies" command: ./mvnw -Pspring -U --fail-never dependency:go-offline || true - save_cache: key: sc-alibaba-{{ .Branch }} paths: - ~/.m2 - run: name: "Running build" command: ./mvnw clean install -U -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -Dgpg.skip - run: name: "Aggregate test results" when: always command: | mkdir -p ~/junit/ find . -type f -regex ".*/target/.*-reports/.*" -exec cp {} ~/junit/ \; bash <(curl -s https://codecov.io/bash) - store_artifacts: path: ~/junit/ destination: artifacts - store_test_results: path: ~/junit/ destination: testartifacts ================================================ FILE: .codecov.yml ================================================ coverage: status: project: off patch: off ================================================ FILE: .editorconfig ================================================ root = true [*.java] indent_style = tab indent_size = 4 continuation_indent_size = 8 [*.groovy] indent_style = tab indent_size = 4 continuation_indent_size = 8 [*.xml] indent_style = tab indent_size = 4 continuation_indent_size = 8 [*.yml] indent_style = space indent_size = 2 [*.yaml] indent_style = space indent_size = 2 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- 我们鼓励使用英文,如果不能直接使用,可以使用翻译软件,您仍旧可以保留中文原文。另外请按照如下要求提交相关信息节省社区维护同学的理解成本,否则该讨论极有可能直接被忽视或关闭。 We recommend using English. If you are non-native English speaker, you can use the translation software. We recommend using English. If you are non-native English speaker, you can use the translation software. In addition, please submit relevant information according to the following requirements to save the understanding cost of community maintenances, otherwise the discussion is very likely to be ignored or closed directly. **Which Component** eg. Nacos Discovery, Sentinel **Describe the bug** A clear and concise description of what the bug is. **Simplest demo** The URL of the simplest demo to reproduce the problem. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. e.g. MacOS 、Java8 、 Version 0.2.1.RELEASE ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- 我们鼓励使用英文,如果不能直接使用,可以使用翻译软件,您仍旧可以保留中文原文。另外请按照如下要求提交相关信息节省社区维护同学的理解成本,否则该讨论极有可能直接被忽视或关闭。 We recommend using English. If you are non-native English speaker, you can use the translation software. In addition, please submit relevant information according to the following requirements to save the understanding cost of community maintenances, otherwise the discussion is very likely to be ignored or closed directly. **Which Component** eg. Nacos Discovery, Sentinel **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/question.md ================================================ --- name: Question about: how to ask a valid question title: '' labels: '' assignees: '' --- 我们鼓励使用英文,如果不能直接使用,可以使用翻译软件,您仍旧可以保留中文原文。另外请按照如下要求提交相关信息节省社区维护同学的理解成本,否则该讨论极有可能直接被忽视或关闭。 We recommend using English. If you are non-native English speaker, you can use the translation software. We recommend using English. If you are non-native English speaker, you can use the translation software. In addition, please submit relevant information according to the following requirements to save the understanding cost of community maintenances, otherwise the discussion is very likely to be ignored or closed directly. **Which Component** eg. Nacos Discovery, Sentinel **Describe what problem you have encountered** A clear and concise description of what you want to do. **Describe what information you have read** eg. I have read the reference doc of Sentinel ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ### Describe what this PR does / why we need it ### Does this pull request fix one issue? ### Describe how you did it ### Describe how to verify it ### Special notes for reviews ================================================ FILE: .github/dependbot.yml ================================================ # `dependabot.yml` file with # maven version update. version: 2 updates: - package-ecosystem: "maven" directory: "/" open-pull-requests-limit: 20 # Ignore major version updates ignore: - dependency-name: "*" update-types: ["version-update:semver-major"] schedule: interval: "weekly" day: "monday" time: "03:00" timezone: "US/Eastern" ================================================ FILE: .github/labels.yml ================================================ area: - 'ai' - 'ci' - 'nacos' - 'sentinel' - 'rocketmq' - 'community' - 'example' - 'seata' - 'openSergo' - 'spring cloud' - 'testing' kind: - 'bug' - 'chore' - 'discussion' - 'documention' - 'question' - 'refactor' - 'invalid' - 'enhancement' ================================================ FILE: .github/workflows/github-packages-release.yml ================================================ name: GitHub Packages Release on: push: branches: - 2023.x - 2025.0.x - 2025.1.x jobs: release: name: Release runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' - name: Dependencies Cache uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: install dependencies run: mvn clean -B install -U package -pl '!spring-cloud-alibaba-coverage' -DskipTests - name: cat ~/.m2/settings.xml run: | cat ~/.m2/settings.xml - name: Deploy to GitHub Package Maven run: | VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) echo "Project version: $VERSION" if [[ "$VERSION" != *"-SNAPSHOT" ]]; then echo "Not a snapshot version. Skipping deployment." exit 0 fi mvn clean -B deploy -pl . -DskipTests cd $GITHUB_WORKSPACE/spring-cloud-alibaba-dependencies mvn clean -B deploy -DskipTests cd $GITHUB_WORKSPACE/spring-cloud-alibaba-starters mvn clean -B deploy -DskipTests env: GITHUB_TOKEN: ${{ github.token }} - name: Delete snapshots from the Maven local repository run: find ~/.m2/repository -type d -name '*-SNAPSHOT' -print -exec rm -r {} + ================================================ FILE: .github/workflows/integration-test.yml ================================================ name: Integration Testing on: push: branches: - 2023.x - 2025.0.x - 2025.1.x pull_request: branches: - 2023.x - 2025.0.x - 2025.1.x jobs: deploy-docker-image: runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Check out the repo uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 integration-testing: name: Integration Testing runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' - name: Dependencies Cache uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Compile & Checkstyle run: mvn clean -B compile - name: install dependencies run: mvn clean -B install -U package -pl '!spring-cloud-alibaba-coverage' -DskipTests - name: Testing run: ./mvnw verify -B -Dmaven.test.skip=false # run: mvn clean -Dit.enabled=true test - name: Delete snapshots from the Maven local repository run: find ~/.m2/repository -type d -name '*-SNAPSHOT' -print -exec rm -r {} + ================================================ FILE: .github/workflows/issue-command.yml ================================================ name: Issue and PR comment commands permissions: {} on: issue_comment: types: - created - edited jobs: execute: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: jpmcb/prow-github-actions@f4d01dd4b13f289014c23fe5a19878a2479cb35b # v1.1.3 with: prow-commands: '/assign /unassign /area /kind /priority /remove /close /reopen /lock /milestone /hold /cc /uncc' github-token: "${{ secrets.GITHUB_TOKEN }}" ================================================ FILE: .github/workflows/md-link-check.yml ================================================ name: 'Link Checker' # **What it does**: Renders the content of every page and check all internal links. # **Why we have it**: To make sure all links connect correctly. # **Who does it impact**: Docs content. on: workflow_dispatch: push: # branches: [master, 'release/**'] paths: - '**/*.md' - '**/link-check.yml' pull_request: branches: [2023.x, 2025.0.x, 2025.1.x, "release/**"] paths: - '**/*.md' - '**/link-check.yml' permissions: contents: read # Needed for the 'trilom/file-changes-action' action pull-requests: read # This allows a subsequently queued workflow run to interrupt previous runs concurrency: group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' cancel-in-progress: true jobs: check-links: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Get script run: | wget https://raw.githubusercontent.com/xuruidong/markdown-link-checker/main/link_checker.py - name: Setup python uses: actions/setup-python@v4 with: python-version: '3.9' - name: Link check (critical, all files) run: | # python link_checker.py ./ --enable-external --ignore "http://apisix.iresty.com" "https://www.upyun.com" "https://github.com/apache/apisix/actions/workflows/build.yml/badge.svg" "https://httpbin.org/" "https://en.wikipedia.org/wiki/Cache" python link_checker.py ./ ================================================ FILE: .github/workflows/stale.yml ================================================ name: "Stale bot of marking stale issues" on: schedule: - cron: "50 18 * * *" permissions: issues: write jobs: stale: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been open 30 days with no activity. This will be closed in 7 days.' close-issue-message: "This issue has been automatically marked as stale because it hasn't had any recent activity.If you think this should still be open, or the problem still persists, just pop a reply in the comments and one of the maintainers will (try!) to follow up. Thank you for your interest and contribution to the Sping Cloud Alibaba Community." days-before-issue-stale: 30 days-before-issue-close: 7 stale-issue-label: "stale" close-issue-label: "wait-for-feedback" exempt-all-milestones: true operations-per-run: 10 days-before-pr-stale: -1 days-before-pr-close: -1 exempt-issue-labels: discussion,bug,question,good first issue ================================================ FILE: .gitignore ================================================ # Compiled class file *.class *.classpath *.factorypath # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar !.mvn/wrapper/maven-wrapper.jar *.war *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* # IDE Files # *.iml .idea .idea/ .project .settings target .DS_Store .qoder .cursor .vscode #Seata test data file sessionStore/ # temp ignore *.cache *.diff *.patch *.tmp # Maven ignore .flattened-pom.xml ================================================ FILE: .licenscheckconfig.yaml ================================================ header: license: spdx-id: Apache-2.0 copyright-owner: alibaba content: | Copyright 2013-2023 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. paths-ignore: - '.gitignore' - '.gitattributes' - '.travis.yml' - 'codecov.yml' - 'CONTRIBUTING.md' - 'CONTRIBUTING_CN.md' - 'CODE_OF_CONDUCT.md' - 'README.md' - 'LICENSE' - 'NOTICE' - '**/*.md' - '.github/**' - '**/*.yaml' - "**/*.yml" - '**/*.xml' - '**/*.conf' - '**/*.sql' - '*.adoc' - 'eclipse' - '**/Dockerfile' - '**/*.properties' - '**/*.json' - '**/*.html' - "spring-cloud-alibaba-examples/integrated-example/docker-compose/.env" #SPI、spring.factories、spring-configuration-metadata.json、additional-spring-configuration-metadata.json - '**/src/test/resources/META-INF/**' - '**/src/main/resources/META-INF/**' - '**/target/**' - '**/*.iml' - 'mvnw' - 'mvnw.cmd' - '**/*.sh' - '.mvn/**' - 'sessionStore/**' - 'distribution/LICENSE-BIN' - 'distribution/NOTICE-BIN' - 'test/src/test/resources/**' - '**/src/test/resources/statelang/**' comment: on-failure dependency: files: - pom.xml ================================================ FILE: .mvn/jvm.config ================================================ -Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ # Copyright 2013-2023 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar ================================================ FILE: CODE_OF_CONDUCT.md ================================================ ## Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery - Personal attacks - Trolling or insulting/derogatory comments - Public or private harassment - Publishing other’s private information, such as physical or electronic addresses, without explicit permission - Other unethical or unprofessional conduct Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org/), version 1.3.0, available at [contributor-covenant.org/version/1/3/0/](http://contributor-covenant.org/version/1/3/0/) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README-zh.md ================================================ # Spring Cloud Alibaba [![CircleCI](https://circleci.com/gh/alibaba/spring-cloud-alibaba/tree/2025.1.x.svg?style=svg)](https://circleci.com/gh/alibaba/spring-cloud-alibaba/tree/2025.1.x) [![Maven Central](https://img.shields.io/maven-central/v/com.alibaba.cloud/spring-cloud-alibaba-dependencies.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:com.alibaba.cloud%20AND%20a:spring-cloud-alibaba-dependencies) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![actions](https://github.com/alibaba/spring-cloud-alibaba/workflows/Integration%20Testing/badge.svg)](https://github.com/alibaba/spring-cloud-alibaba/actions) [![Leaderboard](https://img.shields.io/badge/SCA-Check%20Your%20Contribution-orange)](https://opensource.alibaba.com/contribution_leaderboard/details?projectValue=sca) Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。 依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。 此外,阿里云同时还提供了 Spring Cloud Alibaba 企业版 [微服务解决方案](https://www.aliyun.com/product/aliware/mse?spm=github.spring.com.topbar),包括无侵入服务治理(全链路灰度,无损上下线,离群实例摘除等),企业级 Nacos 注册配置中心和企业级云原生网关等众多产品。 参考文档 请查看 [WIKI](https://github.com/alibaba/spring-cloud-alibaba/wiki) 。 为 Spring Cloud Alibaba 贡献代码请参考 [如何贡献](https://sca.aliyun.com/docs/developer/contributor-guide/new-contributor-guide_dev/) 。 ## 主要功能 * **服务限流降级**:默认支持 WebServlet、WebFlux、OpenFeign、RestTemplate、Spring Cloud Gateway、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。 * **服务注册与发现**:适配 Spring Cloud 服务注册与发现标准,默认集成对应 Spring Cloud 版本所支持的负载均衡组件的适配。 * **分布式配置管理**:支持分布式系统中的外部化配置,配置更改时自动刷新。 * **消息驱动能力**:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。 * **分布式事务**:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。 * **阿里云对象存储**:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。 * **分布式任务调度**:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。 * **阿里云短信服务**:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。 更多功能请参考 [Roadmap](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/Roadmap-zh.md) 除了上述所具有的功能外,针对企业级用户的场景,Spring Cloud Alibaba 配套的企业版微服务治理方案 [微服务引擎MSE](https://www.aliyun.com/product/aliware/mse?spm=github.spring.com.topbar) 还提供了企业级微服务治理中心,包括全链路灰度、服务预热、无损上下线和离群实例摘除等更多更强大的治理能力,同时还提供了企业级 Nacos 注册配置中心,企业级云原生网关等多种产品及解决方案。 ## 组件 **[Sentinel](https://github.com/alibaba/Sentinel)**:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 **[Nacos](https://github.com/alibaba/Nacos)**:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 **[RocketMQ](https://rocketmq.apache.org/)**:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。 **[Seata](https://github.com/seata/seata)**:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。 **[Alibaba Cloud OSS](https://www.aliyun.com/product/oss)**: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。 **[Alibaba Cloud SchedulerX](https://cn.aliyun.com/aliware/schedulerx)**: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。 **[Alibaba Cloud SMS](https://www.aliyun.com/product/sms)**: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。 更多组件请参考 [Roadmap](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/Roadmap-zh.md)。 ## 如何构建 * 2025.1.x 分支对应的是 Spring Cloud 2025.1.x 与 Spring Boot 4.0.x,最低支持 JDK 17。 * 2025.0.x 分支对应的是 Spring Cloud 2025.0.x 与 Spring Boot 3.5.x,最低支持 JDK 17。 * 2023.x 分支对应的是 Spring Cloud 2023 与 Spring Boot 3.2.x,最低支持 JDK 17。 * 2022.x 分支对应的是 Spring Cloud 2022 与 Spring Boot 3.0.x,最低支持 JDK 17。 * 2021.x 分支对应的是 Spring Cloud 2021 与 Spring Boot 2.6.x,最低支持 JDK 1.8。 * 2020.0 分支对应的是 Spring Cloud 2020 与 Spring Boot 2.4.x,最低支持 JDK 1.8。 * 2.2.x 分支对应的是 Spring Cloud Hoxton 与 Spring Boot 2.2.x,最低支持 JDK 1.8。 * greenwich 分支对应的是 Spring Cloud Greenwich 与 Spring Boot 2.1.x,最低支持 JDK 1.8。 * finchley 分支对应的是 Spring Cloud Finchley 与 Spring Boot 2.0.x,最低支持 JDK 1.8。 * 1.x 分支对应的是 Spring Cloud Edgware 与 Spring Boot 1.x,最低支持 JDK 1.7。 Spring Cloud 使用 Maven 来构建,最快的使用方式是将本项目 clone 到本地,然后执行以下命令: ```bash ./mvnw install ``` 执行完毕后,项目将被安装到本地 Maven 仓库。 ## 如何使用 ### 如何引入依赖 #### 正式版 如果需要使用已发布的`正式版本`,在 `dependencyManagement` 中添加如下配置。 ```xml com.alibaba.cloud spring-cloud-alibaba-dependencies 2025.1.0.0 pom import ``` 然后在 `dependencies` 中添加自己所需使用的依赖即可使用。如果你想选择老版本,可以参考[版本说明](https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E)。 #### 快照 如果需要使用已发布的`快照版本`,在 `dependencyManagement` 中添加如下配置。 ```xml com.alibaba.cloud spring-cloud-alibaba-dependencies 2025.1.0.0-SNAPSHOT pom import ``` 在 `repositories` 中添加如下配置。 ```xml github https://maven.pkg.github.com/alibaba/spring-cloud-alibaba false true ``` 在 `settings.xml` 中添加如下配置。 ```xml github 你的 GitHub 用户名 你的 GitHub Token(需要 read:packages 权限) ``` ## 演示 Demo 为了演示如何使用,Spring Cloud Alibaba 项目包含了一个子模块`spring-cloud-alibaba-examples`。此模块中提供了演示用的 example ,您可以阅读对应的 example 工程下的 readme 文档,根据里面的步骤来体验。 Example 列表: [Sentinel Example](https://github.com/alibaba/spring-cloud-alibaba/tree/2025.1.x/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md) [Nacos Example](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/spring-cloud-alibaba-examples/nacos-example/readme-zh.md) [RocketMQ Example](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md) [Seata Example](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/spring-cloud-alibaba-examples/seata-example/readme-zh.md) [Alibaba Cloud OSS Example](https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample) [Alibaba Cloud SMS Example](https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-sms-spring-boot-sample) [Alibaba Cloud SchedulerX Example](https://github.com/alibaba/aliyun-spring-boot) ## 版本管理规范 项目的版本号格式为 x.x.x 的形式,其中 x 的数值类型为数字,从 0 开始取值,且不限于 0~9 这个范围。项目处于孵化器阶段时,第一位版本号固定使用 0,即版本号为 0.x.x 的格式。 由于 Spring Boot 1 和 Spring Boot 2 在 Actuator 模块的接口和注解有很大的变更,且 spring-cloud-commons 从 1.x.x 版本升级到 2.0.0 版本也有较大的变更,因此我们采取跟 SpringBoot 版本号一致的版本: * 1.5.x 版本适用于 Spring Boot 1.5.x * 2.0.x 版本适用于 Spring Boot 2.0.x * 2.1.x 版本适用于 Spring Boot 2.1.x * 2.2.x 版本适用于 Spring Boot 2.2.x * 2020.x 版本适用于 Spring Boot 2.4.x * 2021.x 版本适用于 Spring Boot 2.6.x * 2022.x 版本适用于 Spring Boot 3.0.x * 2023.x 版本适用于 Spring Boot 3.2.x * 2025.0.x 版本适用于 Spring Boot 3.5.x * 2025.1.x 版本适用于 Spring Boot 4.0.x ## 社区交流 ### 邮件列表 spring-cloud-alibaba@googlegroups.com,欢迎通过此邮件列表讨论与 spring-cloud-alibaba 相关的一切。 ### 钉钉群 * Spring Cloud Alibaba 开源交流群(1群):21914947 * Spring Cloud Alibaba 开源交流群(2群,已满):21992595 * Spring Cloud Alibaba 开源交流群(3群,已满):35153903 * Spring Cloud Alibaba 开源交流群(4群,已满):30301472 * Spring Cloud Alibaba 开源交流群(5群,已满):34930571 * Spring Cloud Alibaba 开源交流群(6群,已满):34351718 * Spring Cloud Alibaba 开源交流群(7群):2415000986 ## 社区相关开源 **[Nepxion Discovery](https://github.com/Nepxion/Discovery)**:一款集成Spring Cloud Alibaba、Nacos、Sentinel等阿里巴巴中间件,实现网关和服务的灰度发布、路由、权重、限流、熔断、降级、隔离、监控、追踪等功能的微服务开源解决。使用指南 请参考 **[Nepxion Discovery Guide](https://github.com/Nepxion/DiscoveryGuide)**。 ================================================ FILE: README.md ================================================ # Spring Cloud Alibaba [![CircleCI](https://circleci.com/gh/alibaba/spring-cloud-alibaba/tree/2025.1.x.svg?style=svg)](https://circleci.com/gh/alibaba/spring-cloud-alibaba/tree/2025.1.x) [![Maven Central](https://img.shields.io/maven-central/v/com.alibaba.cloud/spring-cloud-alibaba-dependencies.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:com.alibaba.cloud%20AND%20a:spring-cloud-alibaba-dependencies) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![actions](https://github.com/alibaba/spring-cloud-alibaba/workflows/Integration%20Testing/badge.svg)](https://github.com/alibaba/spring-cloud-alibaba/actions) [![Leaderboard](https://img.shields.io/badge/SCA-Check%20Your%20Contribution-orange)](https://opensource.alibaba.com/contribution_leaderboard/details?projectValue=sca) A project maintained by Alibaba. See the [中文文档](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/README-zh.md) for Chinese readme. Spring Cloud Alibaba provides a one-stop solution for distributed application development. It contains all the components required to develop distributed applications, making it easy for you to develop your applications using Spring Cloud. With Spring Cloud Alibaba, you only need to add some annotations and a small amount of configurations to connect Spring Cloud applications to the distributed solutions of Alibaba, and build a distributed application system with Alibaba middleware. ## Features * **Flow control and service degradation**: Flow control for HTTP services is supported by default. You can also customize flow control and service degradation rules using annotations. The rules can be changed dynamically. * **Service registration and discovery**: Service can be registered and clients can discover the instances using Spring-managed beans. Load balancing is consistent with that supported by the corresponding Spring Cloud. * **Distributed configuration**: Support for externalized configuration in a distributed system, auto refresh when configuration changes. * **Event-driven**: Support for building highly scalable event-driven microservices connected with shared messaging systems. * **Distributed Transaction**: Support for distributed transaction solution with high performance and ease of use. * **Alibaba Cloud Object Storage**: Massive, secure, low-cost, and highly reliable cloud storage services. Support for storing and accessing any type of data in any application, anytime, anywhere. * **Alibaba Cloud SchedulerX**: Accurate, highly reliable, and highly available scheduled job scheduling services with response time within seconds. * **Alibaba Cloud SMS**: A messaging service that covers the globe, Alibaba SMS provides convenient, efficient, and intelligent communication capabilities that help businesses quickly contact their customers. For more features, please refer to [Roadmap](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/Roadmap.md). In addition to the above-mentioned features, for the needs of enterprise users' scenarios, [Microservices Engine (MSE)](https://www.aliyun.com/product/aliware/mse?spm=github.spring.com.topbar) of Spring Cloud Alibaba's enterprise version provides an enterprise-level microservices governance center, which includes more powerful governance capabilities such as Grayscale Release, Service Warm-up, Lossless Online and Offline and Outlier Ejection. At the same time, it also provides a variety of products and solutions such as enterprise-level Nacos registration / configuration center, enterprise-level cloud native gateway. ## Components **[Sentinel](https://github.com/alibaba/Sentinel)**: Sentinel takes "traffic flow" as the breakthrough point, and provides solutions in areas such as flow control, concurrency, circuit breaking, and load protection to protect service stability. **[Nacos](https://github.com/alibaba/Nacos)**: An easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications. **[RocketMQ](https://rocketmq.apache.org/)**: A distributed messaging and streaming platform with low latency, high performance and reliability, trillion-level capacity and flexible scalability. **[Seata](https://github.com/seata/seata)**: A distributed transaction solution with high performance and ease of use for microservices architecture. **[Alibaba Cloud OSS](https://www.aliyun.com/product/oss)**: An encrypted and secure cloud storage service which stores, processes and accesses massive amounts of data from anywhere in the world. **[Alibaba Cloud SMS](https://www.aliyun.com/product/sms)**: A messaging service that covers the globe, Alibaba SMS provides convenient, efficient, and intelligent communication capabilities that help businesses quickly contact their customers. **[Alibaba Cloud SchedulerX](https://www.aliyun.com/aliware/schedulerx?spm=5176.10695662.784137.1.4b07363dej23L3)**: Accurate, highly reliable, and highly available scheduled job scheduling services with response time within seconds. For more features please refer to [Roadmap](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/Roadmap.md). ## How to build * **2025.1.x branch**: Corresponds to Spring Cloud 2025.1.x & Spring Boot 4.0.x, JDK 17 or later versions are supported. * **2025.0.x branch**: Corresponds to Spring Cloud 2025.0.x & Spring Boot 3.5.x, JDK 17 or later versions are supported. * **2023.x branch**: Corresponds to Spring Cloud 2023 & Spring Boot 3.2.x, JDK 17 or later versions are supported. * **2022.x branch**: Corresponds to Spring Cloud 2022 & Spring Boot 3.0.x, JDK 17 or later versions are supported. * **2021.x branch**: Corresponds to Spring Cloud 2021 & Spring Boot 2.6.x. JDK 1.8 or later versions are supported. * **2020.0 branch**: Corresponds to Spring Cloud 2020 & Spring Boot 2.4.x. JDK 1.8 or later versions are supported. * **2.2.x branch**: Corresponds to Spring Cloud Hoxton & Spring Boot 2.2.x. JDK 1.8 or later versions are supported. * **greenwich branch**: Corresponds to Spring Cloud Greenwich & Spring Boot 2.1.x. JDK 1.8 or later versions are supported. * **finchley branch**: Corresponds to Spring Cloud Finchley & Spring Boot 2.0.x. JDK 1.8 or later versions are supported. * **1.x branch**: Corresponds to Spring Cloud Edgware & Spring Boot 1.x, JDK 1.7 or later versions are supported. Spring Cloud uses Maven for most build-related activities, and you should be able to get off the ground quite quickly by cloning the project you are interested in and typing: ```bash ./mvnw install ``` ## How to Use ### Add maven dependency #### Release Version These artifacts are available from Maven Central and Spring Release repository via BOM: ```xml com.alibaba.cloud spring-cloud-alibaba-dependencies 2025.1.0.0 pom import ``` add the module in `dependencies`. If you want to choose an older version, you can refer to the [Release Notes](https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E). #### Snapshot If you need to use the already published `Snapshot Version`, add the following configuration in the `dependencyManagement`. ```xml com.alibaba.cloud spring-cloud-alibaba-dependencies 2025.1.0.0-SNAPSHOT pom import ``` Add the following configuration in `repositories`. ```xml github https://maven.pkg.github.com/alibaba/spring-cloud-alibaba false true ``` Add the following configuration in `settings.xml`. ```xml github Your GitHub Username Your GitHub Token (requires read:packages permission) ``` ## Examples A `spring-cloud-alibaba-examples` module is included in our project for you to get started with Spring Cloud Alibaba quickly. It contains an example, and you can refer to the readme file in the example project for a quick walkthrough. Examples: [Sentinel Example](https://github.com/alibaba/spring-cloud-alibaba/tree/2025.1.x/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme.md) [Nacos Example](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/spring-cloud-alibaba-examples/nacos-example/readme.md) [RocketMQ Example](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/spring-cloud-alibaba-examples/rocketmq-example/readme.md) [Alibaba Cloud OSS Example](https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample) ## Version control guidelines The version number of the project is in the form of x.x.x, where x is a number, starting from 0, and is not limited to the range 0~9. When the project is in the incubator phase, the version number is 0.x.x. As the interfaces and annotations of Spring Boot 1 and Spring Boot 2 have been changed significantly in the Actuator module, and spring-cloud-commons is also changed quite a lot from 1.x.x to 2.0.0, we take the same version rule as SpringBoot version number. * 1.5.x for Spring Boot 1.5.x * 2.0.x for Spring Boot 2.0.x * 2.1.x for Spring Boot 2.1.x * 2.2.x for Spring Boot 2.2.x * 2020.x for Spring Boot 2.4.x * 2021.x for Spring Boot 2.6.x * 2022.x for Spring Boot 3.0.x * 2023.x for Spring Boot 3.2.x * 2025.0.x for Spring Boot 3.5.x * 2025.1.x for Spring Boot 4.0.x ## Code of Conduct This project is a sub-project of Spring Cloud, it adheres to the Contributor Covenant [code of conduct](https://sca.aliyun.com/en-us/community/developer/contributor-guide/new-contributor-guide_dev/). By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io. ## Code Conventions and Housekeeping None of these is essential for a pull request, but they will all help. They can also be added after the original pull request but before a merge. Use the Spring Framework code format conventions. If you use Eclipse you can import formatter settings using the eclipse-code-formatter.xml file from the Spring Cloud Build project. If using IntelliJ, you can use the Eclipse Code Formatter Plugin to import the same file. Make sure all new .java files to have a simple Javadoc class comment with at least an @author tag identifying you, and preferably at least a paragraph on what the class is for. Add the ASF license header comment to all new .java files (copy from existing files in the project) Add yourself as an @author to the .java files that you modify substantially (more than cosmetic changes). Add some Javadocs and, if you change the namespace, some XSD doc elements. A few unit tests would help a lot as well —— someone has to do it. If no-one else is using your branch, please rebase it against the current 2023.x (or other target branch in the main project). When writing a commit message please follow these conventions, if you are fixing an existing issue please add Fixes gh-XXXX at the end of the commit message (where XXXX is the issue number). ## Contact Us Mailing list is recommended for discussing almost anything related to spring-cloud-alibaba. spring-cloud-alibaba@googlegroups.com: You can ask questions here if you encounter any problem when using or developing spring-cloud-alibaba. ================================================ FILE: Roadmap-zh.md ================================================ # Roadmap [Spring Cloud Alibaba](https://github.com/alibaba/spring-cloud-alibaba) 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。 此项目包含的组件内容,主要选取自阿里巴巴开源中间件,但也不限定于这些产品。 如果您对 Roadmap 有任何建议或想法,欢迎在 issues 中或者通过其他社区渠道向我们提出,一起讨论。 - Github Issue:https://github.com/alibaba/spring-cloud-alibaba/issues - 钉钉交流群:“群8 Spring Cloud Alibaba交流群”群的钉钉群号: 33610001098 ## 已包含的组件 **Sentinel** 阿里巴巴开源产品,把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 **Nacos** 阿里巴巴开源产品,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 **RocketMQ** Apache RocketMQ™ 一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。 **Seata** 阿里巴巴开源产品(现捐赠给 Apache 基金会),一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 ## 未来发展方向 ## Spring Cloud Admin 服务治理(可观测方向) Spring Cloud Admin 定位为一款可视化的微服务管控平台,通过它能够查看整个 Spring Cloud 微服务状态(包括服务数、实例数、应用数等)。同时 Spring Cloud Admin 还应与主流的 [Apache SkyWalking](https://skywalking.apache.org/),[OpenTelemetry](https://opentelemetry.io/) 等可观测性系统集成,提供对集群状态的指标查询与监控能力。 ## Spring Cloud Alibaba AI 随着 LLM 的爆火,各种 AI 应用开发框架应运而生。其中包括 LangChain,LangChain4J,Spring AI 等项目,为 AI 应用开发提供了一系列的解决方案。 本项目是基于 Spring AI 提供对阿里通义系列大模型的完整支持,包括对话,文生图,文生语音,语音转录等功能。旨在为开发微服务 AI 应用提供便利,屏蔽底层复杂性,使得 AI 可以快速接入 Spring Cloud 微服务体系。 ## Proxyless Mesh Spring Cloud Alibaba 也积极在 Proxyless 方向上探索,目前已经完成了 Routing,Xds-adapter 等功能。未来会推出更多云原生场景下的 Proxyless 功能特性。 ## RPC 方向上的探索 Spring Cloud Alibaba RPC 组件主要依赖于 OpenFeign,RestTemplate 等。社区计划通过加入 GRPC,Dubbo 的 rpc 解决方案,进一步增强社区的 RPC 组件能力。 ## 分布式任务调度 Spring Cloud Alibaba 缺少对分布式调度任务的支持,社区计划通过适配开源的分布式任务调度框架,来完善这部分能力。 ================================================ FILE: Roadmap.md ================================================ # Roadmap See the [中文文档](https://github.com/alibaba/spring-cloud-alibaba/blob/2025.1.x/Roadmap-zh.md) for Chinese Roadmap. Spring Cloud Alibaba provides a one-stop solution for microservices development. It contains all the components required to develop distributed applications, making it easy for you to develop your applications through the Spring Cloud programming model. This project contains components from both open-source and commercialized Alibaba middleware products,but are not limited to them. If you have any suggestions on our roadmap, feel free to submit issues or contact us via the other channels. - Github Issue:https://github.com/alibaba/spring-cloud-alibaba/issues - DingTalk communication group: "Group 8 Spring Cloud Alibaba communication group" DingTalk group number: 33610001098 ## Components **Sentinel** An open-source project of Alibaba, Sentinel takes "flow" as breakthrough point, and provides solutions in areas such as flow control, concurrency, circuit breaking, and load protection to protect service stability. **Nacos** An opensource project of Alibaba, an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications. **RocketMQ** Apache RocketMQ™ is an open source distributed messaging system based on highly available distributed cluster technology, providing low latency, highly reliable message publishing and subscription services. **Seata** An opensource project of Alibaba(now donated to the Apache Foundation), a distributed transaction solution dedicated to providing high-performance and easy-to-use distributed transaction services under a microservice architecture. ## Future Development Direction ## Spring Cloud Admin Service Governance (Observable Direction) Spring Cloud Admin is positioned as a visual microservice management and control platform, through which you can view the status of the entire Spring Cloud microservice (including the number of services, the number of instances, the number of applications, etc.). At the same time, Spring Cloud Admin should also be integrated with mainstream observability systems such as [Apache SkyWalking](https://skywalking.apache.org/) and [OpenTelemetry](https://opentelemetry.io/) to provide indicator query and monitoring capabilities for cluster status. ## Spring Cloud Alibaba AI With the explosion of LLM, various AI application development frameworks have emerged as the times require. These include LangChain, LangChain4J, Spring AI and other projects, providing a series of solutions for AI application development. This project is based on Spring AI to provide complete support for Ali Tongyi series large models, providing for chat, text-to-image, audiotranscription and other functions. It aims to facilitate the development of microservice AI applications, shield the underlying complexity, and enable AI to quickly access the Spring Cloud microservice system. ## Proxyless Mesh Spring Cloud Alibaba is also actively exploring in the direction of Proxyless, and has completed functions such as Routing and Xds-adapter. In the future, more Proxyless features in cloud-native scenarios will be launched. ## Exploration in the direction of RPC Spring Cloud Alibaba RPC components mainly rely on OpenFeign, RestTemplate, etc. The community plans to further enhance the community's RPC component capabilities by joining GRPC, Dubbo's RPC solution. ## Distributed task scheduling Spring Cloud Alibaba lacks support for distributed scheduling tasks, the community plans to improve this part of the ability by adapting open source distributed task scheduling framework. ================================================ FILE: eclipse/checkstyle-suppressions.xml ================================================ ================================================ FILE: eclipse/eclipse-code-formatter.xml ================================================ ================================================ FILE: eclipse/org.eclipse.jdt.core.prefs ================================================ eclipse.preferences.version=1 org.eclipse.jdt.core.codeComplete.argumentPrefixes= org.eclipse.jdt.core.codeComplete.argumentSuffixes= org.eclipse.jdt.core.codeComplete.fieldPrefixes= org.eclipse.jdt.core.codeComplete.fieldSuffixes= org.eclipse.jdt.core.codeComplete.localPrefixes= org.eclipse.jdt.core.codeComplete.localSuffixes= org.eclipse.jdt.core.codeComplete.staticFieldPrefixes= org.eclipse.jdt.core.codeComplete.staticFieldSuffixes= org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes= org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes= org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.autoboxing=ignore org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning org.eclipse.jdt.core.compiler.problem.deadCode=warning org.eclipse.jdt.core.compiler.problem.deprecation=warning org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=default org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=default org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore org.eclipse.jdt.core.compiler.problem.nullReference=ignore org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled org.eclipse.jdt.core.compiler.problem.unusedImport=warning org.eclipse.jdt.core.compiler.problem.unusedLabel=warning org.eclipse.jdt.core.compiler.problem.unusedLocal=warning org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_assignment=0 org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16 org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.blank_lines_before_field=0 org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 org.eclipse.jdt.core.formatter.blank_lines_before_method=1 org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 org.eclipse.jdt.core.formatter.blank_lines_before_package=0 org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false org.eclipse.jdt.core.formatter.comment.format_block_comments=true org.eclipse.jdt.core.formatter.comment.format_header=false org.eclipse.jdt.core.formatter.comment.format_html=true org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true org.eclipse.jdt.core.formatter.comment.format_line_comments=true org.eclipse.jdt.core.formatter.comment.format_source_code=false org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true org.eclipse.jdt.core.formatter.comment.indent_root_tags=false org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert org.eclipse.jdt.core.formatter.comment.line_length=90 org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false org.eclipse.jdt.core.formatter.compact_else_if=true org.eclipse.jdt.core.formatter.continuation_indentation=2 org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true org.eclipse.jdt.core.formatter.indent_empty_lines=false org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false org.eclipse.jdt.core.formatter.indentation.size=4 org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.join_lines_in_comments=true org.eclipse.jdt.core.formatter.join_wrapped_lines=true org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.lineSplit=90 org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true org.eclipse.jdt.core.formatter.tabulation.char=tab org.eclipse.jdt.core.formatter.tabulation.size=4 org.eclipse.jdt.core.formatter.use_on_off_tags=true org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter ================================================ FILE: eclipse/org.eclipse.jdt.ui.prefs ================================================ cleanup.add_default_serial_version_id=true cleanup.add_generated_serial_version_id=false cleanup.add_missing_annotations=true cleanup.add_missing_deprecated_annotations=true cleanup.add_missing_methods=false cleanup.add_missing_nls_tags=false cleanup.add_missing_override_annotations=true cleanup.add_missing_override_annotations_interface_methods=true cleanup.add_serial_version_id=false cleanup.always_use_blocks=true cleanup.always_use_parentheses_in_expressions=false cleanup.always_use_this_for_non_static_field_access=true cleanup.always_use_this_for_non_static_method_access=false cleanup.convert_functional_interfaces=false cleanup.convert_to_enhanced_for_loop=false cleanup.correct_indentation=false cleanup.format_source_code=true cleanup.format_source_code_changes_only=false cleanup.insert_inferred_type_arguments=false cleanup.make_local_variable_final=false cleanup.make_parameters_final=false cleanup.make_private_fields_final=false cleanup.make_type_abstract_if_missing_method=false cleanup.make_variable_declarations_final=false cleanup.never_use_blocks=false cleanup.never_use_parentheses_in_expressions=true cleanup.organize_imports=true cleanup.qualify_static_field_accesses_with_declaring_class=false cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true cleanup.qualify_static_member_accesses_with_declaring_class=true cleanup.qualify_static_method_accesses_with_declaring_class=false cleanup.remove_private_constructors=true cleanup.remove_redundant_type_arguments=true cleanup.remove_trailing_whitespaces=true cleanup.remove_trailing_whitespaces_all=true cleanup.remove_trailing_whitespaces_ignore_empty=false cleanup.remove_unnecessary_casts=true cleanup.remove_unnecessary_nls_tags=false cleanup.remove_unused_imports=true cleanup.remove_unused_local_variables=false cleanup.remove_unused_private_fields=true cleanup.remove_unused_private_members=false cleanup.remove_unused_private_methods=true cleanup.remove_unused_private_types=true cleanup.sort_members=false cleanup.sort_members_all=false cleanup.use_anonymous_class_creation=false cleanup.use_blocks=true cleanup.use_blocks_only_for_return_and_throw=false cleanup.use_lambda=true cleanup.use_parentheses_in_expressions=false cleanup.use_this_for_non_static_field_access=false cleanup.use_this_for_non_static_field_access_only_if_necessary=false cleanup.use_this_for_non_static_method_access=false cleanup.use_this_for_non_static_method_access_only_if_necessary=true cleanup.use_type_arguments=false cleanup_profile=_Spring Cloud Cleanup Conventions cleanup_settings_version=2 eclipse.preferences.version=1 editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true formatter_profile=_Spring Cloud Java Conventions formatter_settings_version=13 org.eclipse.jdt.ui.exception.name=e org.eclipse.jdt.ui.gettersetter.use.is=true org.eclipse.jdt.ui.ignorelowercasenames=true org.eclipse.jdt.ui.importorder=java;javax;;org.springframework;\#; org.eclipse.jdt.ui.javadoc=true org.eclipse.jdt.ui.keywordthis=false org.eclipse.jdt.ui.ondemandthreshold=9999 org.eclipse.jdt.ui.overrideannotation=true org.eclipse.jdt.ui.staticondemandthreshold=9999 org.eclipse.jdt.ui.text.custom_code_templates= sp_cleanup.add_default_serial_version_id=true sp_cleanup.add_generated_serial_version_id=false sp_cleanup.add_missing_annotations=true sp_cleanup.add_missing_deprecated_annotations=true sp_cleanup.add_missing_methods=false sp_cleanup.add_missing_nls_tags=false sp_cleanup.add_missing_override_annotations=true sp_cleanup.add_missing_override_annotations_interface_methods=true sp_cleanup.add_serial_version_id=false sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=true sp_cleanup.always_use_this_for_non_static_field_access=true sp_cleanup.always_use_this_for_non_static_method_access=false sp_cleanup.convert_to_enhanced_for_loop=false sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=false sp_cleanup.make_type_abstract_if_missing_method=false sp_cleanup.make_variable_declarations_final=false sp_cleanup.never_use_blocks=false sp_cleanup.never_use_parentheses_in_expressions=false sp_cleanup.on_save_use_additional_actions=true sp_cleanup.organize_imports=true sp_cleanup.qualify_static_field_accesses_with_declaring_class=false sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true sp_cleanup.qualify_static_member_accesses_with_declaring_class=true sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false sp_cleanup.remove_unnecessary_casts=true sp_cleanup.remove_unnecessary_nls_tags=false sp_cleanup.remove_unused_imports=true sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true sp_cleanup.remove_unused_private_members=false sp_cleanup.remove_unused_private_methods=true sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false sp_cleanup.use_blocks=true sp_cleanup.use_blocks_only_for_return_and_throw=false sp_cleanup.use_parentheses_in_expressions=false sp_cleanup.use_this_for_non_static_field_access=true sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false sp_cleanup.use_this_for_non_static_method_access=false sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Maven Start Up Batch script # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -f /usr/local/etc/mavenrc ] ; then . /usr/local/etc/mavenrc fi if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ] ; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; mingw=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then export JAVA_HOME="`/usr/libexec/java_home`" else export JAVA_HOME="/Library/Java/Home" fi fi ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi if [ -z "$M2_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` M2_HOME=`dirname "$PRG"`/.. # make it fully qualified M2_HOME=`cd "$M2_HOME" && pwd` cd "$saveddir" # echo Using m2 at $M2_HOME fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=`which readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if $darwin ; then javaHome="`dirname \"$javaExecutable\"`" javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" else javaExecutable="`readlink -f \"$javaExecutable\"`" fi javaHome="`dirname \"$javaExecutable\"`" javaHome=`expr "$javaHome" : '\(.*\)/bin'` JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ] ; then if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="`\\unset -f command; \\command -v java`" fi fi if [ ! -x "$JAVACMD" ] ; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=`cd "$wdir/.."; pwd` fi # end of workaround done echo "${basedir}" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")" fi } BASE_DIR=`find_maven_basedir "$(pwd)"` if [ -z "$BASE_DIR" ]; then exit 1; fi ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found .mvn/wrapper/maven-wrapper.jar" fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" else jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" fi while IFS="=" read key value; do case "$key" in (wrapperUrl) jarUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then echo "Downloading from: $jarUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if $cygwin; then wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` fi if command -v wget > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl -o "$wrapperJarPath" "$jarUrl" -f else curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaClass=`cygpath --path --windows "$javaClass"` fi if [ -e "$javaClass" ]; then if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class ("$JAVA_HOME/bin/javac" "$javaClass") fi if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." fi ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") fi fi fi fi ########################################################################################## # End of extension ########################################################################################## export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"` [ -n "$MAVEN_PROJECTBASEDIR" ] && MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM https://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %DOWNLOAD_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% ^ %JVM_CONFIG_MAVEN_PROPS% ^ %MAVEN_OPTS% ^ %MAVEN_DEBUG_OPTS% ^ -classpath %WRAPPER_JAR% ^ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%"=="on" pause if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% cmd /C exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 org.springframework.cloud spring-cloud-build 5.0.0 com.alibaba.cloud spring-cloud-alibaba ${revision} pom Spring Cloud Alibaba Spring Cloud Alibaba https://github.com/alibaba/spring-cloud-alibaba Apache License, Version 2.0 https://www.apache.org/licenses/LICENSE-2.0.txt repo https://github.com/alibaba/spring-cloud-alibaba scm:git:git://github.com/alibaba/spring-cloud-alibaba.git scm:git:ssh://git@github.com/alibaba/spring-cloud-alibaba.git HEAD xiaojing flystar32@163.com Jim Fang fangjian0423@gmail.com Alibaba https://github.com/fangjian0423 xiaolongzuo 150349407@qq.com hengyunabc hengyunabc@gmail.com mercyblitz Mercy Ma mercyblitz@gmail.com Alibaba https://github.com/mercyblitz yunzheng yunzheng1228@gmail.com theonefx theonefx chenxilzx1@gmail.com Alibaba https://github.com/theonefx 2025.1.0.1-SNAPSHOT 2025.1.0 4.0.0 17 17 3.14.0 3.1.4 3.5.3 3.2.1 3.11.2 3.2.7 1.7.0 4.4.1 0.8.13 0.10.6 0.7.0 spring-cloud-alibaba-dependencies spring-cloud-alibaba-examples spring-cloud-alibaba-starters spring-cloud-alibaba-coverage spring-cloud-alibaba-tests org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.springframework.cloud spring-cloud-dependencies ${spring.cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${project.version} pom import org.jacoco jacoco-maven-plugin ${jacoco.version} io.spring.javaformat spring-javaformat-maven-plugin ${spring-javaformat.version} validate true org.sonatype.central central-publishing-maven-plugin ${central.publishing.maven.version} org.apache.maven.plugins maven-eclipse-plugin false .settings/org.eclipse.jdt.ui.prefs ${maven.multiModuleProjectDirectory}/eclipse/org.eclipse.jdt.ui.prefs .settings/org.eclipse.jdt.core.prefs ${maven.multiModuleProjectDirectory}/eclipse/org.eclipse.jdt.core.prefs org.apache.maven.plugins maven-checkstyle-plugin checkstyle-validation validate check ${maven.multiModuleProjectDirectory}/eclipse/checkstyle-suppressions.xml true true true true warning org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} true ${maven.compiler.source} ${maven.compiler.target} org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} true 1 false org.codehaus.mojo flatten-maven-plugin ${flatten-maven-plugin.version} true resolveCiFriendliesOnly flatten process-resources flatten flatten.clean clean clean org.apache.maven.plugins maven-checkstyle-plugin github https://maven.pkg.github.com/alibaba/spring-cloud-alibaba release org.apache.maven.plugins maven-source-plugin ${maven-source-plugin.version} package jar-no-fork org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} none ${maven.compiler.source} package jar org.apache.maven.plugins maven-gpg-plugin ${maven-gpg-plugin.version} verify sign org.sonatype.central central-publishing-maven-plugin true central spring-cloud-alibaba-coverage sentinel-core-example sentinel-openfeign-example sentinel-resttemplate-example sentinel-circuitbreaker-example sentinel-webflux-example sentinel-spring-cloud-gateway-example nacos-discovery-example nacos-config-example nacos-gateway-example business-service order-service storage-service account-service spring-cloud-alibaba-examples rocketmq-comprehensive-example rocketmq-orderly-consume-example rocketmq-broadcast-producer-example rocketmq-broadcast-consumer1-example rocketmq-broadcast-consumer2-example rocketmq-delay-consume-example rocketmq-sql-consume-example rocketmq-example-common rocketmq-tx-example rocketmq-pollable-consume-example spring-cloud-bus-rocketmq-example spring-cloud-alibaba-sidecar-nacos-example spring-cloud-alibaba-sidecar-consul-example nacos-gateway-discovery-example nacos-discovery-provider-example nacos-discovery-consumer-sclb-example nacos-discovery-spring-cloud-config-client-example nacos-discovery-consumer-example nacos-reactivediscovery-consumer-example nacos-gateway-provider-example nacos-discovery-spring-cloud-config-server-example integrated-storage integrated-account integrated-order integrated-gateway integrated-praise-provider integrated-praise-consumer integrated-common integrated-frontend spring-cloud-scheduling-example spring-cloud-alibaba-test-support nacos-tests nacos-config-test nacos-discovery-test rocketmq-tests rocketmq-stream-test sentinel-tests sentinel-flowcontrol-test sentinel-degrade-test ================================================ FILE: spring-cloud-alibaba-coverage/pom.xml ================================================ 4.0.0 com.alibaba.cloud spring-cloud-alibaba ${revision} ../pom.xml spring-cloud-alibaba-coverage Spring Cloud Alibaba Coverage com.alibaba.cloud spring-cloud-alibaba-sentinel-datasource ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ${revision} com.alibaba.cloud spring-cloud-circuitbreaker-sentinel ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-seata ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config ${revision} com.alibaba.cloud spring-cloud-starter-stream-rocketmq ${revision} com.alibaba.cloud spring-cloud-starter-bus-rocketmq ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-sidecar ${revision} org.jacoco jacoco-maven-plugin ${jacoco.version} report-aggregate test report-aggregate ${basedir}/../target/site/jacoco org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-dependencies/pom.xml ================================================ 4.0.0 org.springframework.cloud spring-cloud-dependencies-parent 5.0.0 com.alibaba.cloud spring-cloud-alibaba-dependencies ${revision} pom Spring Cloud Alibaba Dependencies Spring Cloud Alibaba Dependencies 2025.1.0.1-SNAPSHOT 1.8.9 3.1.1 2.6.0 5.3.1 5.10.0 1.13.3 17 17 3.2.1 3.11.2 3.2.7 1.7.0 0.7.0 1.2.27 3.0.5 2.25.1 2.0.17 2.0.58 2.25.1 33.4.8-jre 1.18.38 com.alibaba.nacos nacos-client ${nacos.client.version} com.alibaba.csp sentinel-core ${sentinel.version} com.alibaba.csp sentinel-parameter-flow-control ${sentinel.version} com.alibaba.csp sentinel-datasource-extension ${sentinel.version} com.alibaba.csp sentinel-datasource-apollo ${sentinel.version} com.alibaba.csp sentinel-datasource-zookeeper ${sentinel.version} com.alibaba.csp sentinel-datasource-nacos ${sentinel.version} com.alibaba.csp sentinel-datasource-redis ${sentinel.version} com.alibaba.csp sentinel-datasource-consul ${sentinel.version} com.alibaba.csp sentinel-web-servlet ${sentinel.version} com.alibaba.csp sentinel-spring-cloud-gateway-v6x-adapter ${sentinel.version} com.alibaba.csp sentinel-transport-simple-http ${sentinel.version} com.alibaba.csp sentinel-annotation-aspectj ${sentinel.version} com.alibaba.csp sentinel-reactor-adapter ${sentinel.version} com.alibaba.csp sentinel-cluster-server-default ${sentinel.version} com.alibaba.csp sentinel-cluster-client-default ${sentinel.version} com.alibaba.csp sentinel-spring-webflux-adapter ${sentinel.version} com.alibaba.csp sentinel-api-gateway-adapter-common ${sentinel.version} com.alibaba.csp sentinel-spring-webmvc-v6x-adapter ${sentinel.version} org.apache.seata seata-spring-boot-starter ${seata.version} org.apache.rocketmq rocketmq-client ${rocketmq.version} org.apache.rocketmq rocketmq-acl ${rocketmq.version} net.javacrumbs.shedlock shedlock-spring ${shedlock.version} net.javacrumbs.shedlock shedlock-provider-jdbc-template ${shedlock.version} com.aliyun.schedulerx schedulerx2-worker ${schedulerx.worker.version} com.alibaba.cloud spring-cloud-alibaba-sentinel-datasource ${revision} com.alibaba.cloud spring-cloud-alibaba-sentinel-gateway ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ${revision} com.alibaba.cloud spring-cloud-circuitbreaker-sentinel ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-seata ${revision} com.alibaba.cloud spring-alibaba-nacos-config ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config ${revision} com.alibaba.cloud spring-cloud-starter-stream-rocketmq ${revision} com.alibaba.cloud spring-cloud-starter-bus-rocketmq ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-sidecar ${revision} com.alibaba.cloud spring-cloud-alibaba-commons ${revision} com.alibaba.cloud spring-cloud-starter-alibaba-schedulerx ${revision} com.alibaba druid-spring-boot-starter ${druid.version} com.alibaba druid ${druid.version} org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis.version} org.apache.logging.log4j log4j-core ${log4j-core.version} org.slf4j slf4j-api ${slf4j-api.version} com.alibaba fastjson ${fastjson.version} org.apache.logging.log4j log4j-slf4j2-impl ${log4j-slf4j2-impl.version} com.google.guava guava ${guava.version} org.projectlombok lombok ${lombok.version} github https://maven.pkg.github.com/alibaba/spring-cloud-alibaba release org.apache.maven.plugins maven-source-plugin ${maven-source-plugin.version} package jar-no-fork org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} package jar org.apache.maven.plugins maven-gpg-plugin ${maven-gpg-plugin.version} verify sign org.codehaus.mojo flatten-maven-plugin ${flatten-maven-plugin.version} true resolveCiFriendliesOnly flatten process-resources flatten flatten.clean clean clean org.sonatype.central central-publishing-maven-plugin ${central.publishing.maven.version} true central ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/config/datasource-config.yaml ================================================ spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: 'root' password: 'root' main: allow-bean-definition-overriding: true mybatis: configuration: map-underscore-to-camel-case: true ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/config/integrated-account.yaml ================================================ spring: datasource: url: jdbc:mysql://integrated-mysql:3306/integrated_account?useSSL=false&characterEncoding=utf8 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/config/integrated-consumer.yaml ================================================ spring: datasource: url: jdbc:mysql://integrated-mysql:3306/integrated_praise?useSSL=false&characterEncoding=utf8 cloud: stream: function: definition: consumer; bindings: consumer-in-0: destination: PRAISE-TOPIC-01 content-type: application/json group: praise-consumer-group-PRAISE-TOPIC-01 rocketmq: binder: name-server: rocketmq:9876 bindings: consumer-in-0: consumer: pullInterval: 4000 pullBatchSize: 4 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/config/integrated-gateway.yaml ================================================ spring: cloud: gateway: routes: - id: placeOrder uri: lb://integrated-order predicates: - Path=/order/create - id: queryStorage uri: lb://integrated-storage predicates: - Path=/storage/ - id: queryAccount uri: lb://integrated-account predicates: - Path=/account/ - id: praiseItemRocketMQ uri: lb://integrated-provider predicates: - Path=/praise/rocketmq - id: praiseItemSentinel uri: lb://integrated-provider predicates: - Path=/praise/sentinel - id: queryPraise uri: lb://integrated-consumer predicates: - Path=/praise/query ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/config/integrated-order.yaml ================================================ spring: datasource: url: jdbc:mysql://integrated-mysql:3306/integrated_order?useSSL=false&characterEncoding=utf8 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/config/integrated-provider.yaml ================================================ spring: cloud: stream: bindings: praise-output: destination: PRAISE-TOPIC-01 content-type: application/json rocketmq: binder: name-server: rocketmq:9876 bindings: praise-output: producer: group: test ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/config/integrated-storage.yaml ================================================ spring: datasource: url: jdbc:mysql://integrated-mysql:3306/integrated_storage?useSSL=false&characterEncoding=utf8 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/rocketmq/broker.conf ================================================ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. brokerIP1=172.20.0.4 brokerClusterName = DefaultCluster brokerName = broker-a brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = ASYNC_MASTER flushDiskType = ASYNC_FLUSH ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/scripts/nacos-config-quick.sh ================================================ #!/bin/sh echo "Nacos auto config started" datasourceConfig=$(cat ../config/datasource-config.yaml) storageConfig=$(cat ../config/integrated-storage.yaml) accountConfig=$(cat ../config/integrated-account.yaml) orderConfig=$(cat ../config/integrated-order.yaml) gatewayConfig=$(cat ../config/integrated-gateway.yaml) providerConfig=$(cat ../config/integrated-provider.yaml) consumerConfig=$(cat ../config/integrated-consumer.yaml) groupId="integrated-example" curl -X POST "nacos-server:8848/nacos/v1/cs/configs" -d "dataId=datasource-config.yaml&group=${groupId}&content=${datasourceConfig}" curl -X POST "nacos-server:8848/nacos/v1/cs/configs" -d "dataId=integrated-storage.yaml&group=${groupId}&content=${storageConfig}" curl -X POST "nacos-server:8848/nacos/v1/cs/configs" -d "dataId=integrated-account.yaml&group=${groupId}&content=${accountConfig}" curl -X POST "nacos-server:8848/nacos/v1/cs/configs" -d "dataId=integrated-order.yaml&group=${groupId}&content=${orderConfig}" curl -X POST "nacos-server:8848/nacos/v1/cs/configs" -d "dataId=integrated-gateway.yaml&group=${groupId}&content=${gatewayConfig}" curl -X POST "nacos-server:8848/nacos/v1/cs/configs" -d "dataId=integrated-provider.yaml&group=${groupId}&content=${providerConfig}" curl -X POST "nacos-server:8848/nacos/v1/cs/configs" -d "dataId=integrated-consumer.yaml&group=${groupId}&content=${consumerConfig}" echo "Nacos config pushed successfully finished" ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/config-init/sql/init.sql ================================================ -- Storage库存微服务的数据库业务初始化 DROP DATABASE IF EXISTS integrated_storage; CREATE DATABASE integrated_storage; USE integrated_storage; CREATE TABLE `storage` ( `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT '0', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `commodity_code` (`commodity_code`) ) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8; INSERT INTO `storage` VALUES ('1', '1', '100', '2022-08-07 22:48:29', '2022-08-14 13:49:05'); -- Account账户微服务的数据库业务初始化 DROP DATABASE IF EXISTS integrated_account; CREATE DATABASE integrated_account; USE integrated_account; CREATE TABLE `account` ( `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `money` int(11) DEFAULT '0', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8; INSERT INTO `account` VALUES ('1', 'admin', '3', '2022-08-07 22:53:01', '2022-08-14 13:49:05'); -- Order订单微服务的数据库业务初始化 DROP DATABASE IF EXISTS integrated_order; CREATE DATABASE integrated_order; USE integrated_order; CREATE TABLE `order` ( `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT NULL, `money` int(11) DEFAULT '0', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB AUTO_INCREMENT = 16 DEFAULT CHARSET = utf8; -- 点赞业务的数据库初始化 DROP DATABASE IF EXISTS integrated_praise; CREATE DATABASE integrated_praise; USE integrated_praise; CREATE TABLE `item` ( `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT, `praise` int(11) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8; INSERT INTO `item` VALUES ('1', '0', '2022-08-14 00:33:50', '2022-08-14 14:07:34'); -- Storage库存微服务的数据库Seata初始化 USE integrated_storage; CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -- Storage库存微服务的数据库Seata初始化 USE integrated_account; CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -- Storage库存微服务的数据库Seata初始化 USE integrated_order; CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docker-compose/docker-compose-env.yml ================================================ version: "3" services: # nacos nacos: image: nacos/nacos-server:v3.1.0 hostname: nacos-server restart: always container_name: integrated-example-nacos environment: - PREFER_HOST_MODE=hostname - MODE=standalone ports: - "8848:8848" # mysql mysql: container_name: integrated-example-mysql hostname: integrated-mysql restart: always image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: integrated_storage ports: - "3306:3306" volumes: - ../config-init/sql/init.sql:/docker-entrypoint-initdb.d/init.sql command: [ --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci ] # rocketMQ rmqnamesrv: image: apache/rocketmq:4.9.4 hostname: rocketmq restart: always container_name: integrated-example-rmqnamesrv ports: - "9876:9876" command: sh mqnamesrv rmqbroker: image: apache/rocketmq:4.9.4 restart: always container_name: integrated-example-rmqbroker ports: - "10909:10909" - "10911:10911" volumes: - ../config-init/rocketmq/broker.conf:/opt/rocketmq-4.9.4/conf/broker.conf environment: NAMESRV_ADDR: "rmqnamesrv:9876" JAVA_OPTS: " -Duser.home=/opt" JAVA_OPT_EXT: "-server -Xms128m -Xmx128m -Xmn128m" command: sh mqbroker -c /opt/rocketmq-4.9.4/conf/broker.conf depends_on: - rmqnamesrv # seata seata-server: image: seataio/seata-server:1.5.1 hostname: seata-server restart: always container_name: integrated-example-seata-server ports: - "8091:8091" environment: - SEATA_PORT=8091 - STORE_MODE=file ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docker-compose/docker-compose-service.yml ================================================ version: "3" services: # integrated example module integrated-frontend: image: integrated-frontend hostname: integrated-frontend build: dockerfile: ./integrated-frontend/Dockerfile context: ../ env_file: - .env container_name: integrated-frontend ports: - ${FRONTEND_PORT}:${FRONTEND_PORT} depends_on: - integrated-gateway - integrated-account - integrated-order - integrated-storage - integrated-praise-consumer - integrated-praise-provider integrated-gateway: image: integrated-gateway hostname: gateway-service build: dockerfile: ./integrated-gateway/Dockerfile context: ../ env_file: - .env container_name: integrated-gateway ports: - ${GATEWAY_PORT}:${GATEWAY_PORT} integrated-account: image: integrated-account build: dockerfile: ./integrated-account/Dockerfile context: ../ env_file: - .env container_name: integrated-account ports: - ${ACCOUNT_PORT}:${ACCOUNT_PORT} integrated-order: image: integrated-order build: dockerfile: ./integrated-order/Dockerfile context: ../ env_file: - .env container_name: integrated-order ports: - ${ORDER_PORT}:${ORDER_PORT} integrated-storage: image: integrated-storage build: dockerfile: ./integrated-storage/Dockerfile context: ../ env_file: - .env container_name: integrated-storage ports: - ${STORAGE_PORT}:${STORAGE_PORT} integrated-praise-provider: image: integrated-praise-provider build: dockerfile: ./integrated-praise-provider/Dockerfile context: ../ env_file: - .env container_name: integrated-praise-provider ports: - ${PRAISE_PROVIDER_PORT}:${PRAISE_PROVIDER_PORT} integrated-praise-consumer: image: integrated-praise-consumer build: dockerfile: ./integrated-praise-consumer/Dockerfile context: ../ env_file: - .env container_name: integrated-praise-consumer ports: - ${PRAISE_CONSUMER_PORT}:${PRAISE_CONSUMER_PORT} depends_on: - integrated-praise-provider ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docs/en/docker-compose-deployment.md ================================================ # Spring Cloud Alibaba Containerized Deployment Best Practices | Docker-Compose Edition ## Preparation If you have not installed Docker or Docker-Compose, please follow the official documentation to build the environment > Note: When using Docker-Compose to experience the demo, please make sure that the local machine memory resource is >= 24G! - Docker:https://docs.docker.com/desktop/install/linux-install/ - Docker-Compose:https://docs.docker.com/compose/install/ ### Hosts configuration To ensure that the code can start properly, please configure the local host mapping first, add the following mapping to the configuration file. ```shell # for integrated-example 127.0.0.1 integrated-mysql 127.0.0.1 nacos-server 127.0.0.1 seata-server 127.0.0.1 rocketmq 127.0.0.1 gateway-service 127.0.0.1 integrated-frontend ``` ### Preparing jar packages Go to the `spring-cloud-alibaba-examples` directory and run the `mvn package` command to compile the project and generate the jar package, so as to prepare for the subsequent construction of the docker service image. ## Quickly start ### Component start Enter `spring-cloud-alibaba-examples/integration-example` directory, run the following command in the terminal to quickly deploy the components required to run example: `docker-compose -f ./docker-compose/docker-compose-env.yml up -d`. ### Add configuration After docker-compose-env.yml is run successfully, add the Nacos configuration: - Enter `spring-cloud-alibaba-examples/integration-example` directory; - Execute the `config-init/scripts/nacos-config-quick.sh` script file in the terminal. The one-click import of all micro-service configurations is complete. > Note: windows operating systems can use `git bash` to execute shell script files to complete the configuration import. ### Service start Enter `spring-cloud-alibaba-examples/integration-example` directory, Run the following command in the terminal to quickly deploy the services required for running example: `docker-compose -f ./docker-compose/docker-compose-service.yml up -d`. ## Stop all containers ### Stops the service container Enter `spring-cloud-alibaba-examples/integration-examplee` directory, Run the following command in the terminal to `docker-compose -f ./docker-compose/docker-compose-service.yml down` to stop the running example service container. ### Stops the component container Enter `spring-cloud-alibaba-examples/integration-example` directory, Run the following command in the terminal to `docker-compose -f ./docker-compose/docker-compose-env.yml down` to stop the running example component container. > When the container starts, you can observe the startup process of the container through `docker-compose- f docker-compose-*.yml up` ! ## Experience Demo ### Distributed Transaction Capabilities #### Scenario Description For the distributed transaction capability, we provide the scenario **where a user places an order for goods** and after placing the order. - First request the inventory module and deduct the inventory - Deduct the account balance - Generate order information to return a response #### Start test Visit `http://integrated-frontend:8080/order` to experience the corresponding scenario. By clicking the order button directly to submit the form, we simulate the client sending a request to the gateway to create an order. - The user's userId is admin - The item number of the user's order is 1 - The number of items purchased in this order is 1 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155416524.png) In this demo example, the unit price of each item is 2 for demonstration purposes. And in the previous preparation, **initialize business database table** we created a new user userId = admin with a balance of $3, and a new item numbered 1 with 100 units in stock. So by doing the above, we will create an order, deduct the number of items in stock corresponding to item number 1 (100-1=99), and deduct the balance of the admin user (3-2=1). ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155429801.png) If the same interface is requested again, again the inventory is deducted first (99-1=98), but an exception is thrown because the admin user's balance is insufficient and is caught by Seata, which performs a two-stage commit of the distributed transaction and rolls back the transaction. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155436112.png) You can see that the database still has 99 records in stock because of the rollback. ### Fused flow limiting, peak shaving capability #### Scenario Description For service fusion limiting and peak and valley cutting in the context of high traffic, we provide a scenario **where users make likes for products**. In this scenario, we provide two ways to deal with high traffic. - Sentinel binds specified gateway routes on the gateway side for fusion degradation of services. - RocketMQ performs traffic clipping, where the producer sends messages to RocketMQ under high traffic requests, while the consumer pulls and consumes through a configurable consumption rate, reducing the pressure of high traffic direct requests to the database to increase the number of likes requests. #### Start test - Sentinel service meltdown degradation Visit `http://integrated-frontend:8080/sentinel` to experience the corresponding scenario. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155501290.png) The Gateway routing point service has a flow limit rule of 5, while 10 concurrent requests are simulated on the front end through asynchronous processing. Therefore, we can see that Sentinel performs a service fusion on the Gateway side to return the fallback to the client for the extra traffic, while the number of likes in the database is updated (+5). ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20220914155755103.png) - RocketMQ is performing peak and valley reduction Visit `http://integrated-frontend:8080/rocketmq` to experience the corresponding scenario. Since we previously configured the consumption rate and interval of the `integrated-praise-consumer` consumer module in Nacos, we simulate 1000 requests for likes at the click of a button, and the `integrated-praise-provider` will deliver 1000 requests to the Broker, and the consumer module will consume them according to the configured consumption rate, and update the database with the product data of the likes, simulating the characteristics of RocketMQ to cut the peaks and fill the valleys under high traffic. You can see that the number of likes in the database is being dynamically updated. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016173604059.png) ## Other This example **is just a selection of typical features for each component to serve the application scenario**. If you are interested or want to go deeper, you are welcome to study the individual example documentation for each component. - Nacos examples - [Nacos config example](../../../nacos-example/readme.md) - [Nacos discovery example](../../../nacos-example/readme.md) - [Sentinel core example](../../../sentinel-example/README.md) - [Seata example](../../../seata-example/readme.md) - [RocketMQ example](../../../rocketmq-example/readme.md) ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docs/en/kubernetes-deployment.md ================================================ # Spring Cloud Alibaba Containerized Deployment Best Practices | Kubernetes Helm-Chart Edition ## Preparation This is the Spring Cloud Alibaba (hereinafter referred to as SCA) Best Practices Kubernetes deployment version, which requires you to prepare the following environment. - Kubernetes (We recommend using Docker Desktop's built-in integrated Kubernetes environment for this experience.) - Helm If the test machine does not already have the above environment, please go to the official documentation to build the environment. - [Helm Installation](https://helm.sh/zh/docs/intro/install/) - [Kubernetes Docker Desktop Quick Installation](https://docs.docker.com/desktop/kubernetes/) Here expose the services of the Pod in Kubernetes to the outside world by means of NodePort, and configure the ip mapping of the Kubernetes cluster node before starting the test. ```shell # Please adjust with the public ip of your K8S node 120.24.xxx.xxx integrated-frontend 120.24.xxx.xxx gateway-service 120.24.xxx.xxx integrated-mysql-web 120.24.xxx.xxx nacos-mysql-web 120.24.xxx.xxx nacos-svc ``` ## Start the test Go to the ``spring-cloud-alibaba-examples/integrated-example`` directory and execute the following command to deploy the application using Helm. ```shell helm package helm-chart helm install integrated-example integrated-example-1.0.0.tgz ``` By running the above command, quickly deploy the best practice example through Helm according to the Helm Chart documentation provided by the SCA community. You can check the deployment status of each container resource through the `kubectl` command provided by Kubernetes, and wait patiently for **all containers to finish starting** to experience the usage scenarios and capabilities of each component on the corresponding page. If you want to stop the experience, enter the following command. ```shell helm uninstall integrated-example ``` ### Distributed Transaction Capabilities #### Scenario Description For the distributed transaction capability, SCA community provide a scenario **where a user places an order to purchase goods** and after placing the order. - First request the inventory module and deduct the inventory - Deduct the account balance - Generate order information to return a response ##### Start test Visit `http://integrated-frontend:30080/order` to experience the corresponding scenario. By clicking directly on the order button to submit the form, simulate the client sending a request to the gateway to create an order. - The user's userId is admin - The user places an order with item number 1 - The number of items purchased in this order is 1 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143033445.png) In this demo example, the unit price of each item is 2 for demonstration purposes. While initializing the `integrated-mysql` container, **initializing the business database table** creates a new user, the user's userId is admin, with a balance of $3; and a new item numbered 1 with 100 units in stock. So by doing the above, application will create an order, deduct the number of items in stock corresponding to item number 1 (100-1=99), and deduct the balance of the admin user (3-2=1). ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143057730.png) If the same interface is requested again, again the inventory is deducted first (99-1=98), but an exception is thrown because the admin user's balance is insufficient and is caught by Seata, which performs a two-stage commit of the distributed transaction and rolls back the transaction. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143104810.png) You can see that the database still has 99 records in stock because of the rollback. ### Fused flow limiting, peak shaving capability #### Scenario Description For service fusion limiting and peak and valley cutting in the context of high traffic, SCA community provide a scenario** where users make likes for products**. In this scenario, we provide two ways to deal with high traffic. - Sentinel binds specified gateway routes on the gateway side for fusion degradation of services. - RocketMQ performs traffic clipping, where the producer sends messages to RocketMQ under high traffic requests, while the consumer pulls and consumes through a configurable consumption rate, reducing the pressure of high traffic direct requests to the database to increase the number of likes requests. #### Startup test - Sentinel Service Meltdown Degradation Visit `http://integrated-frontend:30080/sentinel` to experience the corresponding scenario. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143120697.png) The Gateway routing point service has a flow limit rule of 5, while 10 concurrent requests are simulated on the front end through asynchronous processing. Therefore, we can see that Sentinel performs a service fusion on the Gateway side to return the fallback to the client for the extra traffic, while the number of likes in the database is updated (+5). ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143203773.png) - RocketMQ is performing peak and valley reduction Visit `http://integrated-frontend:30080/rocketmq` to experience the corresponding scenario. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143342664.png) Since the consumption rate and interval of the `integrated-praise-consumer` consumer module is configured in Nacos before, the application will simulate 1000 "like" requests when clicking the button, `integrated-praise-provider` will deliver 1000 requests to the Broker, and the consumer module will consume them according to the configured consumption rate, and update the database with the product data of the likes, simulating the characteristics of RocketMQ to cut the peaks and fill the valleys under high traffic. You can see that the number of likes in the database is being dynamically updated. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143352619.png) ## Other This example **is just a selection of typical features for each component to serve the application scenario**. Of course, there is more to each component than just what is demonstrated in the best practices, so if you are interested or want to go deeper, feel free to read the individual example documentation for each component. - Nacos examples - [Nacos config example](../../../nacos-example/readme.md) - [Nacos discovery example](../../../nacos-example/readme.md) - [Sentinel core example](../../../sentinel-example/README.md) - [Seata example](../../../seata-example/readme.md) - [RocketMQ example](../../../rocketmq-example/readme.md) ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docs/en/local-deployment.md ================================================ # Spring Cloud Alibaba Containerized Deployment Best Practices | Local Deployment Edition ## Preparation ### Environment Declaration Before running the local example, you need to ensure that the local machine has the following basic environment. If you do not have the current local environment, the following steps to demonstrate the construction process. You can also quickly launch the component through the docker-compose file provided by the Spring Cloud Alibaba (SCA) community. - Nacos server - Seata server - RocketMQ server - MySQL server ### Component Service Versions For each component version of this project, please go to the release page of each community to download and decompression run. - [Nacos: version 2.1.0](https://github.com/alibaba/nacos/releases) - [Seata: version 1.5.1](https://github.com/seata/seata/releases) - [RocketMQ: version 4.9.4](https://github.com/apache/rocketmq/releases) - MySQL: version 5.7 ### Hosts configuration To ensure that the code can start properly, please configure the local host mapping first, add the following mapping to the configuration file. ```shell # for integrated-example 127.0.0.1 integrated-mysql 127.0.0.1 nacos-server 127.0.0.1 seata-server 127.0.0.1 rocketmq 127.0.0.1 gateway-service 127.0.0.1 integrated-frontend ``` ### Database configuration Before you start the database configuration, please make sure the MySQL server is on. #### Initialize business tables For the first scenario, the order, account, and inventory microservices all need their own databases, while the second scenario simulates a database for storing like information as well. Run the sql script `spring-cloud-alibaba-examples/integrated-example/config-init/sql/init.sql` to create the environment required for the business and the Seata-related tables in one click. ### Nacos Configuration At this point, the database services are configured and you need to configure the Nacos configuration center for all the microservice configuration files. #### Nacos startup For the sake of example, here we use the ``standalone`` mode of Nacos, go to the unpacked directory of Nacos and execute the following command. ```shell #Linux/Mac environment sh bin/startup.sh -m standalone #If you are in Ubuntu and the above command gives you an error [[symbol not found, you can run the following command bash bin/startup.sh -m standalone #Win environment . \bin\startup.cmd -m standalone ```` #### Adding configuration files Before bulk importing the configuration, please modify the datasource configuration (username and password) in `spring-cloud-alibaba-examples/integrated-example/config-init/config/datasource-config.yaml`. After that, run `spring-cloud-alibaba-examples/integrated-example/config/scripts/nacos-config-quick.sh` to complete the one-click import of all microservice configurations. ```shell # linux sh nacos-config-quick.sh # windows can use git bash to import the configuration, run the command as above ``` ### Seata Configuration After the Nacos service registry and configuration center are deployed, here is the configuration of the Seata server. Seata's db mode requires additional configuration of database information and modification of the Seata Server configuration file, and the configuration file has been merged in the new version compared to the old version, so for demonstration purposes, Seata Server is started in `file` mode on Seata standalone. #### Start Seata Server Go to the seata directory after the release and execute the following command. ```shell #Linux/Mac environment sh . /bin/seata-server.sh #Win environment bin\seata-server.bat ``` ### RocketMQ configuration After the Seata service starts, you can start the RocketMQ NameServer and Broker services. Go to the unpacked rocketmq directory after the release and execute the following command. #### Start the NameServer ```shell #Linux/Mac environment sh bin/mqnamesrv #Win environment . \bin\mqnamesrv.cmd ``` #### Start Broker ```shell #Linux/Mac environment sh bin/mqbroker #Win environment . \bin\mqbroker.cmd ``` ## Run the demo example After the preparation work is done, you can run the demo, mainly according to different usage scenarios, you can experience the user order (distributed transaction capability) and simulate the high traffic point (meltdown and limit the flow as well as the ability to cut the peak and fill the valley) respectively. First, you need to start the `integrated-frontend` and `integrated-gateway` projects separately. - `integrated-frontend` module is front page for best practice examples. - `integral-gateway` module is the gateway for the entire best practice example. ### Distributed Transaction Capabilities #### Scenario Description For the distributed transaction capability, we provide the scenario **where a user places an order for goods** and after placing the order. - First request the inventory module and deduct the inventory - Deduct the account balance - Generate order information to return a response ##### Start test Start `integrated-storage`,`integrated-account`,`integrated-order` microservices respectively. Visit `http://integrated-frontend:8080/order` to experience the corresponding scenario. By clicking the order button directly to submit the form, application simulate the client sending a request to the gateway to create an order. - The user's userId is admin - The item number of the user's order is 1 - The number of items purchased in this order is 1 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155416524.png) In this demo example, the unit price of each item is 2 for demonstration purposes. And in the previous preparation, **initialize business database table** application created a new user, the user's userId is admin with a balance of $3, and a new item numbered 1 with 100 units in stock. So by doing the above, we will create an order, deduct the number of items in stock corresponding to item number 1 (100-1=99), and deduct the balance of the admin user (3-2=1). ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155429801.png) If the same interface is requested again, again the inventory is deducted first (99-1=98), but an exception is thrown because the admin user's balance is insufficient and is caught by Seata, which performs a two-stage commit of the distributed transaction and rolls back the transaction. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155436112.png) You can see that the database still has 99 records in stock because of the rollback. ### Fused flow limiting, peak shaving capability #### Scenario Description For service fusion limiting and peak and valley cutting in the context of high traffic, SCA community provide a scenario **where users make likes for products**. In this scenario, we provide two ways to deal with high traffic. - Sentinel binds specified gateway routes on the gateway side for fusion degradation of services. - RocketMQ performs traffic clipping, where the producer sends messages to RocketMQ under high traffic requests, while the consumer pulls and consumes through a configurable consumption rate, reducing the pressure of high traffic direct requests to the database to increase the number of likes requests. #### Startup test Start the `integrated-praise-provider` and `integrated-praise-consumer` modules separately. - Sentinel service meltdown degradation Visit `http://integrated-frontend:8080/sentinel` to experience the corresponding scenario. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155501290.png) The Gateway routing point service has a flow limit rule of 5, while 10 concurrent requests are simulated on the front end through asynchronous processing. Therefore, we can see that Sentinel performs a service fusion on the Gateway side to return the fallback to the client for the extra traffic, while the number of likes in the database is updated (+5). ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20220914155755103.png) - RocketMQ is performing peak and valley reduction Visit `http://integrated-frontend:8080/rocketmq` to experience the corresponding scenario. Since previously configured the consumption rate and interval of the `integrated-praise-consumer` consumer module in Nacos, simulate 1000 requests for likes at the click of a button, and the `integrated-praise-provider` will deliver 1000 requests to the Broker, and the consumer module will consume them according to the configured consumption rate, and update the database with the product data of the likes, simulating the characteristics of RocketMQ to cut the peaks and fill the valleys under high traffic. You can see that the number of likes in the database is being dynamically updated. ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016173604059.png) ## Other This example **is just a selection of typical features for each component to serve the application scenario**. If you are interested or want to go deeper, you are welcome to study the individual example documentation for each component. - Nacos examples - [Nacos config example](../../../nacos-example/readme.md) - [Nacos discovery example](../../../nacos-example/readme.md) - [Sentinel core example](../../../sentinel-example/README.md) - [Seata example](../../../seata-example/readme.md) - [RocketMQ example](../../../rocketmq-example/readme.md) ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docs/en/readme.md ================================================ # Integrated Example ## Project Description This project is a demo of Spring Cloud Alibaba (hereinafter referred to as SCA) containerized deployment best practices, and is an example project integrating SCA components (Nacos, Sentinel, Seata, RocketMQ). The main components used and their usage features are as follows. - Spring Cloud Gateway: gateway - Nacos: configuration centre and service registry - Sentinel: fusion flow limiting - Seata: Distributed Transactions - RocketMQ: message queues for peak and valley reduction - Docker: Microservices Containerized Deployment - Kubernetes Helm Chart ![Overall Overview](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20220816004541921.png) ## Application Scenario Description In this demo, SCA community provide two business scenarios. 1) A scenario where a user places an order for goods and after placing the order. - First request the inventory module to deduct the inventory - Deduct the account balance - Generate order information and return a response 2) The user likes the goods (simulating the producer-consumer application scenario of MQ) and returns the details (number of likes, etc.) after the goods have been liked. ### Detailed description of the component 1) In which the scenario where the user places an order for the goods mainly uses Seata to perform distributed transactions to represent the capabilities. 2) The scenario where the user likes a product simulates a high traffic environment with Sentinel for flow limiting or RocketMQ for peak shaving. In this scenario, SCA community provide two ways to deal with high traffic. - Sentinel binds a specified gateway route on the gateway side for service fusion degradation. - RocketMQ performs peak-shaving, where producers send messages to RocketMQ and consumers pull and consume at configurable consumption rates, reducing the pressure of high traffic direct requests to the database and increasing the number of likes. #### Spring Cloud Gateway A gateway to the microservices module. Spring Cloud GateWay integrates with Nacos, enabling dynamic routing configuration. By listening for changes to the Nacos configuration, the service gateway routing configuration is dynamically refreshed so that each time the routing information changes, there is no need to modify the configuration file and then restart the service. #### Nacos The configuration centre for each microservice, the service registry. - Configuration Centre - Shared configuration: MySQL data source related information configuration. - Registration Centre - All microservice modules are registered to Nacos for service registration and discovery. - Integration with Spring Cloud Gateway gateway. #### Seata Seata-based AT model for distributed transaction processing for the Inventory, Accounts and Orders modules. Roll back transactions whenever stock is low/account balance is low. #### Sentinel Service fusion flow limiting for point and click scenarios. Integrates Nacos Configuration Center with Spring Cloud Gateway to enable dynamic configuration of fused flow limiting rules for specified routing rules. #### RocketMQ Used for peaks and valleys reduction of like service traffic. By sending high volume like requests from the producer to the mq, the consumer module pulls from the mq and consumes them with a certain frequency, rather than simply fusing and limiting the degradation of the service directly, enabling RocketMQ's ability to shave peaks and valleys for high volume traffic. ## Release Notes This project provides a [local-deployment](local-deployment.md), [docker-compose version](docker-compose-deployment.md) and a [Kubernetes Helm-Chart version](kubernetes-deployment.md). - To learn how to configure the components and build the complete environment, we recommend learning the [local-deployment](local-deployment.md). - If you only want to run the sample code, avoid the tedious local environment construction process, and do not want to use the K8S cluster. You can try using [docker-compose version] (docker-compose-deployment.md). - If you want to quickly experience the components on a K8S cluster and skip the process of deploying each component, please check out the [Kubernetes Helm-Chart version](kubernetes-deployment.md). ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docs/zh/docker-compose-deploy-zh.md ================================================ # Spring Cloud Alibaba 容器化部署最佳实践 | Docker-Compose 版本 ## 准备工作 > Note: 使用 Docker-Compose 方式体验 Demo 时,请确保本地机器内存资源 >= 24G! 如果您还没有安装 Docker 和 Docker-Compose,请按照官方文档来构建运行环境: - Docker:https://docs.docker.com/desktop/install/linux-install/ - Docker-Compose:https://docs.docker.com/compose/install/ ### Hosts 配置 为确保代码能够正常启动,请先配置本地主机映射,将以下映射添加到配置文件中。 ```shell # for integrated-example 127.0.0.1 integrated-mysql 127.0.0.1 nacos-server 127.0.0.1 seata-server 127.0.0.1 rocketmq 127.0.0.1 gateway-service 127.0.0.1 integrated-frontend ``` ### 准备 jar 包 进入 `spring-cloud-alibaba-examples` 目录下,执行 `mvn package` 命令编译项目生成 jar 包,为后续 Docker 构建服务镜像做准备。 ## 快速启动 ### 组件启动 进入 `spring-cloud-alibaba-examples/integrated-example` 目录下,在终端中执行以下命令 `docker-compose -f ./docker-compose/docker-compose-env.yml up -d` 来快速部署运行 example 所需组件。 ### 添加配置 docker-compose-env.yml 文件运行成功之后,添加 Nacos 配置: 1. 进入 `spring-cloud-alibaba-examples/integrated-example` 目录下; 2. 在终端中执行 `config-init/scripts/nacos-config-quick.sh` 脚本文件。 完成所有微服务配置的一键导入。 > 注意:windows 操作系统可以通过 `git bash` 执行 shell 脚本文件完成配置导入。 ### 服务启动 进入 `spring-cloud-alibaba-examples/integrated-example` 目录下,在终端中执行以下命令 `docker-compose -f ./docker-compose/docker-compose-service.yml up -d` 来快速部署运行 example 所需服务。 ## 停止所有容器 ### 停止服务容器 进入 `spring-cloud-alibaba-examples/integrated-example` 目录下,在终端中执行以下命令 `docker-compose -f ./docker-compose/docker-compose-service.yml down` 来停止正在运行的 example 服务容器。 ### 停止组件容器 进入 `spring-cloud-alibaba-examples/integrated-example` 目录下,在终端中执行以下命令 `docker-compose -f ./docker-compose/docker-compose-env.yml down` 来停止正在运行的 example 组件容器。 > 在容器启动时,可以通过 `docker-compose -f docker-compose-*.yml up` 观察容器的启动过程! ## 体验 Demo ### 分布式事务能力 #### 场景说明 针对分布式事务能力,SCA 社区提供了**用户下单购买货物的场景**,下单后: - 先请求库存模块,扣减库存 - 扣减账户余额 - 生成订单信息返回响应 #### 启动测试 访问 `http://integrated-frontend:8080/order` 来体验对应场景。 直接点击下单按钮提交表单,应用模拟客户端向网关发送了一个创建订单的请求。 - 用户的 userId 为 admin - 用户下单的商品编号为1号 - 此次订单购买的商品个数为1个 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155416524.png) 在本 demo 示例中,为了便于演示,每件商品的单价都为2。 而在前面的准备工作中,**初始化业务数据库表**的时候应用新建了一个用户,用户 userId 为 admin,余额为 3 元;同时新建了一个编号为 1 号的商品,库存为 100 件。 因此通过上述的操作,应用会创建一个订单,扣减对应商品编号为 1 号的库存个数(100-1=99),扣减 admin 用户的余额(3-2=1)。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155429801.png) 如果再次请求相同的接口,同样是先扣减库存(99-1=98),但是会因为 admin 用户余额不足而抛出异常,并被 Seata 捕获,执行分布式事务二阶段提交,回滚事务。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155436112.png) 可以看到数据库中库存的记录因为回滚之后仍然为 99 件。 ### 熔断限流,削峰填谷能力 #### 场景说明 针对大流量背景下的服务熔断限流,削峰填谷,SCA 社区提供了**用户为商品进行点赞的场景**。在此场景下,SCA 社区提供了两种应对大流量的处理方式。 - Sentinel 在网关侧绑定指定网关路由进行服务的熔断降级。 - RocketMQ 进行流量削峰填谷,在大流量请求下,生产者向 RocketMQ 发送消息,而消费者则通过可配置的消费速率进行拉取消费,减少大流量直接请求数据库增加点赞请求的压力。 #### 启动测试 - Sentinel 服务熔断降级 访问 `http://integrated-frontend:8080/sentinel` 体验对应场景。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155501290.png) 网关路由点赞服务的限流规则为 5,而在前端通过异步处理模拟了 10 次并发请求。 因此可以看到 Sentinel 在 Gateway 侧针对多出的流量进行了服务熔断返回 fallback 给客户端,同时数据库的点赞数进行了更新(+5)。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20220914155755103.png) - RocketMQ 进行流量削峰填谷 访问 `http://integrated-frontend:8080/rocketmq` 体验对应场景。 由于之前在 Nacos 中配置了 `integrated-praise-consumer` 消费者模块的消费速率以及间隔,在点击按钮时应用模拟 1000 个点赞请求,针对 1000 个点赞请求,`integrated-praise-provider` 会将 1000 次请求都向 Broker 投递消息,而在消费者模块中会根据配置的消费速率进行消费,向数据库更新点赞的商品数据,模拟大流量下 RocketMQ 削峰填谷的特性。 可以看到数据库中点赞的个数正在动态更新。 ![image-20221016173604059](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016173604059.png) ## 其他 本示例**仅是针对各个组件选取出了较为典型的功能特性来服务应用场景** 当然各个组件的功能特性不仅仅只包含最佳实践中演示的这些,如果您感兴趣或是想要深入了解,欢迎学习各个组件的独立 example 相关文档。 - Nacos examples - [Nacos config example](../../../nacos-example/readme.md) - [Nacos discovery example](../../../nacos-example/readme.md) - [Sentinel core example](../../../sentinel-example/README-zh.md) - [Seata example](../../../seata-example/readme.md) - [RocketMQ example](../../../rocketmq-example/readme.md) ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docs/zh/kubernetes-deployment-zh.md ================================================ # Spring Cloud Alibaba容器化部署最佳实践 | Kubernetes Helm-Chart 版本 ## 准备工作 此版本为 Spring Cloud Alibaba (后文简称为SCA)最佳实践 Kubernetes 部署版本,运行示例需要准备如下环境: - Kubernetes(建议使用 Docker Desktop 内置集成的 Kubernetes 环境进行体验。) - Helm 如果测试机器上还未具备如上环境,请移步至对应官方文档进行环境搭建。 - [Helm 安装](https://helm.sh/zh/docs/intro/install/) - [Kubernetes Docker Desktop 快捷安装](https://docs.docker.com/desktop/kubernetes/) 在这里通过 NodePort 的方式来向外界暴露 Kubernetes 中 Pod 的服务,在启动测试前还需配置好 Kubernetes 集群节点的 ip 映射。 ```sh # 实际情况请结合您的 K8S 节点的公网 ip 进行调整 120.24.xxx.xxx integrated-frontend 120.24.xxx.xxx gateway-service 120.24.xxx.xxx integrated-mysql-web 120.24.xxx.xxx nacos-mysql-web 120.24.xxx.xxx nacos-svc ``` ## 启动测试 进入到 `spring-cloud-alibaba-examples/integrated-example` 目录下,执行如下命令利用 Helm 部署应用程序。 ```shell helm package helm-chart helm install integrated-example integrated-example-1.0.0.tgz ``` 通过运行上述命令,根据SCA社区提供的 Helm Chart 文档通过 Helm 快速完成最佳实践示例的部署。 可以通过 Kubernetes 提供的 `kubectl` 命令查看各容器资源部署的情况,耐心等待**所有容器完成启动后**即可到对应页面体验各个组件的使用场景及能力。 如果您想停止体验,输入如下命令。 ```shell helm uninstall integrated-example ``` ### 分布式事务能力 #### 场景说明 针对分布式事务能力,SCA社区提供了**用户下单购买货物的场景**,下单后: - 先请求库存模块,扣减库存 - 扣减账户余额 - 生成订单信息返回响应 ##### 启动测试 访问 `http://integrated-frontend:30080/order` 来体验对应场景。 直接点击下单按钮提交表单,模拟客户端向网关发送了一个创建订单的请求。 - 用户的 userId 为 admin - 用户下单的商品编号为1号 - 此次订单购买的商品个数为1个 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143033445.png) 在本 demo 示例中,为了便于演示,每件商品的单价都为2。 而在 `integrated-mysql` 容器的初始化时,**初始化业务数据库表**的时候新建了一个用户,用户的userId为admin,余额为 3 元;同时新建了一个编号为 1 号的商品,库存为 100 件。 因此通过上述的操作,应用会创建一个订单,扣减对应商品编号为 1 号的库存个数(100-1=99),扣减 admin 用户的余额(3-2=1)。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143057730.png) 如果再次请求相同的接口,同样是先扣减库存(99-1=98),但是会因为 admin 用户余额不足而抛出异常,并被 Seata 捕获,执行分布式事务二阶段提交,回滚事务。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143104810.png) 可以看到数据库中库存的记录因为回滚之后仍然为 99 件。 ### 熔断限流,削峰填谷能力 #### 场景说明 针对大流量背景下的服务熔断限流,削峰填谷,SCA社区提供了**用户为商品进行点赞的场景**。在此场景下,SCA社区提供了两种应对大流量的处理方式。 - Sentinel 在网关侧绑定指定网关路由进行服务的熔断降级。 - RocketMQ 进行流量削峰填谷,在大流量请求下,生产者向 RocketMQ 发送消息,而消费者则通过可配置的消费速率进行拉取消费,减少大流量直接请求数据库增加点赞请求的压力。 #### 启动测试 - Sentinel 服务熔断降级 访问 `http://integrated-frontend:30080/sentinel` 体验对应场景。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143120697.png) 网关路由点赞服务的限流规则为 5,而在前端通过异步处理模拟了 10 次并发请求。 因此可以看到 Sentinel 在 Gateway 侧针对多出的流量进行了服务熔断返回 fallback 给客户端,同时数据库的点赞数进行了更新(+5)。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143203773.png) - RocketMQ 进行流量削峰填谷 访问 `http://integrated-frontend:30080/rocketmq` 体验对应场景。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143342664.png) 由于之前在 Nacos 中配置了 `integrated-praise-consumer` 消费者模块的消费速率以及间隔,在点击按钮时应用将会模拟 1000 个点赞请求,针对 1000 个点赞请求,`integrated-praise-provider` 会将 1000 次请求都向 Broker 投递消息,而在消费者模块中会根据配置的消费速率进行消费,向数据库更新点赞的商品数据,模拟大流量下 RocketMQ 削峰填谷的特性。 可以看到数据库中点赞的个数正在动态更新。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016143352619.png) ## 其他 本示例**仅是针对各个组件选取出了较为典型的功能特性来服务应用场景**。 当然各个组件的功能特性不仅仅只包含最佳实践中演示的这些,如果您对SCA感兴趣或是想要深入了解SCA项目,欢迎阅览各个组件的独立 example 相关文档。 - Nacos examples - [Nacos config example](../../../nacos-example/readme.md) - [Nacos discovery example](../../../nacos-example/readme.md) - [Sentinel core example](../../../sentinel-example/README-zh.md) - [Seata example](../../../seata-example/readme.md) - [RocketMQ example](../../../rocketmq-example/readme.md) - ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docs/zh/local-deployment-zh.md ================================================ # Spring Cloud Alibaba 容器化部署最佳实践 | 本地部署版本 ## 准备工作 ### 环境声明 在运行本地示例之前,需要保证本机具备以下的基础环境,如果您的本地没有当前的环境,下面会一步步进行搭建,演示搭建过程。 当然您也可以通过 Spring Cloud Alibaba (下文简称为SCA)社区提供的docker-compose文件快速启动相应组件。 - Nacos 服务端 - Seata 服务端 - RocketMQ 服务端 - MySQL 服务端 ### 组件服务版本 本项目的各个组件版本请移步至各个社区的 release 页面进行下载并解压运行。 - [Nacos: 2.1.0 版本](https://github.com/alibaba/nacos/releases) - [Seata: 1.5.1 版本](https://github.com/seata/seata/releases) - [RocketMQ: 4.9.4 版本](https://github.com/apache/rocketmq/releases) - MySQL: 5.7 版本 ### Hosts配置 为了保证代码可以正常启动,请先配置好本机的 host 映射,在配置文件中新增如下的映射。 ```shell # for integrated-example 127.0.0.1 integrated-mysql 127.0.0.1 nacos-server 127.0.0.1 seata-server 127.0.0.1 rocketmq 127.0.0.1 gateway-service 127.0.0.1 integrated-frontend ``` ### 数据库配置 下面开始本地环境搭建准备,在数据库配置开始之前,请确保 MySQL 的服务端开启。 #### 初始化业务表 针对第一个场景,订单、账户、库存微服务都需要各自的数据库,而第二个场景模拟点赞也需要存储点赞信息的数据库。 运行 `spring-cloud-alibaba-examples/integrated-example/config-init/sql/init.sql` 的 sql 脚本一键创建业务所需的环境以及 Seata 相关的表。 ### Nacos配置 至此,数据库的服务配置完毕,下面需要配置 Nacos 的配置中心有关所有的微服务配置文件。 #### Nacos启动 为了便于 example 的演示,这里采用 Nacos 的 `standalone` 模式启动,进入到 Nacos 解压后的目录下,执行如下命令。 ```shell #Linux/Mac环境 sh bin/startup.sh -m standalone #如果您是Ubuntu环境,执行上述命令启动报错提示[[符号找不到,可以执行如下的命令 bash bin/startup.sh -m standalone #Win环境 .\bin\startup.cmd -m standalone ``` #### 新增配置文件 在批量导入配置之前,请先修改 `spring-cloud-alibaba-examples/integrated-example/config-init/config/datasource-config.yaml` 中的数据源配置**(用户名和密码)**。 之后运行 `spring-cloud-alibaba-examples/integrated-example/config-init/scripts/nacos-config-quick.sh` 来完成所有微服务配置的一键导入。 ```shell # linux sh nacos-config-quick.sh # windows 可以使用git bash来完成配置的导入 执行命令同上 ``` ### Seata 配置 Nacos 服务注册中心以及配置中心部署完毕之后,下面是 Seata 服务端的配置。 Seata 的 db 模式需要额外配置数据库信息以及修改 Seata 服务端的配置文件,且在新版本中配置文件相较于旧版本进行了合并,因此这里为了便于演示方便,采用 Seata 单机的`file`模式启动 Seata Server。 #### 启动 Seata Server 进入到 release 解压后的 seata 目录中,执行如下命令。 ```shell #Linux/Mac环境 sh ./bin/seata-server.sh #Win环境 bin\seata-server.bat ``` ### RocketMQ 配置 Seata 服务启动后可以启动 RocketMQ 的 NameServer 以及 Broker 服务。 进入到 release 解压后的 rocketmq 目录中,执行如下命令。 #### 启动 NameServer ```shell #Linux/Mac环境 sh bin/mqnamesrv #Win环境 .\bin\mqnamesrv.cmd ``` #### 启动 Broker ```shell #Linux/Mac环境 sh bin/mqbroker #Win环境 .\bin\mqbroker.cmd -n localhost:9876 ``` ## 运行 Demo 示例 准备工作完成后可以运行 demo 示例,主要根据不同的使用场景,可以分别体验用户下单(分布式事务能力)以及模拟高流量点赞(熔断限流以及削峰填谷的能力)。 首先需要分别启动 `integrated-frontend` 以及 `integrated-gateway` 微服务应用。 - `integrated-gateway` 模块是整个最佳实践示例的网关。 - `integrated-frontend` 为最佳实践示例的简易前端页面。 ### 分布式事务能力 #### 场景说明 针对分布式事务能力,SCA社区提供了**用户下单购买货物的场景**,下单后: - 先请求库存模块,扣减库存 - 扣减账户余额 - 生成订单信息返回响应 ##### 启动测试 分别启动 `integrated-storage`,`integrated-account`,`integrated-order` 三个微服务应用。 访问 `http://integrated-frontend:8080/order` 来体验对应场景。 直接点击下单按钮提交表单,应用模拟客户端向网关发送了一个创建订单的请求。 - 用户的 userId 为 admin - 用户下单的商品编号为1号 - 此次订单购买的商品个数为1个 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155416524.png) 在本 demo 示例中,为了便于演示,每件商品的单价都为2。 而在前面的准备工作中,**初始化业务数据库表**的时候应用新建了一个用户,用户userId 为 admin,余额为 3 元;同时新建了一个编号为 1 号的商品,库存为 100 件。 因此通过上述的操作,应用会创建一个订单,扣减对应商品编号为 1 号的库存个数(100-1=99),扣减 admin 用户的余额(3-2=1)。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155429801.png) 如果再次请求相同的接口,同样是先扣减库存(99-1=98),但是会因为 admin 用户余额不足而抛出异常,并被 Seata 捕获,执行分布式事务二阶段提交,回滚事务。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155436112.png) 可以看到数据库中库存的记录因为回滚之后仍然为 99 件。 ### 熔断限流,削峰填谷能力 #### 场景说明 针对大流量背景下的服务熔断限流,削峰填谷,SCA社区提供了**用户为商品进行点赞的场景**。在此场景下,SCA社区提供了两种应对大流量的处理方式。 - Sentinel 在网关侧绑定指定网关路由进行服务的熔断降级。 - RocketMQ 进行流量削峰填谷,在大流量请求下,生产者向 RocketMQ 发送消息,而消费者则通过可配置的消费速率进行拉取消费,减少大流量直接请求数据库增加点赞请求的压力。 #### 启动测试 分别启动 `integrated-praise-provider` 以及 `integrated-praise-consumer` 模块。 - Sentinel 服务熔断降级 访问 `http://integrated-frontend:8080/sentinel` 体验对应场景。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016155501290.png) 网关路由点赞服务的限流规则为 5,而在前端通过异步处理模拟了 10 次并发请求。 因此可以看到 Sentinel 在 Gateway 侧针对多出的流量进行了服务熔断返回 fallback 给客户端,同时数据库的点赞数进行了更新(+5)。 ![](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20220914155755103.png) - RocketMQ 进行流量削峰填谷 访问 `http://integrated-frontend:8080/rocketmq` 体验对应场景。 由于之前在 Nacos 中配置了 `integrated-praise-consumer` 消费者模块的消费速率以及间隔,在点击按钮时应用模拟 1000 个点赞请求,针对 1000 个点赞请求,`integrated-praise-provider` 会将 1000 次请求都向 Broker 投递消息,而在消费者模块中会根据配置的消费速率进行消费,向数据库更新点赞的商品数据,模拟大流量下 RocketMQ 削峰填谷的特性。 可以看到数据库中点赞的个数正在动态更新。 ![image-20221016173604059](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20221016173604059.png) ## 其他 本示例**仅是针对各个组件选取出了较为典型的功能特性来服务应用场景** 当然各个组件的功能特性不仅仅只包含最佳实践中演示的这些,如果您感兴趣或是想要深入了解,欢迎学习各个组件的独立 example 相关文档。 - Nacos examples - [Nacos config example](../../../nacos-example/readme.md) - [Nacos discovery example](../../../nacos-example/readme.md) - [Sentinel core example](../../../sentinel-example/README-zh.md) - [Seata example](../../../seata-example/readme.md) - [RocketMQ example](../../../rocketmq-example/readme.md) - ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/docs/zh/readme-zh.md ================================================ # Integrated Example ## 项目说明 本项目为 Spring Cloud Alibaba (后文简称为 SCA)容器化部署最佳实践的 Demo 演示项目,是整合了 SCA 相关组件(Nacos, Sentinel, Seata, RocketMQ)的 Example 示例项目。 主要使用的组件及及其使用特性如下: - Spring Cloud Gateway 网关 - Nacos 配置中心和服务注册中心 - Sentinel 熔断限流 - Seata 分布式事务 - RocketMQ 消息队列削峰填谷 - Docker 微服务容器化部署 - Kubernetes Helm Chart ![整体概览](https://my-img-1.oss-cn-hangzhou.aliyuncs.com/image-20220816004541921.png) ## 应用场景说明 在本 demo 示例中,SCA 社区提供了两种业务场景。 1) 用户下单购买货物的场景,下单后: - 先请求库存模块,扣减库存 - 扣减账户余额 - 生成订单信息返回响应 2) 用户为商品进行点赞(模拟 MQ 的生产者消费者应用场景)返回商品点赞后的详细信息(点赞数等)。 ### 组件详细说明 1) 其中,用户下单购买货物的场景主要使用 Seata 来进行分布式事务的能力体现。 2) 用户为商品进行点赞的场景,模拟大流量环境下通过 Sentinel 进行限流或是 RocketMQ 进行削峰填谷。在此场景下,SCA社区提供了两种应对大流量的处理方式: - Sentinel 在网关侧绑定指定网关路由进行服务的熔断降级。 - RocketMQ 进行流量削峰填谷,在大流量请求下,生产者向 RocketMQ 发送消息,而消费者则通过可配置的消费速率进行拉取消费,减少大流量直接请求数据库增加点赞请求的压力。 #### Spring Cloud Gateway 微服务模块的网关。 Spring Cloud GateWay 整合 Nacos,实现动态路由配置。 通过监听 Nacos 配置的改变,实现服务网关路由配置动态刷新,每次路由信息变更,无需修改配置文件而后重启服务。 #### Nacos 各个微服务的配置中心,服务注册中心。 - 配置中心 - 共享配置:MySQL 数据源相关信息配置。 - 注册中心 - 所有的微服务模块都注册到 Nacos 中进行服务注册与发现。 - 整合 Spring Cloud Gateway 网关。 #### Seata 基于 Seata 的 AT 模式,用于库存模块,账户模块,订单模块的分布式事务处理。 只要库存不足/账户余额不足,回滚事务。 #### Sentinel 用于点赞场景的服务熔断限流。 整合 Nacos 配置中心与 Spring Cloud Gateway,实现指定路由规则熔断限流规则动态配置。 #### RocketMQ 用于进行点赞服务流量的削峰填谷。 通过将大流量的点赞请求从生产者发送到 mq,消费者模块从 mq 中拉取进行一定频率的消费,不是简单的直接服务熔断限流降级,实现 RocketMQ 针对大流量的削峰填谷能力。 ## 版本说明 本项目提供了[本地部署运行版本](local-deployment-zh.md)、[docker-compose 版本](docker-compose-deploy-zh.md)以及 [Kubernetes Helm-Chart 版本](kubernetes-deployment-zh.md)。 - 如果想要了解具体如何配置各项组件以及完整环境搭建,推荐学习[本地部署运行版本](local-deployment-zh.md)。 - 如果只想运行示例代码,避免繁琐的本地环境搭建过程,又不想使用 k8s 集群。您可以尝试使用 [docker-compose 版本](docker-compose-deploy-zh.md)。 - 如果想要在 K8S 集群上快速体验组件效果,跳过各个组件环境部署等过程,请查看 [Kubernetes Helm-Chart 版本](kubernetes-deployment-zh.md)。 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/Chart.yaml ================================================ apiVersion: v1 appVersion: '1.0' description: Spring Cloud Alibaba Best Practice Example name: integrated-example version: 1.0.0 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-account.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: integrated-account spec: replicas: 1 selector: matchLabels: app: integrated-account template: metadata: labels: appName: integrated-account app: integrated-account spec: containers: - name: integrated-account image: "{{ .Values.image.repository }}integrated-account" imagePullPolicy: Always ports: - name: http-port containerPort: 8012 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-frontend.yaml ================================================ apiVersion: v1 kind: Service metadata: name: integrated-frontend labels: app: integrated-frontend spec: type: NodePort ports: - port: 8080 name: web targetPort: 8080 nodePort: 30080 selector: app: integrated-frontend --- apiVersion: apps/v1 kind: Deployment metadata: name: integrated-frontend spec: replicas: 1 selector: matchLabels: app: integrated-frontend template: metadata: labels: appName: integrated-frontend app: integrated-frontend spec: containers: - name: integrated-frontend image: "{{ .Values.image.repository }}integrated-frontend" imagePullPolicy: Always ports: - name: http-port containerPort: 8080 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-gateway.yaml ================================================ apiVersion: v1 kind: Service metadata: name: gateway-service labels: app: integrated-gateway spec: type: NodePort ports: - port: 30010 name: server targetPort: 30010 nodePort: 30010 selector: app: integrated-gateway --- apiVersion: apps/v1 kind: Deployment metadata: name: integrated-gateway spec: replicas: 1 selector: matchLabels: app: integrated-gateway template: metadata: labels: appName: integrated-gateway app: integrated-gateway spec: containers: - name: integrated-gateway image: "{{ .Values.image.repository }}integrated-gateway" imagePullPolicy: Always ports: - name: http-port containerPort: 30010 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-mysql.yaml ================================================ apiVersion: v1 kind: Service metadata: name: integrated-mysql-web spec: ports: - name: integrated-mysql-port protocol: TCP port: 3306 targetPort: 3306 nodePort: 30306 type: NodePort selector: app: integrated-mysql --- apiVersion: v1 kind: Service metadata: name: integrated-mysql labels: app: integrated-mysql spec: type: ClusterIP ports: - port: 3306 name: 'server' selector: app: integrated-mysql --- apiVersion: apps/v1 kind: Deployment metadata: name: integrated-mysql spec: selector: matchLabels: app: integrated-mysql replicas: 1 template: metadata: labels: app: integrated-mysql spec: containers: - name: integrated-mysql image: '{{ .Values.image.repository }}integrated-mysql' imagePullPolicy: Always env: - name: MYSQL_ROOT_PASSWORD value: '123456' ports: - containerPort: 3306 protocol: TCP ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-nacos-mysql.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: nacos-mysql labels: name: nacos-mysql spec: replicas: 1 selector: matchLabels: name: nacos-mysql template: metadata: labels: name: nacos-mysql spec: containers: - name: mysql image: registry.cn-hangzhou.aliyuncs.com/sca-community/integrated-nacos-mysql ports: - containerPort: 3306 env: - name: MYSQL_ROOT_PASSWORD value: 'root' - name: MYSQL_DATABASE value: 'nacos_config' - name: MYSQL_USER value: 'nacos' - name: MYSQL_PASSWORD value: 'nacos' --- apiVersion: v1 kind: Service metadata: name: nacos-mysql labels: name: nacos-mysql spec: ports: - port: 3307 targetPort: 3306 selector: name: nacos-mysql --- apiVersion: v1 kind: Service metadata: name: nacos-mysql-web spec: ports: - name: nacos-mysql-port protocol: TCP port: 3307 targetPort: 3306 nodePort: 30307 type: NodePort selector: name: nacos-mysql ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-nacos-stand.yaml ================================================ --- apiVersion: v1 kind: Service metadata: name: nacos-svc labels: app: nacos-svc spec: type: NodePort ports: - port: 8848 name: server targetPort: 8848 nodePort: 30848 selector: app: nacos-standalone --- apiVersion: v1 kind: Service metadata: name: nacos-server labels: app: nacos-server spec: type: ClusterIP ports: - port: 8848 name: server targetPort: 8848 - port: 9848 name: client-rpc targetPort: 9848 - port: 9849 name: raft-rpc targetPort: 9849 - port: 7848 name: old-raft-rpc targetPort: 7848 selector: app: nacos-standalone --- apiVersion: v1 kind: ConfigMap metadata: name: nacos-cm data: mysql.host: "nacos-mysql" mysql.db.name: "nacos_config" mysql.port: "3307" mysql.user: "nacos" mysql.password: "nacos" --- apiVersion: apps/v1 kind: Deployment metadata: name: nacos-standalone spec: replicas: 1 template: metadata: labels: app: nacos-standalone annotations: pod.alpha.kubernetes.io/initialized: "true" spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: "app" operator: In values: - nacos-standalone topologyKey: "kubernetes.io/hostname" containers: - name: local-nacos imagePullPolicy: Always image: registry.cn-hangzhou.aliyuncs.com/sca-community/nacos-server ports: - containerPort: 8848 name: client - containerPort: 9848 name: client-rpc - containerPort: 9849 name: raft-rpc - containerPort: 7848 name: old-raft-rpc env: - name: SPRING_DATASOURCE_PLATFORM value: "mysql" - name: MYSQL_SERVICE_HOST valueFrom: configMapKeyRef: name: nacos-cm key: mysql.host - name: MYSQL_SERVICE_DB_NAME valueFrom: configMapKeyRef: name: nacos-cm key: mysql.db.name - name: MYSQL_SERVICE_PORT valueFrom: configMapKeyRef: name: nacos-cm key: mysql.port - name: MYSQL_SERVICE_USER valueFrom: configMapKeyRef: name: nacos-cm key: mysql.user - name: MYSQL_SERVICE_PASSWORD valueFrom: configMapKeyRef: name: nacos-cm key: mysql.password - name: MODE value: "standalone" - name: NACOS_SERVER_PORT value: "8848" - name: PREFER_HOST_MODE value: "hostname" selector: matchLabels: app: nacos-standalone ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-order.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: integrated-order spec: replicas: 1 selector: matchLabels: app: integrated-order template: metadata: labels: appName: integrated-order app: integrated-order spec: containers: - name: integrated-order image: "{{ .Values.image.repository }}integrated-order" imagePullPolicy: Always ports: - name: http-port containerPort: 8013 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-praise-consumer.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: integrated-praise-consumer spec: replicas: 1 selector: matchLabels: app: integrated-praise-consumer template: metadata: labels: appName: integrated-praise-consumer app: integrated-praise-consumer spec: containers: - name: integrated-praise-consumer image: "{{ .Values.image.repository }}integrated-praise-consumer" imagePullPolicy: Always ports: - name: http-port containerPort: 8014 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-praise-provider.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: integrated-praise-provider spec: replicas: 1 selector: matchLabels: app: integrated-praise-provider template: metadata: labels: appName: integrated-praise-provider app: integrated-praise-provider spec: containers: - name: integrated-praise-provider image: "{{ .Values.image.repository }}integrated-praise-provider" imagePullPolicy: Always ports: - name: http-port containerPort: 8015 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-rocketmq.yaml ================================================ apiVersion: v1 kind: Service metadata: name: rocketmq spec: ports: - port: 9876 protocol: TCP targetPort: 9876 selector: app: mqnamesrv --- apiVersion: apps/v1 kind: StatefulSet metadata: name: mqnamesrv spec: serviceName: mqnamesrv replicas: 1 selector: matchLabels: app: mqnamesrv template: metadata: labels: app: mqnamesrv spec: containers: - name: mqnamesrv image: registry.cn-hangzhou.aliyuncs.com/sca-community/rocketmq-server command: ["sh","/usr/local/rocketmq-4.8.0/bin/mqnamesrv"] imagePullPolicy: IfNotPresent ports: - containerPort: 9876 protocol: TCP --- apiVersion: apps/v1 kind: StatefulSet metadata: name: mqbroker spec: serviceName: mqbroker replicas: 1 selector: matchLabels: app: mqbroker template: metadata: labels: app: mqbroker spec: containers: - name: mqbroker image: registry.cn-hangzhou.aliyuncs.com/sca-community/rocketmq-server command: ["sh","/usr/local/rocketmq-4.8.0/bin/mqbroker", "-n","rocketmq:9876"] imagePullPolicy: IfNotPresent env: - name: JAVA_OPT value: "-server -XX:ParallelGCThreads=1 -Xms1g -Xmx1g -Xmn512m" ports: - containerPort: 10909 - containerPort: 10911 resources: requests: memory: 128Mi ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-seata.yaml ================================================ apiVersion: v1 kind: Service metadata: name: seata-server labels: k8s-app: seata-server spec: type: ClusterIP ports: - port: 8091 name: server selector: k8s-app: seata-server --- apiVersion: apps/v1 kind: Deployment metadata: name: seata-server labels: k8s-app: seata-server spec: replicas: 1 selector: matchLabels: k8s-app: seata-server template: metadata: labels: k8s-app: seata-server spec: containers: - name: seata-server image: registry.cn-hangzhou.aliyuncs.com/sca-community/seata-server imagePullPolicy: IfNotPresent env: - name: SEATA_PORT value: '8091' - name: STORE_MODE value: file ports: - name: http containerPort: 8091 protocol: TCP ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/templates/integrated-storage.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: integrated-storage spec: replicas: 1 selector: matchLabels: app: integrated-storage template: metadata: labels: appName: integrated-storage app: integrated-storage spec: containers: - name: integrated-storage image: '{{ .Values.image.repository }}integrated-storage' imagePullPolicy: Always ports: - name: http-port containerPort: 8011 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/helm-chart/values.yaml ================================================ image: repository: registry.cn-hangzhou.aliyuncs.com/sca-community/ ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-account/Dockerfile ================================================ FROM openjdk:8-jdk-alpine as builder LABEL author="yuluo" \ email="yuluo829@aliyun.com" ADD ./integrated-account/target/integrated-account-*.jar /app.jar RUN sh -c 'touch /app.jar' EXPOSE 8012 ENTRYPOINT ["java", "-jar","/app.jar"] ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-account/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 integrated-account Spring Cloud Alibaba Integrated Account Example org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-jdbc com.mysql mysql-connector-j com.alibaba druid-spring-boot-starter org.mybatis.spring.boot mybatis-spring-boot-starter com.alibaba.cloud spring-cloud-starter-alibaba-seata com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud integrated-common ${revision} org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-account/src/main/java/com/alibaba/cloud/integration/account/AccountServiceApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.account; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author TrevorLink */ @SpringBootApplication public class AccountServiceApplication { public static void main(String[] args) { SpringApplication.run(AccountServiceApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-account/src/main/java/com/alibaba/cloud/integration/account/controller/AccountController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.account.controller; import com.alibaba.cloud.integration.account.dto.AccountDTO; import com.alibaba.cloud.integration.account.service.AccountService; import com.alibaba.cloud.integration.common.BusinessException; import com.alibaba.cloud.integration.common.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author TrevorLink */ @RestController @RequestMapping("/account") public class AccountController { @Autowired private AccountService accountService; @PostMapping("/reduce-balance") public Result reduceBalance(@RequestBody AccountDTO accountDTO) { try { accountService.reduceBalance(accountDTO.getUserId(), accountDTO.getPrice()); } catch (BusinessException e) { return Result.failed(e.getMessage()); } return Result.success(""); } @GetMapping("/") public Result getRemainAccount(String userId) { return accountService.getRemainAccount(userId); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-account/src/main/java/com/alibaba/cloud/integration/account/dto/AccountDTO.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.account.dto; /** * @author TrevorLink */ public class AccountDTO { private String userId; private Integer price; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-account/src/main/java/com/alibaba/cloud/integration/account/mapper/AccountMapper.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.account.mapper; import java.sql.Timestamp; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import org.springframework.stereotype.Repository; /** * @author TrevorLink */ @Mapper @Repository public interface AccountMapper { @Select("SELECT money FROM account WHERE user_id = #{userId}") Integer getBalance(@Param("userId") String userId); @Update("UPDATE account SET money = money - #{price},update_time = #{updateTime} WHERE user_id = #{userId} AND money >= ${price}") int reduceBalance(@Param("userId") String userId, @Param("price") Integer price, @Param("updateTime") Timestamp updateTime); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-account/src/main/java/com/alibaba/cloud/integration/account/service/AccountService.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.account.service; import com.alibaba.cloud.integration.common.BusinessException; import com.alibaba.cloud.integration.common.Result; /** * @author TrevorLink */ public interface AccountService { void reduceBalance(String userId, Integer price) throws BusinessException; Result getRemainAccount(String userId); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-account/src/main/java/com/alibaba/cloud/integration/account/service/impl/AccountServiceImpl.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.account.service.impl; import java.sql.Timestamp; import com.alibaba.cloud.integration.account.mapper.AccountMapper; import com.alibaba.cloud.integration.account.service.AccountService; import com.alibaba.cloud.integration.common.BusinessException; import com.alibaba.cloud.integration.common.Result; import org.apache.seata.core.context.RootContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @author TrevorLink */ @Service public class AccountServiceImpl implements AccountService { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private AccountMapper accountMapper; @Override @Transactional public void reduceBalance(String userId, Integer price) throws BusinessException { logger.info("[reduceBalance] currenet XID: {}", RootContext.getXID()); checkBalance(userId, price); Timestamp updateTime = new Timestamp(System.currentTimeMillis()); int updateCount = accountMapper.reduceBalance(userId, price, updateTime); if (updateCount == 0) { throw new BusinessException("reduce balance failed"); } } @Override public Result getRemainAccount(String userId) { Integer balance = accountMapper.getBalance(userId); if (balance == null) { return Result.failed("wrong userId,please check the userId"); } return Result.success(balance); } private void checkBalance(String userId, Integer price) throws BusinessException { Integer balance = accountMapper.getBalance(userId); if (balance < price) { throw new BusinessException("no enough balance"); } } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-account/src/main/resources/application.yaml ================================================ server: port: 8012 spring: application: name: integrated-account cloud: nacos: discovery: server-addr: nacos-server:8848 group: integrated-example config: server-addr: nacos-server:8848 group: integrated-example file-extension: yaml config: import: - optional:nacos:integrated-account.yaml - optional:nacos:datasource-config.yaml seata: application-id: ${spring.application.name} tx-service-group: ${spring.application.name}-group service: vgroup-mapping: integrated-account-group: default grouplist: default: seata-server:8091 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-common/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 integrated-common Spring Cloud Alibaba Integrated Common Example ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-common/src/main/java/com/alibaba/cloud/integration/common/BusinessException.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.common; /** * @author TrevorLink */ public class BusinessException extends RuntimeException { public BusinessException(String message) { super(message); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-common/src/main/java/com/alibaba/cloud/integration/common/IResult.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.common; public interface IResult { /** * Get result code. * @return result code */ Integer getCode(); /** * Get result message. * @return result message */ String getMessage(); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-common/src/main/java/com/alibaba/cloud/integration/common/Result.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.common; /** * @author TrevorLink */ public class Result { private Integer code; private String message; private T data; public static Result success(T data) { return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data); } public static Result success(String message, T data) { return new Result<>(ResultEnum.SUCCESS.getCode(), message, data); } public static Result failed() { return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null); } public static Result failed(String message) { return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null); } public static Result failed(IResult errorResult) { return new Result<>(errorResult.getCode(), errorResult.getMessage(), null); } public Result() { } public Result(Integer code, String message, T data) { this.code = code; this.message = message; this.data = data; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } public static Result instance(Integer code, String message, T data) { Result result = new Result<>(); result.setCode(code); result.setMessage(message); result.setData(data); return result; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-common/src/main/java/com/alibaba/cloud/integration/common/ResultEnum.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.common; /** * Integrated Example common result enum class. * @author TrevorLink */ public enum ResultEnum implements IResult { /** * return success result. */ SUCCESS(2001, "接口调用成功"), /** * return business common failed. */ COMMON_FAILED(2003, "接口调用失败"); private Integer code; private String message; ResultEnum() { } ResultEnum(Integer code, String message) { this.code = code; this.message = message; } @Override public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } @Override public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-frontend/Dockerfile ================================================ FROM openjdk:8-jdk-alpine as builder LABEL author="yuluo" \ email="yuluo829@aliyun.com" ADD ./integrated-frontend/target/integrated-frontend-*.jar /app.jar RUN sh -c 'touch /app.jar' EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app.jar"] ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-frontend/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 jar integrated-frontend Spring Cloud Alibaba Integrated Frontend Example org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-frontend/src/main/java/com/alibaba/cloud/integration/frontend/FrontendApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.frontend; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author HuangSir * @date 2022-09-08 14:11 */ @SpringBootApplication public class FrontendApplication { public static void main(String[] args) { SpringApplication.run(FrontendApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-frontend/src/main/java/com/alibaba/cloud/integration/frontend/controller/IntegrationController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.frontend.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @author HuangSir * @date 2022-09-08 14:00 */ @Controller public class IntegrationController { @RequestMapping("/order") public String order() { return "order"; } @RequestMapping("/rocketmq") public String rocketmq() { return "rocketmq"; } @RequestMapping("/sentinel") public String sentinel() { return "sentinel"; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-frontend/src/main/resources/templates/order.html ================================================
================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-frontend/src/main/resources/templates/rocketmq.html ================================================


================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-frontend/src/main/resources/templates/sentinel.html ================================================


================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-gateway/Dockerfile ================================================ FROM openjdk:8-jdk-alpine as builder LABEL author="yuluo" \ email="yuluo829@aliyun.com" ADD ./integrated-gateway/target/integrated-gateway-*.jar /app.jar RUN sh -c 'touch /app.jar' EXPOSE 30010 ENTRYPOINT ["java", "-jar","/app.jar"] ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-gateway/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 integrated-gateway Spring Cloud Alibaba Integrated Gateway Example org.springframework.cloud spring-cloud-starter-gateway-server-webflux com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-netflix-ribbon com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.csp sentinel-spring-cloud-gateway-v6x-adapter org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-gateway/src/main/java/com/alibaba/cloud/integration/gateway/GatewayApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @author TrevorLink */ @SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-gateway/src/main/java/com/alibaba/cloud/integration/gateway/config/GatewayConfig.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.gateway.config; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import jakarta.annotation.PostConstruct; import reactor.core.publisher.Mono; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; /** * @author TrevorLink */ @Configuration public class GatewayConfig { private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public GatewayConfig(ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void initGatewayRules() { Set rules = new HashSet<>(); rules.add( new GatewayFlowRule("praiseItemSentinel").setCount(5).setIntervalSec(1)); GatewayRuleManager.loadRules(rules); } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @PostConstruct public void initBlockHandlers() { BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { return ServerResponse.status(HttpStatus.OK) .contentType(new MediaType("application", "json", StandardCharsets.UTF_8)) .body(BodyInserters.fromValue("此接口被限流了")); } }; GatewayCallbackManager.setBlockHandler(blockRequestHandler); } @Bean public CorsWebFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.addAllowedOriginPattern("*"); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-gateway/src/main/resources/application.yaml ================================================ server: port: 30010 spring: application: name: integrated-gateway cloud: nacos: config: server-addr: nacos-server:8848 group: integrated-example file-extension: yaml discovery: server-addr: nacos-server:8848 group: integrated-example config: import: optional:nacos:integrated-gateway.yaml ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/Dockerfile ================================================ FROM openjdk:8-jdk-alpine as builder LABEL author="yuluo" \ email="yuluo829@aliyun.com" ADD ./integrated-order/target/integrated-order-*.jar /app.jar RUN sh -c 'touch /app.jar' EXPOSE 8013 ENTRYPOINT ["java", "-jar","/app.jar"] ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 integrated-order Spring Cloud Alibaba Integrated Order Example org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-jdbc com.mysql mysql-connector-j com.alibaba druid-spring-boot-starter org.mybatis.spring.boot mybatis-spring-boot-starter com.alibaba.cloud spring-cloud-starter-alibaba-seata com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud integrated-common ${revision} org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/OrderServiceApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @author TrevorLink */ @SpringBootApplication @EnableFeignClients public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/controller/OrderController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order.controller; import com.alibaba.cloud.integration.common.BusinessException; import com.alibaba.cloud.integration.common.Result; import com.alibaba.cloud.integration.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author TrevorLink */ @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @PostMapping("/create") public Result createOrder(@RequestParam("userId") String userId, @RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count) { Result res = null; try { res = orderService.createOrder(userId, commodityCode, count); } catch (BusinessException e) { return Result.failed(e.getMessage()); } return res; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/entity/Order.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order.entity; import java.sql.Timestamp; /** * @author TrevorLink */ public class Order { private Integer id; private String userId; private String commodityCode; private Integer count; private Integer money; private Timestamp createTime; private Timestamp updateTime; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getCommodityCode() { return commodityCode; } public void setCommodityCode(String commodityCode) { this.commodityCode = commodityCode; } public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } public Integer getMoney() { return money; } public void setMoney(Integer money) { this.money = money; } public Timestamp getCreateTime() { return createTime; } public void setCreateTime(Timestamp createTime) { this.createTime = createTime; } public Timestamp getUpdateTime() { return updateTime; } public void setUpdateTime(Timestamp updateTime) { this.updateTime = updateTime; } @Override public String toString() { return "Order{" + "id=" + id + ", userId='" + userId + '\'' + ", commodityCode='" + commodityCode + '\'' + ", count=" + count + ", money=" + money + ", createTime=" + createTime + ", updateTime=" + updateTime + '}'; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/feign/AccountServiceFeignClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order.feign; import com.alibaba.cloud.integration.common.Result; import com.alibaba.cloud.integration.order.feign.dto.AccountDTO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; /** * @author TrevorLink */ @FeignClient(name = "integrated-account") public interface AccountServiceFeignClient { @PostMapping("/account/reduce-balance") Result reduceBalance(@RequestBody AccountDTO accountReduceBalanceDTO); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/feign/StorageServiceFeignClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order.feign; import com.alibaba.cloud.integration.common.Result; import com.alibaba.cloud.integration.order.feign.dto.StorageDTO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; /** * @author TrevorLink */ @FeignClient(name = "integrated-storage") public interface StorageServiceFeignClient { @PostMapping("/storage/reduce-stock") Result reduceStock(@RequestBody StorageDTO productReduceStockDTO); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/feign/dto/AccountDTO.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order.feign.dto; /** * @author TrevorLink */ public class AccountDTO { private String userId; private Integer price; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/feign/dto/StorageDTO.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order.feign.dto; /** * @author TrevorLink */ public class StorageDTO { private String commodityCode; private Integer count; public String getCommodityCode() { return commodityCode; } public void setCommodityCode(String commodityCode) { this.commodityCode = commodityCode; } public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/mapper/OrderMapper.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order.mapper; import com.alibaba.cloud.integration.order.entity.Order; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Options; import org.springframework.stereotype.Repository; /** * @author TrevorLink */ @Mapper @Repository public interface OrderMapper { @Insert("INSERT INTO `order` (user_id, commodity_code,money,create_time,update_time) VALUES (#{userId}, #{commodityCode},#{money},#{createTime},#{updateTime})") @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id") int saveOrder(Order order); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/service/OrderService.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order.service; import com.alibaba.cloud.integration.common.BusinessException; import com.alibaba.cloud.integration.common.Result; /** * @author TrevorLink */ public interface OrderService { Result createOrder(String userId, String commodityCode, Integer count) throws BusinessException; } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/java/com/alibaba/cloud/integration/order/service/impl/OrderServiceImpl.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.order.service.impl; import java.sql.Timestamp; import com.alibaba.cloud.integration.common.BusinessException; import com.alibaba.cloud.integration.common.Result; import com.alibaba.cloud.integration.order.entity.Order; import com.alibaba.cloud.integration.order.feign.AccountServiceFeignClient; import com.alibaba.cloud.integration.order.feign.StorageServiceFeignClient; import com.alibaba.cloud.integration.order.feign.dto.AccountDTO; import com.alibaba.cloud.integration.order.feign.dto.StorageDTO; import com.alibaba.cloud.integration.order.mapper.OrderMapper; import com.alibaba.cloud.integration.order.service.OrderService; import org.apache.seata.core.context.RootContext; import org.apache.seata.spring.annotation.GlobalTransactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import static com.alibaba.cloud.integration.common.ResultEnum.COMMON_FAILED; /** * @author TrevorLink */ @Service public class OrderServiceImpl implements OrderService { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private OrderMapper orderMapper; @Autowired private AccountServiceFeignClient accountService; @Autowired private StorageServiceFeignClient storageService; @Override @GlobalTransactional public Result createOrder(String userId, String commodityCode, Integer count) { logger.info("[createOrder] current XID: {}", RootContext.getXID()); // deduct storage StorageDTO storageDTO = new StorageDTO(); storageDTO.setCommodityCode(commodityCode); storageDTO.setCount(count); Integer storageCode = storageService.reduceStock(storageDTO).getCode(); if (storageCode.equals(COMMON_FAILED.getCode())) { throw new BusinessException("stock not enough"); } // deduct balance int price = count * 2; AccountDTO accountDTO = new AccountDTO(); accountDTO.setUserId(userId); accountDTO.setPrice(price); Integer accountCode = accountService.reduceBalance(accountDTO).getCode(); if (accountCode.equals(COMMON_FAILED.getCode())) { throw new BusinessException("balance not enough"); } // save order Order order = new Order(); order.setUserId(userId); order.setCommodityCode(commodityCode); order.setCount(count); order.setMoney(price); order.setCreateTime(new Timestamp(System.currentTimeMillis())); order.setUpdateTime(new Timestamp(System.currentTimeMillis())); orderMapper.saveOrder(order); logger.info("[createOrder] orderId: {}", order.getId()); return Result.success(order); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-order/src/main/resources/application.yaml ================================================ server: port: 8013 spring: application: name: integrated-order cloud: nacos: discovery: server-addr: nacos-server:8848 group: integrated-example config: server-addr: nacos-server:8848 group: integrated-example file-extension: yaml config: import: - optional:nacos:integrated-order.yaml - optional:nacos:datasource-config.yaml seata: application-id: ${spring.application.name} tx-service-group: ${spring.application.name}-group service: vgroup-mapping: integrated-order-group: default grouplist: default: seata-server:8091 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/Dockerfile ================================================ FROM openjdk:8-jdk-alpine as builder LABEL author="yuluo" \ email="yuluo829@aliyun.com" ADD ./integrated-praise-consumer/target/integrated-praise-consumer-*.jar /app.jar RUN sh -c 'touch /app.jar' EXPOSE 8014 ENTRYPOINT ["java", "-jar","/app.jar"] ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 integrated-praise-consumer Spring Cloud Alibaba Integrated Praise Consumer Example org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-jdbc com.mysql mysql-connector-j com.alibaba druid-spring-boot-starter org.mybatis.spring.boot mybatis-spring-boot-starter com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/src/main/java/com/alibaba/cloud/integration/consumer/PraiseConsumerApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author TrevorLink */ @SpringBootApplication public class PraiseConsumerApplication { public static void main(String[] args) { SpringApplication.run(PraiseConsumerApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/src/main/java/com/alibaba/cloud/integration/consumer/controller/PraiseController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.consumer.controller; import com.alibaba.cloud.integration.consumer.service.PraiseService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author TrevorLink */ @RestController @RequestMapping("/praise") public class PraiseController { @Autowired private PraiseService praiseService; @GetMapping("/query") public Integer getPraise(Integer itemId) { return praiseService.getPraise(itemId); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/src/main/java/com/alibaba/cloud/integration/consumer/listener/ListenerAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.consumer.listener; import java.util.function.Consumer; import com.alibaba.cloud.integration.consumer.message.PraiseMessage; import com.alibaba.cloud.integration.consumer.service.PraiseService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.Message; @Configuration public class ListenerAutoConfiguration { @Bean public Consumer> consumer(PraiseService praiseService) { return msg -> { praiseService.praiseItem(msg.getPayload().getItemId()); }; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/src/main/java/com/alibaba/cloud/integration/consumer/mapper/PraiseMapper.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.consumer.mapper; import java.sql.Timestamp; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import org.springframework.stereotype.Repository; /** * @author TrevorLink */ @Mapper @Repository public interface PraiseMapper { @Update("update item set praise = praise+1,update_time=#{updateTime} where id = #{itemId}") int praiseItem(@Param("itemId") Integer itemId, @Param("updateTime") Timestamp updateTime); @Select("select praise from item where id = #{itemId}") int getPraise(@Param("itemId") Integer itemId); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/src/main/java/com/alibaba/cloud/integration/consumer/message/PraiseMessage.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.consumer.message; /** * @author TrevorLink */ public class PraiseMessage { private Integer itemId; public Integer getItemId() { return itemId; } public void setItemId(Integer itemId) { this.itemId = itemId; } @Override public String toString() { return "PraiseMessage{" + "itemId=" + itemId + '}'; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/src/main/java/com/alibaba/cloud/integration/consumer/service/PraiseService.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.consumer.service; /** * @author TrevorLink */ public interface PraiseService { void praiseItem(Integer itemId); int getPraise(Integer itemId); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/src/main/java/com/alibaba/cloud/integration/consumer/service/impl/PraiseServiceImpl.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.consumer.service.impl; import java.sql.Timestamp; import com.alibaba.cloud.integration.consumer.mapper.PraiseMapper; import com.alibaba.cloud.integration.consumer.service.PraiseService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author TrevorLink */ @Service public class PraiseServiceImpl implements PraiseService { @Autowired private PraiseMapper praiseMapper; @Override public void praiseItem(Integer itemId) { Timestamp updateTime = new Timestamp(System.currentTimeMillis()); praiseMapper.praiseItem(itemId, updateTime); } @Override public int getPraise(Integer itemId) { return praiseMapper.getPraise(itemId); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-consumer/src/main/resources/application.yaml ================================================ spring: application: name: integrated-consumer cloud: nacos: config: file-extension: yaml server-addr: nacos-server:8848 group: integrated-example discovery: server-addr: nacos-server:8848 group: integrated-example config: import: - optional:nacos:integrated-consumer.yaml - optional:nacos:datasource-config.yaml server: port: 8014 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-provider/Dockerfile ================================================ FROM openjdk:8-jdk-alpine as builder LABEL author="yuluo" \ email="yuluo829@aliyun.com" ADD ./integrated-praise-provider/target/integrated-praise-provider-*.jar /app.jar RUN sh -c 'touch /app.jar' EXPOSE 8015 ENTRYPOINT ["java", "-jar","/app.jar"] ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-provider/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 integrated-praise-provider Spring Cloud Alibaba Integrated Praise Provider Example org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-stream-rocketmq com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-provider/src/main/java/com/alibaba/cloud/integration/provider/PraiseProviderApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author TrevorLink */ @SpringBootApplication public class PraiseProviderApplication { public static void main(String[] args) { SpringApplication.run(PraiseProviderApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-provider/src/main/java/com/alibaba/cloud/integration/provider/controller/PraiseController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.provider.controller; import com.alibaba.cloud.integration.provider.message.PraiseMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @author TrevorLink */ @RestController @RequestMapping("/praise") public class PraiseController { private static final String BINDING_NAME = "praise-output"; @Autowired private StreamBridge streamBridge; @GetMapping({ "/rocketmq", "/sentinel" }) public boolean praise(@RequestParam Integer itemId) { PraiseMessage message = new PraiseMessage(); message.setItemId(itemId); Message praiseMessage = MessageBuilder.withPayload(message) .build(); return streamBridge.send(BINDING_NAME, praiseMessage); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-provider/src/main/java/com/alibaba/cloud/integration/provider/message/PraiseMessage.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.provider.message; /** * @author TrevorLink */ public class PraiseMessage { private Integer itemId; public Integer getItemId() { return itemId; } public void setItemId(Integer itemId) { this.itemId = itemId; } @Override public String toString() { return "PraiseMessage{" + "itemId=" + itemId + '}'; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-praise-provider/src/main/resources/application.yaml ================================================ spring: application: name: integrated-provider cloud: nacos: config: file-extension: yaml server-addr: nacos-server:8848 group: integrated-example discovery: server-addr: nacos-server:8848 group: integrated-example config: import: optional:nacos:integrated-provider.yaml server: port: 8015 ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-storage/Dockerfile ================================================ FROM openjdk:8-jdk-alpine as builder LABEL author="yuluo" \ email="yuluo829@aliyun.com" ADD ./integrated-storage/target/integrated-storage-*.jar /app.jar RUN sh -c 'touch /app.jar' EXPOSE 8011 ENTRYPOINT ["java", "-jar","/app.jar"] ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-storage/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 integrated-storage Spring Cloud Alibaba Integrated Storage Example org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-jdbc com.mysql mysql-connector-j com.alibaba druid-spring-boot-starter org.mybatis.spring.boot mybatis-spring-boot-starter com.alibaba.cloud spring-cloud-starter-alibaba-seata com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud integrated-common ${revision} org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/java/com/alibaba/cloud/integration/storage/StorageServiceApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.storage; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author TrevorLink */ @SpringBootApplication public class StorageServiceApplication { public static void main(String[] args) { SpringApplication.run(StorageServiceApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/java/com/alibaba/cloud/integration/storage/controller/StorageController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.storage.controller; import com.alibaba.cloud.integration.common.BusinessException; import com.alibaba.cloud.integration.common.Result; import com.alibaba.cloud.integration.storage.dto.StorageDTO; import com.alibaba.cloud.integration.storage.service.StorageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author TrevorLink */ @RestController @RequestMapping("/storage") public class StorageController { @Autowired private StorageService storageService; @PostMapping("/reduce-stock") public Result reduceStock(@RequestBody StorageDTO storageDTO) { try { storageService.reduceStock(storageDTO.getCommodityCode(), storageDTO.getCount()); } catch (BusinessException e) { return Result.failed(e.getMessage()); } return Result.success(""); } @GetMapping("/") public Result getRemainCount(String commodityCode) { return storageService.getRemainCount(commodityCode); } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/java/com/alibaba/cloud/integration/storage/dto/StorageDTO.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.storage.dto; /** * @author TrevorLink */ public class StorageDTO { private String commodityCode; private Integer count; public String getCommodityCode() { return commodityCode; } public void setCommodityCode(String commodityCode) { this.commodityCode = commodityCode; } public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/java/com/alibaba/cloud/integration/storage/mapper/StorageMapper.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.storage.mapper; import java.sql.Timestamp; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import org.springframework.stereotype.Repository; /** * @author TrevorLink */ @Mapper @Repository public interface StorageMapper { @Select("SELECT `count` FROM storage WHERE commodity_code = #{commodityCode}") Integer getStock(@Param("commodityCode") String commodityCode); @Update("UPDATE storage SET count = count - #{count},update_time=#{updateTime} WHERE commodity_code = #{commodityCode}") int reduceStock(@Param("commodityCode") String commodityCode, @Param("count") Integer count, @Param("updateTime") Timestamp updateTime); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/java/com/alibaba/cloud/integration/storage/service/StorageService.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.storage.service; import com.alibaba.cloud.integration.common.BusinessException; import com.alibaba.cloud.integration.common.Result; /** * @author TrevorLink */ public interface StorageService { void reduceStock(String commodityCode, Integer orderCount) throws BusinessException; Result getRemainCount(String commodityCode); } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/java/com/alibaba/cloud/integration/storage/service/impl/StorageServiceImpl.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.integration.storage.service.impl; import java.sql.Timestamp; import com.alibaba.cloud.integration.common.BusinessException; import com.alibaba.cloud.integration.common.Result; import com.alibaba.cloud.integration.storage.mapper.StorageMapper; import com.alibaba.cloud.integration.storage.service.StorageService; import org.apache.seata.core.context.RootContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @author TrevorLink */ @Service public class StorageServiceImpl implements StorageService { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private StorageMapper storageMapper; @Override @Transactional public void reduceStock(String commodityCode, Integer count) throws BusinessException { logger.info("[reduceStock] current XID: {}", RootContext.getXID()); checkStock(commodityCode, count); Timestamp updateTime = new Timestamp(System.currentTimeMillis()); int updateCount = storageMapper.reduceStock(commodityCode, count, updateTime); if (updateCount == 0) { throw new BusinessException("deduct stock failed"); } } @Override public Result getRemainCount(String commodityCode) { Integer stock = storageMapper.getStock(commodityCode); if (stock == null) { return Result.failed("commodityCode wrong,please check commodity code"); } return Result.success(stock); } private void checkStock(String commodityCode, Integer count) throws BusinessException { Integer stock = storageMapper.getStock(commodityCode); if (stock < count) { throw new BusinessException("no enough stock"); } } } ================================================ FILE: spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/resources/application.yaml ================================================ server: port: 8011 spring: application: name: integrated-storage cloud: nacos: discovery: server-addr: nacos-server:8848 group: integrated-example config: server-addr: nacos-server:8848 group: integrated-example config: import: - optional:nacos:integrated-storage.yaml - optional:nacos:datasource-config.yaml seata: application-id: ${spring.application.name} tx-service-group: ${spring.application.name}-group service: vgroup-mapping: integrated-storage-group: default grouplist: default: seata-server:8091 ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-config-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 nacos-config-example Spring Cloud Starter Alibaba Nacos Config Example Example demonstrating how to use nacos config jar org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/NacosConfigApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author xiaojing, Jianwei Mao */ @SpringBootApplication public class NacosConfigApplication { public static void main(String[] args) { SpringApplication.run(NacosConfigApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/example/BeanAutoRefreshConfigExample.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.example; import java.util.HashMap; import java.util.Map; import com.alibaba.cloud.examples.model.NacosConfigInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Dynamic bean refresh example. * * @author lixiaoshuang */ @RestController @RequestMapping("/nacos/bean") public class BeanAutoRefreshConfigExample { @Autowired private NacosConfigInfo nacosConfigInfo; @GetMapping public Map getConfigInfo() { Map result = new HashMap<>(); result.put("serverAddr", nacosConfigInfo.getServerAddr()); result.put("prefix", nacosConfigInfo.getPrefix()); result.put("group", nacosConfigInfo.getGroup()); result.put("namespace", nacosConfigInfo.getNamespace()); return result; } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/example/ConfigListenerExample.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.example; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.exception.NacosException; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Configuration listener example. * * @author lixiaoshuang */ @Component public class ConfigListenerExample { Logger logger = LoggerFactory.getLogger(ConfigListenerExample.class); /** * Nacos dataId. */ public static final String DATA_ID = "nacos-config-example.properties"; /** * Nacos group. */ public static final String GROUP = "DEFAULT_GROUP"; @Autowired private NacosConfigManager nacosConfigManager; @PostConstruct public void init() throws NacosException { ConfigService configService = nacosConfigManager.getConfigService(); configService.addListener(DATA_ID, GROUP, new Listener() { @Override public Executor getExecutor() { return Executors.newSingleThreadExecutor(); } @Override public void receiveConfigInfo(String configInfo) { logger.info("[dataId]:[" + DATA_ID + "],Configuration changed to:" + configInfo); } }); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/example/DockingInterfaceExample.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.example; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.exception.NacosException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * Example of docking with Nacos interface. * * @author lixiaoshuang */ @RestController @RequestMapping("/nacos") public class DockingInterfaceExample { Logger logger = LoggerFactory.getLogger(DockingInterfaceExample.class); /** * Nacos group. */ public static final String DEFAULT_GROUP = "DEFAULT_GROUP"; @Autowired private NacosConfigManager nacosConfigManager; /** * Get configuration information. * * @param dataId dataId * @param group group * @return config */ @RequestMapping("/getConfig") public String getConfig(@RequestParam("dataId") String dataId, @RequestParam(value = "group", required = false) String group) throws NacosException { if (StringUtils.isEmpty(group)) { group = DEFAULT_GROUP; } ConfigService configService = nacosConfigManager.getConfigService(); return configService.getConfig(dataId, group, 2000); } /** * Publish configuration. * * @param dataId dataId * @param group group * @param content content * @return boolean */ @RequestMapping("/publishConfig") public boolean publishConfig(@RequestParam("dataId") String dataId, @RequestParam(value = "group", required = false) String group, @RequestParam("content") String content) throws NacosException { if (StringUtils.isEmpty(group)) { group = DEFAULT_GROUP; } ConfigService configService = nacosConfigManager.getConfigService(); return configService.publishConfig(dataId, group, content); } /** * Delete configuration. * * @param dataId dataId * @param group group * @return boolean */ @RequestMapping("/removeConfig") public boolean removeConfig(@RequestParam("dataId") String dataId, @RequestParam(value = "group", required = false) String group) throws NacosException { if (StringUtils.isEmpty(group)) { group = DEFAULT_GROUP; } ConfigService configService = nacosConfigManager.getConfigService(); return configService.removeConfig(dataId, group); } /** * Add listener configuration information. * * @param dataId dataId * @param group group */ @RequestMapping("/listener") public String listenerConfig(@RequestParam("dataId") String dataId, @RequestParam(value = "group", required = false) String group) throws NacosException { if (StringUtils.isEmpty(group)) { group = DEFAULT_GROUP; } ConfigService configService = nacosConfigManager.getConfigService(); configService.addListener(dataId, group, new Listener() { @Override public Executor getExecutor() { return Executors.newSingleThreadExecutor(); } @Override public void receiveConfigInfo(String configInfo) { logger.info("[Listen for configuration changes]:{}", configInfo); } }); return "Add Lister successfully!"; } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/example/ValueAnnotationExample.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.example; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Use the @Value annotation to get configuration example. * * @author lixiaoshuang */ @RestController @RequestMapping("/nacos/annotation") @RefreshScope public class ValueAnnotationExample { @Value("${spring.cloud.nacos.config.serverAddr:}") private String serverAddr; @Value("${spring.cloud.nacos.config.prefix:}") private String prefix; @Value("${spring.cloud.nacos.config.group:}") private String group; @Value("${spring.cloud.nacos.config.namespace:}") private String namespace; @GetMapping public Map getConfigInfo() { Map result = new HashMap<>(4); result.put("serverAddr", serverAddr); result.put("prefix", prefix); result.put("group", group); result.put("namespace", namespace); return result; } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/model/NacosConfigInfo.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.model; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * @author lixiaoshuang */ @ConfigurationProperties(prefix = "spring.cloud.nacos.config") @Component public class NacosConfigInfo { /** * Nacos server address. */ private String serverAddr; /** * Data Id prefix. */ private String prefix; /** * Nacos group. */ private String group; /** * Nacos namespace. */ private String namespace; public String getServerAddr() { return serverAddr; } public void setServerAddr(String serverAddr) { this.serverAddr = serverAddr; } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/resources/application.yaml ================================================ server: port: 18084 spring: application: name: nacos-config-example cloud: nacos: config: serverAddr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' config: import: - nacos:nacos-config-example.properties?refreshEnabled=true management: endpoint: health: show-details: always endpoints: web: exposure: include: '*' logging: level: com.alibaba.cloud.nacos.configdata: debug ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml ================================================ com.alibaba.cloud nacos-discovery-example ${revision} 4.0.0 nacos-discovery-consumer-example Spring Cloud Starter Alibaba Nacos Discovery Consumer Example Example demonstrating how to use nacos discovery jar org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-loadbalancer org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/scripts/error.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/test` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/scripts/feign-defaultmethod-error.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/divide-feign2?a=1` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/scripts/feign-error.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/divide-feign?a=1\&b=0` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/scripts/index.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/index` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/scripts/sleep.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/sleep` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/com/alibaba/cloud/examples/ConsumerApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @author xiaojing, fangjian0423, MieAh */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @LoadBalancerClients({ @LoadBalancerClient("service-provider") }) public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/com/alibaba/cloud/examples/TestController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import com.alibaba.cloud.examples.feign.EchoClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * Example of remote invocation of service fusing and load balancing. * * @author xiaojing, fangjian0423, MieAh */ @RestController public class TestController { @Autowired private RestTemplate urlCleanedRestTemplate; @Autowired private RestTemplate restTemplate; @Autowired private EchoClient echoClient; @Autowired private DiscoveryClient discoveryClient; private static final String SERVICE_PROVIDER_ADDRESS = "http://service-provider"; @GetMapping("/echo-rest/{str}") public String rest(@PathVariable String str) { return urlCleanedRestTemplate .getForObject(SERVICE_PROVIDER_ADDRESS + "/echo/" + str, String.class); } @GetMapping("/index") public String index() { return restTemplate.getForObject(SERVICE_PROVIDER_ADDRESS, String.class); } @GetMapping("/test") public String test() { return restTemplate .getForObject(SERVICE_PROVIDER_ADDRESS + "/test", String.class); } @GetMapping("/sleep") public String sleep() { return restTemplate .getForObject(SERVICE_PROVIDER_ADDRESS + "/sleep", String.class); } @GetMapping("/notFound-feign") public String notFound() { return echoClient.notFound(); } @GetMapping("/divide-feign") public String divide(@RequestParam Integer a, @RequestParam Integer b) { return echoClient.divide(a, b); } @GetMapping("/divide-feign2") public String divide(@RequestParam Integer a) { return echoClient.divide(a); } @GetMapping("/echo-feign/{str}") public String feign(@PathVariable String str) { return echoClient.echo(str); } @GetMapping("/services/{service}") public Object client(@PathVariable String service) { return discoveryClient.getInstances(service); } @GetMapping("/services") public Object services() { return discoveryClient.getServices(); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/com/alibaba/cloud/examples/configuration/FeignConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.configuration; import com.alibaba.cloud.examples.feign.EchoClient; import com.alibaba.cloud.examples.feign.EchoClientFallback; import org.springframework.context.annotation.Bean; /** * Configuration for Feign. * * @author fangjian0423, MieAh */ public class FeignConfiguration { @Bean public EchoClient echoClientFallback() { return new EchoClientFallback(); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/com/alibaba/cloud/examples/configuration/RestTemplateConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.configuration; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * Load balancing and sentinel configuration for RestTemplate. * * @author fangjian0423, MieAh */ @Configuration public class RestTemplateConfiguration { @LoadBalanced @Bean // todo sentinel need to support GraalVM in future // @SentinelRestTemplate(urlCleanerClass = UrlCleaner.class, urlCleaner = "clean") public RestTemplate urlCleanedRestTemplate() { return new RestTemplate(); } @LoadBalanced @Bean // todo sentinel need to support GraalVM in future // @SentinelRestTemplate public RestTemplate restTemplate() { return new RestTemplate(); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/com/alibaba/cloud/examples/configuration/UrlCleaner.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Change the request path containing echo. * * @author fangjian0423, MieAh */ public class UrlCleaner { private static final Logger LOGGER = LoggerFactory.getLogger(UrlCleaner.class); private static final String URL_CLEAN_ECHO = ".*/echo/.*"; public static String clean(String url) { LOGGER.info("enter urlCleaner"); if (url.matches(URL_CLEAN_ECHO)) { LOGGER.info("change url"); url = url.replaceAll("/echo/.*", "/echo/{str}"); } return url; } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/com/alibaba/cloud/examples/feign/EchoClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; /** * Provide the external exposure interface of the service calling client. * * @author fangjian0423, MieAh */ @FeignClient(name = "service-provider", contextId = "service-provider") public interface EchoClient { /** * Call the echo method of the remote provider or roll back when the service is blown. * * @param str str * @return {@link String} */ @GetMapping("/echo/{str}") String echo(@PathVariable("str") String str); /** * Call the divide method of the remote provider or roll back when the service is blown. * * @param a a * @param b b * @return {@link String} */ @GetMapping("/divide") String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b); /** * Test that the default method calls the remote method is still a remote call. * * @param a a * @return {@link String} */ default String divide(Integer a) { return divide(a, 0); } /** * Call the notFound method of the remote provider or roll back when the service is blown. * * @return {@link String} */ @GetMapping("/notFound") String notFound(); } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/java/com/alibaba/cloud/examples/feign/EchoClientFallback.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.feign; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; /** * When the service is blown, the fallback operation is performed. * * @author fangjian0423, MieAh */ public class EchoClientFallback implements EchoClient { @Override public String echo(@PathVariable("str") String str) { return "echo fallback"; } @Override public String divide(@RequestParam Integer a, @RequestParam Integer b) { return "divide fallback"; } @Override public String notFound() { return "notFound fallback"; } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/application.properties ================================================ spring.application.name=service-consumer server.port=18083 management.endpoints.web.exposure.include=* spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.fail-fast=true spring.cloud.nacos.username=nacos spring.cloud.nacos.password=nacos feign.sentinel.enabled=true spring.cloud.sentinel.transport.dashboard=localhost:8080 spring.cloud.sentinel.eager=true spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json spring.cloud.sentinel.datasource.ds1.file.data-type=json spring.cloud.sentinel.datasource.ds1.file.rule-type=flow spring.cloud.sentinel.datasource.ds2.file.file=classpath: degraderule.json spring.cloud.sentinel.datasource.ds2.file.data-type=json spring.cloud.sentinel.datasource.ds2.file.rule-type=degrade spring.cloud.loadbalancer.nacos.enabled=true # use feign client in GraalVM environment need to set below config spring.cloud.refresh.enabled=false ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/degraderule.json ================================================ [ { "resource": "GET:http://service-provider/test", "count": 0.5, "grade": 1, "timeWindow": 30 }, { "resource": "GET:http://service-provider", "count": 0.5, "grade": 1, "timeWindow": 10 }, { "resource": "GET:http://service-provider/sleep", "count": 20.0, "grade": 0, "timeWindow": 30 }, { "resource": "GET:http://service-provider/divide", "count": 0.5, "grade": 1, "timeWindow": 30 } ] ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/src/main/resources/flowrule.json ================================================ [ { "resource": "GET:http://service-provider/echo/{str}", "controlBehavior": 0, "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/pom.xml ================================================ com.alibaba.cloud nacos-discovery-example ${revision} 4.0.0 nacos-discovery-consumer-sclb-example Spring Cloud Starter Alibaba Nacos Discovery x Load Balancer Example Example demonstrating how to use nacos discovery jar org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-netflix-ribbon org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.cloud spring-cloud-commons org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-netflix-ribbon org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/scripts/error.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/test` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/scripts/feign-defaultmethod-error.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/divide-feign2?a=1` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/scripts/feign-error.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/divide-feign?a=1\&b=0` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/scripts/index.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/index` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/scripts/resttemplate.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/echo-rest/resttemplate` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/scripts/sleep.sh ================================================ #!/usr/bin/env bash n=1 while [ $n -le 10 ] do echo `curl -s http://localhost:18083/sleep` let n++ done ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/ConsumerSCLBApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; /** * * @author fangjian0423, MieAh */ @SpringBootApplication @EnableDiscoveryClient(autoRegister = false) @EnableFeignClients public class ConsumerSCLBApplication { public static void main(String[] args) { SpringApplication.run(ConsumerSCLBApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/RandomLoadBalancer.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.util.List; import java.util.Random; import reactor.core.publisher.Mono; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.DefaultResponse; import org.springframework.cloud.client.loadbalancer.EmptyResponse; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; /** * Self-defined randomLoadBalancer. * * @author fangjian0423, MieAh */ public class RandomLoadBalancer implements ReactorServiceInstanceLoadBalancer { private ObjectProvider serviceInstanceListSupplierProvider; private final String serviceId; private final Random random; public RandomLoadBalancer( ObjectProvider serviceInstanceListSupplierProvider, String serviceId) { this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; this.serviceId = serviceId; this.random = new Random(); } @Override public Mono> choose( org.springframework.cloud.client.loadbalancer.Request request) { ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new); return supplier.get().next().map(this::getInstanceResponse); } @Override public Mono> choose() { ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new); return supplier.get().next().map(this::getInstanceResponse); } private Response getInstanceResponse( List instances) { if (instances.isEmpty()) { return new EmptyResponse(); } ServiceInstance instance = instances.get(random.nextInt(instances.size())); return new DefaultResponse(instance); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/TestController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import com.alibaba.cloud.examples.feign.EchoClient; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * Example of remote invocation of service fusing and custom load balancing. * * @author fangjian0423, MieAh */ @RestController public class TestController { @Resource private RestTemplate urlCleanedRestTemplate; @Resource private RestTemplate restTemplate; @Resource private EchoClient echoClient; @Resource private DiscoveryClient discoveryClient; @Value("${spring.cloud.loadbalancer.zone:null}") private String zone; private static final String SERVICE_PROVIDER_ADDRESS = "http://service-provider"; @GetMapping("/echo-rest/{str}") public String rest(@PathVariable String str) { return urlCleanedRestTemplate .getForObject(SERVICE_PROVIDER_ADDRESS + "/echo/" + str, String.class); } @GetMapping("/zone") public String zone() { return "consumer zone " + zone + "\n" + urlCleanedRestTemplate .getForObject(SERVICE_PROVIDER_ADDRESS + "/zone", String.class); } @GetMapping("/echo-feign/{str}") public String feign(@PathVariable String str) { return echoClient.echo(str); } @GetMapping("/index") public String index() { return restTemplate.getForObject(SERVICE_PROVIDER_ADDRESS, String.class); } @GetMapping("/test") public String test() { return restTemplate.getForObject(SERVICE_PROVIDER_ADDRESS + "/test", String.class); } @GetMapping("/sleep") public String sleep() { return restTemplate.getForObject(SERVICE_PROVIDER_ADDRESS + "/sleep", String.class); } @GetMapping("/notFound-feign") public String notFound() { return echoClient.notFound(); } @GetMapping("/divide-feign") public String divide(@RequestParam Integer a, @RequestParam Integer b) { return echoClient.divide(a, b); } @GetMapping("/divide-feign2") public String divide(@RequestParam Integer a) { return echoClient.divide(a); } @GetMapping("/services/{service}") public Object client(@PathVariable String service) { return discoveryClient.getInstances(service); } @GetMapping("/services") public Object services() { return discoveryClient.getServices(); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/config/FeignConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.config; import com.alibaba.cloud.examples.feign.EchoClient; import com.alibaba.cloud.examples.feign.EchoClientFallback; import org.springframework.context.annotation.Bean; /** * Configuration for Feign. * * @author fangjian0423, MieAh */ public class FeignConfiguration { @Bean public EchoClient echoClientFallback() { return new EchoClientFallback(); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/config/MyLoadBalancerConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.config; import com.alibaba.cloud.examples.RandomLoadBalancer; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; /** * Configure for load balancing. * * @author fangjian0423, MieAh */ public class MyLoadBalancerConfiguration { @Bean @ConditionalOnMissingBean public ReactorLoadBalancer reactorServiceInstanceLoadBalancer( Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/config/MySCLBConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.config; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.context.annotation.Configuration; /** * Configuration for Self-defined randomLoadBalancer. * * @author fangjian0423, MieAh */ @Configuration @LoadBalancerClient(value = "service-provider", configuration = MyLoadBalancerConfiguration.class) public class MySCLBConfiguration { } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/config/RestTemplateConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.config; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * Load balancing and sentinel configuration for RestTemplate. * * @author fangjian0423, MieAh */ @Configuration public class RestTemplateConfiguration { @LoadBalanced @Bean @SentinelRestTemplate(urlCleanerClass = UrlCleaner.class, urlCleaner = "clean") public RestTemplate urlCleanedRestTemplate() { return new RestTemplate(); } @LoadBalanced @Bean @SentinelRestTemplate public RestTemplate restTemplate() { return new RestTemplate(); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/config/UrlCleaner.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Change the request path containing echo. * * @author fangjian0423, MieAh */ public class UrlCleaner { private static final Logger LOGGER = LoggerFactory.getLogger(UrlCleaner.class); private static final String URL_CLEAN_ECHO = ".*/echo/.*"; public static String clean(String url) { LOGGER.info("enter urlCleaner"); if (url.matches(URL_CLEAN_ECHO)) { LOGGER.info("change url"); url = url.replaceAll("/echo/.*", "/echo/{str}"); } return url; } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/feign/EchoClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.feign; import com.alibaba.cloud.examples.config.FeignConfiguration; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; /** * Provide the external exposure interface of the service calling client. * * @author fangjian0423, MieAh */ @FeignClient(name = "service-provider", fallback = EchoClientFallback.class, configuration = FeignConfiguration.class) public interface EchoClient { /** * Call the echo method of the remote provider or roll back when the service is blown. * * @param str str * @return {@link String} */ @GetMapping("/echo/{str}") String echo(@PathVariable("str") String str); /** * Call the divide method of the remote provider or roll back when the service is blown. * * @param a a * @param b b * @return {@link String} */ @GetMapping("/divide") String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b); /** * Test that the default method calls the remote method is still a remote call. * * @param a a * @return {@link String} */ default String divide(Integer a) { return divide(a, 0); } /** * Call the notFound method of the remote provider or roll back when the service is blown. * * @return {@link String} */ @GetMapping("/notFound") String notFound(); } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/java/com/alibaba/cloud/examples/feign/EchoClientFallback.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.feign; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; /** * When the service is blown, the fallback operation is performed. * * @author fangjian0423, MieAh */ public class EchoClientFallback implements EchoClient { @Override public String echo(@PathVariable("str") String str) { return "echo fallback"; } @Override public String divide(@RequestParam Integer a, @RequestParam Integer b) { return "divide fallback"; } @Override public String notFound() { return "notFound fallback"; } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/resources/application.properties ================================================ spring.application.name=service-consumer-sclb server.port=18083 management.endpoints.web.exposure.include=* spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.username=nacos spring.cloud.nacos.password=nacos spring.cloud.loadbalancer.ribbon.enabled=false spring.cloud.loadbalancer.configurations=zone-preference spring.cloud.loadbalancer.zone=hangzhou feign.sentinel.enabled=true spring.cloud.sentinel.transport.dashboard=localhost:8080 spring.cloud.sentinel.eager=true spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json spring.cloud.sentinel.datasource.ds1.file.data-type=json spring.cloud.sentinel.datasource.ds1.file.rule-type=flow spring.cloud.sentinel.datasource.ds2.file.file=classpath: degraderule.json spring.cloud.sentinel.datasource.ds2.file.data-type=json spring.cloud.sentinel.datasource.ds2.file.rule-type=degrade ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/resources/degraderule.json ================================================ [ { "resource": "GET:http://service-provider/test", "count": 0.5, "grade": 1, "timeWindow": 30 }, { "resource": "GET:http://service-provider", "count": 0.5, "grade": 1, "timeWindow": 10 }, { "resource": "GET:http://service-provider/sleep", "count": 20.0, "grade": 0, "timeWindow": 30 }, { "resource": "GET:http://service-provider/divide", "count": 0.5, "grade": 1, "timeWindow": 30 } ] ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-sclb-example/src/main/resources/flowrule.json ================================================ [ { "resource": "GET:http://service-provider/echo/{str}", "controlBehavior": 0, "count": 2, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/pom.xml ================================================ com.alibaba.cloud nacos-discovery-example ${revision} 4.0.0 nacos-discovery-provider-example Spring Cloud Starter Alibaba Nacos Discovery Provider Example Example demonstrating how to use nacos discovery jar org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/com/alibaba/cloud/examples/EchoController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.util.Map; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import jakarta.annotation.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * Provide interfaces to consumers. * * @author fangjian0423, MieAh */ @RestController public class EchoController { @Resource private NacosDiscoveryProperties nacosDiscoveryProperties; @GetMapping("/") public ResponseEntity index() { return new ResponseEntity<>("index error", HttpStatus.INTERNAL_SERVER_ERROR); } @GetMapping("/test") public ResponseEntity test() { return new ResponseEntity<>("error", HttpStatus.INTERNAL_SERVER_ERROR); } @GetMapping("/sleep") public String sleep() { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } return "ok"; } @GetMapping("/echo/{string}") public String echo(@PathVariable String string) { return "hello Nacos Discovery " + string; } @GetMapping("/divide") public String divide(@RequestParam Integer a, @RequestParam Integer b) { if (b == 0) { return String.valueOf(0); } else { return String.valueOf(a / b); } } @GetMapping("/zone") public String zone() { Map metadata = nacosDiscoveryProperties.getMetadata(); return "provider zone " + metadata.get("zone"); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/java/com/alibaba/cloud/examples/ProviderApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @author xiaojing, fangjian0423, MieAh */ @EnableDiscoveryClient @SpringBootApplication public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/src/main/resources/application.properties ================================================ server.port=0 spring.application.name=service-provider spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.enabled=true #spring.cloud.nacos.discovery.instance-enabled=true #only register IPv4 instance #spring.cloud.nacos.discovery.ip-type=IPv4 #only register IPv6 instance #spring.cloud.nacos.discovery.ip-type=IPv6 spring.cloud.nacos.username=nacos spring.cloud.nacos.password=nacos management.endpoints.web.exposure.include=* management.endpoint.health.show-details=always ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-spring-cloud-config-client-example/pom.xml ================================================ com.alibaba.cloud nacos-discovery-example ${revision} 4.0.0 nacos-discovery-spring-cloud-config-client-example Spring Cloud Starter Alibaba Nacos Discovery x Config Client Example Example demonstrating how to use nacos discovery jar org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-config org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-spring-cloud-config-client-example/src/main/java/com/alibaba/cloud/examples/GetConfigController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * Provide the interface method of config. * * @author MieAh */ @RestController public class GetConfigController { @Value("${config}") private String config; @GetMapping("/config") public String getConfig() { return config; } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-spring-cloud-config-client-example/src/main/java/com/alibaba/cloud/examples/SpringCloudConfigClientApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @author JevonYang */ @SpringBootApplication @EnableDiscoveryClient public class SpringCloudConfigClientApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudConfigClientApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-spring-cloud-config-client-example/src/main/resources/application.yml ================================================ server: port: 18083 spring: application: name: client config: import: optional:configserver:http://localhost:7070 cloud: nacos: username: nacos password: nacos discovery: server-addr: 127.0.0.1:8848 config: config-from-yml ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-spring-cloud-config-server-example/pom.xml ================================================ com.alibaba.cloud nacos-discovery-example ${revision} 4.0.0 nacos-discovery-spring-cloud-config-server-example Spring Cloud Starter Alibaba Nacos Discovery x Config Server Example Example demonstrating how to use nacos discovery jar org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-config-server com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-spring-cloud-config-server-example/src/main/java/com/alibaba/cloud/examples/SpringCloudConfigServerApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.config.server.EnableConfigServer; /** * @author Jim */ @SpringBootApplication @EnableDiscoveryClient @EnableConfigServer public class SpringCloudConfigServerApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudConfigServerApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-spring-cloud-config-server-example/src/main/resources/application.yml ================================================ server: port: 7070 spring: application: name: configserver cloud: nacos: username: 'nacos' password: 'nacos' discovery: server-addr: 127.0.0.1:8848 config: server: git: uri: https://github.com/fangjian0423/blogimages search-paths: / ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-reactivediscovery-consumer-example/pom.xml ================================================ com.alibaba.cloud nacos-discovery-example ${revision} 4.0.0 nacos-reactivediscovery-consumer-example Spring Cloud Starter Alibaba Nacos Discovery Reactive Example Example demonstrating how to use nacos discovery jar org.springframework.boot spring-boot-starter-webflux com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-netflix-ribbon org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-loadbalancer org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-reactivediscovery-consumer-example/src/main/java/com/alibaba/cloud/examples/ConsumerReactiveApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; /** * @author Jim */ @SpringBootApplication @LoadBalancerClients({ @LoadBalancerClient("service-provider") }) public class ConsumerReactiveApplication { public static void main(String[] args) { SpringApplication.run(ConsumerReactiveApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-reactivediscovery-consumer-example/src/main/java/com/alibaba/cloud/examples/MyController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import jakarta.annotation.Resource; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.client.WebClient; /** * Example of responsive discovery client. * * @author fangjian0423, MieAh */ @RestController public class MyController { @Resource private ReactiveDiscoveryClient reactiveDiscoveryClient; @Resource private WebClient.Builder webClientBuilder; @GetMapping("/all-services") public Flux allServices() { return reactiveDiscoveryClient.getInstances("service-provider") .map(serviceInstance -> serviceInstance.getHost() + ":" + serviceInstance.getPort()); } @GetMapping("/service-call/{name}") public Mono serviceCall(@PathVariable("name") String name) { return webClientBuilder.build().get() .uri("http://service-provider/echo/" + name).retrieve() .bodyToMono(String.class); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-reactivediscovery-consumer-example/src/main/java/com/alibaba/cloud/examples/WebClientConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; /** * Configuration for web client. * * @author fangjian0423, MieAh */ @Configuration public class WebClientConfiguration { @Bean @LoadBalanced public WebClient.Builder webClient() { return WebClient.builder(); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-reactivediscovery-consumer-example/src/main/resources/application.properties ================================================ spring.application.name=service-consumer-reactive server.port=18083 management.endpoints.web.exposure.include=* spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.username=nacos spring.cloud.nacos.password=nacos spring.cloud.loadbalancer.ribbon.enabled=false ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 nacos-discovery-example Spring Cloud Starter Alibaba Nacos Discovery Examples Example demonstrating how to use nacos discovery pom nacos-discovery-consumer-example nacos-discovery-consumer-sclb-example nacos-reactivediscovery-consumer-example nacos-discovery-provider-example nacos-discovery-spring-cloud-config-server-example nacos-discovery-spring-cloud-config-client-example ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/pom.xml ================================================ com.alibaba.cloud nacos-gateway-example ${revision} 4.0.0 nacos-gateway-discovery-example Spring Cloud Starter Alibaba Nacos Discovery x Gateway Example Example demonstrating how to use gateway with nacos jar org.springframework.cloud spring-cloud-starter-gateway-server-webflux com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-loadbalancer org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/src/main/java/com/alibaba/cloud/examples/GatewayApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; /** * @author lengleng */ @SpringBootApplication @EnableDiscoveryClient @LoadBalancerClients({ @LoadBalancerClient("service-gateway-provider") }) public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/src/main/resources/application.properties ================================================ server.port=18085 spring.application.name=service-gateway spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.username=nacos spring.cloud.nacos.password=nacos management.endpoints.web.exposure.include=* # spring cloud route config spring.cloud.gateway.server.webflux.routes[0].id=nacos-route spring.cloud.gateway.server.webflux.routes[0].uri=lb://service-gateway-provider spring.cloud.gateway.server.webflux.routes[0].predicates[0].name=Path spring.cloud.gateway.server.webflux.routes[0].predicates[0].args[pattern]=/nacos/** spring.cloud.gateway.server.webflux.routes[0].filters[0]=StripPrefix=1 ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/pom.xml ================================================ com.alibaba.cloud nacos-gateway-example ${revision} 4.0.0 nacos-gateway-provider-example Spring Cloud Starter Alibaba Nacos Discovery x Gateway - Provider Example Example demonstrating how to use gateway with nacos jar org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/src/main/java/com/alibaba/cloud/examples/EchoController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * Provide a service interface to the gateway for forwarding calls. * * @author MieAh */ @RestController public class EchoController { @GetMapping("/echo/{string}") public String echo(@PathVariable String string) { return "hello Nacos Discovery " + string; } @GetMapping("/divide") public String divide(@RequestParam Integer a, @RequestParam Integer b) { return String.valueOf(a / b); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/src/main/java/com/alibaba/cloud/examples/ProviderApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @author xiaojing */ @SpringBootApplication @EnableDiscoveryClient public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/src/main/resources/application.properties ================================================ server.port=18086 spring.application.name=service-gateway-provider spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.username=nacos spring.cloud.nacos.password=nacos management.endpoints.web.exposure.include=* ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 nacos-gateway-example Spring Cloud Starter Alibaba Nacos Discovery x Gateway Examples Example demonstrating how to use gateway with nacos pom nacos-gateway-discovery-example nacos-gateway-provider-example ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/readme-zh.md ================================================ # Spring Cloud Alibaba Nacos Example ## 项目说明 本项目演示如何使用 Spring Cloud Alibaba Nacos 相关 Starter 完成 Spring Cloud 应用的服务发现和配置管理。 [Nacos](https://github.com/alibaba/Nacos) 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 ## 正确配置并启动 Nacos Server 3.1.0 在 Nacos 3.1.0 中,加入了用户鉴权相关的功能,在首次启动 Nacos Server 时,需要正确配置,避免出现启动失败的问题。 ### 下载 Nacos Server > 本示例中使用 Nacos Server 版本为 3.1.0! Nacos 支持直接下载和源码构建两种方式。**推荐在 Spring Cloud Alibaba 2023.x 中使用 Nacos Server 3.1.0 版本。** 1. 直接下载:[Nacos Server 下载页](https://github.com/alibaba/nacos/releases) 2. 源码构建:进入 Nacos [Github 项目页面](https://github.com/alibaba/nacos),将代码 git clone 到本地自行编译打包,[参考文档](https://nacos.io/zh-cn/docs/quick-start.html)。 ### 配置 Nacos Server 打开 `\nacos-server-3.1.0\conf\application.properties` 配置文件,修改以下配置项: #### 配置数据源 此处以 MySQL 数据库为例,使用 `nacos-server-3.1.0\conf\mysql-schema.sql` 初始化数据库表文件。同时修改以下配置 ```properties #*************** Config Module Related Configurations ***************# ### If use MySQL as datasource: spring.datasource.platform=mysql ### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true db.user.0=root db.password.0=root ### Connection pool configuration: hikariCP db.pool.config.connectionTimeout=30000 db.pool.config.validationTimeout=10000 db.pool.config.maximumPoolSize=20 db.pool.config.minimumIdle=2 ``` #### 开启鉴权 **注意:不开启在 3.1.0 中会出现登陆失败异常!** ```properties ### The auth system to use, currently only 'nacos' and 'ldap' is supported: nacos.core.auth.system.type=nacos ### If turn on auth system: nacos.core.auth.enabled=true ``` #### 设置服务端验证 key ```properties nacos.core.auth.server.identity.key=test nacos.core.auth.server.identity.value=test ``` #### 设置默认 token ```properties ### The default token (Base64 String): nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789 ``` **在使用 Nacos 服务发现和配置功能时,一定要配置 `username` 和 `password` 属性,否则会出现用户未找到异常!** #### Open API 鉴权 在 nacos server 3.1.0 中使用 Open api 接口时需要鉴权:更多细节请参考:[Nacos api 鉴权](https://nacos.io/zh-cn/docs/auth.html) 1. 获取 accessToken:使用用户名和密码登陆 nacos server: `curl -X POST '127.0.0.1:8848/nacos/v1/auth/login' -d 'username=nacos&password=nacos'` 若用户名和密码正确,返回信息如下: `{"accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTYwNTYyOTE2Nn0.2TogGhhr11_vLEjqKko1HJHUJEmsPuCxkur-CfNojDo","tokenTtl":18000,"globalAdmin":true}` 2. 使用 accessToken 请求 nacos api 接口: `curl -X GET '127.0.0.1:8848/nacos/v1/cs/configs?accessToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTYwNTYyMzkyM30.O-s2yWfDSUZ7Svd3Vs7jy9tsfDNHs1SuebJB4KlNY8Q&dataId=nacos.example.1&group=nacos_group'` ### 启动 Nacos Server 1. 启动 Nacos Server,进入下载到本地并解压完成后的文件夹(使用源码构建的方式则进入编译打包好的文件夹),再进去其相对文件夹 `nacos/bin`,并对照操作系统实际情况执行如下命令。[详情参考此文档](https://nacos.io/zh-cn/docs/quick-start.html)。 1. Linux/Unix/Mac 操作系统,执行命令 `sh startup.sh -m standalone` 2. Windows 操作系统,执行命令 `cmd startup.cmd` 2. 访问 Nacos Server Console 浏览器输入地址 http://127.0.0.1:8848/nacos ,**首次登陆需要绑定 nacos 用户,因为新版本增加了鉴权,需要应用注册和配置绑定时配置用户名和密码。** ## Nacos 应用示例 ### Spring Cloud Alibaba Nacos Config #### 配置方式更新说明 在2023.0.1.3版本中,为了支持在SpringBoot中接入Nacos配置中心以及基于原有的nacos config模块之上支持@NacosConfig,@NacosConfigListener注解,将spring-cloud-starter-alibaba-nacos-config模块进行了拆分 - **spring-alibaba-nacos-config**:仅依赖SpringBoot,支持在非SpringCloud应用中独立使用 - **spring-cloud-starter-alibaba-nacos-config**:仅保留依赖SpringCloud的组件 在模块拆分的过程中,发现随着代码不断的变更,配置的加载逻辑存在多个分支,包括最初版本中通过拼接spring.application.name以及fileExtension等参数,通过share-configs,extension-configs以及spring.config.import。多个属性源加载时机不一致且代码逻辑分叉,**不利于配置模块的扩展**。 出于代码的可维护性考虑,对配置加载逻辑进行了删减,仅保留了spring在Spring Boot 2.4.0 (2020年11月12日)推出的**spring.config.import标准配置导入方式**,在该版本中Spring同时建议**废弃bootstrap模式**启动,统一迁移到**application.properties**。 对于之前通过application.name拼接模式以及share-configs,extension-configs等方式导入的配置,需要**统一修改**为**spring.config.import**模式进行配置导入。 **接入NacosConfig的标准用法如下** - 导入单个配置 ``` spring: config: import:nacos:application.propertise?refreshEnabled=true&group=DEFAULT_GROUP cloud: nacos: config: serverAddr: {nacos server addr} namespace: {nacos namespace id} ``` - 导入多个配置 ``` spring: config: import: - nacos:application.propertise?group=refreshEnabled=true&group=DEFAULT_GROUP - nacos:{other config data id}?group={other config group}&refreshEnabled=true cloud: nacos: config: serverAddr: {nacos server addr} namespace: {nacos namespace id} ``` #### 应用接入 在启动应用示例进行项目功能演示之前,先了解一下 Spring Cloud 应用如何接入 Nacos Config 作为服务配置中心。 **注意 本章节只是为了便于理解接入方式,本示例代码中已经完成接入工作,无需再进行修改。** 1. 首先,修改 `pom.xml` 文件,引入 spring-cloud-starter-alibaba-nacos-config ; ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config ``` 2. 在应用的 `/src/main/resources/application.yaml` 配置文件中配置 Nacos 地址并引入服务配置; ```yml spring: cloud: nacos: serverAddr: 127.0.0.1:8848 # 以下配置项必须填写 username: 'nacos' password: 'nacos' config: import: - nacos:nacos-config-example.properties?refreshEnabled=true&group=DEFAULT_GROUP ``` 3. 完成上述两步后,应用会从 Nacos Server 中获取相应的配置,并添加在 Spring Environment 的 PropertySources 中。使用 Nacos 配置中心保存 Nacos 的部分配置时,有以下四种方式: - BeanAutoRefreshConfigExample: 通过将配置信息配置为bean,支持配置变自动刷新的例子; - ConfigListenerExample: 监听配置信息的例子; - DockingInterfaceExample: 对接 Nacos 接口,通过接口完成对配置信息增删改查的例子; - ValueAnnotationExample: 通过 @Value 注解进行配置信息获取的例子。 #### Nacos Server 中添加配置 在命令行执行如下命令,向 Nacos Server 中添加一条配置。**可直接通过 Nacos 控制台注入!** > **注意:需要替换 accessToken。** ```shell $ curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?accessToken=XXXXXXXXXXXXXXXXXXXXXXXX&dataId=nacos-config-example.properties&group=DEFAULT_GROUP&content=spring.cloud.nacos.config.serverAddr=127.0.0.1:8848%0Aspring.cloud.nacos.config.prefix=PREFIX%0Aspring.cloud.nacos.config.group=GROUP%0Aspring.cloud.nacos.config.namespace=NAMESPACE" ``` 添加的配置详情如下: ```properties # dataId 为 nacos-config-example.properties # group 为 DEFAULT_GROUP # 内容如下: spring.cloud.nacos.config.serveraddr=127.0.0.1:8848 spring.cloud.nacos.config.prefix=PREFIX spring.cloud.nacos.config.group=GROUP spring.cloud.nacos.config.namespace=NAMESPACE ``` #### 应用启动 1. 增加配置,在应用的 `/src/main/resources/application.yml` 中添加基本配置信息; ```yml server: port: 18084 management: endpoints: web: exposure: include: '*' ``` 2. 启动应用,支持 IDE 直接启动和编译打包后启动。 1. IDE直接启动:找到主类 `NacosConfigApplication`,执行 main 方法启动应用。 2. 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,进入 `target` 文件夹执行 `java -jar nacos-config-example.jar` 启动应用。 #### 验证 ##### 验证自动注入 在浏览器地址栏输入 `http://127.0.0.1:18084/nacos/bean`,并点击调转,可以看到成功从 Nacos Server 中获取了数据。 ![get](https://tva1.sinaimg.cn/large/e6c9d24ely1h2gbowleyrj20o40bo753.jpg) ##### 验证动态刷新 1. 执行如下命令,修改 Nacos Server 端的配置数据 ```shell $ curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?accessToken=XXXXXXXXXXXXXXXXXXXXXXXXXXX&dataId=nacos-config-example.properties&group=DEFAULT_GROUP&content=spring.cloud.nacos.config.serveraddr=127.0.0.1:8848%0Aspring.cloud.nacos.config.prefix=PREFIX%0Aspring.cloud.nacos.config.group=DEFAULT_GROUP%0Aspring.cloud.nacos.config.namespace=NAMESPACE" ``` 2. 在浏览器地址栏输入 `http://127.0.0.1:18084/nacos/bean`,并点击调转,可以看到应用从 Nacos Server 中获取了最新的数据,group 变成了 DEFAULT_GROUP。 ![refresh](https://tva1.sinaimg.cn/large/e6c9d24ely1h2gbpram9rj20nq0ccmxz.jpg) #### 原理 ##### Nacos Config 数据结构 Nacos Config 主要通过 dataId 和 group 来唯一确定一条配置,假定您已经了解此背景。如果不了解,请参考 [Nacos 文档](https://nacos.io/zh-cn/docs/concepts.html)。 Nacos Client 从 Nacos Server 端获取数据时,调用的是此接口 `ConfigService.getConfig(String dataId, String group, long timeoutMs)`。 ##### Spring Cloud 应用获取数据 ###### dataID 在 Nacos Config Starter 中,dataId 的拼接格式如下 ${prefix} - ${spring.profiles.active} . ${file-extension} * `prefix` 默认为 `spring.application.name` 的值,也可以通过配置项 `spring.cloud.nacos.config.prefix`来配置。 * `spring.profiles.active` 即为当前环境对应的 profile,详情可以参考 [Spring Boot文档](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html#boot-features-profiles) **注意,当 active profile 为空时,对应的连接符 `-` 也将不存在,dataId 的拼接格式变成 `${prefix}`.`${file-extension}`** * `file-extension` 为配置内容的数据格式,可以通过配置项 `spring.cloud.nacos.config.file-extension`来配置。 目前只支持 `properties` 类型。 ###### group * `group` 默认为 `DEFAULT_GROUP`,可以通过 `spring.cloud.nacos.config.group` 配置。 ##### 自动注入 Spring Cloud Alibaba Nacos Config Starter 实现了 `org.springframework.boot.context.config.ConfigDataLoader` 接口,并将优先级设置成了最高。 在 Spring Cloud 应用启动阶段,会主动从 Nacos Server 端获取对应的数据,并将获取到的数据转换成 PropertySource 且注入到 Environment 的 PropertySources 属性中,所以使用 @Value 注解也能直接获取 Nacos Server 端配置的内容。 ##### 动态刷新 Nacos Config Starter 默认为所有获取数据成功的 Nacos 的配置项添加了监听功能,在监听到服务端配置发生变化时会实时触发 `org.springframework.cloud.context.refresh.ContextRefresher` 的 refresh 方法 。 如果需要对 Bean 进行动态刷新,请参照 Spring 和 Spring Cloud 规范。推荐给类添加 `@RefreshScope` 或 `@ConfigurationProperties ` 注解, 更多详情请参考 [ContextRefresher Java Doc](http://static.javadoc.io/org.springframework.cloud/spring-cloud-context/2.0.0.RELEASE/org/springframework/cloud/context/refresh/ContextRefresher.html)。 #### Endpoint 信息查看 Spring Boot 应用支持通过 Endpoint 来暴露相关信息,Spring Cloud Alibaba Nacos Config Starter 也支持这一点。 在使用之前需要在 maven 中添加 `spring-boot-starter-actuator`依赖,并在配置中允许 Endpoints 的访问。 Spring Boot 3.x 可以通过访问 http://127.0.0.1:18084/actuator/nacosconfig 来访问。 ![actuator](https://cdn.nlark.com/lark/0/2018/png/54319/1536986344822-279e1edc-ebca-4201-8362-0ddeff240b85.png) 如上图所示,Sources 表示此客户端从哪些 Nacos Config 配置项中获取了信息,RefreshHistory 表示动态刷新的历史记录,最多保存20条,NacosConfigProperties 则为 Nacos Config Starter 本身的配置。 #### More ##### 更多配置项 配置项|key|默认值|说明 ----|----|-----|----- 服务端地址|spring.cloud.nacos.config.server-addr||服务器ip和端口 DataId前缀|spring.cloud.nacos.config.prefix|${spring.application.name}|DataId的前缀,默认值为应用名称 Group|spring.cloud.nacos.config.group|DEFAULT_GROUP| DataId后缀及内容文件格式|spring.cloud.nacos.config.file-extension|properties|DataId的后缀,同时也是配置内容的文件格式,目前只支持 properties 配置内容的编码方式|spring.cloud.nacos.config.encode|UTF-8|配置的编码 获取配置的超时时间|spring.cloud.nacos.config.timeout|3000|单位为 ms 配置的命名空间|spring.cloud.nacos.config.namespace||常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源隔离等。 AccessKey|spring.cloud.nacos.config.access-key|| SecretKey|spring.cloud.nacos.config.secret-key|| 相对路径|spring.cloud.nacos.config.context-path||服务端 API 的相对路径 接入点|spring.cloud.nacos.config.endpoint||地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址 是否开启监听和自动刷新|spring.cloud.nacos.config.refresh-enabled|true| 集群服务名|spring.cloud.nacos.config.cluster-name|| 是否开启 Nacos Config 的 Health Indicator|spring.nacos.config.health-indicator.enabled|false| 是否开启 Nacos Discovery 的 Health Indicator|spring.cloud.nacos.discovery.health-indicator.enabled|false| ### Spring Cloud Alibaba Nacos Discovery #### 如何接入 在启动 Nacos Discovery 示例进行演示之前,了解一下 Spring Cloud 应用如何接入 Nacos Discovery。 **注意 本章节只是为了便于您理解接入方式,本示例代码中已经完成接入工作,您无需再进行修改。** 1. 首先,修改 `pom.xml` 文件,引入 spring-cloud-alibaba-nacos-discovery-starter; ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` 2. 在应用的 `/src/main/resources/application.properties` 配置文件中配置 Nacos Server 地址; ```properties spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 ``` 3. 使用 @EnableDiscoveryClient 注解开启服务注册与发现功能; ```java @SpringBootApplication @EnableDiscoveryClient public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } @RestController class EchoController { @GetMapping(value = "/echo/{string}") public String echo(@PathVariable String string) { return string; } } } ``` #### 应用启动 1. 增加配置,在 nacos-discovery-provider-example 项目的 `/src/main/resources/application.properties` 中添加基本配置信息; ```properties spring.application.name=service-provider server.port=18082 ``` 2. 启动应用,支持 IDE 直接启动和编译打包后启动。 1. IDE直接启动:找到 nacos-discovery-provider-example 项目的主类 `ProviderApplication`,执行 main 方法启动应用。 2. 打包编译后启动:在 nacos-discovery-provider-example 项目中执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar nacos-discovery-provider-example.jar`启动应用。 #### 查询服务验证 > **注意:需要替换 accessToken!** 在浏览器输入此地址 `http://127.0.0.1:8848/nacos/v1/ns/catalog/instances?accessToken=XXXXXXXXXXXXXXXXXXXXXX&serviceName=service-provider&clusterName=DEFAULT&pageSize=10&pageNo=1&namespaceId=`,并点击跳转,可以看到服务节点已经成功注册到 Nacos Server。 ![查询服务](https://cdn.nlark.com/lark/0/2018/png/54319/1536986288092-5cf96af9-9a26-466b-85f6-39ad1d92dfdc.png) #### 服务发现集成 Spring Cloud Loadbalancer ```xml org.springframework.cloud spring-cloud-loadbalancer ``` 增加如下配置,使用 Spring Cloud Alibaba 社区针对 Spring Cloud Loadbalancer 负载均衡依赖提供的负载均衡策略,以便使用 Spring Cloud Alibaba 提供的所有的能力: ```properties spring.cloud.loadbalancer.ribbon.enabled=false spring.cloud.loadbalancer.nacos.enabled=true ``` #### IPv4 至 IPv6 地址迁移方案 ##### IPv4 和 IPv6 地址双注册 在配置完成 Spring Cloud Loadbalancer 作为负载均衡策略后,应用启动后会默认将微服务的 IPv4 地址和 IPv6 地址注册到注册中心中,其中 IPv4 地址会存放在 Nacos 服务列表中的 IP 字段下,IPv6 地址在 Nacos 的 metadata 字段中,其对应的 Key 为 IPv6。当服务消费者调用服务提供者时,会根据自身的 IP 地址栈支持情况,选择合适的 IP 地址类型发起服务调用。具体规则: (1)服务消费者本身支持 IPv4 和 IPv6 双地址栈或仅支持 IPv6 地址栈的情况下,服务消费者会使用服务提供的 IPv6 地址发起服务调用,IPv6 地址调用失败如本身还同时支持 IPv4 地址栈时,暂不支持切换到 IPv4 再发起重试调用; (2)服务消费者本身仅支持 IPv4 单地址栈的情况下,服务消费者会使用服务提供的 IPv4 地址发起服务调用。 ##### 仅注册 IPv4 如果您只想使用 IPv4 地址进行注册,可以在 application.properties 使用以下配置: ```properties spring.cloud.nacos.discovery.ip-type=IPv4 ``` ##### 仅注册 IPv6 如果您只想使用 IPv6 地址,可以在 application.properties 使用以下配置: ```properties spring.cloud.nacos.discovery.ip-type=IPv6 ``` #### 使用 RestTemplate 和 FeignClient 下面将分析 nacos-discovery-consumer-example 项目的代码,演示如何 RestTemplate 与 FeignClient。 **注意 本章节只是为了便于理解接入方式,本示例代码中已经完成接入工作,您无需再进行修改。此处只涉及 Ribbon、RestTemplate、FeignClient 相关的内容,如果已经使用了其他服务发现组件,可以通过直接替换依赖来接入 Nacos Discovery。** 1. 添加 @LoadBalanced 注解,使得 RestTemplate 接入 Ribbon ```java @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } ``` 2. FeignClient 已经默认集成了 Ribbon ,此处演示如何配置一个 FeignClient。 ```java @FeignClient(name = "service-provider") public interface EchoService { @GetMapping(value = "/echo/{str}") String echo(@PathVariable("str") String str); } ``` 使用 @FeignClient 注解将 EchoService 这个接口包装成一个 FeignClient,属性 name 对应服务名 service-provider。 echo 方法上的 @RequestMapping 注解将 echo 方法与 URL "/echo/{str}" 相对应,@PathVariable 注解将 URL 路径中的 `{str}` 对应成 echo 方法的参数 str。 3. 完成以上配置后,将两者自动注入到 TestController 中。 ```java @RestController public class TestController { @Autowired private RestTemplate restTemplate; @Autowired private EchoService echoService; @GetMapping(value = "/echo-rest/{str}") public String rest(@PathVariable String str) { return restTemplate.getForObject("http://service-provider/echo/" + str, String.class); } @GetMapping(value = "/echo-feign/{str}") public String feign(@PathVariable String str) { return echoService.echo(str); } } ``` 4. 配置必要的配置,在 nacos-discovery-consumer-example 项目的 `/src/main/resources/application.properties` 中添加基本配置信息 ```properties spring.application.name=service-consumer server.port=18083 management.endpoints.web.exposure.include=* spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.fail-fast=true spring.cloud.nacos.username=nacos spring.cloud.nacos.password=nacos ``` 5.启动应用,支持 IDE 直接启动和编译打包后启动。 1. IDE 直接启动:找到 nacos-discovery-consumer-example 项目的主类 `ConsumerApplication`,执行 main 方法启动应用。 2. 打包编译后启动:在 nacos-discovery-consumer-example 项目中执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar nacos-discovery-consumer-example.jar` 启动应用。 #### 验证 1. 在浏览器地址栏中输入 [http://127.0.0.1:18083/echo-rest/1234](http://127.0.0.1:18083/echo-rest/1234),点击跳转,可以看到浏览器显示了 nacos-discovery-provider-example 返回的消息 "hello Nacos Discovery 1234",证明服务发现生效。 ![rest](https://cdn.nlark.com/lark/0/2018/png/54319/1536986302124-ee27670d-bdcc-4210-9f5d-875acec6d3ea.png) 2. 在浏览器地址栏中输入 [http://127.0.0.1:18083/echo-feign/12345](http://127.0.0.1:18083/echo-feign/12345),点击跳转,可以看到浏览器显示 nacos-discovery-provider-example 返回的消息 "hello Nacos Discovery 12345",证明服务发现生效。 ![feign](https://cdn.nlark.com/lark/0/2018/png/54319/1536986311685-6d0c1f9b-a453-4ec3-88ab-f7922d210f65.png) #### 原理 ##### 服务注册 Spring Cloud Alibaba Nacos Discovery 遵循了 Spring Cloud Common 标准,实现了 AutoServiceRegistration、ServiceRegistry、Registration 这三个接口。 在 Spring Cloud 应用的启动阶段,监听了 WebServerInitializedEvent 事件,当 Web 容器初始化完成后,即收到 WebServerInitializedEvent 事件后,会触发注册的动作,调用 ServiceRegistry 的 register 方法,将服务注册到 Nacos Server。 #### Endpoint 信息查看 Spring Boot 应用支持通过 Endpoint 来暴露相关信息,Spring Cloud Alibaba Nacos Discovery Starter 也支持这一点。 在使用之前需要在 maven 中添加 `spring-boot-starter-actuator`依赖,并在配置中允许 Endpoints 的访问。 Spring Boot 3.x 可以通过访问 http://127.0.0.1:18083/actuator/nacos-discovery 来访问。 ![actuator](https://cdn.nlark.com/lark/0/2018/png/54319/1536986319285-d542dc5f-5dff-462a-9f52-7254776bcd99.png) 如上图所示,NacosDiscoveryProperties 则为 Spring Cloud Alibaba Nacos Discovery 本身的配置,也包括本机注册的内容,subscribe 为本机已订阅的服务信息。 #### More ##### 更多配置项 配置项|key|默认值|说明 ----|----|-----|----- 服务端地址|spring.cloud.nacos.discovery.server-addr|| 服务名|spring.cloud.nacos.discovery.service|${spring.application.name}|注册到Nacos上的服务名称,默认值为应用名称 权重|spring.cloud.nacos.discovery.weight|1|取值范围 1 到 100,数值越大,权重越大 网卡名|spring.cloud.nacos.discovery.network-interface||当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址 注册的IP地址|spring.cloud.nacos.discovery.ip||优先级最高 注册的IP地址类型|spring.cloud.nacos.discovery.ip-type|双栈地址|可以配置IPv4和IPv6两种类型,如果网卡同类型IP地址存在多个,希望制定特定网段地址,可使用`spring.cloud.inetutils.preferred-networks`配置筛选地址 注册的端口|spring.cloud.nacos.discovery.port|-1|默认情况下不用配置,会自动探测 命名空间|spring.cloud.nacos.discovery.namespace||常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。 AccessKey|spring.cloud.nacos.discovery.access-key|| SecretKey|spring.cloud.nacos.discovery.secret-key|| Metadata|spring.cloud.nacos.discovery.metadata||使用Map格式配置 日志文件名|spring.cloud.nacos.discovery.log-name|| 集群|spring.cloud.nacos.discovery.cluster-name|DEFAULT|Nacos集群名称 接入点|spring.cloud.nacos.discovery.endpoint||地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址 是否集成LoadBalancer|spring.cloud.loadbalancer.nacos.enabled|false| 是否开启Nacos Watch|spring.cloud.nacos.discovery.watch.enabled|false|可以设置成true来开启 watch 是否启用Nacos|spring.cloud.nacos.discovery.enabled|true|默认启动,设置为false时会关闭自动向Nacos注册的功能 ### Spring Cloud Alibaba Nacos 集成 Spring Cloud Gateway #### 如何接入 在启动示例进行演示之前,了解一下 Spring Cloud 应用如何接入 Spring Cloud 如何接入 Nacos Discovery、Spring Cloud Gateway。 **注意 本章节只是为了便于您理解接入方式,本示例代码中已经完成接入工作,您无需再进行修改。** 1. 首先,修改 `pom.xml` 文件,引入 Spring Cloud Alibaba Nacos Discovery Starter、Spring Cloud Gateway Starter。 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-gateway-server-webflux ``` 2. 在应用的 `/src/main/resources/application.properties` 配置文件中配置 Nacos Server 地址 ```properties spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 ``` 3. 在应用的 `/src/main/resources/application.properties` 配置文件中配置 Spring Cloud Gateway 路由 ```properties spring.cloud.gateway.server.webflux.routes[0].id=nacos-route spring.cloud.gateway.server.webflux.routes[0].uri=lb://service-gateway-provider spring.cloud.gateway.server.webflux.routes[0].predicates[0].name=Path spring.cloud.gateway.server.webflux.predicates[0].args[pattern]=/nacos/** spring.cloud.gateway.server.webflux.routes[0].filters[0]=StripPrefix=1 ``` 4. 使用 @EnableDiscoveryClient 注解开启服务注册与发现功能 ```java @SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } } ``` #### Spring Cloud Gateway 应用启动 启动应用,支持 IDE 直接启动和编译打包后启动。 1. IDE直接启动:找到 nacos-gateway-discovery-example 项目的主类 `GatewayApplication`,执行 main 方法启动应用。 2. 打包编译后启动:在 nacos-gateway-discovery-example 项目中执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar nacos-gateway-discovery-example.jar`启动应用。 #### 服务提供方应用启动 启动应用,支持 IDE 直接启动和编译打包后启动。 1. IDE 直接启动:找到 nacos-gateway-provider-example 项目的主类 `ProviderApplication`,执行 main 方法启动应用。 2. 打包编译后启动:在 nacos-gateway-provider-example 项目中执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar nacos-gateway-provider-example.jar`启动应用。 #### 验证 1. ```bash curl 'http://127.0.0.1:18085/nacos/echo/hello-world' hello Nacos Discovery hello-world⏎ ``` 2. ```bash curl 'http://127.0.0.1:18085/nacos/divide?a=6&b=2' 3⏎ ``` ## Native Image构建 请参考 Spring Cloud Alibaba 官网中的 [Graalvm 快速开始](https://sca.aliyun.com/zh-cn/docs/2022.0.0.0/user-guide/graalvm/quick-start) ## 更多介绍 Nacos 为用户提供包括动态服务发现,配置管理,服务管理等服务基础设施,帮助用户更灵活,更轻松地构建,交付和管理他们的微服务平台,基于 Nacos, 用户可以更快速的构建以“服务”为中心的现代云原生应用。Nacos 可以和 Spring Cloud、Kubernetes/CNCF、Dubbo 等微服务生态无缝融合,为用户提供更卓越的体验。更多 Nacos 相关的信息,请参考 [Nacos 项目](https://github.com/alibaba/Nacos)。 未来,Spring-Alibaba-Nacos-Config模块将承载更多职责,面向二方中间件组件,对SpringBoot(包括Spring AI)以及SpringCloud应用提供统一的配置托管以及运行时无损轮转功能,面向普通的业务组件,通过@NacosConfig,@NacosConfigListener注解提供灵活易用的配置注入和变更回调能力。 如果您对 Spring Cloud Nacos Discovery 有任何建议或想法,欢迎在 issue 中或者通过其他社区渠道向我们提出。 ================================================ FILE: spring-cloud-alibaba-examples/nacos-example/readme.md ================================================ # Spring Cloud Alibaba Nacos Example ## Project description This project demonstrates how to use Spring Cloud Alibaba Nacos related Starters to complete the service discovery and configuration management of Spring Cloud applications. [Nacos](https://github.com/alibaba/Nacos) It is Alibaba's open source dynamic service discovery, configuration management and service management platform that is easier to build cloud-native applications. ## Nacos Server 3.1.0 is properly configured and started In Nacos 3.1.0, functions related to user authentication are added. When starting Nacos Server for the first time, it needs to be configured correctly to avoid the problem of startup failure. ### Download Nacos Server > The Nacos server version used in this example is 3.1.0! Nacos supports both direct download and source code construction. **Nacos Server version 3.1.0 is recommended for Spring Cloud Alibaba 2022.x.** 1. Direct download: [Nacos Server download page](https://github.com/alibaba/nacos/releases) 2. Source code construction: Enter Nacos [Github project page](https://github.com/alibaba/nacos), git clone the code to the local compilation and packaging [参考文档](https://nacos.io/zh-cn/docs/quick-start.html). ### Configure the Nacos Server Open the `\nacos-server-3.1.0\conf\application.properties` configuration file and modify the following configuration items: #### Configure the data source Take the MySQL database as an example here, and use the `nacos-server-3.1.0\conf\mysql-schema.sql` initialization database table file. Modify the following configuration as well ```properties #*************** Config Module Related Configurations ***************# ### If use MySQL as datasource: spring.datasource.platform=mysql ### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true db.user.0=root db.password.0=root ### Connection pool configuration: hikariCP db.pool.config.connectionTimeout=30000 db.pool.config.validationTimeout=10000 db.pool.config.maximumPoolSize=20 db.pool.config.minimumIdle=2 ``` #### Turn on authentication **Note: If it is not enabled, login failure exception will occur in 3.1.0!** ```properties ### The auth system to use, currently only 'nacos' and 'ldap' is supported: nacos.core.auth.system.type=nacos ### If turn on auth system: nacos.core.auth.enabled=true ``` #### Set the server authentication key ```properties nacos.core.auth.server.identity.key=test nacos.core.auth.server.identity.value=test ``` #### Set the default token ```properties ### The default token (Base64 String): nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789 ``` ** When using the Nacos service discovery and configuration function, be sure to configure `username` and `password` attribute, otherwise the user will not be found! ** #### Open API authentication Authentication is required when using the Open api interface in nacos server 3.1.0: For more details, please refer to: [Nacos api authentication](https://nacos.io/zh-cn/docs/auth.html) 1. Obtain accessToken: Use username and password to log in to the nacos server: `curl -X POST '127.0.0.1:8848/nacos/v1/auth/login' -d 'username=nacos&password=nacos'` If the username and password are correct, the returned information is as follows: `{"accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTYwNTYyOTE2Nn0.2TogGhhr11_vLEjqKko1HJHUJEmsPuCxkur-CfNojDo", "tokenTtl": 18000, "globalAdmin": true}` 2. Use accessToken to request the nacos api interface: `curl -X GET '127.0.0.1:8848/nacos/v1/cs/configs?accessToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTYwNTYyMzkyM30.O-s2yWfDSUZ7Svd3Vs7jy9tsfDNHs1SuebJB4KlNY8Q&dataId=nacos.example.1&group=nacos_group'` ### Start the Nacos Server 1. Start Nacos Server, enter the folder after downloading to the local and decompressing (enter the folder after compiling and packaging by using the source code construction method), then enter its relative folder `nacos/bin`, and execute the following command according to the actual situation of the operating system. [详情参考此文档](https://nacos.io/zh-cn/docs/quick-start.html)。 1. Linux/Unix/Mac operating system, execute the command `sh startup.sh -m standalone` 2. Windows operating system, executing command `cmd startup.cmd` 2. Access Nacos Server Console. The browser enters the address http://127.0.0.1:8848/nacos , **The first login needs to bind the nacos user, because the new version adds authentication, and the user name and password need to be configured during application registration and configuration binding.** ## Nacos application example ### Spring Cloud Alibaba Nacos Config #### Configuration Update Instructions In version 2023.0.1.3, to support integration with Nacos Configuration Center in Spring Boot applications and to enable annotations such as `@NacosConfig` and `@NacosConfigListener` based on the original `nacos config` module, the `spring-cloud-starter-alibaba-nacos-config` module has been split into two: - **spring-alibaba-nacos-config**: Depends only on Spring Boot and can be used independently in non-Spring Cloud applications. - **spring-cloud-starter-alibaba-nacos-config**: Retains only the components that depend on Spring Cloud. During the module refactoring, it was observed that as the code evolved, multiple branches of configuration loading logic emerged. This includes the original approach of concatenating `spring.application.name` with `fileExtension` and loading configurations via `share-configs`, `extension-configs`, and `spring.config.import`. These different property sources were loaded at inconsistent times, resulting in **fragmented logic that hinders the extensibility of the configuration module**. To improve code maintainability, the configuration loading logic has been streamlined, retaining only the **`spring.config.import` standard configuration import mechanism**, which was introduced in **Spring Boot 2.4.0 (November 12, 2020)**. In that release, Spring also **recommended deprecating the bootstrap mode** and encouraged unifying configuration into **`application.properties`**. For configurations previously loaded using the `application.name` concatenation approach or via `share-configs`, `extension-configs`, etc., you now need to **migrate to using the `spring.config.import`** mechanism for configuration import. **The standard usage for integrating with NacosConfig is as follows**: - Importing a Single Configuration ``` spring: config: import:nacos:application.propertise?refreshEnabled=true&group=DEFAULT_GROUP cloud: nacos: config: serverAddr: {nacos server addr} namespace: {nacos namespace id} ``` - Importing Multiple Configurations ``` spring: config: import: - nacos:application.propertise?group=refreshEnabled=true&group=DEFAULT_GROUP - nacos:{other config data id}?group={other config group}&refreshEnabled=true cloud: nacos: config: serverAddr: {nacos server addr} namespace: {nacos namespace id} ``` #### Application access Before starting the application sample to demonstrate the project function, first understand how the Spring Cloud application accesses Nacos Config as the service configuration center. **Note that this section is only for the convenience of understanding the access method. The access work has been completed in this sample code, and no further modification is required.** 1. First, modify the `pom.xml` file to introduce spring-cloud-starter-alibaba-nacos-config; ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config ``` 2. Configuring a Nacos address in an `/src/main/resources/application.yaml` applied configuration file and introducing a service configuration; ```yml spring: cloud: nacos: serverAddr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' config: import: - nacos:nacos-config-example.properties?refreshEnabled=true&group=DEFAULT_GROUP ``` 3. After completing the above two steps, the application will obtain the corresponding configuration from Nacos Server and add it to the Property Sources of Spring Environment. There are four ways to save a portion of the Nacos configuration using the Nacos Configuration Center: - BeanAutoRefres hConfig Example: An example of supporting automatic refresh of configuration changes by configuring configuration information as beans; - ConfigListenerEx ample: example of monitoring configuration information; - Docking Interface Example: An example of docking the Nacos interface and completing the addition, deletion, modification and query of the configuration information through the interface; - ValueAnnotation Example: An example of obtaining configuration information through the @ Value annotation. #### Add Configuration in Nacos Server Add a configuration to the Nacos Server by executing the following command from the command line. **Can be injected directly through the Nacos console!** ```shell $ curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?accessToken=XXXXXXXXXXXXXXXXXXXXXXXXXXX&dataId=nacos-config-example.properties&group=DEFAULT_GROUP&content=spring.cloud.nacos.config.serverAddr=127.0.0.1:8848%0Aspring.cloud.nacos.config.prefix=PREFIX%0Aspring.cloud.nacos.config.group=GROUP%0Aspring.cloud.nacos.config.namespace=NAMESPACE" ``` Details of the added configuration are as follows: ```properties # dataId is nacos-config-example.properties # group is DEFAULT_GROUP # content is: spring.cloud.nacos.config.serveraddr=127.0.0.1:8848 spring.cloud.nacos.config.prefix=PREFIX spring.cloud.nacos.config.group=GROUP spring.cloud.nacos.config.namespace=NAMESPACE ``` #### The application starts 1. Add configuration, and add basic configuration information in the application `/src/main/resources/application.yml`; ```yml server: port: 18084 management: endpoints: web: exposure: include: '*' ``` 2. Start the application, support IDE direct start and start after compilation and packaging. 1. IDE direct startup: find the main class `NacosConfigApplication` and execute the main method to start the application. 2. Start after packaging and compiling: First `mvn clean package`, compile and package the project, and then enter the `target` folder to `java -jar nacos-config-example.jar` start the application. #### Validate ##### Verify automatic injection Enter `http://127.0.0.1:18084/nacos/bean` in the browser address bar and click turn around to see that the data is successfully obtained from the Nacos Server. ![get](https://tva1.sinaimg.cn/large/e6c9d24ely1h2gbowleyrj20o40bo753.jpg) ##### Verify dynamic refresh 1. Execute the following command to modify the configuration data on the Nacos Server side ```shell $ curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?accessToken=XXXXXXXXXXXXXXXXXXXXXXXXXXX&dataId=nacos-config-example.properties&group=DEFAULT_GROUP&content=spring.cloud.nacos.config.serveraddr=127.0.0.1:8848%0Aspring.cloud.nacos.config.prefix=PREFIX%0Aspring.cloud.nacos.config.group=DEFAULT_GROUP%0Aspring.cloud.nacos.config.namespace=NAMESPACE" ``` 2. Type `http://127.0.0.1:18084/nacos/bean` in the address bar of the browser and click Turn, and you can see that the application obtains the latest data from the Nacos Server, and the group becomes the DEFAULT _ GROUP. ![refresh](https://tva1.sinaimg.cn/large/e6c9d24ely1h2gbpram9rj20nq0ccmxz.jpg) #### Principle ##### Nacos Config Data Structure Nacos Config uniquely identifies a piece of configuration primarily by dataId and group, assuming you already know this background. If you don't know, please refer to [Nacos 文档](https://nacos.io/zh-cn/docs/concepts.html). When the Nacos Client obtains data from the Nacos Server, it calls this interface `ConfigService.getConfig(String dataId, String group, long timeoutMs)`. ##### Spring Cloud App Get Data ###### dataID In the Nacos Config Starter, the concatenation format of dataId is as follow ${prefix} - ${spring.profiles.active} . ${file-extension} * The `prefix` default value is `spring.application.name`. It can also be configured through configuration items `spring.cloud.nacos.config.prefix`. * `spring.profiles.active` This is the profile corresponding to the current environment. For details, please refer to ** Note that when the active profile is empty, the corresponding connector `-` does not exist, and the concatenation format of dataId becomes `${prefix}`. `${file-extension}` ** * `file-extension` It is used to configure the data format of content, which can be configured by configuration item `spring.cloud.nacos.config.file-extension`. Currently only types are supported `properties`. ###### group * `group` The default is `DEFAULT_GROUP`, which can be `spring.cloud.nacos.config.group` configured. ##### Automatic injection The Spring Cloud Alibaba Nacos Config Starter implements the `org.springframework.boot.context.config.ConfigDataLoader` interface and sets the priority to the highest. During the Spring Cloud application startup phase, it will actively obtain the corresponding data from the Nacos Server side, convert the obtained data into PropertySource, and inject it into the Property Sources attribute of the Environment. Therefore, the @ Value annotation can also be used to directly obtain the contents of the Nacos Server configuration. ##### Dynamic refresh By default, the Nacos Config Starter is a refresh method that is triggered `org.springframework.cloud.context.refresh.ContextRefresher` in real time when the monitoring function is added to the configuration item of all Nacos that have successfully obtained data and changes in the server configuration are monitored. If you need to refresh the Bean dynamically, refer to the Spring and Spring Cloud specifications. It is recommended that you add `@RefreshScope` or `@ConfigurationProperties ` annotate the class, For more details, please refer to [ContextRefresher Java Doc](http://static.javadoc.io/org.springframework.cloud/spring-cloud-context/2.0.0.RELEASE/org/springframework/cloud/context/refresh/ContextRefresher.html). #### Endpoint information viewing Spring Boot applications support the use of Endpoints to expose relevant information, as does the Spring Cloud Alibaba Nacos Config Starter. Before using it, you need to add `spring-boot-starter-actuator` a dependency in Maven and allow Endpoints access in the configuration. Spring Boot 3.x can be accessed by visiting http://127.0.0.1:18084/actuator/nacosconfig . ![actuator](https://cdn.nlark.com/lark/0/2018/png/54319/1536986344822-279e1edc-ebca-4201-8362-0ddeff240b85.png) As shown in the figure above, Sources indicates which Nacos Config configuration items the client has obtained information from, and RefreshHistory indicates the history of dynamic refresh, with a maximum of 20 entries saved. NacosConfig Properties is the configuration of the Nacos Config Starter itself. #### More ##### More configuration items Configuration item | key | Default value | Description ----|----|-----|----- Server address | spring. Cloud. Nacos. Config. Server-addr | | server IP and port Prefix of DataId | spring. Cloud. Nacos. Config. Prefix | ${ spring. Application. Name } | Prefix of DataId. The default value is the application name Group|spring.cloud.nacos.config.group|DEFAULT_GROUP| Suffix of DataId and content file format | spring. Cloud. Nacos. Config. File -extension | properties | Suffix of DataId, which is also the file format of configuration content. Currently, only properties is supported Encoding method of the configured content | spring. Cloud. Nacos. Config. Encode | UTF-8 | Configured encoding | Obtain the configured timeout | spring. Cloud. Nacos. Config. Timeout | 3000 | Unit: ms | Namespace of configuration | spring. Cloud. Nacos. Config. Namespace | | One of the common scenarios is to distinguish and isolate the configurations of different environments, such as the resource isolation between the development and testing environments and the production environment. AccessKey|spring.cloud.nacos.config.access-key|| SecretKey|spring.cloud.nacos.config.secret-key|| Relative path | spring. Cloud. Nacos. Config. Context-path | | Relative path of the server API Access point | spring. Cloud. Nacos. Config. Endpoint | | The domain name of a service in a region. The server address can be obtained dynamically through this domain name Whether to enable listening and automatic refresh | spring. Cloud. Nacos. Config. Refresh -enabled | true | Cluster service name | spring. Cloud. Nacos. Config. Cluster -name | | Whether to enable nacos-config health indicator |spring.nacos.config.health-indicator.enabled|false| Whether to enable nacos-discovery health indicator |spring.cloud.nacos.discovery.health-indicator.enabled|false| ### Spring Cloud Alibaba Nacos Discovery #### How to access Before launching the Nacos Discovery sample for demonstration, take a look at how Spring Cloud applications access Nacos Discovery. **Note that this section is only for your convenience to understand the access method. The access work has been completed in this sample code, and you do not need to modify it.** 1. First, modify the `pom.xml` file and introduce spring-cloud-alibaba-nacos-discovery-starter; ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` 2. Configuring a Nacos Server address in an `/src/main/resources/application.properties` applied configuration file; ```properties spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 ``` 3. Use @ EnableDiscoveryClient annotation to enable service registration and discovery; ```java @SpringBootApplication @EnableDiscoveryClient public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } @RestController class EchoController { @GetMapping(value = "/echo/{string}") public String echo(@PathVariable String string) { return string; } } } ``` #### The application starts 1. Add the configuration, and add the basic configuration information in the nacos-discovery-provider-example project `/src/main/resources/application.properties`; ```properties spring.application.name=service-provider server.port=18082 ``` 2. Start the application, support IDE direct start and start after compilation and packaging. 1. IDE direct startup: Find the main class `ProviderApplication` of the nacos-discovery-provider-example project, and execute the main method to start the application. 2. Start after compilation: Compile and package `mvn clean package` the project in the nacos-discovery-provider-example project, and then `java -jar nacos-discovery-provider-example.jar` start the application. #### Query service validation Enter this address `http://127.0.0.1:8848/nacos/v1/ns/catalog/instances?accessToken=XXXXXXXXXXXXXXXXXXXXXXXXXXX&serviceName=service-provider&clusterName=DEFAULT&pageSize=10&pageNo=1&namespaceId=` in the browser and click Jump to see that the service node has been successfully registered to the Nacos Server. ![查询服务](https://cdn.nlark.com/lark/0/2018/png/54319/1536986288092-5cf96af9-9a26-466b-85f6-39ad1d92dfdc.png) #### Service discovery integration Spring Cloud Loadbalancer. ```xml org.springframework.cloud spring-cloud-loadbalancer ``` Add the following configuration to use the load balancing strategy provided by the Spring Cloud Alibaba community for Spring Cloud Loadbalancer load balancing dependencies, so as to use all the capabilities provided by the Spring Cloud Alibaba: ```properties spring.cloud.loadbalancer.ribbon.enabled=false spring.cloud.loadbalancer.nacos.enabled=true ``` #### IPv4 to IPv6 address migration schema ##### Dual registration of IPv4 and IPv6 addresses After configuring the Spring Cloud Loadbalancer as a load balancing strategy, the application will register the IPv4 address and IPv6 address of the microservice in the registration center by default after starting, where the IPv4 address will be stored under the IP field in the Nacos service list. The IPv6 address is in the metadata field of Nacos, and its corresponding Key is IPv6. When the service consumer calls the service provider, it will select the appropriate IP address type to initiate the service call according to its own IP address stack support. Specific rules: (1) If the service consumer itself supports both IPv4 and IPv6 address stacks or only supports IPv6 address stacks, the service consumer will use the IPv6 address provided by the service to initiate a service call. If the IPv6 address call fails, if it also supports IPv4 address stacks, (2) When the service consumer itself only supports IPv4 single address stack, the service consumer will use the IPv4 address provided by the service to initiate a service call. ##### Register IPv4 only If you want to register using only IPv4 addresses, you can use the following configuration at the application. Properties: ```properties spring.cloud.nacos.discovery.ip-type=IPv4 ``` ##### Register for IPv6 only If you want to use only IPv6 addresses, you can use the following configuration on the application. Properties: ```properties spring.cloud.nacos.discovery.ip-type=IPv6 ``` #### Using RestTemplate and FeignClient Here's a look at the code for the nacos-discovery-consumer-example project to show how to RestTemplate and FeignClient. **Note that this section is only for the convenience of understanding the access method. The access work has been completed in this sample code, and you do not need to modify it. Only Ribbon, RestTemplate and FeignClient are involved here. If other service discovery components have been used, Nacos Discovery can be accessed by directly replacing dependencies.** 1. Add the @ LoadBalanced annotation to make the RestTemplate access the Ribbon ```java @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } ``` 2. FeignClient has been integrated with Ribbon by default. Here is how to configure a FeignClient. ```java @FeignClient(name = "service-provider") public interface EchoService { @GetMapping(value = "/echo/{str}") String echo(@PathVariable("str") String str); } ``` Use the @ FeignClient annotation to wrap the Echo Service interface as a FeignClient, and the attribute name corresponds to the service name service-provider. The @ RequestMapping annotation on the echo method corresponds the echo method to the URL "/echo/{ str}", and the @ PathVariable annotation corresponds the str in `{str}` the URL path to the parameter of the echo method. 3. After the above configuration is completed, the two are automatically injected into the TestController. ```java @RestController public class TestController { @Autowired private RestTemplate restTemplate; @Autowired private EchoService echoService; @GetMapping(value = "/echo-rest/{str}") public String rest(@PathVariable String str) { return restTemplate.getForObject("http://service-provider/echo/" + str, String.class); } @GetMapping(value = "/echo-feign/{str}") public String feign(@PathVariable String str) { return echoService.echo(str); } } ``` 4. Configure the necessary configuration and add the basic configuration information in the nacos-discovery-consumer-example project `/src/main/resources/application.properties`. ```java @RestController public class TestController { @Autowired private RestTemplate restTemplate; @Autowired private EchoService echoService; @GetMapping(value = "/echo-rest/{str}") public String rest(@PathVariable String str) { return restTemplate.getForObject("http://service-provider/echo/" + str, String.class); } @GetMapping(value = "/echo-feign/{str}") public String feign(@PathVariable String str) { return echoService.echo(str); } } ``` 5.Start the application, support IDE direct start and start after compilation and packaging. 1. IDE direct startup: Find the main class `ConsumerApplication` of the nacos-discovery-consumer-example project, and execute the main method to start the application. 2. Start after compilation: Compile and package `mvn clean package` the project in the nacos-discovery-consumer-example project, and then `java -jar nacos-discovery-consumer-example.jar` start the application. #### Validate 1. Enter [http://127.0.0.1:18083/echo-rest/1234](http://127.0.0.1:18083/echo-rest/1234) and click Go, you can see that the message "hello Nacos Discovery 1234" returned by nacos-discovery-provider-example in the browser address bar to prove that the service discovery is valid. ![rest](https://cdn.nlark.com/lark/0/2018/png/54319/1536986302124-ee27670d-bdcc-4210-9f5d-875acec6d3ea.png) 2. Enter [http://127.0.0.1:18083/echo-feign/12345](http://127.0.0.1:18083/echo-feign/12345) and click Go, you can see that the message "hello Nacos Discovery 12345" returned by nacos-discovery-provider-example in the browser address bar to prove that the service discovery is effective. ![feign](https://cdn.nlark.com/lark/0/2018/png/54319/1536986311685-6d0c1f9b-a453-4ec3-88ab-f7922d210f65.png) #### Principle ##### Service registration The Spring Cloud Alibaba Nacos Discovery follows the Spring cloud Common standard. The three interfaces of AutoService Registration, Service Registry and Registration are implemented. In the startup phase of the Spring Cloud application, the WebServerInitializedEvent event is monitored. After the Web container is initialized, that is, after the WebServerInitializedEvent event is received, the registration action is triggered. Call the register method of the Service Registry to register the service with the Nacos Server. #### Endpoint information viewing Spring Boot applications support the use of Endpoints to expose relevant information, as does the Spring Cloud Alibaba Nacos Discovery Starter. Before using it, you need to add `spring-boot-starter-actuator` a dependency in Maven and allow Endpoints access in the configuration. Spring Boot 3.x can be accessed by visiting http://127.0.0.1:18083/actuator/nacos-discovery . ![actuator](https://cdn.nlark.com/lark/0/2018/png/54319/1536986319285-d542dc5f-5dff-462a-9f52-7254776bcd99.png) As shown in the figure above, NacosDiscovery Properties refers to the configuration of the Spring Cloud Alibaba Nacos Discovery itself, including the content registered by the local machine, and subscribe refers to the subscribed service information of the local machine. #### More ##### More configuration items Configuration item | key | Default value | Description ----|----|-----|----- Server address | spring. Cloud. Nacos. Discovery. Server -addr | | Service Name | spring. Cloud. Nacos. Discovery. Service | ${ spring. Application. Name } | The name of the service registered to Nacos. The default is the application name Weight | spring. Cloud. Nacos. Discovery. Weight | 1 | Values range from 1 to 100. The larger the value, the greater the weight Network card name | spring. Cloud. Nacos. Discovery. Network -interface | | When IP is not configured, the registered IP is the IP address corresponding to this network card. If this item is not configured, the address of the first network card is used by default Registered IP Address | spring. Cloud. Nacos. Discovery. IP | | Highest Priority Registered IP address type | spring. Cloud. Nacos. Discovery. IP -type | Dual stack address | Both IPv4 and IPv6 types can be configured. If there are multiple IP addresses of the same type in the network card and you want to designate a specific network segment address, you can configure the filtering address with `spring.cloud.inetutils.preferred-networks` Registered port | spring. Cloud. Nacos. Discovery. Port | -1 | is not configured by default and will be detected automatically Namespace | spring. Cloud. Nacos. Discovery. Namespace | | One of the common scenarios is to distinguish and isolate the registration of different environments, such as the isolation of resources (such as configurations and services) between the development and testing environments and the production environment. AccessKey|spring.cloud.nacos.discovery.access-key|| SecretKey|spring.cloud.nacos.discovery.secret-key|| Metadata | spring. Cloud. Nacos. Discovery. Metadata | | Configure using Map format Log file name | spring. Cloud. Nacos. Discovery. Log -name | | Cluster | spring. Cloud. Nacos. Discovery. Cluster -name | DEFAULT | Nacos cluster name Access point | spring. Cloud. Nacos. Discovery. Endpoint | | The domain name of a service in a region. The server address can be obtained dynamically through this domain name Integrate LoadBalancer | spring. Cloud. Loadbalancer. Nacos. Enabled | false | Nacos Watch enabled | spring. Cloud. Nacos. Discovery. Watch. Enabled | false | can be set to true to enable watch Enable Nacos | spring. Cloud. Nacos. Discovery. Enabled | true | Start by default. If set to false, automatic registration with Nacos will be disabled ### Spring Cloud Alibaba Nacos integrated Spring Cloud Gateway. #### How to access Before starting the demo, learn how Spring Cloud applications connect to Spring Cloud, Nacos Discovery, and Spring Cloud Gateway. **Note that this section is only for your convenience to understand the access method. The access work has been completed in this sample code, and you do not need to modify it.** 1. First, modify the `pom.xml` file to introduce Spring Cloud Alibaba Nacos Discovery Starter and Spring Cloud Gateway Starter. ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-gateway-server-webflux ``` 2. Configure the Nacos Server address in the applied `/src/main/resources/application.properties` configuration films ```properties spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 ``` 3. Configure Spring Cloud Gateway routing in the application's `/src/main/resources/application.properties` configuration file ```properties spring.cloud.gateway.server.webflux.routes[0].id=nacos-route spring.cloud.gateway.server.webflux.routes[0].uri=lb://service-gateway-provider spring.cloud.gateway.server.webflux.routes[0].predicates[0].name=Path spring.cloud.gateway.server.webflux.routes[0].predicates[0].args[pattern]=/nacos/** spring.cloud.gateway.server.webflux.routes[0].filters[0]=StripPrefix=1 ``` 4. Use the @ EnableDiscoveryClient annotation to turn on service registration and discovery ```java @SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } } ``` #### Spring Cloud Gateway App Launch Start the application, support IDE direct start and start after compilation and packaging. 1. IDE direct startup: Find the main class `GatewayApplication` of the nacos-gateway-discovery-example project, and execute the main method to start the application. 2. Start after compilation: Compile and package `mvn clean package` the project in the nacos-gateway-discovery-example project, and then `java -jar nacos-gateway-discovery-example.jar` start the application. #### Service Provider Application Launch Start the application, support IDE direct start and start after compilation and packaging. 1. Start the IDE directly: find the main class `ProviderApplication` of the nacos-gateway-provider-example project, and execute the main method to start the application. 2. Start after compilation: Compile and package `mvn clean package` the project in the nacos-gateway-provider-example project, and then `java -jar nacos-gateway-provider-example.jar` start the application. #### Validate 1. ```bash curl 'http://127.0.0.1:18085/nacos/echo/hello-world' hello Nacos Discovery hello-world⏎ ``` 2. ```bash curl 'http://127.0.0.1:18085/nacos/divide?a=6&b=2' 3⏎ ``` ## Native Image build Please refer to the Spring Cloud Alibaba website ## More introduction Nacos provides users with service infrastructure including dynamic service discovery, configuration management, service management, etc., to help users build, deliver and manage their microservice platforms more flexibly and easily. Based on Nacos, users can build modern cloud native applications centered on "services" more quickly. Nacos can be seamlessly integrated with Spring Cloud, Kubernetes/CNCF, Dubbo and other micro-service ecosystems to provide users with a better experience. For more information about Nacos, see the [Nacos 项目](https://github.com/alibaba/Nacos). In the future, the **Spring-Alibaba-Nacos-Config** module will take on more responsibilities. It will serve as a unified configuration management solution for both second-party middleware components and business components. For **Spring Boot** (including **Spring AI**) and **Spring Cloud** applications, it will provide centralized configuration hosting and seamless runtime configuration rotation. For general business components, it will offer flexible and user-friendly configuration injection and change callback capabilities through the `@NacosConfig` and `@NacosConfigListener` annotations. If you have any suggestions or ideas about Spring Cloud Nacos Discovery, please feel free to send them to us in the issue or through other community channels. ================================================ FILE: spring-cloud-alibaba-examples/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba ${revision} ../pom.xml 4.0.0 spring-cloud-alibaba-examples pom Spring Cloud Alibaba Examples Example showing how to use features of Spring Cloud Alibaba sentinel-example/sentinel-core-example sentinel-example/sentinel-openfeign-example sentinel-example/sentinel-resttemplate-example sentinel-example/sentinel-circuitbreaker-example sentinel-example/sentinel-webflux-example sentinel-example/sentinel-spring-cloud-gateway-example nacos-example/nacos-discovery-example nacos-example/nacos-config-example nacos-example/nacos-gateway-example seata-example/business-service seata-example/order-service seata-example/storage-service seata-example/account-service rocketmq-example/rocketmq-comprehensive-example rocketmq-example/rocketmq-orderly-consume-example rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-producer-example rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-consumer1-example rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-consumer2-example rocketmq-example/rocketmq-delay-consume-example rocketmq-example/rocketmq-sql-consume-example rocketmq-example/rocketmq-example-common rocketmq-example/rocketmq-tx-example rocketmq-example/rocketmq-pollable-consume-example spring-cloud-bus-rocketmq-example spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example integrated-example/integrated-storage integrated-example/integrated-account integrated-example/integrated-order integrated-example/integrated-gateway integrated-example/integrated-praise-provider integrated-example/integrated-praise-consumer integrated-example/integrated-common integrated-example/integrated-frontend spring-cloud-scheduling-example org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true org.graalvm.buildtools native-maven-plugin ${native-maven-plugin.version} org.apache.maven.plugins maven-javadoc-plugin true org.apache.maven.plugins maven-source-plugin true native org.springframework.boot spring-boot-maven-plugin -agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image/ paketobuildpacks/builder:tiny true process-aot process-aot org.graalvm.buildtools native-maven-plugin ${project.build.outputDirectory} true 22.3 add-reachability-metadata add-reachability-metadata ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md ================================================ # RocketMQ Example ## 项目说明 本项目演示如何使用 RocketMQ Binder 完成 Spring Cloud 应用消息的订阅和发布。 [RocketMQ](https://rocketmq.apache.org/) 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。 在说明 RocketMQ 的示例之前,我们先了解一下 Spring Cloud Stream。 这是官方对 Spring Cloud Stream 的一段介绍: Spring Cloud Stream 是一个用于构建基于消息的微服务应用框架。它基于 SpringBoot 来创建具有生产级别的单机 Spring 应用,并且使用 `Spring Integration` 与 Broker 进行连接。 Spring Cloud Stream 提供了消息中间件配置的统一抽象,推出了 publish-subscribe、consumer groups、partition 这些统一的概念。 Spring Cloud Stream 内部有两个概念:Binder 和 Binding。 * Binder: 跟外部消息中间件集成的组件,用来创建 Binding,各消息中间件都有自己的 Binder 实现。 比如 `Kafka` 的实现 `KafkaMessageChannelBinder`,`RabbitMQ` 的实现 `RabbitMessageChannelBinder` 以及 `RocketMQ` 的实现 `RocketMQMessageChannelBinder`。 * Binding: 包括 Input Binding 和 Output Binding。 Binding 在消息中间件与应用程序提供的 Provider 和 Consumer 之间提供了一个桥梁,实现了开发者只需使用应用程序的 Provider 或 Consumer 生产或消费数据即可,屏蔽了开发者与底层消息中间件的接触。 下图是 Spring Cloud Stream 的架构设计。 ![](https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/images/SCSt-with-binder.png) ## 准备工作 ### 下载并启动 RocketMQ **在接入 RocketMQ Binder 之前,首先需要启动 RocketMQ 的 Name Server 和 Broker。** 1. 下载[RocketMQ最新的二进制文件](https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.3.2/rocketmq-all-4.3.2-bin-release.zip),并解压 2. 启动 Name Server ```bash sh bin/mqnamesrv ``` 3. 启动 Broker ```bash sh bin/mqbroker -n localhost:9876 ``` ### 引入依赖 修改 `pom.xml` 文件,引入 RocketMQ Stream Starter。 ```xml com.alibaba.cloud spring-cloud-starter-stream-rocketmq ``` ## 简单示例 ### 创建Topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t test-topic ``` ### 示例代码 配置 Input 和 Output 的 Binding 信息并配合 `@EnableBinding` 注解使其生效 ```java @SpringBootApplication @EnableBinding({ Source.class, Sink.class }) public class RocketMQApplication { public static void main(String[] args) { SpringApplication.run(RocketMQApplication.class, args); } } ``` 配置 Binding 信息: ```properties # 配置rocketmq的nameserver地址 spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876 # 定义name为output的binding spring.cloud.stream.bindings.output.destination=test-topic spring.cloud.stream.bindings.output.content-type=application/json # 定义name为input的binding spring.cloud.stream.bindings.input.destination=test-topic spring.cloud.stream.bindings.input.content-type=application/json spring.cloud.stream.bindings.input.group=test-group ``` ### 应用启动 1. 增加配置,在应用的 /src/main/resources/application.properties 中添加基本配置信息 ```properties spring.application.name=rocketmq-example server.port=28081 ``` 2. 启动应用,支持 IDE 直接启动和编译打包后启动。 1. IDE 直接启动:找到主类 `RocketMQApplication`,执行 main 方法启动应用。 2. 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar rocketmq-example.jar` 启动应用。 ### 消息处理 使用 name 为 output 对应的 binding 发送消息到 test-topic 这个 topic。 使用2个 input binding 订阅数据。 * input1: 订阅 topic 为 test-topic 的消息,顺序消费所有消息(顺序消费的前提是所有消息都在一个 MessageQueue 中) * input2: 订阅 topic 为 test-topic 的消息,异步消费 tags 为 tagStr 的消息,Consumer 端线程池个数为20 配置信息如下: ```properties spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876 spring.cloud.stream.bindings.output.destination=test-topic spring.cloud.stream.bindings.output.content-type=application/json spring.cloud.stream.bindings.input1.destination=test-topic spring.cloud.stream.bindings.input1.content-type=text/plain spring.cloud.stream.bindings.input1.group=test-group1 spring.cloud.stream.rocketmq.bindings.input1.consumer.orderly=true spring.cloud.stream.bindings.input2.destination=test-topic spring.cloud.stream.bindings.input2.content-type=text/plain spring.cloud.stream.bindings.input2.group=test-group2 spring.cloud.stream.rocketmq.bindings.input2.consumer.orderly=false spring.cloud.stream.rocketmq.bindings.input2.consumer.tags=tagStr spring.cloud.stream.bindings.input2.consumer.concurrency=20 ``` #### 消息发送 使用 MessageChannel 进行消息发送: ```java public class ProducerRunner implements CommandLineRunner { @Autowired private MessageChannel output; // 获取name为output的binding @Override public void run(String... args) throws Exception { Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_TAGS, "tagStr"); Message message = MessageBuilder.createMessage(msg, new MessageHeaders(headers)); output.send(message); } } ``` 或者使用 RocketMQ 原生的 API 进行消息发送: ```java public class RocketMQProducer { DefaultMQProducer producer = new DefaultMQProducer("producer_group"); producer.setNamesrvAddr("127.0.0.1:9876"); producer.start(); Message msg = new Message("test-topic", "tagStr", "message from rocketmq producer".getBytes()); producer.send(msg); } ``` #### 消息接收 使用 `@StreamListener` 注解接收消息: ```java @Service public class ReceiveService { @StreamListener("input1") public void receiveInput1(String receiveMsg) { System.out.println("input1 receive: " + receiveMsg); } @StreamListener("input2") public void receiveInput2(String receiveMsg) { System.out.println("input2 receive: " + receiveMsg); } } ``` ## 广播消费示例 ​ 广播会发送消息给所有消费者。如果你想同一消费组下所有消费者接收到同一个topic下的消息,广播消费非常适合此场景。 ### 创建Topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t broadcast ``` ### 生产者 **application.yml** ```yaml server: port: 28085 spring: application: name: rocketmq-broadcast-producer-example cloud: stream: rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 bindings: producer-out-0: destination: broadcast logging: level: org.springframework.context.support: debug ``` **code** 使用`ApplicationRunner`和`StreamBridge`发送消息。 ```java @SpringBootApplication public class RocketMQBroadcastProducerApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQBroadcastProducerApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQBroadcastProducerApplication.class, args); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); Message msg = new GenericMessage(new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } } ``` ### 消费者 启动两个消费者实例。 #### 消费者1 **application.yml** ```yaml server: port: 28084 spring: application: name: rocketmq-broadcast-consumer1-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: consumer-in-0: consumer: messageModel: BROADCASTING bindings: consumer-in-0: destination: broadcast group: broadcast-consumer logging: level: org.springframework.context.support: debug ``` **code** ```java @SpringBootApplication public class RocketMQBroadcastConsumer1Application { private static final Logger log = LoggerFactory .getLogger(RocketMQBroadcastConsumer1Application.class); public static void main(String[] args) { SpringApplication.run(RocketMQBroadcastConsumer1Application.class, args); } @Bean public Consumer> consumer() { return msg -> { log.info(Thread.currentThread().getName() + " Consumer1 Receive New Messages: " + msg.getPayload().getMsg()); }; } } ``` #### 消费者2 **application.yml** ```yaml server: port: 28083 spring: application: name: rocketmq-broadcast-consumer2-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: consumer-in-0: consumer: messageModel: BROADCASTING bindings: consumer-in-0: destination: broadcast group: broadcast-consumer logging: level: org.springframework.context.support: debug ``` **code** ```java @SpringBootApplication public class RocketMQBroadcastConsumer2Application { private static final Logger log = LoggerFactory .getLogger(RocketMQBroadcastConsumer2Application.class); public static void main(String[] args) { SpringApplication.run(RocketMQBroadcastConsumer2Application.class, args); } @Bean public Consumer> consumer() { return msg -> { log.info(Thread.currentThread().getName() + " Consumer2 Receive New Messages: " + msg.getPayload().getMsg()); }; } } ``` ## 顺序消费示例 顺序消息(FIFO消息)是消息队列RocketMQ版提供的一种严格按照顺序来发布和消费的消息类型。 顺序消息分为两类: - 全局顺序:对于指定的一个Topic,所有消息按照严格的先入先出FIFO(First In First Out)的顺序进行发布和消费。分区顺序:对于指定的一个Topic,所有消息根据Sharding Key进行区块分区。同一个分区内的消息按照严格的FIFO顺序进行发布和消费。Sharding Key是顺序消息中用来区分不同分区的关键字段,和普通消息的Key是完全不同的概念。 ### 创建Topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t orderly ``` ### 示例代码 **application.yml** ```yaml server: port: 28082 spring: application: name: rocketmq-orderly-consume-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 # 定义messageSelector messageQueueSelector: orderlyMessageQueueSelector consumer-in-0: consumer: # tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } . subscription: 'TagA || TagC || TagD' push: orderly: true bindings: producer-out-0: destination: orderly consumer-in-0: destination: orderly group: orderly-consumer logging: level: org.springframework.context.support: debug ``` **MessageQueueSelector** 选择适合自己的分区选择算法,保证同一个参数得到的结果相同。 ```java @Component public class OrderlyMessageQueueSelector implements MessageQueueSelector { private static final Logger log = LoggerFactory .getLogger(OrderlyMessageQueueSelector.class); @Override public MessageQueue select(List mqs, Message msg, Object arg) { Integer id = (Integer) ((MessageHeaders) arg).get(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID); String tag = (String) ((MessageHeaders) arg).get(MessageConst.PROPERTY_TAGS); int index = id % RocketMQOrderlyConsumeApplication.tags.length % mqs.size(); return mqs.get(index); } } ``` **生产者&消费者** ```java @SpringBootApplication public class RocketMQOrderlyConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQOrderlyConsumeApplication.class); @Autowired private StreamBridge streamBridge; /*** * tag array. */ public static final String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; public static void main(String[] args) { SpringApplication.run(RocketMQOrderlyConsumeApplication.class, args); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_TAGS, tags[i % tags.length]); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); Message msg = new GenericMessage(new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } @Bean public Consumer> consumer() { return msg -> { String tagHeaderKey = RocketMQMessageConverterSupport.toRocketHeaderKey( MessageConst.PROPERTY_TAGS).toString(); log.info(Thread.currentThread().getName() + " Receive New Messages: " + msg.getPayload().getMsg() + " TAG:" + msg.getHeaders().get(tagHeaderKey).toString()); try { Thread.sleep(100); } catch (InterruptedException ignored) { } }; } } ``` ## 延时消息示例 - 延时消息:Producer将消息发送到消息队列RocketMQ服务端,但并不期望立马投递这条消息,而是延迟一定时间后才投递到Consumer进行消费,该消息即延时消息。 ### 创建Topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t delay ``` ### 示例代码 **application.yml** ```yaml server: port: 28086 spring: application: name: rocketmq-delay-consume-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 bindings: producer-out-0: destination: delay consumer-in-0: destination: delay group: delay-group logging: level: org.springframework.context.support: debug ``` **code** ```java @SpringBootApplication public class RocketMQDelayConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQDelayConsumeApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQDelayConsumeApplication.class, args); } @Bean public ApplicationRunner producerDelay() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); // 设置延时等级1~10 headers.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, 2); Message msg = new GenericMessage(new SimpleMsg("Delay RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } @Bean public Consumer> consumer() { return msg -> { log.info(Thread.currentThread().getName() + " Consumer Receive New Messages: " + msg.getPayload().getMsg()); }; } } ``` ## 过滤消息示例 ### 创建Topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t sql ``` ### 示例代码 **application.yml** 支持tag过滤或者sql过滤,设置`spring.cloud.stream.rocketmq.bindings..consumer.subscription`即可。 tag示例: `tag:red || blue` sql示例: `sql:(color in ('red1', 'red2', 'red4') and price>3)` 更多请参考: [Filter](https://rocketmq.apache.org/docs/filter-by-sql92-example/) ```yaml server: port: 28087 spring: application: name: rocketmq-sql-consume-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 consumer-in-0: consumer: # tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } . subscription: sql:(color in ('red1', 'red2', 'red4') and price>3) bindings: producer-out-0: destination: sql consumer-in-0: destination: sql group: sql-group logging: level: org.springframework.context.support: debug ``` **code** ```java @SpringBootApplication public class RocketMQSqlConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQSqlConsumeApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQSqlConsumeApplication.class, args); } /** * color array. */ public static final String[] color = new String[] {"red1", "red2", "red3", "red4", "red5"}; /** * price array. */ public static final Integer[] price = new Integer[] {1, 2, 3, 4, 5}; @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put("color", color[i % color.length]); headers.put("price", price[i % price.length]); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); Message msg = new GenericMessage(new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } @Bean public Consumer> consumer() { return msg -> { String colorHeaderKey = "color"; String priceHeaderKey = "price"; log.info(Thread.currentThread().getName() + " Receive New Messages: " + msg.getPayload().getMsg() + " COLOR:" + msg.getHeaders().get(colorHeaderKey).toString() + " " + "PRICE: " + msg.getHeaders().get(priceHeaderKey).toString()); }; } } ``` #### 常见问题 - MQClientException: The broker does not support consumer to filter message by SQL92 1. 修改 RocketMQ 服务端配置文件。 在 `conf/2m-2s-async/broker-a.properties` 配置文件末尾添加 `enablePropertyFilter=true` 2. 重启 mqbroker 并指定配置文件。 `mqbroker` 启动时指定配置文件:`conf/2m-2s-async/broker-a.properties`,例如: ```shell bin/mqbroker -n 127.0.0.1:9876 -c conf/2m-2s-async/broker-a.properties autoCreateTopicEnable=true ``` ## 事务消息示例 ### 什么是事务消息? 参考[Transaction Example](https://rocketmq.apache.org/docs/transaction-example/). > 可以被认为是一个两阶段的提交消息实现,以确保分布式系统的最终一致性。 事务性消息确保本地事务的执行和消息的发送可以原子地执行。 ### Application > 1、 事务装填 > > 事务消息有三个状态: > (1) TransactionStatus.CommitTransaction: 提交事务,意味着消费者可以消费事务 > (2) TransactionStatus.RollbackTransaction: 回滚事务,消息将被删除,并且不允许被消费。 > (3) TransactionStatus.Unknown: 中间状态,意味着MQ需要回查最终状态。 ### 创建Topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t tx ``` ### 示例代码 **application.yml** ```yaml server: port: 28088 spring: application: name: rocketmq-tx-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 transactionListener: myTransactionListener producerType: Trans bindings: producer-out-0: destination: tx consumer-in-0: destination: tx group: tx-group logging: level: org.springframework.context.support: debug ``` **TransactionListenerImpl** 执行本地事务。 ```java @Component("myTransactionListener") public class TransactionListenerImpl implements TransactionListener { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { Object num = msg.getProperty("test"); if ("1".equals(num)) { System.out.println("executer: " + new String(msg.getBody()) + " unknown"); return LocalTransactionState.UNKNOW; } else if ("2".equals(num)) { System.out.println("executer: " + new String(msg.getBody()) + " rollback"); return LocalTransactionState.ROLLBACK_MESSAGE; } System.out.println("executer: " + new String(msg.getBody()) + " commit"); return LocalTransactionState.COMMIT_MESSAGE; } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { System.out.println("check: " + new String(msg.getBody())); return LocalTransactionState.COMMIT_MESSAGE; } } ``` **producer and consumer** ```java @SpringBootApplication public class RocketMQTxApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQTxApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQTxApplication.class, args); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 1; i <= 4; i++) { MessageBuilder builder = MessageBuilder.withPayload(new SimpleMsg("Hello Tx msg " + i)); builder.setHeader("test", String.valueOf(i)) .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON); builder.setHeader(RocketMQConst.USER_TRANSACTIONAL_ARGS, "binder"); Message msg = builder.build(); streamBridge.send("producer-out-0", msg); System.out.println("send Msg:" + msg.toString()); } }; } @Bean public Consumer> consumer() { return msg -> { Object arg = msg.getHeaders(); log.info(Thread.currentThread().getName() + " Receive New Messages: " + msg.getPayload().getMsg() + " ARG:" + arg.toString()); }; } } ``` ## Endpoint 信息查看 Spring Boot 应用支持通过 Endpoint 来暴露相关信息,RocketMQ Stream Starter 也支持这一点。 在使用之前需要在 Maven 中添加 `spring-boot-starter-actuator`依赖,并在配置中允许 Endpoints 的访问。 * Spring Boot 1.x 中添加配置 `management.security.enabled=false` * Spring Boot 2.x 中添加配置 `management.endpoints.web.exposure.include=*` Spring Boot 1.x 可以通过访问 http://127.0.0.1:18083/rocketmq_binder 来查看 RocketMQ Binder Endpoint 的信息。Spring Boot 2.x 可以通过访问 http://127.0.0.1:28081/actuator/rocketmq-binder 来访问。 这里会统计消息最后一次发送的数据,消息发送成功或失败的次数,消息消费成功或失败的次数等数据。 ```json { "runtime": { "lastSend.timestamp": 1542786623915 }, "metrics": { "scs-rocketmq.consumer.test-topic.totalConsumed": { "count": 11 }, "scs-rocketmq.consumer.test-topic.totalConsumedFailures": { "count": 0 }, "scs-rocketmq.producer.test-topic.totalSentFailures": { "count": 0 }, "scs-rocketmq.consumer.test-topic.consumedPerSecond": { "count": 11, "fifteenMinuteRate": 0.012163847780107841, "fiveMinuteRate": 0.03614605351360527, "meanRate": 0.3493213353657594, "oneMinuteRate": 0.17099243039490175 }, "scs-rocketmq.producer.test-topic.totalSent": { "count": 5 }, "scs-rocketmq.producer.test-topic.sentPerSecond": { "count": 5, "fifteenMinuteRate": 0.005540151995103271, "fiveMinuteRate": 0.01652854617838251, "meanRate": 0.10697493212602836, "oneMinuteRate": 0.07995558537067671 }, "scs-rocketmq.producer.test-topic.sentFailuresPerSecond": { "count": 0, "fifteenMinuteRate": 0.0, "fiveMinuteRate": 0.0, "meanRate": 0.0, "oneMinuteRate": 0.0 }, "scs-rocketmq.consumer.test-topic.consumedFailuresPerSecond": { "count": 0, "fifteenMinuteRate": 0.0, "fiveMinuteRate": 0.0, "meanRate": 0.0, "oneMinuteRate": 0.0 } } } ``` 注意:要想查看统计数据需要在pom里加上 [metrics-core依赖](https://mvnrepository.com/artifact/io.dropwizard.metrics/metrics-core)。如若不加,endpoint 将会显示 warning 信息而不会显示统计信息: ```json { "warning": "please add metrics-core dependency, we use it for metrics" } ``` ## More RocketMQ 是一款功能强大的分布式消息系统,广泛应用于多个领域,包括异步通信解耦、企业解决方案、金融支付、电信、电子商务、快递物流、广告营销、社交、即时通信、移动应用、手游、视频、物联网、车联网等。 此 Demo 仅演示了 RocketMQ 与 Spring Cloud Stream 结合后的使用,更多 RocketMQ 相关的信息,请参考 [RocketMQ 项目](https://github.com/apache/rocketmq)。 如果您对 spring cloud starter stream rocketmq 有任何建议或想法,欢迎在 issue 中或者通过其他社区渠道向我们提出。 ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/readme.md ================================================ # RocketMQ Example ## Project Instruction This example illustrates how to use RocketMQ Binder implement pub/sub messages for Spring Cloud applications. [RocketMQ](https://rocketmq.apache.org/) is a distributed messaging and streaming platform with low latency, high performance and reliability, trillion-level capacity and flexible scalability. Before we start the demo, let's look at Spring Cloud Stream. Spring Cloud Stream is a framework for building message-driven microservice applications. Spring Cloud Stream builds upon Spring Boot to create standalone, production-grade Spring applications and uses Spring Integration to provide connectivity to message brokers. It provides opinionated configuration of middleware from several vendors, introducing the concepts of persistent publish-subscribe semantics, consumer groups, and partitions. There are two concepts in Spring Cloud Stream: Binder 和 Binding. * Binder: A strategy interface used to bind an app interface to a logical name. Binder Implementations includes `KafkaMessageChannelBinder` of kafka, `RabbitMessageChannelBinder` of RabbitMQ and `RocketMQMessageChannelBinder` of `RocketMQ`. * Binding: Including Input Binding and Output Binding. Binding is Bridge between the external messaging systems and application provided Producers and Consumers of messages. This is a overview of Spring Cloud Stream. ![](https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/images/SCSt-with-binder.png) ## Preparation ### Download and Startup RocketMQ You should startup Name Server and Broker before using RocketMQ Binder. 1. Download [RocketMQ](https://archive.apache.org/dist/rocketmq/4.3.2/rocketmq-all-4.3.2-bin-release.zip) and unzip it. 2. Startup Name Server ```bash sh bin/mqnamesrv ``` 3. Startup Broker ```bash sh bin/mqbroker -n localhost:9876 ``` ### Declare dependency Add dependency spring-cloud-starter-stream-rocketmq to the `pom.xml` file in your Spring Cloud project. ```xml com.alibaba.cloud spring-cloud-starter-stream-rocketmq ``` ## Simple example ### Create topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t test-topic ``` ### Integration with RocketMQ Binder Configure Input and Output Binding and cooperate with `@EnableBinding` annotation ```java @SpringBootApplication @EnableBinding({ Source.class, Sink.class }) public class RocketMQApplication { public static void main(String[] args) { SpringApplication.run(RocketMQApplication.class, args); } } ``` Configure Binding: ```properties # configure the nameserver of rocketmq spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876 # configure the output binding named output spring.cloud.stream.bindings.output.destination=test-topic spring.cloud.stream.bindings.output.content-type=application/json # configure the input binding named input spring.cloud.stream.bindings.input.destination=test-topic spring.cloud.stream.bindings.input.content-type=application/json spring.cloud.stream.bindings.input.group=test-group ``` ### Start Application 1. Add necessary configurations to file `/src/main/resources/application.properties`. ```properties spring.application.name=rocketmq-example server.port=28081 ``` 2. Start the application in IDE or by building a fatjar. 1. Start in IDE: Find main class `RocketMQApplication`, and execute the main method. 2. Build a fatjar: Execute command `mvn clean package` to build a fatjar, and run command `java -jar rocketmq-example.jar` to start the application. ### Message Handle Using the binding named output and sent messages to `test-topic` topic. And using two input bindings to subscribe messages. * input1: subscribe the message of `test-topic` topic and consume ordered messages(all messages should in the same MessageQueue if you want to consuming ordered messages). * input2: subscribe the message of `test-topic` topic and consume concurrent messages which tags is `tagStr`, the thread number in pool is 20 in Consumer side. see the configuration below: ```properties spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876 spring.cloud.stream.bindings.output.destination=test-topic spring.cloud.stream.bindings.output.content-type=application/json spring.cloud.stream.bindings.input1.destination=test-topic spring.cloud.stream.bindings.input1.content-type=text/plain spring.cloud.stream.bindings.input1.group=test-group1 spring.cloud.stream.rocketmq.bindings.input1.consumer.orderly=true spring.cloud.stream.bindings.input2.destination=test-topic spring.cloud.stream.bindings.input2.content-type=text/plain spring.cloud.stream.bindings.input2.group=test-group2 spring.cloud.stream.rocketmq.bindings.input2.consumer.orderly=false spring.cloud.stream.rocketmq.bindings.input2.consumer.tags=tagStr spring.cloud.stream.bindings.input2.consumer.concurrency=20 ``` #### Pub Messages Using MessageChannel to send messages: ```java public class ProducerRunner implements CommandLineRunner { @Autowired private MessageChannel output; @Override public void run(String... args) throws Exception { Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_TAGS, "tagStr"); Message message = MessageBuilder.createMessage(msg, new MessageHeaders(headers)); output.send(message); } } ``` Or you can using the native API of RocketMQ to send messages: ```java public class RocketMQProducer { DefaultMQProducer producer = new DefaultMQProducer("producer_group"); producer.setNamesrvAddr("127.0.0.1:9876"); producer.start(); Message msg = new Message("test-topic", "tagStr", "message from rocketmq producer".getBytes()); producer.send(msg); } ``` #### Sub Messages Using `@StreamListener` to receive messages: ```java @Service public class ReceiveService { @StreamListener("input1") public void receiveInput1(String receiveMsg) { System.out.println("input1 receive: " + receiveMsg); } @StreamListener("input2") public void receiveInput2(String receiveMsg) { System.out.println("input2 receive: " + receiveMsg); } } ``` ## Broadcasting exmaple ### Create topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t broadcast ``` ### Producer **application.yml** ```yaml server: port: 28085 spring: application: name: rocketmq-broadcast-producer-example cloud: stream: rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 bindings: producer-out-0: destination: broadcast logging: level: org.springframework.context.support: debug ``` **code** Use `ApplicationRunner` and `StreamBridge` to send messages. ```java @SpringBootApplication public class RocketMQBroadcastProducerApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQBroadcastProducerApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQBroadcastProducerApplication.class, args); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); Message msg = new GenericMessage(new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } } ``` ### Consumer Startup two consumers. #### Consumer1 **application.yml** ```yaml server: port: 28084 spring: application: name: rocketmq-broadcast-consumer1-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: consumer-in-0: consumer: messageModel: BROADCASTING bindings: consumer-in-0: destination: broadcast group: broadcast-consumer logging: level: org.springframework.context.support: debug ``` **code** ```java @SpringBootApplication public class RocketMQBroadcastConsumer1Application { private static final Logger log = LoggerFactory .getLogger(RocketMQBroadcastConsumer1Application.class); public static void main(String[] args) { SpringApplication.run(RocketMQBroadcastConsumer1Application.class, args); } @Bean public Consumer> consumer() { return msg -> { log.info(Thread.currentThread().getName() + " Consumer1 Receive New Messages: " + msg.getPayload().getMsg()); }; } } ``` #### Consumer2 **application.yml** ```yaml server: port: 28083 spring: application: name: rocketmq-broadcast-consumer2-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: consumer-in-0: consumer: messageModel: BROADCASTING bindings: consumer-in-0: destination: broadcast group: broadcast-consumer logging: level: org.springframework.context.support: debug ``` **code** ```java @SpringBootApplication public class RocketMQBroadcastConsumer2Application { private static final Logger log = LoggerFactory .getLogger(RocketMQBroadcastConsumer2Application.class); public static void main(String[] args) { SpringApplication.run(RocketMQBroadcastConsumer2Application.class, args); } @Bean public Consumer> consumer() { return msg -> { log.info(Thread.currentThread().getName() + " Consumer2 Receive New Messages: " + msg.getPayload().getMsg()); }; } } ``` ## Order example ​ RocketMQ provides ordered messages using FIFO order. ​ There are two types of ordered messages. * Global: For a specified topic, all messages are published and consumed in strict FIFO (First In First Out) order. * Partition: For a specified topic, all messages are partitioned according to the `Sharding Key`. Messages within the same partition are published and consumed in strict FIFO order. `Sharding Key` is a key field used to distinguish different partitions in sequential messages, and it is a completely different concept from the Key of ordinary messages. ### Create Topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t orderly ``` ### Example code **application.yml** ```yaml server: port: 28082 spring: application: name: rocketmq-orderly-consume-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 # 定义messageSelector messageQueueSelector: orderlyMessageQueueSelector consumer-in-0: consumer: # tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } . subscription: 'TagA || TagC || TagD' push: orderly: true bindings: producer-out-0: destination: orderly consumer-in-0: destination: orderly group: orderly-consumer logging: level: org.springframework.context.support: debug ``` **MessageQueueSelector** Choose a partition selection algorithm for you, and ensure that the same parameters get the same results. ```java @Component public class OrderlyMessageQueueSelector implements MessageQueueSelector { private static final Logger log = LoggerFactory .getLogger(OrderlyMessageQueueSelector.class); @Override public MessageQueue select(List mqs, Message msg, Object arg) { Integer id = (Integer) ((MessageHeaders) arg).get(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID); String tag = (String) ((MessageHeaders) arg).get(MessageConst.PROPERTY_TAGS); int index = id % RocketMQOrderlyConsumeApplication.tags.length % mqs.size(); return mqs.get(index); } } ``` **Producer&Consumer** ```java @SpringBootApplication public class RocketMQOrderlyConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQOrderlyConsumeApplication.class); @Autowired private StreamBridge streamBridge; /*** * tag array. */ public static final String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; public static void main(String[] args) { SpringApplication.run(RocketMQOrderlyConsumeApplication.class, args); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_TAGS, tags[i % tags.length]); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); Message msg = new GenericMessage(new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } @Bean public Consumer> consumer() { return msg -> { String tagHeaderKey = RocketMQMessageConverterSupport.toRocketHeaderKey( MessageConst.PROPERTY_TAGS).toString(); log.info(Thread.currentThread().getName() + " Receive New Messages: " + msg.getPayload().getMsg() + " TAG:" + msg.getHeaders().get(tagHeaderKey).toString()); try { Thread.sleep(100); } catch (InterruptedException ignored) { } }; } } ``` ## Schedule example Scheduled messages differ from normal messages in that they won’t be delivered until a provided time later. ### Create topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t delay ``` ### Example code **application.yml** ```yaml server: port: 28086 spring: application: name: rocketmq-delay-consume-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 bindings: producer-out-0: destination: delay consumer-in-0: destination: delay group: delay-group logging: level: org.springframework.context.support: debug ``` **code** ```java @SpringBootApplication public class RocketMQDelayConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQDelayConsumeApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQDelayConsumeApplication.class, args); } @Bean public ApplicationRunner producerDelay() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); // Set the delay level 1~10 headers.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, 2); Message msg = new GenericMessage(new SimpleMsg("Delay RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } @Bean public Consumer> consumer() { return msg -> { log.info(Thread.currentThread().getName() + " Consumer Receive New Messages: " + msg.getPayload().getMsg()); }; } } ``` ## Filter example ### Create topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t sql ``` ### Example code **application.yml** RocketMQ stream binder supports filter by tag or sql, just setting `spring.cloud.stream.rocketmq.bindings..consumer.subscription`. Tag example: `tag:red || blue` Sql example: `sql:(color in ('red1', 'red2', 'red4') and price>3)` More: [Filter](https://rocketmq.apache.org/docs/filter-by-sql92-example/) ```yaml server: port: 28087 spring: application: name: rocketmq-sql-consume-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 consumer-in-0: consumer: # tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } . subscription: sql:(color in ('red1', 'red2', 'red4') and price>3) bindings: producer-out-0: destination: sql consumer-in-0: destination: sql group: sql-group logging: level: org.springframework.context.support: debug ``` **code** ```java @SpringBootApplication public class RocketMQSqlConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQSqlConsumeApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQSqlConsumeApplication.class, args); } /** * color array. */ public static final String[] color = new String[] {"red1", "red2", "red3", "red4", "red5"}; /** * price array. */ public static final Integer[] price = new Integer[] {1, 2, 3, 4, 5}; @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put("color", color[i % color.length]); headers.put("price", price[i % price.length]); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); Message msg = new GenericMessage(new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } @Bean public Consumer> consumer() { return msg -> { String colorHeaderKey = "color"; String priceHeaderKey = "price"; log.info(Thread.currentThread().getName() + " Receive New Messages: " + msg.getPayload().getMsg() + " COLOR:" + msg.getHeaders().get(colorHeaderKey).toString() + " " + "PRICE: " + msg.getHeaders().get(priceHeaderKey).toString()); }; } } ``` #### 常见问题 - MQClientException: The broker does not support consumer to filter message by SQL92 1. Modify RocketMQ server configuration file. In the `conf/2m-2s-async/broker-a.properties` configuration file, add `enablePropertyFilter=true`. 2. Restart mqbroker and specify the configuration file. Specify the configuration file when `mqbroker` starts: `conf/2m-2s-async/broker-a.properties`, for example: ```shell bin/mqbroker -n 127.0.0.1:9876 -c conf/2m-2s-async/broker-a.properties autoCreateTopicEnable=true ``` ## Transaction example ### What is transactional message? Refer to [Transaction Example](https://rocketmq.apache.org/docs/transaction-example/). > It can be thought of as a two-phase commit message implementation to ensure eventual consistency in distributed system. Transactional message ensures that the execution of local transaction and the sending of message can be performed atomically. ### Application > 1、 Transactional status > > There are three states for transactional message: > (1) TransactionStatus.CommitTransaction: commit transaction,it means that allow consumers to consume this message. > (2) TransactionStatus.RollbackTransaction: rollback transaction,it means that the message will be deleted and not allowed to consume. > (3) TransactionStatus.Unknown: intermediate state,it means that MQ is needed to check back to determine the status. ### Create topic ```sh sh bin/mqadmin updateTopic -n localhost:9876 -c DefaultCluster -t tx ``` ### Example code **application.yml** ```yaml server: port: 28088 spring: application: name: rocketmq-tx-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 transactionListener: myTransactionListener producerType: Trans bindings: producer-out-0: destination: tx consumer-in-0: destination: tx group: tx-group logging: level: org.springframework.context.support: debug ``` **TransactionListenerImpl** To execute local transaction. ```java @Component("myTransactionListener") public class TransactionListenerImpl implements TransactionListener { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { Object num = msg.getProperty("test"); if ("1".equals(num)) { System.out.println("executer: " + new String(msg.getBody()) + " unknown"); return LocalTransactionState.UNKNOW; } else if ("2".equals(num)) { System.out.println("executer: " + new String(msg.getBody()) + " rollback"); return LocalTransactionState.ROLLBACK_MESSAGE; } System.out.println("executer: " + new String(msg.getBody()) + " commit"); return LocalTransactionState.COMMIT_MESSAGE; } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { System.out.println("check: " + new String(msg.getBody())); return LocalTransactionState.COMMIT_MESSAGE; } } ``` **producer and consumer** ```java @SpringBootApplication public class RocketMQTxApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQTxApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQTxApplication.class, args); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 1; i <= 4; i++) { MessageBuilder builder = MessageBuilder.withPayload(new SimpleMsg("Hello Tx msg " + i)); builder.setHeader("test", String.valueOf(i)) .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON); builder.setHeader(RocketMQConst.USER_TRANSACTIONAL_ARGS, "binder"); Message msg = builder.build(); streamBridge.send("producer-out-0", msg); System.out.println("send Msg:" + msg.toString()); } }; } @Bean public Consumer> consumer() { return msg -> { Object arg = msg.getHeaders(); log.info(Thread.currentThread().getName() + " Receive New Messages: " + msg.getPayload().getMsg() + " ARG:" + arg.toString()); }; } } ``` ## Endpoint Add dependency `spring-cloud-starter-stream-rocketmq` to your pom.xml file, and configure your endpoint security strategy. * Spring Boot1.x: Add configuration `management.security.enabled=false` * Spring Boot2.x: Add configuration `management.endpoints.web.exposure.include=*` To view the endpoint information, visit the following URLS: * Spring Boot1.x: Sentinel Endpoint URL is http://127.0.0.1:18083/rocketmq_binder. * Spring Boot2.x: Sentinel Endpoint URL is http://127.0.0.1:18083/actuator/rocketmq-binder. Endpoint will metrics some data like last send timestamp, sending or receive message successfully times or unsuccessfully times. ```json { "runtime": { "lastSend.timestamp": 1542786623915 }, "metrics": { "scs-rocketmq.consumer.test-topic.totalConsumed": { "count": 11 }, "scs-rocketmq.consumer.test-topic.totalConsumedFailures": { "count": 0 }, "scs-rocketmq.producer.test-topic.totalSentFailures": { "count": 0 }, "scs-rocketmq.consumer.test-topic.consumedPerSecond": { "count": 11, "fifteenMinuteRate": 0.012163847780107841, "fiveMinuteRate": 0.03614605351360527, "meanRate": 0.3493213353657594, "oneMinuteRate": 0.17099243039490175 }, "scs-rocketmq.producer.test-topic.totalSent": { "count": 5 }, "scs-rocketmq.producer.test-topic.sentPerSecond": { "count": 5, "fifteenMinuteRate": 0.005540151995103271, "fiveMinuteRate": 0.01652854617838251, "meanRate": 0.10697493212602836, "oneMinuteRate": 0.07995558537067671 }, "scs-rocketmq.producer.test-topic.sentFailuresPerSecond": { "count": 0, "fifteenMinuteRate": 0.0, "fiveMinuteRate": 0.0, "meanRate": 0.0, "oneMinuteRate": 0.0 }, "scs-rocketmq.consumer.test-topic.consumedFailuresPerSecond": { "count": 0, "fifteenMinuteRate": 0.0, "fiveMinuteRate": 0.0, "meanRate": 0.0, "oneMinuteRate": 0.0 } } } ``` Note: You should add [metrics-core dependency](https://mvnrepository.com/artifact/io.dropwizard.metrics/metrics-core) if you want to see metrics data. endpoint will show warning information if you don't add that dependency: ```json { "warning": "please add metrics-core dependency, we use it for metrics" } ``` ## More For more information about RocketMQ, see [RocketMQ Project](https://rocketmq.apache.org). If you have any ideas or suggestions for Spring Cloud RocketMQ Binder, please don't hesitate to tell us by submitting github issues. ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-consumer1-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../../pom.xml 4.0.0 rocketmq-broadcast-consumer1-example Spring Cloud Starter Stream Alibaba RocketMQ Broadcasting Consume Example Cosumer1 Example demonstrating how to broadcast consumption jar com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-json com.alibaba.cloud rocketmq-example-common ${revision} org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-consumer1-example/src/main/java/com/alibaba/cloud/examples/broadcast/RocketMQBroadcastConsumer1Application.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.broadcast; import java.util.function.Consumer; import com.alibaba.cloud.examples.common.SimpleMsg; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; /** * @author sorie */ @SpringBootApplication public class RocketMQBroadcastConsumer1Application { private static final Logger log = LoggerFactory .getLogger(RocketMQBroadcastConsumer1Application.class); public static void main(String[] args) { SpringApplication.run(RocketMQBroadcastConsumer1Application.class, args); } @Bean public Consumer> consumer() { return msg -> { log.info(Thread.currentThread().getName() + " Consumer1 Receive New Messages: " + msg.getPayload().getMsg()); }; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-consumer1-example/src/main/resources/application.yml ================================================ server: port: 28084 spring: application: name: rocketmq-broadcast-consumer1-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: consumer-in-0: consumer: messageModel: BROADCASTING bindings: consumer-in-0: destination: broadcast group: broadcast-consumer logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-consumer2-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../../pom.xml 4.0.0 rocketmq-broadcast-consumer2-example Spring Cloud Starter Stream Alibaba RocketMQ Broadcasting Consume Example Consumer2 Example demonstrating how to broadcast consumption jar com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-json com.alibaba.cloud rocketmq-example-common ${revision} org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-consumer2-example/src/main/java/com/alibaba/cloud/examples/broadcast/RocketMQBroadcastConsumer2Application.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.broadcast; import java.util.function.Consumer; import com.alibaba.cloud.examples.common.SimpleMsg; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; /** * @author sorie */ @SpringBootApplication public class RocketMQBroadcastConsumer2Application { private static final Logger log = LoggerFactory .getLogger(RocketMQBroadcastConsumer2Application.class); public static void main(String[] args) { SpringApplication.run(RocketMQBroadcastConsumer2Application.class, args); } @Bean public Consumer> consumer() { return msg -> { log.info(Thread.currentThread().getName() + " Consumer2 Receive New Messages: " + msg.getPayload().getMsg()); }; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-consumer2-example/src/main/resources/application.yml ================================================ server: port: 28083 spring: application: name: rocketmq-broadcast-consumer2-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: consumer-in-0: consumer: messageModel: BROADCASTING bindings: consumer-in-0: destination: broadcast group: broadcast-consumer logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-producer-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../../pom.xml 4.0.0 rocketmq-broadcast-producer-example Spring Cloud Starter Stream Alibaba RocketMQ Broadcasting Consume Example Producer Example demonstrating how to use rocketmq to produce jar com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-json com.alibaba.cloud rocketmq-example-common ${revision} org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-producer-example/src/main/java/com/alibaba/cloud/examples/broadcast/RocketMQBroadcastProducerApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.broadcast; import java.util.HashMap; import java.util.Map; import com.alibaba.cloud.examples.common.SimpleMsg; import org.apache.rocketmq.common.message.MessageConst; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; /** * @author sorie */ @SpringBootApplication public class RocketMQBroadcastProducerApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQBroadcastProducerApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQBroadcastProducerApplication.class, args); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); Message msg = new GenericMessage(new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-broadcast-example/rocketmq-broadcast-producer-example/src/main/resources/application.yml ================================================ server: port: 28085 spring: application: name: rocketmq-broadcast-producer-example cloud: stream: rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 bindings: producer-out-0: destination: broadcast logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-comprehensive-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 rocketmq-comprehensive-example Spring Cloud Starter Stream Alibaba RocketMQ Comprehensive Example Example demonstrating how to use rocketmq to produce, process and consume jar com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-json org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-comprehensive-example/src/main/java/com/alibaba/cloud/examples/RocketMQComprehensiveApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.time.Duration; import java.util.Arrays; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.integration.support.StringObjectMapBuilder; /** * @author freeman */ @SpringBootApplication public class RocketMQComprehensiveApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQComprehensiveApplication.class); public static void main(String[] args) { SpringApplication.run(RocketMQComprehensiveApplication.class, args); } @Bean public Supplier> producer() { return () -> Flux.interval(Duration.ofSeconds(2)).map(id -> { User user = new User(); user.setId(id.toString()); user.setName("freeman"); user.setMeta(new StringObjectMapBuilder() .put("hobbies", Arrays.asList("movies", "songs")).put("age", 21) .get()); return user; }).log(); } @Bean public Function, Flux> processor() { return flux -> flux.map(user -> { user.setId(String.valueOf( Long.parseLong(user.getId()) * Long.parseLong(user.getId()))); user.setName("not freeman"); user.getMeta().put("hobbies", Arrays.asList("programming")); return user; }); } @Bean public Consumer consumer() { return num -> log.info(num.toString()); } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-comprehensive-example/src/main/java/com/alibaba/cloud/examples/User.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.util.Map; /** * @author freeman */ public class User { private String id; private String name; private Map meta; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Map getMeta() { return meta; } public void setMeta(Map meta) { this.meta = meta; } @Override public String toString() { return "User{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", meta=" + meta + '}'; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-comprehensive-example/src/main/resources/application.yml ================================================ server: port: 28082 spring: application: name: rocketmq-comprehensive-example cloud: function: definition: producer;consumer;processor stream: rocketmq: binder: name-server: 127.0.0.1:9876 bindings: # TODO producer must have a group, need optimization !!! producer-out-0: producer: group: output_1 processor-out-0: producer: group: output_2 bindings: producer-out-0: destination: num processor-out-0: destination: square processor-in-0: destination: num group: processor_group consumer-in-0: destination: square group: consumer_group logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-delay-consume-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 rocketmq-delay-consume-example Spring Cloud Starter Stream Alibaba RocketMQ Delay Consume Example Example demonstrating how to use rocketmq to delay consume jar com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator com.alibaba.cloud rocketmq-example-common ${revision} org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-delay-consume-example/src/main/java/com/alibaba/cloud/examples/delay/RocketMQDelayConsumeApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.delay; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import com.alibaba.cloud.examples.common.SimpleMsg; import org.apache.rocketmq.common.message.MessageConst; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; /** * @author sorie */ @SpringBootApplication public class RocketMQDelayConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQDelayConsumeApplication.class); private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); private static LocalDateTime sendTime; @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQDelayConsumeApplication.class, args); } /** * Produce delay messages. */ @Bean public ApplicationRunner producerDelay() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); headers.put(MessageConst.PROPERTY_DELAY_TIME_LEVEL, 2); Message msg = new GenericMessage(new SimpleMsg("Delay RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } sendTime = LocalDateTime.now(); log.info("All 100 messages sent at {}", sendTime.format(FORMATTER)); }; } @Bean public Consumer> consumer() { return msg -> { log.info(Thread.currentThread().getName() + " Consumer Receive New Messages: " + msg.getPayload().getMsg() + ". Delay time(s): " + (LocalDateTime.now().getSecond() - sendTime.getSecond())); }; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-delay-consume-example/src/main/resources/application.yml ================================================ server: port: 28086 spring: application: name: rocketmq-delay-consume-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 bindings: producer-out-0: destination: delay consumer-in-0: destination: delay group: delay-group logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-example-common/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 rocketmq-example-common Spring Cloud Starter Stream Alibaba RocketMQ Example COMMON Some rocketMQ exmaple common codes jar ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-example-common/src/main/java/com/alibaba/cloud/examples/common/SimpleMsg.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.common; /** * @author sorie */ public class SimpleMsg { private String msg; public SimpleMsg() { } public SimpleMsg(String msg) { this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-example-common/src/main/resources/application.yml ================================================ server: port: 28087 spring: application: name: rocketmq-sql-consume-example cloud: stream: function: definition: producer;consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 consumer-in-0: consumer: # tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } . subscription: sql:(color in ('red1', 'red2', 'red4') and price>3) bindings: producer-out-0: destination: sql consumer-in-0: destination: sql group: sql-group logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-orderly-consume-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 rocketmq-orderly-consume-example Spring Cloud Starter Stream Alibaba RocketMQ Orderly Consume Example Example demonstrating how to use rocketmq to produce, and consume orderly jar com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-json com.alibaba.cloud rocketmq-example-common ${revision} org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-orderly-consume-example/src/main/java/com/alibaba/cloud/examples/orderly/OrderlyMessageQueueSelector.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.orderly; import java.util.List; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Component; /** * @author sorie */ @Component public class OrderlyMessageQueueSelector implements MessageQueueSelector { private static final Logger log = LoggerFactory .getLogger(OrderlyMessageQueueSelector.class); /** * to select a fixed queue by id. * @param mqs all message queues of this topic. * @param msg mq message. * @param arg mq arguments. * @return message queue selected. */ @Override public MessageQueue select(List mqs, Message msg, Object arg) { Integer id = (Integer) ((MessageHeaders) arg).get(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID); int index = id % RocketMQOrderlyConsumeApplication.tags.length % mqs.size(); return mqs.get(index); } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-orderly-consume-example/src/main/java/com/alibaba/cloud/examples/orderly/RocketMQOrderlyConsumeApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.orderly; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import com.alibaba.cloud.examples.common.SimpleMsg; import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport; import org.apache.rocketmq.common.message.MessageConst; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; /** * @author sorie */ @SpringBootApplication public class RocketMQOrderlyConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQOrderlyConsumeApplication.class); @Autowired private StreamBridge streamBridge; /*** * tag array. */ public static final String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; public static void main(String[] args) { SpringApplication.run(RocketMQOrderlyConsumeApplication.class, args); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_TAGS, tags[i % tags.length]); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); Message msg = new GenericMessage(new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } @Bean public Consumer> consumer() { return msg -> { String tagHeaderKey = RocketMQMessageConverterSupport.toRocketHeaderKey( MessageConst.PROPERTY_TAGS).toString(); log.info(Thread.currentThread().getName() + " Receive New Messages: " + msg.getPayload().getMsg() + " TAG:" + msg.getHeaders().get(tagHeaderKey).toString()); try { Thread.sleep(100); } catch (InterruptedException ignored) { } }; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-orderly-consume-example/src/main/resources/application.yml ================================================ server: port: 28082 spring: application: name: rocketmq-orderly-consume-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 messageQueueSelector: orderlyMessageQueueSelector consumer-in-0: consumer: # tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } . subscription: 'TagA || TagC || TagD' push: orderly: true bindings: producer-out-0: destination: orderly consumer-in-0: destination: orderly group: orderly-consumer logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-pollable-consume-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 rocketmq-pollable-consume-example Spring Cloud Starter Stream Alibaba RocketMQ PollableMessageSource Consume Example Example demonstrating how to use rocketmq to produce, and consume by PollableMessageSource jar com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-json com.alibaba.cloud rocketmq-example-common ${revision} org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-pollable-consume-example/src/main/java/com/alibaba/cloud/examples/pollable/RocketMQPollableConsumeApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.pollable; import java.util.HashMap; import java.util.Map; import com.alibaba.cloud.examples.common.SimpleMsg; import org.apache.rocketmq.common.message.MessageConst; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.stream.binder.PollableMessageSource; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.core.ParameterizedTypeReference; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; /** * @author sorie */ @SpringBootApplication public class RocketMQPollableConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQPollableConsumeApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(RocketMQPollableConsumeApplication.class, args); PollableMessageSource destIn = context.getBean(PollableMessageSource.class); new Thread(() -> { while (true) { try { if (!destIn.poll((m) -> { SimpleMsg newPayload = (SimpleMsg) m.getPayload(); System.out.println(newPayload.getMsg()); }, new ParameterizedTypeReference() { })) { Thread.sleep(1000); } } catch (Exception e) { // handle failure } } }).start(); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); Message msg = new GenericMessage( new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-pollable-consume-example/src/main/resources/application.yml ================================================ server: port: 28089 spring: application: name: rocketmq-pollable-consume-example cloud: stream: pollable-source: pollable rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 bindings: producer-out-0: destination: pollable pollable-in-0: destination: pollable logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-sql-consume-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 rocketmq-sql-consume-example Spring Cloud Starter Stream Alibaba RocketMQ Sql Consume Example Example demonstrating how to use rocketmq to filter message by sql jar com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator com.alibaba.cloud rocketmq-example-common ${revision} org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-sql-consume-example/src/main/java/com/alibaba/cloud/examples/sql/RocketMQSqlConsumeApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.sql; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import com.alibaba.cloud.examples.common.SimpleMsg; import org.apache.rocketmq.common.message.MessageConst; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; /** * @author sorie */ @SpringBootApplication public class RocketMQSqlConsumeApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQSqlConsumeApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQSqlConsumeApplication.class, args); } /** * color array. */ public static final String[] color = new String[] {"red1", "red2", "red3", "red4", "red5"}; /** * price array. */ public static final Integer[] price = new Integer[] {1, 2, 3, 4, 5}; @Bean public ApplicationRunner producer() { return args -> { for (int i = 0; i < 100; i++) { String key = "KEY" + i; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put("color", color[i % color.length]); headers.put("price", price[i % price.length]); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, i); Message msg = new GenericMessage(new SimpleMsg("Hello RocketMQ " + i), headers); streamBridge.send("producer-out-0", msg); } }; } @Bean public Consumer> consumer() { return msg -> { String colorHeaderKey = "color"; String priceHeaderKey = "price"; log.info(Thread.currentThread().getName() + " Receive New Messages: " + msg.getPayload().getMsg() + " COLOR:" + msg.getHeaders().get(colorHeaderKey).toString() + " " + "PRICE: " + msg.getHeaders().get(priceHeaderKey).toString()); }; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-sql-consume-example/src/main/resources/application.yml ================================================ server: port: 28087 spring: application: name: rocketmq-sql-consume-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 consumer-in-0: consumer: # tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } . subscription: sql:(color in ('red1', 'red2', 'red4') and price>3) bindings: producer-out-0: destination: sql consumer-in-0: destination: sql group: sql-group logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-tx-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 rocketmq-tx-example Spring Cloud Starter Stream Alibaba RocketMQ Transaction message Example Example demonstrating how to send and consume transaction messages in rocketmq jar com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator com.alibaba.cloud rocketmq-example-common ${revision} org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-tx-example/src/main/java/com/alibaba/cloud/examples/tx/RocketMQTxApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.tx; import java.util.function.Consumer; import com.alibaba.cloud.examples.common.SimpleMsg; import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.MimeTypeUtils; /** * @author sorie */ @SpringBootApplication public class RocketMQTxApplication { private static final Logger log = LoggerFactory .getLogger(RocketMQTxApplication.class); @Autowired private StreamBridge streamBridge; public static void main(String[] args) { SpringApplication.run(RocketMQTxApplication.class, args); } @Bean public ApplicationRunner producer() { return args -> { for (int i = 1; i <= 4; i++) { MessageBuilder builder = MessageBuilder.withPayload(new SimpleMsg("Hello Tx msg " + i)); builder.setHeader("test", String.valueOf(i)) .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON); builder.setHeader(RocketMQConst.USER_TRANSACTIONAL_ARGS, "binder"); Message msg = builder.build(); streamBridge.send("producer-out-0", msg); System.out.println("send Msg:" + msg.toString()); } }; } @Bean public Consumer> consumer() { return msg -> { Object arg = msg.getHeaders(); log.info(Thread.currentThread().getName() + " Receive New Messages: " + msg.getPayload().getMsg() + " ARG:" + arg.toString()); }; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-tx-example/src/main/java/com/alibaba/cloud/examples/tx/TransactionListenerImpl.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.tx; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; import org.springframework.stereotype.Component; /** * @author Jim */ @Component("myTransactionListener") public class TransactionListenerImpl implements TransactionListener { /** * Execute local transaction. * @param msg messages * @param arg message args * @return Transaction state */ @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { Object num = msg.getProperty("test"); if ("1".equals(num)) { System.out.println("executer: " + new String(msg.getBody()) + " unknown"); return LocalTransactionState.UNKNOW; } else if ("2".equals(num)) { System.out.println("executer: " + new String(msg.getBody()) + " rollback"); return LocalTransactionState.ROLLBACK_MESSAGE; } System.out.println("executer: " + new String(msg.getBody()) + " commit"); return LocalTransactionState.COMMIT_MESSAGE; } /** * MQ check back local transaction states. * @param msg messages * @return Transaction state */ @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { System.out.println("check: " + new String(msg.getBody())); return LocalTransactionState.COMMIT_MESSAGE; } } ================================================ FILE: spring-cloud-alibaba-examples/rocketmq-example/rocketmq-tx-example/src/main/resources/application.yml ================================================ server: port: 28088 spring: application: name: rocketmq-tx-example cloud: stream: function: definition: consumer; rocketmq: binder: name-server: localhost:9876 bindings: producer-out-0: producer: group: output_1 transactionListener: myTransactionListener producerType: Trans bindings: producer-out-0: destination: tx consumer-in-0: destination: tx group: tx-group logging: level: org.springframework.context.support: debug ================================================ FILE: spring-cloud-alibaba-examples/seata-example/account-service/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 account-service Spring Cloud Starter Alibaba Seata Example - Account Service jar com.alibaba.cloud spring-cloud-starter-alibaba-seata com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-jdbc com.mysql mysql-connector-j org.apache.logging.log4j log4j-core org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/seata-example/account-service/src/main/java/com/alibaba/cloud/examples/AccountApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author xiaojing */ @SpringBootApplication public class AccountApplication { public static void main(String[] args) { SpringApplication.run(AccountApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/account-service/src/main/java/com/alibaba/cloud/examples/AccountController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.util.Random; import org.apache.seata.core.context.RootContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; /** * @author xiaojing */ @RestController public class AccountController { private static final Logger LOGGER = LoggerFactory.getLogger(AccountController.class); private static final String SUCCESS = "SUCCESS"; private static final String FAIL = "FAIL"; private final JdbcTemplate jdbcTemplate; private Random random; public AccountController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; this.random = new Random(); } @PostMapping(value = "/account", produces = "application/json") public String account(String userId, int money) { LOGGER.info("Account Service ... xid: " + RootContext.getXID()); if (random.nextBoolean()) { throw new RuntimeException("this is a mock Exception"); } int result = jdbcTemplate.update( "update account_tbl set money = money - ? where user_id = ?", new Object[] { money, userId }); LOGGER.info("Account Service End ... "); if (result == 1) { return SUCCESS; } return FAIL; } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/account-service/src/main/java/com/alibaba/cloud/examples/DatabaseConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; /** * @author xiaojing */ @Configuration public class DatabaseConfiguration { // druid don't support GraalVM now because of there is CGlib proxy // @Bean // @Primary // @ConfigurationProperties("spring.datasource") // public DataSource storageDataSource() { // return new DruidDataSource(); // } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update("delete from account_tbl where user_id = 'U100001'"); jdbcTemplate.update( "insert into account_tbl(user_id, money) values ('U100001', 10000)"); return jdbcTemplate; } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/account-service/src/main/resources/application.yml ================================================ base: config: mdb: hostname: 127.0.0.1 #your mysql server ip address dbname: seata #your database name for test port: 3306 #your mysql server listening port username: 'root' #your mysql server username password: 'root' #your mysql server password server: port: 18084 spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' application: name: account-service main: allow-bean-definition-overriding: true datasource: name: storageDataSource # druid don't support GraalVM now because of there is CGlib proxy # type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${base.config.mdb.hostname}:${base.config.mdb.port}/${base.config.mdb.dbname}?useSSL=false&serverTimezone=UTC username: ${base.config.mdb.username} password: ${base.config.mdb.password} # druid: # max-active: 20 # min-idle: 2 # initial-size: 2 seata: enabled: true application-id: ${spring.application.name} tx-service-group: ${spring.application.name}-tx-group config: type: nacos nacos: serverAddr: 127.0.0.1:8848 dataId: "seata.properties" group: SEATA_GROUP username: 'nacos' password: 'nacos' registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848 cluster: default group: SEATA_GROUP username: 'nacos' password: 'nacos' ================================================ FILE: spring-cloud-alibaba-examples/seata-example/all.sql ================================================ -- -------------------------------- Create undo_ Log table -------------------------------- -- Seata AT Mode Need to use undo_ Log table. CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; -- -------------------------------- The script used when storeMode is 'db' -------------------------------- -- the table to store GlobalSession data CREATE TABLE IF NOT EXISTS `global_table` ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), KEY `idx_status_gmt_modified` (`status` , `gmt_modified`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store BranchSession data CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), `gmt_create` DATETIME(6), `gmt_modified` DATETIME(6), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store lock data CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(128), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking', `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_status` (`status`), KEY `idx_branch_id` (`branch_id`), KEY `idx_xid_and_branch_id` (`xid` , `branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `distributed_lock` ( `lock_key` CHAR(20) NOT NULL, `lock_value` VARCHAR(20) NOT NULL, `expire` BIGINT, primary key (`lock_key`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0); -- -------------- Create the database tables needed by the business in the example ---------------- DROP TABLE IF EXISTS `storage_tbl`; CREATE TABLE `storage_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY (`commodity_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `order_tbl`; CREATE TABLE `order_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT 0, `money` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `account_tbl`; CREATE TABLE `account_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `money` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ================================================ FILE: spring-cloud-alibaba-examples/seata-example/business-service/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 business-service Spring Cloud Starter Alibaba Seata Example - Business Service jar com.alibaba.cloud spring-cloud-starter-alibaba-seata com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-loadbalancer org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework spring-tx org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/seata-example/business-service/src/main/java/com/alibaba/cloud/examples/BusinessApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.client.RestTemplate; /** * @author xiaojing */ @SpringBootApplication @EnableFeignClients @EnableDiscoveryClient(autoRegister = false) @LoadBalancerClients({ @LoadBalancerClient("storage-service"), @LoadBalancerClient("order-service"), @LoadBalancerClient("service-provider") }) public class BusinessApplication { public static void main(String[] args) { SpringApplication.run(BusinessApplication.class, args); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } @FeignClient("storage-service") public interface StorageService { @GetMapping(path = "/storage/{commodityCode}/{count}") String storage(@PathVariable("commodityCode") String commodityCode, @PathVariable("count") int count); } @FeignClient("order-service") public interface OrderService { @PostMapping(path = "/order") String order(@RequestParam("userId") String userId, @RequestParam("commodityCode") String commodityCode, @RequestParam("orderCount") int orderCount); } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/business-service/src/main/java/com/alibaba/cloud/examples/HomeController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import com.alibaba.cloud.examples.BusinessApplication.OrderService; import com.alibaba.cloud.examples.BusinessApplication.StorageService; import org.apache.seata.spring.annotation.GlobalTransactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * @author xiaojing */ @RestController public class HomeController { private static final Logger LOGGER = LoggerFactory.getLogger(HomeController.class); private static final String SUCCESS = "SUCCESS"; private static final String FAIL = "FAIL"; private static final String USER_ID = "U100001"; private static final String COMMODITY_CODE = "C00321"; private static final int ORDER_COUNT = 2; private final RestTemplate restTemplate; private final OrderService orderService; private final StorageService storageService; public HomeController(RestTemplate restTemplate, OrderService orderService, StorageService storageService) { this.restTemplate = restTemplate; this.orderService = orderService; this.storageService = storageService; } @GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-demo-tx") @GetMapping(value = "/seata/rest", produces = "application/json") public String rest() { String result = restTemplate.getForObject( "http://127.0.0.1:18082/storage/" + COMMODITY_CODE + "/" + ORDER_COUNT, String.class); if (!SUCCESS.equals(result)) { throw new RuntimeException(); } String url = "http://127.0.0.1:18083/order"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap map = new LinkedMultiValueMap(); map.add("userId", USER_ID); map.add("commodityCode", COMMODITY_CODE); map.add("orderCount", ORDER_COUNT + ""); HttpEntity> request = new HttpEntity>( map, headers); ResponseEntity response; try { response = restTemplate.postForEntity(url, request, String.class); } catch (Exception exx) { throw new RuntimeException("mock error"); } result = response.getBody(); if (!SUCCESS.equals(result)) { throw new RuntimeException(); } return SUCCESS; } @GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-demo-tx") @GetMapping(value = "/seata/feign", produces = "application/json") public String feign() { String result = storageService.storage(COMMODITY_CODE, ORDER_COUNT); if (!SUCCESS.equals(result)) { throw new RuntimeException(); } result = orderService.order(USER_ID, COMMODITY_CODE, ORDER_COUNT); if (!SUCCESS.equals(result)) { throw new RuntimeException(); } return SUCCESS; } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/business-service/src/main/java/com/alibaba/cloud/examples/Order.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.io.Serializable; public class Order implements Serializable { /** * order id. */ public long id; /** * user id. */ public String userId; /** * commodity code. */ public String commodityCode; /** * count. */ public int count; /** * money. */ public int money; @Override public String toString() { return "Order{" + "id=" + id + ", userId='" + userId + '\'' + ", commodityCode='" + commodityCode + '\'' + ", count=" + count + ", money=" + money + '}'; } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/business-service/src/main/resources/application.yml ================================================ server: port: 18081 spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' loadbalancer: ribbon: enabled:true application: name: business-service seata: enabled: true application-id: ${spring.application.name} tx-service-group: ${spring.application.name}-tx-group config: type: nacos nacos: serverAddr: 127.0.0.1:8848 dataId: "seata.properties" group: SEATA_GROUP username: 'nacos' password: 'nacos' registry: type: nacos nacos: cluster: default group: SEATA_GROUP application: seata-server server-addr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' feign: client: config: default: connectTimeout: 10000 readTimeout: 10000 logging: level: io: seata: debug ================================================ FILE: spring-cloud-alibaba-examples/seata-example/order-service/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 order-service Spring Cloud Starter Alibaba Seata Example - Order Service jar com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-seata org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-jdbc com.mysql mysql-connector-j org.apache.logging.log4j log4j-core org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/seata-example/order-service/src/main/java/com/alibaba/cloud/examples/DatabaseConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; /** * @author xiaojing */ @Configuration public class DatabaseConfiguration { // druid don't support GraalVM now because of there is CGlib proxy // @Bean // @Primary // @ConfigurationProperties("spring.datasource") // public DataSource storageDataSource() { // return new DruidDataSource(); // } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.execute("TRUNCATE TABLE order_tbl"); return jdbcTemplate; } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/order-service/src/main/java/com/alibaba/cloud/examples/Order.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.io.Serializable; public class Order implements Serializable { /** * id. */ public long id; /** * user id. */ public String userId; /** * commodity code. */ public String commodityCode; /** * count. */ public int count; /** * money. */ public int money; @Override public String toString() { return "Order{" + "id=" + id + ", userId='" + userId + '\'' + ", commodityCode='" + commodityCode + '\'' + ", count=" + count + ", money=" + money + '}'; } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/order-service/src/main/java/com/alibaba/cloud/examples/OrderApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; /** * @author xiaojing */ @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/order-service/src/main/java/com/alibaba/cloud/examples/OrderController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Random; import org.apache.seata.core.context.RootContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * @author xiaojing */ @RestController public class OrderController { private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class); private static final String SUCCESS = "SUCCESS"; private static final String FAIL = "FAIL"; private static final String USER_ID = "U100001"; private static final String COMMODITY_CODE = "C00321"; private final JdbcTemplate jdbcTemplate; private final RestTemplate restTemplate; private Random random; public OrderController(JdbcTemplate jdbcTemplate, RestTemplate restTemplate) { this.jdbcTemplate = jdbcTemplate; this.restTemplate = restTemplate; this.random = new Random(); } @PostMapping(value = "/order", produces = "application/json") public String order(String userId, String commodityCode, int orderCount) { LOGGER.info("Order Service Begin ... xid: " + RootContext.getXID()); int orderMoney = calculate(commodityCode, orderCount); invokerAccountService(orderMoney); final Order order = new Order(); order.userId = userId; order.commodityCode = commodityCode; order.count = orderCount; order.money = orderMoney; KeyHolder keyHolder = new GeneratedKeyHolder(); int result = jdbcTemplate.update(new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement pst = con.prepareStatement( "insert into order_tbl (user_id, commodity_code, count, money) values (?, ?, ?, ?)", PreparedStatement.RETURN_GENERATED_KEYS); pst.setObject(1, order.userId); pst.setObject(2, order.commodityCode); pst.setObject(3, order.count); pst.setObject(4, order.money); return pst; } }, keyHolder); order.id = keyHolder.getKey().longValue(); if (random.nextBoolean()) { throw new RuntimeException("this is a mock Exception"); } LOGGER.info("Order Service End ... Created " + order); if (result == 1) { return SUCCESS; } return FAIL; } private int calculate(String commodityId, int orderCount) { return 2 * orderCount; } private void invokerAccountService(int orderMoney) { String url = "http://127.0.0.1:18084/account"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap map = new LinkedMultiValueMap(); map.add("userId", USER_ID); map.add("money", orderMoney + ""); HttpEntity> request = new HttpEntity>( map, headers); ResponseEntity response = restTemplate.postForEntity(url, request, String.class); } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/order-service/src/main/resources/application.yml ================================================ base: config: mdb: hostname: 127.0.0.1 #your mysql server ip address dbname: seata #your database name for test port: 3306 #your mysql server listening port username: 'root' #your mysql server username password: 'root' #your mysql server password server: port: 18083 spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' application: name: order-service main: allow-bean-definition-overriding: true datasource: name: storageDataSource # druid don't support GraalVM now because of there is CGlib proxy # type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${base.config.mdb.hostname}:${base.config.mdb.port}/${base.config.mdb.dbname}?useSSL=false&serverTimezone=UTC username: ${base.config.mdb.username} password: ${base.config.mdb.password} # druid: # max-active: 20 # min-idle: 2 # initial-size: 2 seata: enabled: true application-id: ${spring.application.name} tx-service-group: ${spring.application.name}-tx-group config: type: nacos nacos: serverAddr: 127.0.0.1:8848 dataId: "seata.properties" group: SEATA_GROUP username: 'nacos' password: 'nacos' registry: type: nacos nacos: cluster: default group: SEATA_GROUP application: seata-server server-addr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' ================================================ FILE: spring-cloud-alibaba-examples/seata-example/readme-zh.md ================================================ # Seata Example ## 项目说明 本项目演示如何使用 Seata Starter 完成 Spring Cloud Alibaba 应用的分布式事务接入。 [Seata](https://github.com/seata/seata) 是阿里巴巴开源的分布式事务中间件,以高效并且对业务 0 侵入的方式,解决微服务场景下面临的分布式事务问题。 ## 准备工作 在运行此示例之前,需要完成以下几步准备工作: ### 1. 配置数据库 > **注意**: 实际上,Seata 支持不同的应用使用完全不相干的数据库,但是这里为了简单地演示 Seata 如何在 Spring Cloud 应用中使用,所以选择了 Mysql 数据库。 将 `account-server`、`order-service`、`storage-service` 这三个应用中 resources 目录下的 `application.yml` 文件中的以下配置修改成本地环境中的数据库配置。 ``` base: config: mdb: hostname: your mysql server ip address dbname: your database name for test port: your mysql server listening port username: your mysql server username password: your mysql server password ``` #### 创建 undo_log 表 Seata AT 模式 需要使用到 undo_log 表。 ```sql -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ``` #### 导入 seata-server db 模式所需要的数据库表 在数据库中初始化[global_table、branch_table、lock_table、distributed_lock](https://github.com/seata/seata/blob/2.1.0/script/server/db/mysql.sql) ```sql -- -------------------------------- The script used when storeMode is 'db' -------------------------------- -- the table to store GlobalSession data CREATE TABLE IF NOT EXISTS `global_table` ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), KEY `idx_status_gmt_modified` (`status` , `gmt_modified`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store BranchSession data CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), `gmt_create` DATETIME(6), `gmt_modified` DATETIME(6), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store lock data CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(128), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking', `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_status` (`status`), KEY `idx_branch_id` (`branch_id`), KEY `idx_xid_and_branch_id` (`xid` , `branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `distributed_lock` ( `lock_key` CHAR(20) NOT NULL, `lock_value` VARCHAR(20) NOT NULL, `expire` BIGINT, primary key (`lock_key`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0); ``` #### 创建应用示例中业务所需要的数据库表 ```sql DROP TABLE IF EXISTS `storage_tbl`; CREATE TABLE `storage_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY (`commodity_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `order_tbl`; CREATE TABLE `order_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT 0, `money` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `account_tbl`; CREATE TABLE `account_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `money` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` ### 2. 配置 Nacos > Spring Cloud Alibaba 适配了 Nacos 3.1.0 版本,在本示例中,使用 Nacos 3.1.0 作为 Seata 的配置中心组件。 创建 Seata 的 Nacos 配置: data-id: `seata.properties` , Group: `SEATA_GROUP` (seata 2.x 默认分组) ,导入 [Seata Config](https://github.com/seata/seata/blob/2.1.0/script/config-center/config.txt) 在 `seata.properties` 配置文件中增加应用示例中需要的以下配置项:[事务群组配置](https://seata.io/zh-cn/docs/user/configurations.html) ```properties service.vgroupMapping.default_tx_group=default # 用于指定全局事务组和本地事务组之间的映射关系 service.vgroupMapping.order-service-tx-group=default service.vgroupMapping.account-service-tx-group=default service.vgroupMapping.business-service-tx-group=default service.vgroupMapping.storage-service-tx-group=default ``` ### 3. 启动 Seata-server > Seata 1.5.1 开始支持控制台 本地访问控制台地址:http://127.0.0.1:7091,通过 Seata 内置的控制台可以观察正在执行的事务信息和全局锁信息,事务执行结束即删除相关信息。 #### 1. 下载 点击下载( [Seata 2.5.0](https://github.com/apache/incubator-seata/releases/tag/v2.5.0)) 版本。 # github链接为源码包,需要使用 Maven 进行编译构建源码并生成 Seata 服务器 JAR 文件 或点击下载( [Apache-seata-2.5.0-incubating-bin.tar.gz](https://seata.apache.org/zh-cn/download/seata-server)) #二进制包,方便配备seata-server进行调试 #### 2. 配置 Seata-server 修改 `seata-server\conf\application.yml` 配置文件中的以下配置项: - 注释 `group: SEATA_GROUP` - 添加 Nacos 用户名和密码 ```yml seata: # nacos配置 config: type: nacos nacos: server-addr: # Nacos 服务地址 # group: SEATA_GROUP # namespace: public # Nacos 命名空间(确保设置为实际值) username: nacos password: nacos data-id: seata.properties # Nacos 中的配置文件名 ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: #secret-key: registry: # support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa 、 seata type: nacos # 使用 Nacos 作为注册中心 nacos: application: seata-server # group: SEATA_GROUP # namespace: public # Nacos 命名空间(确保设置为实际值) cluster: default server-addr: # Nacos 注册中心地址 username: nacos password: nacos ``` - 添加 store 及 server 设置(示例-非必要) ```yml store: # 支持:file、db、redis、raft mode: db # 使用数据库模式 session: mode: file lock: mode: file db: datasource: druid db-type: mysql driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true # MySQL 数据库连接 user: root # MySQL 用户名 password: rootpass # MySQL 密码 min-conn: 10 max-conn: 100 global-table: global_table branch-table: branch_table lock-table: lock_table distributed-lock-table: distributed_lock vgroup-table: vgroup_table query-limit: 1000 max-wait: 5000 server: service-port: 8091 # 配置服务端口 max-commit-retry-timeout: -1 max-rollback-retry-timeout: -1 rollback-failed-unlock-enable: false enable-check-auth: true enable-parallel-request-handle: true enable-parallel-handle-branch: false retry-dead-threshold: 70000 xaer-nota-retry-timeout: 60000 enableParallelRequestHandle: true applicationDataLimitCheck: true applicationDataLimit: 64000 recovery: committing-retry-period: 1000 async-committing-retry-period: 1000 rollbacking-retry-period: 1000 end-status-retry-period: 1000 timeout-retry-period: 1000 undo: log-save-days: 7 log-delete-period: 86400000 session: branch-async-queue-size: 5000 # 异步分支队列大小 enable-branch-async-remove: false # 启用分支异步移除 ratelimit: enable: false bucketTokenNumPerSecond: 999999 bucketTokenMaxNum: 999999 bucketTokenInitialNum: 999999 metrics: enabled: false registry-type: compact exporter-list: prometheus exporter-prometheus-port: 9898 transport: rpc-tc-request-timeout: 15000 enable-tc-server-batch-send-response: false min-http-pool-size: 10 max-http-pool-size: 100 max-http-task-queue-size: 1000 http-pool-keep-alive-time: 500 shutdown: wait: 3 thread-factory: boss-thread-prefix: NettyBoss worker-thread-prefix: NettyServerNIOWorker boss-thread-size: 1 ``` > **注意:** > Nacos 3.1.0 开启鉴权,需要配置 `username` 和 `password` 属性,否则登陆失败。更多 Nacos 3.1.0 版本相关配置,参考 `nacos-example`。 > **Seata-server 启动时的 Nacos 服务注册分组需要和示例应用中的分组保持一致,否则出现无法找到 seata-server 的错误!** > 更多 Seata-server 以 Nacos 作为配置中心的配置请参考:https://seata.io/zh-cn/docs/ops/deploy-by-docker-compose/#nacos-db ### 3. 启动 Seata-server Windows: ```cmd ./seata-server.bat ``` Linux/Mac ```shell sh seata-server.sh ``` 更多配置启动参数请参考:https://seata.io/zh-cn/docs/user/quickstart/#%E6%AD%A5%E9%AA%A4-4-%E5%90%AF%E5%8A%A8%E6%9C%8D%E5%8A%A1 **注意** 如果你修改了 endpoint 且注册中心使用默认 file 类型,那么记得需要在各个示例工程中的 `file.conf` 文件中,修改 grouplist 的值(当 registry.conf 中 registry.type 或 config.type 为 file 时会读取内部的 file 节点中的文件名,若 type 不为 file 将直接从配置类型的对应元数据的注册配置中心读取数据),推荐大家使用 nacos 作为配置注册中心。 ## 运行示例 分别运行 `account-server`、`order-service`、`storage-service` 和 `business-service` 这三个应用的 Main 函数,启动示例。 启动示例后,通过 HTTP 的 GET 方法访问如下 URL,可以分别验证在 `business-service` 中 通过 RestTemplate 和 FeignClient 调用其他服务的场景。 ```shell http://127.0.0.1:18081/seata/feign http://127.0.0.1:18081/seata/rest ``` 调用服务接口时,可能出现两种返回 1. SUCCESS:调用接口服务成功; 2. 500 异常,business-service mock 异常。 ## 如何验证分布式事务成功? ### Xid 信息是否成功传递 在 `account-server`、`order-service` 和 `storage-service` 三个 服务的 Controller 中,第一个执行的逻辑都是输出 RootContext 中的 Xid 信息,如果看到都输出了正确的 Xid 信息,即每次都发生变化,且同一次调用中所有服务的 Xid 都一致。则表明 Seata 的 Xid 的传递和还原是正常的。 ```bash # 分别查看服务运行日志(示例) Account Service ... xid: 192.168.44.1:8091:4540309594179612673 Order Service Begin ... xid: 192.168.44.1:8091:4540309594179612673 Storage Service Begin ... xid: 192.168.44.1:8091:4540309594179612673 ... Begin new global transaction [192.168.44.1:8091:4540309594179612673] ``` ### 数据库中数据是否一致 在本示例中,我们模拟了一个用户购买货物的场景,StorageService 负责扣减库存数量,OrderService 负责保存订单,AccountService 负责扣减用户账户余额。 为了演示样例,我们在 OrderService 和 AccountService 中 使用 Random.nextBoolean() 的方式来随机抛出异常,模拟了在服务调用时随机发生异常的场景。 如果分布式事务生效的话, 那么以下等式应该成立 - 用户原始金额(1000) = 用户现存的金额 + 货物单价 (2) * 订单数量 * 每单的货物数量(2) - 货物的初始数量(100) = 货物的现存数量 + 订单数量 * 每单的货物数量(2) ```sql # 验证示例 SELECT * FROM account_tbl; SELECT * FROM storage_tbl; SELECT * FROM order_tbl; ``` 注:由于使用了 Random.nextBoolean() 来随机抛出异常,模拟事务的异常情况,也需要验证分布式事务是否能正确回滚: 如果在 OrderService 和 AccountService 中抛出异常,StorageService 应该会回滚库存扣减,账户余额也应恢复到初始状态。 查看分布式事务日志:查看 undo_log 表和 global_table 表,确保在事务回滚时,相关记录被删除或恢复。 ## 对 Spring Cloud 支持点 - 通过 Spring MVC 提供服务的服务提供者,在收到 header 中含有 Seata 信息的 HTTP 请求时,可以自动还原 Seata 上下文。 - 支持服务调用者通过 RestTemplate 调用时,自动传递 Seata 上下文。 - 支持服务调用者通过 FeignClient 调用时,自动传递 Seata 上下文。 - 支持 SeataClient 和 Hystrix 同时使用的场景。 - 支持 SeataClient 和 Sentinel 同时使用的场景。 ================================================ FILE: spring-cloud-alibaba-examples/seata-example/readme.md ================================================ # Seata Example ## Project description This project demonstrates how to use Seata Starter to complete the distributed transaction access of Spring Cloud Alibaba application. [Seata](https://github.com/seata/seata) It is Alibaba's open source distributed transaction middleware, which solves the distributed transaction problems faced by micro-service scenarios in an efficient and zero-intrusion way. ## Preparations Before you run this sample, you need to complete the following steps: ### 1. Configure the database > Seata **Notice** actually supports disparate databases for different applications, but Mysql was chosen here for a simple demonstration of how Seata can be used in a Spring Cloud application. Modify the following configuration in the files under the `application.yml` resources directory in the three applications `account-server`, `order-service`, `storage-service` to the database configuration in the local environment. ``` base: config: mdb: hostname: your mysql server ip address dbname: your database name for test port: your mysql server listening port username: your mysql server username password: your mysql server password ``` #### Create the undo _ log table Seata AT mode requires the undo_log table. ```sql -- Note that 0.3.0+ adds unique index ux_undo_log here CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ``` #### Import the database tables required by the seata-server db schema Initializing [global_table、branch_table、lock_table、distributed_lock](https://github.com/seata/seata/blob/2.1.0/script/server/db/mysql.sql) in the database ```sql -- -------------------------------- The script used when storeMode is 'db' -------------------------------- -- the table to store GlobalSession data CREATE TABLE IF NOT EXISTS `global_table` ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), KEY `idx_status_gmt_modified` (`status` , `gmt_modified`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store BranchSession data CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), `gmt_create` DATETIME(6), `gmt_modified` DATETIME(6), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- the table to store lock data CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(128), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking', `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_status` (`status`), KEY `idx_branch_id` (`branch_id`), KEY `idx_xid_and_branch_id` (`xid` , `branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; CREATE TABLE IF NOT EXISTS `distributed_lock` ( `lock_key` CHAR(20) NOT NULL, `lock_value` VARCHAR(20) NOT NULL, `expire` BIGINT, primary key (`lock_key`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0); INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0); ``` #### Create the database tables required by the business in the application sample ```sql DROP TABLE IF EXISTS `storage_tbl`; CREATE TABLE `storage_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY (`commodity_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `order_tbl`; CREATE TABLE `order_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `commodity_code` varchar(255) DEFAULT NULL, `count` int(11) DEFAULT 0, `money` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `account_tbl`; CREATE TABLE `account_tbl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(255) DEFAULT NULL, `money` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` ### 2. Configure Nacos > Spring Cloud Alibaba is adapted with Nacos 3.1.0. In this example, Nacos 3.1.0 is used as the configuration center component of Seata. Create Nacos configuration for Seata: data-id: `seata.properties`, Group: `SEATA_GROUP` (default grouping for seata 2.1.0), import Add the following configuration items required in the application example to the `seata.properties` configuration file: [事务群组配置](https://seata.io/zh-cn/docs/user/configurations.html) ```properties service.vgroupMapping.default_tx_group=default # Used to specify the mapping relationship between global transaction groups and local transaction groups. service.vgroupMapping.order-service-tx-group=default service.vgroupMapping.account-service-tx-group=default service.vgroupMapping.business-service-tx-group=default service.vgroupMapping.storage-service-tx-group=default ``` ### 3. Start Seata-server > Seata 1.5.1 supports console local access. Console address: http://127.0.0.1:7091, you can view the information about the transaction being executed and the global lock information through the built-in console of Seata. When the transaction is finished, the relevant information will be deleted. #### 1. Download Click Download [Seata 2.5.0](https://github.com/apache/incubator-seata/releases/tag/v2.5.0) Version. \# The GitHub link is for the source code package, which requires Maven to compile and build the source code and generate the Seata server JAR file. Or click Download [Apache-seata-2.5.0-incubating-bin.tar.gz](https://seata.apache.org/zh-cn/download/seata-server) . \# Binary package, convenient for debugging with seata-server #### 2. Configure Seata-server Modify `seata-server\conf\application.yml` the following configuration items in the configuration file: - Comment `group: SEATA_GROUP` - Add Nacos username and password ```yml seata: # nacos configuration config: type: nacos nacos: server-addr: # Nacos service addr # group: SEATA_GROUP # namespace: public # Nacos Namespace username: nacos password: nacos data-id: seata.properties # Configuration file name in Nacos ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: #secret-key: registry: # support: nacos 、 eureka 、 redis 、 zk 、 consul 、 etcd3 、 sofa 、 seata type: nacos # Using Nacos as a registry center nacos: application: seata-server # group: SEATA_GROUP # namespace: public # Nacos namespace (make sure to set it to the actual value) cluster: default server-addr: # Nacos registry center address username: nacos password: nacos ``` - Add store and server settings (example - not required) ```yml store: # Support: file, db, redis, raft mode: db # Using database models session: mode: file lock: mode: file db: datasource: druid db-type: mysql driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true # MySQL database connection user: root # MySQL username password: rootpass # MySQL password min-conn: 10 max-conn: 100 global-table: global_table branch-table: branch_table lock-table: lock_table distributed-lock-table: distributed_lock vgroup-table: vgroup_table query-limit: 1000 max-wait: 5000 server: service-port: 8091 # Configure service port max-commit-retry-timeout: -1 max-rollback-retry-timeout: -1 rollback-failed-unlock-enable: false enable-check-auth: true enable-parallel-request-handle: true enable-parallel-handle-branch: false retry-dead-threshold: 70000 xaer-nota-retry-timeout: 60000 enableParallelRequestHandle: true applicationDataLimitCheck: true applicationDataLimit: 64000 recovery: committing-retry-period: 1000 async-committing-retry-period: 1000 rollbacking-retry-period: 1000 end-status-retry-period: 1000 timeout-retry-period: 1000 undo: log-save-days: 7 log-delete-period: 86400000 session: branch-async-queue-size: 5000 # Asynchronous branch queue size enable-branch-async-remove: false # Enable branch asynchronous removal ratelimit: enable: false bucketTokenNumPerSecond: 999999 bucketTokenMaxNum: 999999 bucketTokenInitialNum: 999999 metrics: enabled: false registry-type: compact exporter-list: prometheus exporter-prometheus-port: 9898 transport: rpc-tc-request-timeout: 15000 enable-tc-server-batch-send-response: false min-http-pool-size: 10 max-http-pool-size: 100 max-http-task-queue-size: 1000 http-pool-keep-alive-time: 500 shutdown: wait: 3 thread-factory: boss-thread-prefix: NettyBoss worker-thread-prefix: NettyServerNIOWorker boss-thread-size: 1 ``` > **Notice** > Nacos 3.1.0 enables authentication. Configuration `username` and `password` properties are required, otherwise login fails. For more Nacos 3.1.0 related configurations, refer to `nacos-example`. > **The Nacos service registration group when seata-server is started must be consistent with the group in the sample application, otherwise an error that seata-server cannot be found will occur!** > For more information about the configuration of Seata-server with Nacos as the configuration center, please refer to https://seata.io/zh-cn/docs/ops/deploy-by-docker-compose/#nacos-db. ### 3. Start Seata-server Windows: ```cmd ./seata-server.bat ``` Linux/Mac ```shell sh seata-server.sh ``` For more configuration startup parameters, please refer to https://seata.io/zh-cn/docs/user/quickstart/#%E6%AD%A5%E9%AA%A4-4-%E5%90%AF%E5%8A%A8%E6%9C%8D%E5%8A%A1. **Notice** If you change the endpoint and the registry uses the default file type, remember that in the `file.conf` file in each sample project, Modify the value of grouplist (when the registry. Type or config. Type in the registry. Conf is file, the file name in the internal file node will be read. If the type is not file, the data will be directly read from the registration configuration center of the corresponding metadata of the configuration type. It is recommended to use nacos as the configuration registration center. ## Run the sample Start the sample by running `account-server` the Main functions of the, `order-service`, `storage-service`, and `business-service` applications separately. After starting the sample, access the following URL through the GET method of HTTP to verify `business-service` the scenarios of calling other services through RestTemplate and FeignClient in respectively. ```shell http://127.0.0.1:18081/seata/feign http://127.0.0.1:18081/seata/rest ``` When a service interface is invoked, two types of returns are possible 1. SUCCESS: calling interface service succeeded; 2. 500 exception, business-service mock exception. ## How do I verify that a distributed transaction is successful? ### Xid information passed successfully In `account-server` the Controllers of, `order-service`, and `storage-service` services, the first logic to be executed is to output the Xid information in the RootContext. If the correct Xid information is output, that is, it changes every time. And that Xid of all the services in the same invocation are the same. Then it indicates that the passing and restoring of Seata's Xid is normal. ```bash # View service operation logs separately (example) Account Service ... xid: 192.168.44.1:8091:4540309594179612673 Order Service Begin ... xid: 192.168.44.1:8091:4540309594179612673 Storage Service Begin ... xid: 192.168.44.1:8091:4540309594179612673 ... Begin new global transaction [192.168.44.1:8091:4540309594179612673] ``` ### Whether the data in the database is consistent In this example, we simulate a scenario in which a user purchases goods. The Storage Service is responsible for deducting the inventory quantity, the Order Service is responsible for saving the order, and the Account service is responsible for deducting the balance of the user's account. To demonstrate the sample, we use Random. NextBoolean () to randomly throw exceptions in Order Service and AccountService, simulating a scenario where exceptions randomly occur during service invocation. If a distributed transaction is in effect, then the following equation should hold - User's original amount (1000) = user's existing amount + unit price of goods (2) *Number of orders* quantity of goods per order (2) - Initial quantity of goods (100) = Quantity on hand of goods + Order quantity * Quantity of goods per order (2) ```sql # Verification example SELECT * FROM account_tbl; SELECT * FROM storage_tbl; SELECT * FROM order_tbl; ``` Note: Since Random.nextBoolean() is used to randomly throw exceptions to simulate transaction exceptions, it is also necessary to verify whether distributed transactions can be rolled back correctly: If exceptions are thrown in OrderService and AccountService, StorageService should roll back the inventory deduction, and the account balance should also be restored to its initial state. View the distributed transaction logs: Check the undo_log table and global_table table to ensure that relevant records are deleted or restored during transaction rollback. ## Support points for Spring Cloud - Service providers that provide services through Spring MVC can automatically restore the Seata context when they receive an HTTP request with Seata information in the header. - Support the automatic passing of the Seata context when the service caller invokes through the RestTemplate. - Support the automatic passing of the Seata context when the service caller calls through FeignClient. - Scenarios where SeataClient and Hystrix are used together are supported. - Scenarios where SeataClient and Sentinel are used together are supported. ================================================ FILE: spring-cloud-alibaba-examples/seata-example/storage-service/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../../pom.xml 4.0.0 storage-service Spring Cloud Starter Alibaba Seata Example - Storage Service jar com.alibaba.cloud spring-cloud-starter-alibaba-seata com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-jdbc com.mysql mysql-connector-j org.apache.logging.log4j log4j-core org.springframework.boot spring-boot-maven-plugin ================================================ FILE: spring-cloud-alibaba-examples/seata-example/storage-service/src/main/java/com/alibaba/cloud/examples/StorageApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author xiaojing */ @SpringBootApplication public class StorageApplication { public static void main(String[] args) { SpringApplication.run(StorageApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/storage-service/src/main/java/com/alibaba/cloud/examples/config/DatabaseConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.config; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; /** * @author xiaojing */ @Configuration public class DatabaseConfiguration { // druid don't support GraalVM now because of there is CGlib proxy /*@Bean @Primary @ConfigurationProperties("spring.datasource") public DataSource storageDataSource() { return new DruidDataSource(); }*/ @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update("delete from storage_tbl where commodity_code = 'C00321'"); jdbcTemplate.update( "insert into storage_tbl(commodity_code, count) values ('C00321', 100)"); return jdbcTemplate; } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/storage-service/src/main/java/com/alibaba/cloud/examples/controller/StorageController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.controller; import org.apache.seata.core.context.RootContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * @author xiaojing */ @RestController public class StorageController { private static final Logger LOGGER = LoggerFactory.getLogger(StorageController.class); private static final String SUCCESS = "SUCCESS"; private static final String FAIL = "FAIL"; private final JdbcTemplate jdbcTemplate; public StorageController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @GetMapping(value = "/storage/{commodityCode}/{count}", produces = "application/json") public String echo(@PathVariable String commodityCode, @PathVariable int count) { LOGGER.info("Storage Service Begin ... xid: " + RootContext.getXID()); int result = jdbcTemplate.update( "update storage_tbl set count = count - ? where commodity_code = ?", new Object[] { count, commodityCode }); LOGGER.info("Storage Service End ... "); if (result == 1) { return SUCCESS; } return FAIL; } } ================================================ FILE: spring-cloud-alibaba-examples/seata-example/storage-service/src/main/resources/application.yml ================================================ base: config: mdb: hostname: 127.0.0.1 #your mysql server ip address dbname: seata #your database name for test port: 3306 #your mysql server listening port username: 'root' #your mysql server username password: 'root' #your mysql server password server: port: 18082 spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' application: name: storage-service main: allow-bean-definition-overriding: true datasource: name: storageDataSource # druid don't support GraalVM now because of there is CGlib proxy # type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${base.config.mdb.hostname}:${base.config.mdb.port}/${base.config.mdb.dbname}?useSSL=false&serverTimezone=UTC username: ${base.config.mdb.username} password: ${base.config.mdb.password} # druid: # max-active: 20 # min-idle: 2 # initial-size: 2 seata: enabled: true application-id: ${spring.application.name} tx-service-group: ${spring.application.name}-tx-group config: type: nacos nacos: serverAddr: 127.0.0.1:8848 dataId: "seata.properties" group: SEATA_GROUP username: 'nacos' password: 'nacos' registry: type: nacos nacos: cluster: default group: SEATA_GROUP application: seata-server server-addr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/README-zh.md ================================================ # Spring Cloud Alibaba Sentinel Example ## 项目说明 本 Example 项目演示如何使用 `spring-cloud-starter-alibaba-sentinel` 完成 Spring Cloud 应用中的流量治理功能。 [Sentinel](https://github.com/alibaba/Sentinel) 是阿里巴巴开源的分布式系统的流量防卫组件,Sentinel 以流量作为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。 ## Sentinel Example 在本 Example 项目中,主要演示 Sentinel 断路器,整合 Spring Cloud Gateway 和 OpenFeign、RestTemplate 以及 Webclient 的使用。 ### 下载并启动 Sentinel Console 1. 首先需要获取 Sentinel 控制台,Sentinel Console 支持直接下载和源码构建两种方式 1. 直接下载:[下载 Sentinel 控制台](https://github.com/alibaba/Sentinel/releases) 2. 源码构建:进入 Sentinel [Github 项目页面](https://github.com/alibaba/Sentinel),将代码 clone 到本地自行编译打包,[参考此文档](https://github.com/alibaba/Sentinel/blob/1.8/sentinel-dashboard/README.md)。 2. 启动控制台,执行 Java 命令 `java -jar sentinel-dashboard.jar` 完成 Sentinel 控制台的启动。 控制台默认的监听端口为 `8080`。Sentinel 控制台使用 Spring Boot 编程模型开发,如果需要指定其他端口,请使用 Spring Boot 容器配置的标准方式,详情请参考 [Spring Boot 文档](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-customizing-embedded-containers)。 ### Sentinel Core Example 在此 Example 模块中,主要演示如何使用 Sentinel 的基本功能完成 Spring Cloud 应用的流量管控。 在启动 Example 进行演示之前,先了解一下如何在 Spring Cloud 应用中接入 Sentinel 组件。 #### 项目编写 > **注意:本文档只是为了便于理解接入方式。本示例代码中已经完成接入工作,您无需再进行修改。** 1. 首先,修改 `pom.xml` 文件,引入 Sentinel starter。 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ``` 2. 接入限流埋点 - HTTP 埋点 `spring-cloud-starter-alibaba-sentinel` 默认为所有的 HTTP 服务提供了限流埋点,如果只想对 HTTP 服务进行限流,那么只需要引入依赖,无需修改代码。 - 自定义埋点 如果需要对某个特定的方法进行限流或降级,可以通过 `@SentinelResource` 注解来完成限流的埋点,示例代码如下: ```java @SentinelResource("resource") public String hello() { return "Hello"; } ``` 当然也可以通过原始的 `SphU.entry(xxx)` 方法进行埋点,可以参见 [Sentinel 文档](https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E5%AE%9A%E4%B9%89%E8%B5%84%E6%BA%90)。 3. 配置限流规则 Sentinel 提供了两种配置限流规则的方式:代码配置 和 控制台配置。本示例使用的方式为通过代码配置。 1. 通过代码来实现限流规则的配置。一个简单的限流规则配置示例代码如下,更多限流规则配置详情请参考 [Sentinel 文档](https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E5%AE%9A%E4%B9%89%E8%A7%84%E5%88%99)。 ```java List rules = new ArrayList(); FlowRule rule = new FlowRule(); rule.setResource(str); // set limit qps to 10 rule.setCount(10); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); ``` 2. 通过控制台进行限流规则配置请参考文章后面的图文说明。 #### 应用启动 1. 增加配置,在应用的 `/src/main/resources/application.yml` 中添加基本配置信息 ```yaml server: port: 18083 spring: application: name: sentinel-core-example cloud: sentinel: transport: dashboard: localhost:8080 ``` 2. 启动应用,支持 IDE 直接启动和编译打包后启动。 1. IDE直接启动:找到主类 `SentinelCoreApplication`,执行 main 方法启动应用。 2. 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar sentinel-core-example.jar` 启动应用。 #### 调用服务验证 使用 curl 命令分别调用两个 URL,可以看到访问成功。 ```shell $ curl http://localhost:18083/test Blocked by Sentinel (flow limiting) $ curl http://localhost:18083/hello Hello ``` #### 配置限流规则并验证 1. 访问 http://localhost:8080 页面,进行登陆,默认用户名和密码均为:`sentinel`。 可以在左侧看到 `sentinel-core-example` 应用已经注册到了控制台,单击 **流控规则** ,可以看到目前的流控规则为空。 > **注意:如果您在控制台没有找到应用,请调用一下进行了 Sentinel 埋点的 URL 或方法,因为 Sentinel 使用了 lazy load 策略。详细的排查过程请参见 [Sentinel FAQ](https://github.com/alibaba/Sentinel/wiki/FAQ)。** image-20240428171413303 2. 配置 URL 限流规则:点击新增流控规则,资源名填写需要限流的 URL 相对路径,单机阈值选择需要限流的阈值,点击新增进行确认。(为了便于演示效果,这里将值设置成了 1)。 image-20240428171245517 3. 配置自定义限流规则:点击新增流控规则,资源名填写 `@SentinelResource` 注解 `value` 字段的值,单机阈值选择需要限流的阈值,点击新增进行确认。(为了便于演示效果,这里将值设置成了 1)。 image-20240428171608519 4. 访问 URL,当 QPS 超过 1 时,可以看到限流效果如下。 ![image-20240428171907705](./images/image-20240428171907705.png) #### 自定义限流处理逻辑 * 默认限流异常处理 URL 限流触发后默认处理逻辑是,直接返回 "Blocked by Sentinel (flow limiting)"。 如果需要自定义处理逻辑,实现的方式如下: ```java public class CustomUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException { // todo add your logic } } WebCallbackManager.setUrlBlockHandler(new CustomUrlBlockHandler()); ``` * 使用 `@SentinelResource` 注解下的限流异常处理 如果需要自定义处理逻辑,填写 `@SentinelResource` 注解的 `blockHandler` 属性(针对所有类型的 `BlockException`,需自行判断)或 `fallback` 属性(针对熔断降级异常),注意**对应方法的签名和位置有限制**,详情见 [Sentinel 注解支持文档](https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81#sentinelresource-%E6%B3%A8%E8%A7%A3)。示例实现如下: ```java public class TestService { // blockHandler 是位于 ExceptionUtil 类下的 handleException 静态方法,需符合对应的类型限制. @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class}) public void test() { System.out.println("Test"); } // blockHandler 是位于当前类下的 exceptionHandler 方法,需符合对应的类型限制. @SentinelResource(value = "hello", blockHandler = "exceptionHandler") public String hello(long s) { return String.format("Hello at %d", s); } public String exceptionHandler(long s, BlockException ex) { // Do some log here. ex.printStackTrace(); return "Oops, error occurred at " + s; } } public final class ExceptionUtil { public static void handleException(BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); } } ``` 一个简单的 `@SentinelResource` 示例可以见 [sentinel-demo-annotation-spring-aop](https://github.com/alibaba/Sentinel/tree/2021.x/sentinel-demo/sentinel-demo-annotation-spring-aop)。 ### Sentinel Circuitbreaker Example 本 Example 主要演示 OpenFeign 整合 Sentinel 断路器的使用。 #### 准备配置文件 1. 添加配置到配置中心。dataId 为 `sentinel-circuitbreaker-rules.yml` ```yml feign: circuitbreaker: enabled: true # 开启 feign 断路器支持 sentinel: default-rule: default # 默认规则名称 rules: # 默认规则, 对所有 feign client 生效 default: - grade: 2 # 根据异常数目降级 count: 1 timeWindow: 15 # 降级后到半开状态的时间 statIntervalMs: 1000 minRequestAmount: 1 # 只对 feign client user 生效 user: - grade: 2 count: 1 timeWindow: 15 statIntervalMs: 1000 minRequestAmount: 1 # 只对 feign client user 的方法 feignMethod 生效 # 括号里是参数类型, 多个逗号分割, 比如 user#method(boolean,String,Map) "[user#feignMethod(boolean)]": - grade: 2 count: 1 timeWindow: 10 statIntervalMs: 1000 minRequestAmount: 1 ``` #### 验证配置生效 启动项目主类 `FeignCircuitBreakerApplication` ##### 验证默认 Feign client 生效 先访问 http://localhost/test/default/false 2 次 (1秒内) 再访问 http://localhost/test/default/true 断路器处于打开状态 ##### 验证指定 Feign client 生效 先访问 http://localhost/test/feign/false 2 次 (1秒内) 再访问 http://localhost/test/feign/true 断路器处于打开状态 ##### 验证 Feign client 指定方法生效 先访问 http://localhost/test/feignMethod/false 2次 (1秒内) 再访问 http://localhost/test/feignMethod/true 断路器处于打开状态 #### 规则动态刷新 修改配置中心的规则, 再访问上述接口。 ### Sentinel OpenFeign Example 本 Example 演示 OpenFeing 与 Sentinel 的整合。Example 中使用 httpbin 充当后台 API 接口服务。 #### 项目编写 > **注意:本项目中代码已经完成相对应的功能,不需要再进行任何修改。** 项目支持两种启动方式:通过主类 `OpenFeignApplication` 启动和 Jar 包启动两种方式。 #### 调用测试 项目启动完成之后,可以通过访问对应的 URL 访问,查看对应的 Sentinel 流控效果。 > **注意:项目中提供的 RestTemplate 和 Webclient Example 同理。** ## Endpoint 信息查看 Spring Boot 应用支持通过 Endpoint 来暴露相关信息,`spring-cloud-starter-alibaba-sentinel` 也支持这一点。 在使用之前需要在 Maven 中添加 `spring-boot-starter-actuator`依赖,并在配置中允许 Endpoints 的访问。 * Spring Boot 1.x 中添加配置 `management.security.enabled=false` * Spring Boot 2.x 中添加配置 `management.endpoints.web.exposure.include=*` Spring Boot 1.x 可以通过访问 http://127.0.0.1:18083/sentinel 来查看 Sentinel Endpoint 的信息。Spring Boot 2.x 可以通过访问 http://127.0.0.1:18083/actuator/sentinel 来访问。

## 查看实时监控 Sentinel 控制台支持实时监控查看,您可以通过 Sentinel 控制台查看各链路的请求的通过数和被限流数等信息。 其中 `p_qps` 为通过(pass) 流控的 QPS,`b_qps` 为被限流 (block) 的 QPS。

## ReadableDataSource 支持 Sentinel 内部提供了[动态规则的扩展实现 ReadableDataSource](https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95#datasource-%E6%89%A9%E5%B1%95)。 Sentinel starter 整合了目前存在的几类 ReadableDataSource。只需要在配置文件中进行相关配置,即可在 Spring 容器中自动注册 DataSource。 比如要定义两个ReadableDataSource,分别是 `FileRefreshableDataSource` 和 `NacosDataSource`,配置如下: ```properties spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json spring.cloud.sentinel.datasource.ds1.file.data-type=json spring.cloud.sentinel.datasource.ds2.nacos.server-addr=127.0.0.1:8848 spring.cloud.sentinel.datasource.ds2.nacos.dataId=sentinel spring.cloud.sentinel.datasource.ds2.nacos.groupId=DEFAULT_GROUP spring.cloud.sentinel.datasource.ds2.nacos.data-type=json ``` `ds1` 和 `ds2` 表示ReadableDataSource的名称,可随意编写。`ds1` 和 `ds2` 后面的 `file` 和 `nacos` 表示ReadableDataSource的类型。 目前支持`file`, `nacos`, `zk`, `apollo`,`redis` 这5种类型。 其中`nacos`,`zk`,`apollo`,`redis` 这4种类型的使用需要加上对应的依赖`sentinel-datasource-nacos`, `sentinel-datasource-zookeeper`, `sentinel-datasource-apollo`, `sentinel-datasource-redis`。 当 `ReadableDataSource` 加载规则数据成功的时候,控制台会打印出相应的日志信息: ``` [Sentinel Starter] DataSource ds1-sentinel-file-datasource load 3 DegradeRule [Sentinel Starter] DataSource ds2-sentinel-nacos-datasource load 2 FlowRule ``` ## More Sentinel 是一款功能强大的中间件,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。此 Demo 仅演示了 使用 Sentinel 作为限流工具的使用,更多 Sentinel 相关的信息,请参考 [Sentinel 项目](https://github.com/alibaba/Sentinel)。 如果您对 `spring-cloud-starter-alibaba-sentinel` 有任何建议或想法,欢迎在 issue 中或者通过其他社区渠道向我们提出。 ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/README.md ================================================ # Spring Cloud Alibaba Sentinel Example ## Project description This Example project demonstrates how to use `spring-cloud-starter-alibaba-sentinel` to complete the traffic management function in Spring Cloud applications. [Sentinel](https://github.com/alibaba/Sentinel) It is the traffic defense component of Alibaba's open source distributed system. Sentinel takes traffic as the entry point to protect the stability of services from multiple dimensions such as traffic control, fuse degradation, system load protection, etc. ## Sentinel Example In this Example project, the Sentinel circuit breaker is mainly demonstrated, and the Spring Cloud Gateway is integrated with the use of OpenFeign, RestTemplate, and Webclient. ### Download and launch Sentinel Console 1. First, you need to obtain the Sentinel Console, which supports direct download and source code construction 1. Direct download: [Download the Sentinel console](https://github.com/alibaba/Sentinel/releases) 2. Source code construction: Enter Sentinel [Github project page](https://github.com/alibaba/Sentinel), clone the code to the local compilation and packaging [参考此文档](https://github.com/alibaba/Sentinel/blob/1.8/sentinel-dashboard/README.md). 2. Start the console and execute the Java command `java -jar sentinel-dashboard.jar` to finish starting the Sentinel console. The default console listening port is `8080`. The Sentinel console is developed using the Spring Boot programming model. If you need to specify other ports, please use the standard method of Spring Boot container configuration. For details, please refer to [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-customizing-embedded-containers). ### Sentinel Core Example This Example module mainly demonstrates how to use the basic functions of Sentinel to complete the traffic control of Spring Cloud applications. Before launching Example for a demonstration, let's look at how to access the Sentinel component in a Spring Cloud application. #### Project preparation > **Note: This document is only for the purpose of understanding the access method. The access work is done in this sample code, and you do not need to modify it.** 1. First, modify `pom.xml` the file to introduce Sentinel starter. ```xml com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ``` 2. Access current-limiting buried point - `spring-cloud-starter-alibaba-sentinel` By default, all HTTP services provide the current limiting embedded point. If you only want to limit the current of the HTTP service, you only need to introduce the dependency and do not need to modify the code. - Custom Buried Point If you need to limit or degrade a specific method, you can use `@SentinelResource` annotations to complete the current limiting buried point. The example code is as follows: ```java @SentinelResource("resource") public String hello() { return "Hello"; } ``` Of course, it can also be buried by the original `SphU.entry(xxx)` method, which can be seen [Sentinel documentation](https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E5%AE%9A%E4%B9%89%E8%B5%84%E6%BA%90). 3. Configure the current limit rule Sentinel provides two ways to configure throttling rules: code configuration and console configuration. The method used in this example is configuration through code. 1. Configure rate limits by code. Here is a simple code example of rate limit configuration. For more configuration details, see [Sentinel documentation](https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E5%AE%9A%E4%B9%89%E8%A7%84%E5%88%99). ```java List rules = new ArrayList(); FlowRule rule = new FlowRule(); rule.setResource(str); // set limit qps to 10 rule.setCount(10); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rules.add(rule); FlowRuleManager.loadRules(rules); ``` 2. Please refer to the graphic description at the end of the article for current limiting rule configuration through the console. #### The application starts 1. Add configuration, and add basic configuration information in the application `/src/main/resources/application.yml` ```yaml server: port: 18083 spring: application: name: sentinel-core-example cloud: sentinel: transport: dashboard: localhost:8080 ``` 2. Start the application, support IDE direct start and start after compilation and packaging. 1. IDE direct startup: find the main class `SentinelCoreApplication` and execute the main method to start the application. 2. Start after packaging and compiling: First execute `mvn clean package` to package the project compilation, and then execute `java -jar sentinel-core-example.jar` to start the application. #### Invoke service validation Use the curl command to call the two URLs separately, and you can see that the access is successful. ```shell $ curl http://localhost:18083/test Blocked by Sentinel (flow limiting) $ curl http://localhost:18083/hello Hello ``` #### Configure and verify the current limit rule 1. Visit the http://localhost:8080 page and log in. The default user name and password are: `sentinel`. On the left, you can see that the Sentinel-Example application has been registered to the console. Click **Flow control rules** it, and you can see that the current flow control rule is empty. > ** Note: If you do not find the application in the console, please call the URL or method with Sentinel buried points, because Sentinel uses lazy load policy. For detailed troubleshooting procedures, see [Sentinel FAQ](https://github.com/alibaba/Sentinel/wiki/FAQ). **

2. Configure URL flow limit rule: click Add Flow Control Rule, fill in the URL relative path to be restricted for the resource name, select the threshold to be restricted for the single machine threshold, and click Add to confirm. (The value is set to 1 for demonstration purposes.).

3. Configure custom current limiting rules: click Add a flow control rule, fill in `@SentinelResource` the value of the comment `value` field for the resource name, select the threshold for current limiting for the single machine threshold, and click Add to confirm. (The value is set to 1 for demonstration purposes.).

4. Visit the URL. When the QPS exceeds 1, you can see the effect of limiting the current as follows.

#### Custom current limit processing logic * Default Current Limit Exception Handling After the URL current limiting is triggered, the default processing logic is to directly return to "Blocked by Sentinel". If you need to customize the processing logic, the implementation method is as follows: ```java public class CustomUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException { // todo add your logic } } WebCallbackManager.setUrlBlockHandler(new CustomUrlBlockHandler()); ``` * Current limit exception handling using `@SentinelResource` annotations If you need to customize the processing logic, fill in `@SentinelResource` the attribute of the note `blockHandler` (for all types `BlockException`, you need to make your own judgment) or `fallback` the attribute (for the fuse degradation exception). **There are restrictions on the signature and location of the corresponding method** See for [Sentinel Annotation Support Document](https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81#sentinelresource-%E6%B3%A8%E8%A7%A3) details. An example implementation is as follows: ```java public class TestService { // blockHandler 是位于 ExceptionUtil 类下的 handleException 静态方法,需符合对应的类型限制. @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class}) public void test() { System.out.println("Test"); } // blockHandler 是位于当前类下的 exceptionHandler 方法,需符合对应的类型限制. @SentinelResource(value = "hello", blockHandler = "exceptionHandler") public String hello(long s) { return String.format("Hello at %d", s); } public String exceptionHandler(long s, BlockException ex) { // Do some log here. ex.printStackTrace(); return "Oops, error occurred at " + s; } } public final class ExceptionUtil { public static void handleException(BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); } } ``` A simple `@SentinelResource` example can be found in [sentinel-demo-annotation-spring-aop](https://github.com/alibaba/Sentinel/tree/2021.x/sentinel-demo/sentinel-demo-annotation-spring-aop). ### Sentinel Circuitbreaker Example This Example mainly demonstrates the use of OpenFeign integrated Sentinel circuit breaker. #### Prepare the configuration file 1. Add a configuration to the Configuration Center. DataId is `sentinel-circuitbreaker-rules.yml` ```yml feign: circuitbreaker: enabled: true # 开启 feign 断路器支持 sentinel: default-rule: default # 默认规则名称 rules: # 默认规则, 对所有 feign client 生效 default: - grade: 2 # 根据异常数目降级 count: 1 timeWindow: 15 # 降级后到半开状态的时间 statIntervalMs: 1000 minRequestAmount: 1 # 只对 feign client user 生效 user: - grade: 2 count: 1 timeWindow: 15 statIntervalMs: 1000 minRequestAmount: 1 # 只对 feign client user 的方法 feignMethod 生效 # 括号里是参数类型, 多个逗号分割, 比如 user#method(boolean,String,Map) "[user#feignMethod(boolean)]": - grade: 2 count: 1 timeWindow: 10 statIntervalMs: 1000 minRequestAmount: 1 ``` #### Verify that the configuration is in effect Start the project main class `FeignCircuitBreakerApplication` ##### Verify that the default Feign client takes effect Access http://localhost/test/default/false 2 times (within 1 second) and then access http://localhost/test/default/true the circuit breaker in the open state ##### Verify that the specified Feign client is in effect Access http://localhost/test/feign/false 2 times (within 1 second) and then access http://localhost/test/feign/true the circuit breaker in the open state ##### Verify that the Feign client specified method takes effect Access http://localhost/test/feignMethod/false 2 times (within 1 second) and then access http://localhost/test/feignMethod/true the circuit breaker in the open state #### Rule dynamic refresh Modify the rules of the configuration center, and then access the above interface. ### Sentinel OpenFeign Example This Example demonstrates the integration of OpenFeing and Sentinel. In Example, httpbin is used as a background API interface service. #### Project preparation > **Note: The code in this project has completed the corresponding function and does not need to be modified.** The project supports two startup modes: startup through the main class `OpenFeignApplication` and startup through the Jar package. #### Call the test After the project is started, you can access the corresponding URL to view the corresponding Sentinel flow control effect. > **Note: The RestTemplate provided in the project is the same as the Webclient Example.** ## Endpoint information viewing Spring Boot applications support the exposure of relevant information through Endpoints, `spring-cloud-starter-alibaba-sentinel` as well. Before using it, you need to add `spring-boot-starter-actuator` dependencies in Maven and allow Endpoints access in the configuration. * Add configuration in Spring Boot 1.x * Adding Configuration in Spring Boot 2.x Spring Boot 1.x can view Sentinel Endpoint information by visiting http://127.0.0.1:18083/sentinel . Spring Boot 2.x can be accessed by visiting http://127.0.0.1:18083/actuator/sentinel .

## View real-time monitoring The Sentinel console supports real-time monitoring and viewing. You can view information such as the number of requests passed and the number of throttled flows for each link through the Sentinel console. Where `p_qps` is a pass flow controlled QPS and `b_qps` is a blocked QPS.

## ReadableData Source support The Sentinel provides [Implementation of ReadableDataSource by Extending Dynamic Rule](https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95#datasource-%E6%89%A9%E5%B1%95) internally. Sentinel starter incorporates several classes of ReadableDataSources that exist today. The DataSource is automatically registered in the Spring container by simply making the relevant configuration in the configuration file. `FileRefreshableDataSource` and `NacosDataSource`, configured as follows: ```properties spring.cloud.sentinel.datasource.ds1.file.file=classpath: degraderule.json spring.cloud.sentinel.datasource.ds1.file.data-type=json spring.cloud.sentinel.datasource.ds2.nacos.server-addr=127.0.0.1:8848 spring.cloud.sentinel.datasource.ds2.nacos.dataId=sentinel spring.cloud.sentinel.datasource.ds2.nacos.groupId=DEFAULT_GROUP spring.cloud.sentinel.datasource.ds2.nacos.data-type=json ``` `ds1` And `ds2` the name that represents the ReadableDataSource. Feel free to write. The sum `nacos` following `file` the `ds1` and `ds2` represents the type of the ReadableDataSource. Currently supports `file`, `nacos`, `zk` `apollo`, `redis` these 5 types. Among them `nacos` `zk` `apollo`, the use of `redis` these four types requires the addition of corresponding dependencies `sentinel-datasource-nacos` `sentinel-datasource-zookeeper` `sentinel-datasource-apollo` `sentinel-datasource-redis`. When `ReadableDataSource` the rule data is loaded successfully, the console will print out the corresponding log information: ``` [Sentinel Starter] DataSource ds1-sentinel-file-datasource load 3 DegradeRule [Sentinel Starter] DataSource ds2-sentinel-nacos-datasource load 2 FlowRule ``` ## More Sentinel is a powerful middleware that protects the stability of services from multiple dimensions such as flow control, fuse degradation, and system load protection. This Demo only demonstrates the use of Sentinel as a current limiting tool. For more information about Sentinel, please refer to [Project Sentinel](https://github.com/alibaba/Sentinel). If you `spring-cloud-starter-alibaba-sentinel` have any suggestions or ideas, please feel free to send them to us in the issue or through other community channels. ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 sentinel-circuitbreaker-example Spring Cloud Starter Alibaba Sentinel - Feign With Sentinel CircuitBreaker Example Example demonstrating how to use sentinel circuit breaker with feign jar org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-circuitbreaker-sentinel com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/FeignCircuitBreakerApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @author freeman */ @SpringBootApplication @EnableFeignClients public class FeignCircuitBreakerApplication { public static void main(String[] args) { SpringApplication.run(FeignCircuitBreakerApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/ApiController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * * * @author freeman */ @RestController public class ApiController { @GetMapping("/default/{ok}") public String defaultConfig(@PathVariable boolean ok) { if (ok) { return "ok"; } throw new RuntimeException("fail"); } @GetMapping("/feign/{ok}") public String feignConfig(@PathVariable boolean ok) { if (ok) { return "ok"; } throw new RuntimeException("fail"); } @GetMapping("/feignMethod/{ok}") public String feignMethodConfig(@PathVariable boolean ok) { if (ok) { return "ok"; } throw new RuntimeException("fail"); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/TestController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.controller; import com.alibaba.cloud.examples.feign.OrderClient; import com.alibaba.cloud.examples.feign.UserClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * * * @author freeman */ @RestController public class TestController { @Autowired private UserClient userClient; @Autowired private OrderClient orderClient; @GetMapping("/test/default/{ok}") public String testDefault(@PathVariable boolean ok) { return orderClient.defaultConfig(ok); } @GetMapping("/test/feign/{ok}") public String testFeign(@PathVariable boolean ok) { return userClient.feign(ok); } @GetMapping("/test/feignMethod/{ok}") public String testFeignMethod(@PathVariable boolean ok) { return userClient.feignMethod(ok); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * * * @author freeman */ @FeignClient(value = "order", url = "http://localhost:${server.port}", fallback = OrderClientFallBack.class) public interface OrderClient { @GetMapping("/default/{ok}") String defaultConfig(@PathVariable boolean ok); } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClientFallBack.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.feign; import org.springframework.stereotype.Component; /** * * * @author freeman */ @Component public class OrderClientFallBack implements OrderClient { @Override public String defaultConfig(boolean ok) { return "order fallback"; } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * * * @author freeman */ @FeignClient(value = "user", url = "http://localhost:${server.port}", fallback = UserClientFallBack.class) public interface UserClient { @GetMapping("/feignMethod/{ok}") String feignMethod(@PathVariable boolean ok); @GetMapping("/feign/{ok}") String feign(@PathVariable boolean ok); } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClientFallBack.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.feign; import org.springframework.stereotype.Component; /** * * * @author freeman */ @Component public class UserClientFallBack implements UserClient { @Override public String feignMethod(boolean ok) { return "user fallback"; } @Override public String feign(boolean ok) { return "user fallback"; } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/resources/application.yml ================================================ # # Copyright 2023-2024 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # server: port: 80 spring: application: name: sentinel-circuit-breaker-example cloud: nacos: config: server-addr: 127.0.0.1:8848 username: 'nacos' password: 'nacos' config: import: optional:nacos:sentinel-circuitbreaker-rules.yml ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/resources/sentinel-circuitbreaker-rules.yml ================================================ # # Copyright 2023-2024 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # spring: cloud: openfeign: circuitbreaker: enabled: true feign: sentinel: default-rule: default rules: # global feign client default: - grade: 2 count: 1 timeWindow: 15 statIntervalMs: 1000 minRequestAmount: 1 # specific feign client user: - grade: 2 count: 1 timeWindow: 15 statIntervalMs: 1000 minRequestAmount: 1 # specific feign client single method "[user#feignMethod(boolean)]": - grade: 2 count: 1 timeWindow: 10 statIntervalMs: 1000 minRequestAmount: 1 ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 sentinel-core-example Spring Cloud Starter Alibaba Sentinel Core Example Example demonstrating how to use sentinel jar com.alibaba.cloud spring-cloud-starter-alibaba-sentinel com.alibaba.cloud spring-cloud-alibaba-sentinel-datasource org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-thymeleaf com.alibaba.csp sentinel-datasource-nacos org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/com/alibaba/cloud/examples/ExceptionUtil.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; /** * @author fangjian */ public final class ExceptionUtil { private ExceptionUtil() { } public static SentinelClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); return new SentinelClientHttpResponse("custom block info"); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/com/alibaba/cloud/examples/JsonFlowRuleListConverter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.util.List; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; /** * @author fangjian */ public class JsonFlowRuleListConverter implements Converter> { @Override public List convert(String source) { return JSON.parseObject(source, new TypeReference>() { }); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/com/alibaba/cloud/examples/SentinelCoreApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.util.Collections; import com.alibaba.cloud.circuitbreaker.sentinel.SentinelCircuitBreakerFactory; import com.alibaba.cloud.circuitbreaker.sentinel.SentinelConfigBuilder; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; /** * @author xiaojing */ @SpringBootApplication public class SentinelCoreApplication { @Bean @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class) public RestTemplate restTemplate() { return new RestTemplate(); } @Bean public RestTemplate restTemplate2() { return new RestTemplate(); } @Bean public Converter myConverter() { return new JsonFlowRuleListConverter(); } @Bean public Customizer defaultConfig() { return factory -> { factory.configureDefault( id -> new SentinelConfigBuilder().resourceName(id) .rules(Collections.singletonList(new DegradeRule(id) .setGrade(RuleConstant.DEGRADE_GRADE_RT).setCount(100) .setTimeWindow(10))) .build()); }; } public static void main(String[] args) { SpringApplication.run(SentinelCoreApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/com/alibaba/cloud/examples/TestController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import com.alibaba.csp.sentinel.annotation.SentinelResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * @author xiaojing */ @RestController public class TestController { @Autowired private RestTemplate restTemplate; @Autowired private CircuitBreakerFactory circuitBreakerFactory; @GetMapping("/hello") @SentinelResource("resource") public String hello() { return "Hello"; } @GetMapping("/aa") @SentinelResource("aa") public String aa(int b, int a) { return "Hello test"; } @GetMapping("/test") public String test1() { return "Hello test"; } @GetMapping("/template") public String client() { return restTemplate.getForObject("http://www.taobao.com/test", String.class); } @GetMapping("/slow") public String slow() { return circuitBreakerFactory.create("slow").run(() -> { try { Thread.sleep(500L); } catch (InterruptedException e) { e.printStackTrace(); } return "slow"; }, throwable -> "fallback"); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/java/com/alibaba/cloud/examples/WebMvcConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author yuhuangbin */ @Configuration @EnableWebMvc public class WebMvcConfiguration implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/errorPage").setViewName("errorPage"); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/application.yml ================================================ # # Copyright 2023-2024 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # spring: application: name: sentinel-core-example cloud: sentinel: transport: dashboard: localhost:8080 datasource: ds1: file: file: classpath:flowrule.json data-type: json rule-type: flow ds2: file: file: classpath:degraderule.json data-type: json rule-type: degrade ds3: file: file: classpath:authority.json data-type: json rule-type: authority ds4: file: file: classpath:system.json data-type: json rule-type: system ds5: file: file: classpath:param-flow.json data-type: json rule-type: param_flow ds6: nacos: server-addr: 127.0.0.1:8848 username: nacos password: nacos data-id: flowrule.json data-type: json rule-type: flow block-page: /errorPage # filter: # enabled: false # http-method-specify: false eager: true server: port: 18083 management: endpoint: web: exposure: include: "*" health: show-details: always diskSpace: # we can disable health check, default is enable enabled: false # sentinel: # enabled: false ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/authority.json ================================================ [ { "resource": "good", "limitApp": "abc", "strategy": 0 }, { "resource": "bad", "limitApp": "bcd", "strategy": 1 }, { "resource": "terrible", "limitApp": "aaa", "strategy": 1 } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/degraderule.json ================================================ [ { "resource": "aa", "count": 20.0, "grade": 0, "passCount": 0, "timeWindow": 10 }, { "resource": "abc1", "count": 15.0, "grade": 0, "passCount": 0, "timeWindow": 10 } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.json ================================================ [ { "resource": "/hello", "controlBehavior": 0, "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 }, { "resource": "/test", "controlBehavior": 0, "count": 0, "grade": 1, "limitApp": "default", "strategy": 0 }, { "resource": "GET:http://www.taobao.com", "controlBehavior": 0, "count": 0, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.xml ================================================ resource 0 1 1 default 0 test 0 1 1 default 0 ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/param-flow.json ================================================ [ { "resource": "aa", "count": 0, "grade": 1, "limitApp": "default", "paramIdx": 0, "paramFlowItemList": [ { "object": "2", "classType": "int", "count": 1 } ] } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/system.json ================================================ [ { "highestSystemLoad": -1, "qps": 100, "avgRt": -1, "maxThread": 10 } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/templates/errorPage.html ================================================ Title This is error page. ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 sentinel-openfeign-example Spring Cloud Starter Alibaba Sentinel x Feign - Example Example demonstrating how to use sentinel with feign jar org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/src/main/java/com/alibaba/cloud/examples/OpenFeignApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @author raozihao * @author Steve */ @EnableFeignClients @SpringBootApplication public class OpenFeignApplication { public static void main(String[] args) { SpringApplication.run(OpenFeignApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/src/main/java/com/alibaba/cloud/examples/configuration/EchoServiceFallbackFactory.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.configuration; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.stereotype.Component; /** * @author raozihao * @author Steve */ @Component public class EchoServiceFallbackFactory implements FallbackFactory { @Override public HttpbinClientFallback create(Throwable throwable) { return new HttpbinClientFallback(throwable); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/src/main/java/com/alibaba/cloud/examples/configuration/HttpbinClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.configuration; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; /** * Provide the external exposure interface of the service calling client. * * @author raozihao * @author Steve */ @FeignClient(name = "openfeign-example", url = "https://httpbin.org", contextId = "openfeign-example", fallbackFactory = EchoServiceFallbackFactory.class) public interface HttpbinClient { @GetMapping("/delay/3") String delay(); @GetMapping("/status/500") String status500(); @GetMapping("/get") String get(); } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/src/main/java/com/alibaba/cloud/examples/configuration/HttpbinClientFallback.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.configuration; /** * When the service is blown, the fallback operation is performed. * * @author raozihao */ public class HttpbinClientFallback implements HttpbinClient { @Override public String delay() { return "delay degrade by sentinel"; } @Override public String status500() { return "500 degrade by sentinel"; } @Override public String get() { return "get degrade by sentinel"; } private Throwable throwable; HttpbinClientFallback(Throwable throwable) { this.throwable = throwable; } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/src/main/java/com/alibaba/cloud/examples/configuration/SentinelRulesConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.configuration; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; /** * @author raozihao * @author Steve */ @Component public class SentinelRulesConfiguration { /** * You can configure sentinel rules by referring. * https://sca.aliyun.com/docs/2023/user-guide/sentinel/advanced-guide/#%E6%9B%B4%E5%A4%9A%E9%85%8D%E7%BD%AE%E9%A1%B9 */ @PostConstruct public void init() { System.out.println("Load Sentinel Rules start!"); List flowRules = new ArrayList(); FlowRule flowRule = new FlowRule(); flowRule.setResource("GET:https://httpbin.org/get"); flowRule.setCount(1); flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setLimitApp("default"); flowRules.add(flowRule); FlowRuleManager.loadRules(flowRules); List degradeRules = new ArrayList(); DegradeRule degradeRule1 = new DegradeRule(); degradeRule1.setResource("GET:https://httpbin.org/status/500"); degradeRule1.setCount(1); degradeRule1.setMinRequestAmount(1); degradeRule1.setTimeWindow(30); degradeRule1.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); degradeRule1.setLimitApp("default"); degradeRules.add(degradeRule1); DegradeRule degradeRule2 = new DegradeRule(); degradeRule2.setResource("GET:https://httpbin.org/delay/3"); degradeRule2.setCount(1); degradeRule2.setGrade(RuleConstant.DEGRADE_GRADE_RT); degradeRule2.setSlowRatioThreshold(0.1); degradeRule2.setMinRequestAmount(1); degradeRule2.setTimeWindow(30); degradeRule2.setLimitApp("default"); degradeRules.add(degradeRule2); DegradeRuleManager.loadRules(degradeRules); System.out.println("Load Sentinel Rules end!"); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/src/main/java/com/alibaba/cloud/examples/controller/TestController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.controller; import com.alibaba.cloud.examples.configuration.HttpbinClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author raozihao * @author Steve */ @RestController public class TestController { @Autowired private HttpbinClient httpbinClient; @GetMapping("/rt") public String delay() { return httpbinClient.delay(); } @GetMapping("/exp") public String exp() { return httpbinClient.status500(); } @GetMapping("/get") public String get() { return httpbinClient.get(); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/src/main/resources/application.yml ================================================ # # Copyright 2023-2024 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # server: port: 18087 spring: application: name: sentinel-openfeign-example cloud: sentinel: transport: dashboard: localhost:8080 eager: true # Don't support run in jar by using configuration file method now, refer to https://github.com/alibaba/spring-cloud-alibaba/issues/3033 # datasource: # ds1.file: # file: "classpath: degraderule.json" # ruleType: "degrade" # dataType: "json" # ds2.file: # file: "classpath: flowrule.json" # ruleType: "flow" # dataType: "json" feign: sentinel: enabled: true ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/src/main/resources/degraderule.json ================================================ [ { "resource": "GET:https://httpbin.org/status/500", "count": 1, "grade": 2, "minRequestAmount": 1, "timeWindow": 30 }, { "resource": "GET:https://httpbin.org/delay/3", "count": 1, "grade": 0, "slowRatioThreshold": 0.1, "minRequestAmount": 1, "timeWindow": 30 } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-openfeign-example/src/main/resources/flowrule.json ================================================ [ { "resource": "GET:https://httpbin.org/get", "controlBehavior": 0, "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-resttemplate-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 sentinel-resttemplate-example Spring Cloud Starter Alibaba Sentinel x RestTemplate - Example Example demonstrating how to use sentinel with RestTemplate jar org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-resttemplate-example/src/main/java/com/alibaba/cloud/examples/RestTemplateApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author raozihao * @author Steve */ @SpringBootApplication public class RestTemplateApplication { public static void main(String[] args) { SpringApplication.run(RestTemplateApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-resttemplate-example/src/main/java/com/alibaba/cloud/examples/configuration/RestTemplateConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.configuration; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * @author raozihao * @author Steve */ @Configuration public class RestTemplateConfiguration { @LoadBalanced @Bean @SentinelRestTemplate public RestTemplate restTemplate() { return new RestTemplate(); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-resttemplate-example/src/main/java/com/alibaba/cloud/examples/configuration/SentinelRulesConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.configuration; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; /** * @description * @author ChengPu raozihao * @date 2023/2/11 */ @Component public class SentinelRulesConfiguration { /** * You can configure sentinel rules by referring. * https://sca.aliyun.com/docs/2023/user-guide/sentinel/advanced-guide/#%E6%9B%B4%E5%A4%9A%E9%85%8D%E7%BD%AE%E9%A1%B9 */ @PostConstruct public void init() { System.out.println("Load Sentinel Rules start!"); List flowRules = new ArrayList(); FlowRule flowRule = new FlowRule(); flowRule.setResource("GET:https://httpbin.org/get"); flowRule.setCount(1); flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setLimitApp("default"); flowRules.add(flowRule); FlowRuleManager.loadRules(flowRules); List degradeRules = new ArrayList(); DegradeRule degradeRule1 = new DegradeRule(); degradeRule1.setResource("GET:https://httpbin.org/status/500"); degradeRule1.setCount(1); degradeRule1.setMinRequestAmount(1); degradeRule1.setTimeWindow(30); degradeRule1.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); degradeRule1.setLimitApp("default"); degradeRules.add(degradeRule1); DegradeRule degradeRule2 = new DegradeRule(); degradeRule2.setResource("GET:https://httpbin.org/delay/3"); degradeRule2.setCount(1); degradeRule2.setGrade(RuleConstant.DEGRADE_GRADE_RT); degradeRule2.setSlowRatioThreshold(0.1); degradeRule2.setMinRequestAmount(1); degradeRule2.setTimeWindow(30); degradeRule2.setLimitApp("default"); degradeRules.add(degradeRule2); DegradeRuleManager.loadRules(degradeRules); System.out.println("Load Sentinel Rules end!"); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-resttemplate-example/src/main/java/com/alibaba/cloud/examples/controller/TestController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * @author raozihao * @author Steve */ @RestController public class TestController { @Autowired RestTemplate restTemplate; @GetMapping("/exp") public String exp() { return restTemplate.getForObject("https://httpbin.org/status/500", String.class); } @GetMapping("/rt") public String rt() { return restTemplate.getForObject("https://httpbin.org/delay/3", String.class); } @GetMapping("/get") public String get() { return restTemplate.getForObject("https://httpbin.org/get", String.class); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-resttemplate-example/src/main/resources/application.yml ================================================ # # Copyright 2023-2024 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # server: port: 18088 spring: application: name: sentinel-resttemplate-example cloud: sentinel: transport: dashboard: localhost:8080 eager: true # Don't support run in jar by using configuration file method now, refer to https://github.com/alibaba/spring-cloud-alibaba/issues/3033 # cloud: # sentinel: # datasource: # ds1.file: # file: "classpath: degraderule.json" # ruleType: "degrade" # dataType: "json" # ds2.file: # file: "classpath: flowrule.json" # ruleType: "flow" # dataType: "json" ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-resttemplate-example/src/main/resources/degraderule.json ================================================ [ { "resource": "GET:https://httpbin.org/status/500", "count": 1, "grade": 2, "minRequestAmount": 1, "timeWindow": 30 }, { "resource": "GET:https://httpbin.org/delay/3", "count": 1, "grade": 0, "slowRatioThreshold": 0.1, "minRequestAmount": 1, "timeWindow": 30 } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-resttemplate-example/src/main/resources/flowrule.json ================================================ [ { "resource": "GET:https://httpbin.org/get", "controlBehavior": 0, "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-resttemplate-example/src/test/java/com/alibaba/cloud/examples/RestTemplateApplicationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Test {@link RestTemplateApplication}. * * @author wangliang181230 */ @Disabled("For debugging") public class RestTemplateApplicationTest { /** * Please run this test after execute `mvn clean install -Pnative -e` . * * @throws Exception the exception */ @Test public void runWithSpringAotModeAfterProcessAot() throws Exception { // Enable spring-aot-mode System.setProperty("spring.aot.enabled", "true"); // Start the application RestTemplateApplication.main(new String[0]); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 sentinel-spring-cloud-gateway-example Spring Cloud Starter Alibaba Sentinel x Gateway Example Example demonstrating how to use sentinel with spring cloud gateway jar com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.boot spring-boot-starter-webflux org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-gateway-server-webflux com.alibaba.cloud spring-cloud-alibaba-sentinel-gateway com.alibaba.csp sentinel-datasource-extension org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/com/alibaba/cloud/examples/MySCGConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import reactor.core.publisher.Mono; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import static org.springframework.web.reactive.function.BodyInserters.fromValue; /** * @author Jim */ @Configuration public class MySCGConfiguration { @Bean public BlockRequestHandler blockRequestHandler() { return new BlockRequestHandler() { @Override public Mono handleRequest(ServerWebExchange exchange, Throwable t) { return ServerResponse.status(444).contentType(MediaType.APPLICATION_JSON) .body(fromValue("SCS Sentinel block")); } }; } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/com/alibaba/cloud/examples/RulesWebFluxController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import reactor.core.publisher.Mono; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Jim */ @RestController public class RulesWebFluxController { @GetMapping("/api") public Mono> apiRules() { return Mono.just(GatewayApiDefinitionManager.getApiDefinitions()); } @GetMapping("/gateway") public Mono> apiGateway() { return Mono.just(GatewayRuleManager.getRules()); } @GetMapping("/flow") public Mono> apiFlow() { return Mono.just(FlowRuleManager.getRules()); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/com/alibaba/cloud/examples/SentinelSpringCloudGatewayApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Jim */ @SpringBootApplication public class SentinelSpringCloudGatewayApplication { public static void main(String[] args) { // GatewayCallbackManager.setRequestOriginParser(s -> "123"); SpringApplication.run(SentinelSpringCloudGatewayApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/api.json ================================================ [ { "apiName": "some_customized_api", "predicateItems": [ { "pattern": "/product/baz" }, { "pattern": "/product/foo/**", "matchStrategy": 1 }, { "items": [ { "pattern": "/spring-cloud/**" }, { "pattern": "/spring-cloud-alibaba/**" } ] } ] }, { "apiName": "another_customized_api", "predicateItems": [ { "pattern": "/ahas" } ] } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/application.yaml ================================================ # # Copyright 2023-2024 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # server: port: 18085 spring: application: name: sentinel-spring-cloud-gateway cloud: gateway: enabled: true discovery: locator: lower-case-service-id: true routes: # Add your routes here. - id: aliyun_route uri: https://www.aliyun.com/ predicates: - Path=/product/** - id: httpbin_route uri: https://httpbin.org predicates: - Path=/httpbin/** filters: - RewritePath=/httpbin/(?.*), /$\{segment} sentinel: datasource.ds2.file: file: "classpath: gateway.json" ruleType: gw-flow datasource.ds1.file: file: "classpath: api.json" ruleType: gw-api-group transport: dashboard: localhost:8080 eager: true filter: enabled: true scg.fallback: mode: response response-status: 444 response-body: 1234 scg: order: -100 management: endpoints: web: exposure: include: "*" ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/gateway.json ================================================ [ { "resource": "some_customized_api", "count": 1 }, { "resource": "httpbin_route", "count": 0, "paramItem": { "parseStrategy": 2, "fieldName": "Spring-Cloud-Alibaba" } }, { "resource": "httpbin_route", "count": 0, "paramItem": { "parseStrategy": 3, "fieldName": "name" } } ] ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 sentinel-webflux-example Spring Cloud Starter Alibaba Sentinel x WebFlux Example Example demonstrating how to use sentinel with webflux jar com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.boot spring-boot-starter-webflux org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/com/alibaba/cloud/examples/MyConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import java.util.Collections; import com.alibaba.cloud.circuitbreaker.sentinel.ReactiveSentinelCircuitBreakerFactory; import com.alibaba.cloud.circuitbreaker.sentinel.SentinelConfigBuilder; import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import reactor.core.publisher.Mono; import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.server.ServerWebExchange; import static org.springframework.web.reactive.function.BodyInserters.fromValue; /** * @author Jim */ @Configuration public class MyConfiguration { @Bean public BlockRequestHandler blockRequestHandler() { return new BlockRequestHandler() { @Override public Mono handleRequest(ServerWebExchange exchange, Throwable t) { return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON).body(fromValue("block")); } }; } @Bean public Customizer slowCustomizer() { return factory -> { factory.configure(builder -> builder.rules(Collections.singletonList( new DegradeRule("slow_mono").setGrade(RuleConstant.DEGRADE_GRADE_RT) .setCount(100).setTimeWindow(5))), "slow_mono"); factory.configure(builder -> builder.rules(Collections.singletonList( new DegradeRule("slow_flux").setGrade(RuleConstant.DEGRADE_GRADE_RT) .setCount(100).setTimeWindow(5))), "slow_flux"); factory.configureDefault(id -> new SentinelConfigBuilder().resourceName(id) .rules(Collections.singletonList(new DegradeRule(id) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) .setCount(0.5).setTimeWindow(10))) .build()); }; } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/com/alibaba/cloud/examples/SentinelWebFluxApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Jim */ @SpringBootApplication public class SentinelWebFluxApplication { public static void main(String[] args) { SpringApplication.run(SentinelWebFluxApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/com/alibaba/cloud/examples/SentinelWebFluxController.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples; import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.client.WebClient; /** * @author Jim */ @RestController public class SentinelWebFluxController { @Autowired private ReactiveCircuitBreakerFactory circuitBreakerFactory; @GetMapping("/mono") public Mono mono() { return Mono.just("simple string") // transform the publisher here. .transform(new SentinelReactorTransformer<>("mono")); } @GetMapping("/test") public Mono test() { return Mono.just("simple string") // transform the publisher here. .transform(new SentinelReactorTransformer<>("test")); } @GetMapping("/flux") public Flux flux() { return Flux.fromArray(new String[] { "a", "b", "c" }) // transform the publisher here. .transform(new SentinelReactorTransformer<>("flux")); } @GetMapping("/cbSlow") public Mono cbSlow() { int delaySecs = 2; return WebClient.builder().baseUrl("http://httpbin.org/").build().get() .uri("/delay/" + delaySecs).retrieve().bodyToMono(String.class) .transform(it -> circuitBreakerFactory.create("slow_mono").run(it, t -> { t.printStackTrace(); return Mono.just("fallback"); })); } @GetMapping("/cbError") public Mono cbError() { String code = "500"; return WebClient.builder().baseUrl("http://httpbin.org/").build().get() .uri("/status/" + code).retrieve().bodyToMono(String.class) .transform(it -> circuitBreakerFactory.create("cbError").run(it, t -> { t.printStackTrace(); return Mono.just("fallback"); })); } } ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/resources/application.yml ================================================ # # Copyright 2023-2024 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # server: port: 18084 spring: application: name: sentinel-webflux-example cloud: sentinel: transport: dashboard: localhost:8080 eager: true datasource: ds1: file: file: classpath:flowrule.json data-type: json rule-type: flow management: endpoints: web: exposure: include=*: ================================================ FILE: spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/resources/flowrule.json ================================================ [ { "resource": "/mono", "controlBehavior": 0, "count": 0, "grade": 1, "limitApp": "default", "strategy": 0 }, { "resource": "/flux", "controlBehavior": 0, "count": 0, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/node-service.js ================================================ /* * Copyright 2013-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var http = require('http'); var url = require("url"); var path = require('path'); // 创建server var server = http.createServer(function(req, res) { // 获得请求的路径 var pathname = url.parse(req.url).pathname; res.writeHead(200, { 'Content-Type' : 'application/json; charset=utf-8' }); // 访问http://localhost:8060/,将会返回{"index":"欢迎来到首页"} if (pathname === '/') { res.end(JSON.stringify({ "index" : "欢迎来到首页" })); } // 访问http://localhost:8060/health,将会返回{"status":"UP"} else if (pathname === '/health.json') { res.end(JSON.stringify({ "status" : "UP" })); } // 其他情况返回404 else { res.end("404"); } }); // 创建监听,并打印日志 server.listen(8060, function() { console.log('listening on localhost:8060'); }); ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/readme-zh.md ================================================ # Spring Cloud Alibaba Sidecar Example ## 项目说明 本项目演示如何使用 Nacos + Spring Cloud Alibaba Sidecar 完成 异构语言微服务的接入。 [Spring Cloud Alibaba Sidecar](https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/zh-cn/index.html#_spring_cloud_alibaba_sidecar) 是一个用来快速完美整合 Spring Cloud 与 异构语言微服务 的框架 ## 准备工作 ### 下载并启动 Nacos **在接入 Sidecar 之前,首先需要启动 Nacos服务器。** 1. 下载[Nacos二进制文件](https://github.com/alibaba/nacos/releases/download/2.1.0/nacos-server-2.1.0.zip) 并解压 2. 启动 Nacos Server 下载解压后 我们需要进入到 bin 目录启动 nacos 服务, 一定不要双击启动,双击默认会以集群方式启动,我们以单机方式启动。 ```bash startup.cmd -m standalone ``` 3. 登录 Nacos 我们来到浏览器 输入localhost:8848/nacos 可以看到Nacos的运行的界面 用户名和密码都是 `nacos` ## 简单示例 本文以Nacos作为注册中心为例,Sidecar接入一个非Java语言的服务。 ### Step1: 引入依赖 修改 `pom.xml` 文件,引入 Spring Cloud Alibaba Sidecar Starter。 ```xml org.springframework.cloud spring-cloud-starter-gateway-server-webflux com.alibaba.cloud spring-cloud-starter-alibaba-sidecar com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` ### Step2: 配置 Sidecar 相关信息 然后在项目的application.yml 文件中指定以下配置 ```yaml server: port: 8070 spring: cloud: nacos: username: nacos password: nacos discovery: server-addr: 127.0.0.1:8848 group: test gateway: discovery: locator: enabled: true application: name: node-service # sidecar 相关的配置 sidecar: # 异构微服务的IP ip: 127.0.0.1 # 异构微服务的端口 port: 8060 # 异构微服务的健康检查URL(这里不配置的话,默认会认为是UP) health-check-url: http://localhost:8060/health.json # springboot actuator监控相关 management: endpoint: health: show-details: always ``` 注意:这里的 localhost:8060,是我本机起了一个nginx 代理了这个health.json的请求。在实际使用过程中可以是任意的REST服务,只需要返回正确的JSON格式的健康检测数据即可。 ```json { "status": "DOWN" } ``` ### Step3: 启动应用 之后分别启动 Sidecar 服务、本地异构服务。 IDE 直接启动:找到主类 `com.alibaba.cloud.sidecar.DemoApplication`,执行 main 方法启动应用。 注意:本文是以 `spring-cloud-alibaba-sidecar-nacos-example`项目为例,所以启动的是它下面的`DemoApplication`启动类。 ![idea.png](https://cdn.nlark.com/yuque/0/2022/png/1752280/1662550869316-98d574af-d1ba-4c00-a0af-5e33e13075fd.png) ### Step4: 查看服务注册情况 ![nacos.png](https://cdn.nlark.com/yuque/0/2022/png/1752280/1662548324337-566cc824-4d08-4041-ac83-1968c7347a9e.png) ### Step4: 访问异构服务 完成上面4步,我们发现对应的服务`node-service`已经成功注册到了注册中心。此时,这个服务已经成功的融入到了Spring Cloud 微服务的怀抱。对于Spring Cloud 微服务而言,访问它跟访问其它的Java微服务没有任何的区别。 而这,也正是 Spring Cloud Alibaba Sidecar的魅力所在。接下来,我们将继续演示怎样访问这个服务。 浏览器访问 http://127.0.0.1:8070/node-service/health.json 能调通则说明整合成功。 ![](https://cdn.nlark.com/yuque/0/2022/png/1752280/1662549893322-1b7a761a-ecd7-44ae-88b6-872eca43a866.png) ## More 如果您对 spring cloud starter alibaba sidecar 有任何建议或想法,欢迎在 issue 中或者通过其他社区渠道向我们提出。 ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/readme.md ================================================ # Spring Cloud Alibaba Sidecar Example ## Project Instruction This project demonstrates how to use `Nacos + Spring Cloud Alibaba Sidecar` to access heterogeneous language microservices. [Spring Cloud Alibaba Sidecar](https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/zh-cn/index.html#_spring_cloud_alibaba_sidecar) is a framework for fast and seamless integration of Spring Cloud with heterogeneous language microservices. ## Preparation ### Download and Startup Nacos **You should startup Nacos Server before using Sidecar** 1. Download [Nacos](https://archive.apache.org/dist/rocketmq/4.3.2/rocketmq-all-4.3.2-bin-release.zip) and unzip it. 2. Startup Name Server ```bash startup.cmd -m standalone ``` 3. Sign in Nacos Open you browser then input `localhost:8848/nacos` ,you can see the Nacos dashboard ui . The default username and password are `nacos` ## Simple example In this paper, Sidecar accesses a non-Java language service using Nacos as a registry as an example. ### Step1: Declare dependency Add dependency spring-cloud-starter-alibaba-sidecar to the `pom.xml` file in your Sidecar project. ```xml org.springframework.cloud spring-cloud-starter-gateway-server-webflux com.alibaba.cloud spring-cloud-starter-alibaba-sidecar com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` ### Step2: Configure sidecar Then add necessary configurations to file `/src/main/resources/application.yml`. ```yaml server: port: 8070 spring: cloud: nacos: username: nacos password: nacos discovery: server-addr: 127.0.0.1:8848 group: test gateway: discovery: locator: enabled: true application: name: node-service sidecar: # heterogeneous service‘s ip ip: 127.0.0.1 # heterogeneous service's port port: 8060 # heterogeneous service's health check URL health-check-url: http://localhost:8060/health.json ``` Note: `localhost:8060` here, is my local machine started a nginx proxy for this `health.json` request. In actual use it can be any REST service, just need to return the correct JSON format health detection data. ```json { "status": "DOWN" } ``` ### Step3: Start Application After that, start the `Sidecar` service and the local heterogeneous service respectively. Start in IDE: Find main class `com.alibaba.cloud.sidecar.DemoApplication`, and execute the main method. Note: This article takes the `spring-cloud-alibaba-sidecar-nacos-example` project as an example, so it starts the `DemoApplication` startup class under it. ![idea.png](https://cdn.nlark.com/yuque/0/2022/png/1752280/1662550869316-98d574af-d1ba-4c00-a0af-5e33e13075fd.png) ### Step4: View service registration ![nacos.png](https://cdn.nlark.com/yuque/0/2022/png/1752280/1662605412601-06780784-915c-40f6-b6b2-67176f6c5419.png) ### Step4: Accessing services After completing the above 4 steps, we find that the corresponding service `node-service` has been successfully registered to the registry. At this point, the service has been successfully integrated into the Spring Cloud microservice. For Spring Cloud microservices, accessing it is no different than accessing any other Java microservice. This is where the beauty of Spring Cloud Alibaba Sidecar comes in. Next, we will continue to demonstrate how to access this service. Browser Access below address: http://127.0.0.1:8070/node-service/health.json If you see the following message, the access was successful. ![](https://cdn.nlark.com/yuque/0/2022/png/1752280/1662549893322-1b7a761a-ecd7-44ae-88b6-872eca43a866.png) ## More If you have any ideas or suggestions for `Spring Cloud Alibaba Sidecar`, please don't hesitate to tell us by submitting github issues. ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 spring-cloud-alibaba-sidecar-consul-example Spring Cloud Starter Alibaba Sidecar x Consul Example Example demonstrating how to use Spring Cloud Alibaba Sidecar with consul jar com.alibaba.cloud spring-cloud-starter-alibaba-sidecar com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-consul-discovery org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/src/main/java/com/alibaba/cloud/sidecar/DemoApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; @SpringBootApplication @LoadBalancerClients({ @LoadBalancerClient("node-service") }) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-consul-example/src/main/resources/application.yml ================================================ server: port: 8070 spring: cloud: gateway: discovery: locator: enabled: true consul: host: localhost port: 8500 application: name: node-service sidecar: # 异构微服务的IP ip: 127.0.0.1 # 异构微服务的端口 port: 8060 # 异构微服务的健康检查URL health-check-url: http://localhost:8060/health.json management: endpoint: health: show-details: always ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-examples ${revision} ../../pom.xml 4.0.0 spring-cloud-alibaba-sidecar-nacos-example Spring Cloud Starter Alibaba Sidecar x Nacos Example Example demonstrating how to use Spring Cloud Alibaba Sidecar with nacos jar org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-gateway-server-webflux com.alibaba.cloud spring-cloud-starter-alibaba-sidecar com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-loadbalancer org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/src/main/java/com/alibaba/cloud/sidecar/DemoApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; @SpringBootApplication @LoadBalancerClients({ @LoadBalancerClient("node-service") }) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-alibaba-sidecar-examples/spring-cloud-alibaba-sidecar-nacos-example/src/main/resources/application.yml ================================================ server: port: 8070 spring: cloud: nacos: username: 'nacos' password: 'nacos' discovery: server-addr: 127.0.0.1:8848 group: test gateway: discovery: locator: enabled: true loadbalancer: nacos: enabled: true ribbon: enabled: false application: name: node-service sidecar: # 异构微服务的IP ip: 127.0.0.1 # 异构微服务的端口 port: 8060 # 异构微服务的健康检查URL #health-check-url: http://localhost:8060/health.json management: endpoint: health: show-details: always ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-bus-rocketmq-example/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../pom.xml 4.0.0 spring-cloud-bus-rocketmq-example Spring Cloud Starter Bus Alibaba RocketMQ Example Example demonstrating how to use Spring Cloud Bus RocketMQ jar com.alibaba.cloud spring-cloud-starter-bus-rocketmq org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-bus-rocketmq-example/src/main/java/com/alibaba/cloud/examples/rocketmq/RocketMQBusApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.rocketmq; import tools.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.bus.event.AckRemoteApplicationEvent; import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * RocketMQ Bus Spring Application. * * @author Mercy * @since 0.2.1 */ @RestController @EnableAutoConfiguration @RemoteApplicationEventScan(basePackages = "com.alibaba.cloud.examples.rocketmq") public class RocketMQBusApplication { public static void main(String[] args) { new SpringApplicationBuilder(RocketMQBusApplication.class) .properties("server.port=0") // Random server port .properties("management.endpoints.web.exposure.include=*") // exposure // includes // all .properties("spring.cloud.bus.trace.enabled=true") // Enable trace .run(args); } @Autowired private ApplicationEventPublisher publisher; @Value("${spring.cloud.bus.id}") private String originService; @Value("${server.port}") private int localServerPort; @Autowired private ObjectMapper objectMapper; /** * Publish the {@link UserRemoteApplicationEvent}. * @param name the user name * @param destination the destination * @return If published */ @GetMapping("/bus/event/publish/user") public boolean publish(@RequestParam String name, @RequestParam(required = false) String destination) { User user = new User(); user.setId(System.currentTimeMillis()); user.setName(name); publisher.publishEvent( new UserRemoteApplicationEvent(this, user, originService, destination)); return true; } /** * Listener on the {@link UserRemoteApplicationEvent}. * @param event {@link UserRemoteApplicationEvent} */ @EventListener public void onEvent(UserRemoteApplicationEvent event) { System.out.printf("Server [port : %d] listeners on %s\n", localServerPort, event.getUser()); } @EventListener public void onAckEvent(AckRemoteApplicationEvent event) { System.out.printf("Server [port : %d] listeners on %s\n", localServerPort, objectMapper.writeValueAsString(event)); } } ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-bus-rocketmq-example/src/main/java/com/alibaba/cloud/examples/rocketmq/User.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.rocketmq; /** * User Domain. * * @author Mercy * @since 0.2.1 */ public class User { private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; } } ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-bus-rocketmq-example/src/main/java/com/alibaba/cloud/examples/rocketmq/UserRemoteApplicationEvent.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.rocketmq; import org.springframework.cloud.bus.event.RemoteApplicationEvent; /** * {@link User} {@link RemoteApplicationEvent}. * * @author Mercy * @since 0.2.1 */ public class UserRemoteApplicationEvent extends RemoteApplicationEvent { private User user; public UserRemoteApplicationEvent() { } public UserRemoteApplicationEvent(Object source, User user, String originService, String destinationService) { super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(originService)); this.user = user; } public void setUser(User user) { this.user = user; } public User getUser() { return user; } } ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-bus-rocketmq-example/src/main/resources/application.properties ================================================ spring.application.name=spring-cloud-bus-rocketmq-example spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876 server.port=8080 spring.cloud.bus.id=${spring.application.name}:${server.port} ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-scheduling-example/README-en.md ================================================ # Spring Cloud Alibaba Scheduling Example ## Project description Spring Cloud Alibaba Scheduling provides a timing task scheduling capability based on Spring Scheduling, supporting distributed scenarios for timing task scheduling. It offers a quick integration solution for timing task scheduling services in distributed scenarios. The current offering is based on the open-source ShedLock for distributed lock acquisition, along with Alibaba Cloud's SchedulerX service [quick start](https://sca.aliyun.com/en/docs/2023/user-guide/schedulerx/quick-start/), Subsequent releases will provide access to more open-source solutions implementations. ## Project dependencies ### Access `spring-cloud-starter-alibaba-schedulerx` Add the following dependencies to the project `pom.xml`: ```xml com.alibaba.cloud spring-cloud-starter-alibaba-schedulerx ``` ## Project config description In the Example project, two types of integration configuration modes are provided for `shedlock` and `schedulerx`, Select the required access mode configuration file in `application.yaml`, the example defaults to the shedlock solution. ### Solution 1. Distributed shedlock integration configuration Edit the following configuration to the `application-schedulerx.yml`: ```yaml spring: cloud: scheduling: # Distributed mode: shedlock, schedulerx # Set config value: shedlock distributed-mode: shedlock datasource: driver-class: com.mysql.cj.jdbc.Driver url: {jdbc_url} username: {jdbc.username} password: {jdbc.password} ``` You should replace `{jdbc_url}`,`{jdbc.username}`, and `{jdbc.password}` with your actual database connection information. >️ Precautions:If there's no database instance can be used, please create a database instance first. ### Solution 2. Alibaba Cloud's SchedulerX integration configuration Edit the following configuration to the `application-schedulerx.yml`: ```yaml spring: cloud: scheduling: # Distributed mode: shedlock, schedulerx # Set config value: schedulerx distributed-mode: schedulerx schedulerx: # This configuration is required, Please get it from aliyun schedulerx console endpoint: acm.aliyun.com namespace: aad167f6-xxxx-xxxx-xxxx-xxxxxxxxx groupId: xxxxx appKey: PZm1XXXXXXXXXXXX # Optional config, if you need to sync task to schedulerx # task-sync: true # region-id: public # aliyun-access-key: XXXXXXXXXXXX # aliyun-secret-key: XXXXXXXXXXXX # task-model-default: standalone ``` On Alibaba Cloud service, each account has be granted a free quota for schedulerx. For detailed instructions on how to configure and use cloud product integrations, please refer to the respective product documentation. Refer to: [SchedulerX Spring Task](https://www.alibabacloud.com/help/en/schedulerx/user-guide/spring-jobs) ## Start application After completing the above selection and configuration, simply run the `ScheduleApplication` class in Example to start the application. The `SimpleJob` class in this example project includes two Spring scheduled tasks that run every minute. Upon starting, you can expect to see the following logs: ```text 2024-05-17T11:20:59.981+08:00 INFO 66613 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-17 11:20:59 do job1... 2024-05-17T11:20:59.985+08:00 INFO 66613 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-1] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-17 11:20:59 do job2... ``` ### Distributed running verification Create two application launch configurations in IDEA, each with the startup parameter `--server.port={port}` to start the corresponding application processes. The example project defaults to using `shedlock`, We can observe that `job1` will be triggered by both applications, whereas `job2` which has been annotated with `@SchedulerLock` will only be triggered in one application at the same time. ![idea-server-port](images/idea-server-port.png) - ScheduleApplication-1, startup parameter: `--server.port=18080`, application logs: ```text 2024-05-20T14:02:00.003+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:02:00 do job1... 2024-05-20T14:03:00.008+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:03:00 do job1... 2024-05-20T14:03:00.008+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-1] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:03:00 do job2... 2024-05-20T14:04:00.006+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:04:00 do job1... 2024-05-20T14:04:00.010+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-2] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:04:00 do job2... 2024-05-20T14:05:00.003+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-5] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:05:00 do job1... ``` - ScheduleApplication-2, startup parameter: `--server.port=18081`, application logs: ```text 2024-05-20T14:02:00.003+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:02:00 do job1... 2024-05-20T14:02:00.008+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:02:00 do job2... 2024-05-20T14:03:00.004+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-5] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:03:00 do job1... 2024-05-20T14:04:00.006+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:04:00 do job1... 2024-05-20T14:05:00.004+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-2] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:05:00 do job1... 2024-05-20T14:05:00.007+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:05:00 do job2... ``` ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-scheduling-example/README.md ================================================ # Spring Cloud Alibaba Scheduling Example ## 项目说明 Spring Cloud Alibaba Scheduling 提供了基于 Spring Scheduling 的定时任务调度能力,支持分布式场景下的定时任务调度,为分布式场景下的定时任务调度服务提供快速接入方案。 目前提供基于开源shedlock分布式抢锁模式,以及阿里云上SchedulerX服务的 [快速接入](https://sca.aliyun.com/docs/2023/user-guide/schedulerx/quick-start/) ,后续将提供更多实现的开源方案接入。 ## 应用依赖 ### 接入 `spring-cloud-starter-alibaba-schedulerx` 在项目 pom.xml 中加入以下依赖: ```xml com.alibaba.cloud spring-cloud-starter-alibaba-schedulerx ``` ## 配置说明 在Example工程中提供shedlock和schedulerx的两种接入配置模式(***二选一***),在`application.yaml`中选择需要的接入模式配置文件,案例中默认采用开源的shedlock方案。 ### 方案一、分布式shedlock接入配置说明 在 application-schedulerx.yml 配置文件中修改以下配置: ```yaml spring: cloud: scheduling: # Distributed mode: shedlock, schedulerx # Set config value: shedlock distributed-mode: shedlock datasource: driver-class: com.mysql.cj.jdbc.Driver url: {jdbc_url} username: {jdbc.username} password: {jdbc.password} ``` 使用时请需要替换`{jdbc_url}`、`{jdbc.username}`、`{jdbc.password}`为实际自有的数据库连接信息。 >️ 注意:如未创建数据库,请先手动创建数据实例。 ### 方案二、云产品SchedulerX接入配置说明 在 application-schedulerx.yml 配置文件中修改以下配置: ```yaml spring: cloud: scheduling: # Distributed mode: shedlock, schedulerx # Set config value: schedulerx distributed-mode: schedulerx schedulerx: # This configuration is required, Please get it from aliyun schedulerx console endpoint: acm.aliyun.com namespace: aad167f6-xxxx-xxxx-xxxx-xxxxxxxxx groupId: xxxxx appKey: PZm1XXXXXXXXXXXX # Optional config, if you need to sync task to schedulerx # task-sync: true # region-id: public # aliyun-access-key: XXXXXXXXXXXX # aliyun-secret-key: XXXXXXXXXXXX # task-model-default: standalone ``` 阿里云上产品每个用户开通后都会有免费额度,详细云上产品接入配置使用说明,请参考:[阿里云SchedulerX Spring定时任务](https://help.aliyun.com/zh/schedulerx/user-guide/spring-jobs) ## 启动应用 在完成上述接入选择和配置后,直接运行Example中的 `ScheduleApplication`类即可启动运行。本案例工程中的`SimpleJob`类包含了两个每分钟执行一次的Spring定时任务,启动后可得到如下日志: ```text 2024-05-17T11:20:59.981+08:00 INFO 66613 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-17 11:20:59 do job1... 2024-05-17T11:20:59.985+08:00 INFO 66613 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-1] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-17 11:20:59 do job2... ``` ### 分布式运行验证 在IDEA环境中创建两个应用启动项,各自分别配置启动参数`--server.port={应用端口}`,启动相应的应用进程。案例工程默认采用`shedlock`, 我们可以直接看到`job1`两个应用都会同时触发,而`job2`添加了`@SchedulerLock`注解则会同一时间点只会在一个应用进程中执行。 ![idea-server-port](images/idea-server-port.png) - ScheduleApplication-1,启动参数:`--server.port=18080`,定时任务运行日志如下: ```text 2024-05-20T14:02:00.003+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:02:00 do job1... 2024-05-20T14:03:00.008+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:03:00 do job1... 2024-05-20T14:03:00.008+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-1] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:03:00 do job2... 2024-05-20T14:04:00.006+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:04:00 do job1... 2024-05-20T14:04:00.010+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-2] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:04:00 do job2... 2024-05-20T14:05:00.003+08:00 INFO 80520 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-5] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:05:00 do job1... ``` - ScheduleApplication-2,启动参数:`--server.port=18081`,定时任务运行日志如下: ```text 2024-05-20T14:02:00.003+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:02:00 do job1... 2024-05-20T14:02:00.008+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:02:00 do job2... 2024-05-20T14:03:00.004+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-5] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:03:00 do job1... 2024-05-20T14:04:00.006+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-3] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:04:00 do job1... 2024-05-20T14:05:00.004+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-2] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:05:00 do job1... 2024-05-20T14:05:00.007+08:00 INFO 80596 --- [spring-cloud-alibaba-schedule-example] [ sca-schedule-4] c.a.c.examples.schedule.job.SimpleJob : time=2024-05-20 14:05:00 do job2... ``` ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-scheduling-example/pom.xml ================================================ spring-cloud-alibaba-examples com.alibaba.cloud ${revision} ../pom.xml 4.0.0 spring-cloud-scheduling-example Spring Cloud Starter Alibaba Scheduling Example Example demonstrating how to use Spring Cloud Schedule jar com.alibaba.cloud spring-cloud-starter-alibaba-schedulerx org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-jdbc com.mysql mysql-connector-j org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/java/com/alibaba/cloud/examples/schedule/ScheduleApplication.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.schedule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; /** * ScheduleApplication. * * @author yaohui */ @SpringBootApplication @EnableScheduling public class ScheduleApplication { public static void main(String[] args) { SpringApplication.run(ScheduleApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/java/com/alibaba/cloud/examples/schedule/job/SimpleJob.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.examples.schedule.job; import java.util.concurrent.TimeUnit; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * @author yaohui **/ @Component public class SimpleJob { private static final Logger logger = LoggerFactory.getLogger(SimpleJob.class); /** * run without lock, all instance running at the same time. */ @Scheduled(cron = "0 */1 * * * ?") public void job1() { logger.info("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job1..."); } /** * run with lock, only one instance running at the same time. * * @throws InterruptedException interrupted exception */ @Scheduled(cron = "0 */1 * * * ?") @SchedulerLock(name = "lock-job2", lockAtMostFor = "10s") public void job2() throws InterruptedException { logger.info("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job2..."); TimeUnit.SECONDS.sleep(1L); } } ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application-schedulerx.yaml ================================================ spring: cloud: scheduling: # Distributed mode: shedlock, schedulerx distributed-mode: schedulerx schedulerx: # This configuration is required, Please get it from aliyun schedulerx console endpoint: acm.aliyun.com namespace: aad167f6-xxxx-xxxx-xxxx-xxxxxxxxx groupId: xxxxx appKey: PZm1XXXXXXXXXXXX # Optional config, if you need to sync task to schedulerx # task-sync: true # region-id: public # aliyun-access-key: XXXXXXXXXXXX # aliyun-secret-key: XXXXXXXXXXXX # task-model-default: standalone autoconfigure: exclude: - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application-shedlock.yaml ================================================ spring: cloud: scheduling: # Distributed mode: shedlock, schedulerx distributed-mode: shedlock datasource: driver-class: com.mysql.cj.jdbc.Driver # Change to your database jdbc url value url: jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true # Change to your database username value username: root # Change to your database password value password: 123456 ================================================ FILE: spring-cloud-alibaba-examples/spring-cloud-scheduling-example/src/main/resources/application.yaml ================================================ server: port: 18080 spring: profiles: # Select the config file to use, shedlock or schedulerx active: shedlock application: name: spring-cloud-alibaba-schedule-example # Spring task scheduling config task: scheduling: thread-name-prefix: sca-schedule- pool: size: 5 shutdown: await-termination: true await-termination-period: 60s ================================================ FILE: spring-cloud-alibaba-starters/pom.xml ================================================ 4.0.0 com.alibaba.cloud spring-cloud-alibaba ${revision} ../pom.xml spring-cloud-alibaba-starters pom Spring Cloud Alibaba Starters Spring Cloud Alibaba Starters spring-alibaba-nacos-config spring-cloud-starter-alibaba-nacos-config spring-cloud-starter-alibaba-nacos-discovery spring-cloud-starter-alibaba-seata spring-cloud-starter-stream-rocketmq spring-cloud-starter-bus-rocketmq spring-cloud-starter-alibaba-sidecar spring-cloud-circuitbreaker-sentinel spring-cloud-starter-alibaba-sentinel spring-cloud-alibaba-sentinel-datasource spring-cloud-alibaba-sentinel-gateway spring-cloud-alibaba-commons spring-cloud-starter-alibaba-schedulerx org.jacoco jacoco-maven-plugin ${jacoco.version} jacoco-initialize prepare-agent jacoco-site test report ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/pom.xml ================================================ 4.0.0 com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml spring-alibaba-nacos-config Spring Alibaba Nacos Config org.springframework.boot spring-boot-health provided org.springframework.boot spring-boot-actuator-autoconfigure true org.springframework.boot spring-boot-configuration-processor true com.alibaba.nacos nacos-client org.springframework.boot spring-boot-starter-web test com.alibaba druid provided org.springframework spring-web org.springframework.boot spring-boot-starter-test test org.slf4j slf4j-api jakarta.annotation jakarta.annotation-api ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import com.alibaba.cloud.nacos.annotation.NacosAnnotationProcessor; import com.alibaba.cloud.nacos.refresh.NacosContextRefresher; import com.alibaba.cloud.nacos.refresh.NacosRefreshHistory; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** * @author juven.xuxb * @author freeman */ @Configuration(proxyBeanMethods = false) @Conditional(NacosConfigEnabledCondition.class) public class NacosConfigAutoConfiguration { @Bean @ConditionalOnMissingBean(value = NacosConfigProperties.class, search = SearchStrategy.CURRENT) public NacosConfigProperties nacosConfigProperties(ApplicationContext context) { if (context.getParent() != null && BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context.getParent(), NacosConfigProperties.class).length > 0) { return BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(), NacosConfigProperties.class); } if (NacosConfigManager.getInstance() == null) { // this should never happen except for some unit tests return new NacosConfigProperties(); } else { return NacosConfigManager.getInstance().getNacosConfigProperties(); } } @Bean public NacosRefreshHistory nacosRefreshHistory() { return new NacosRefreshHistory(); } @Bean public NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) { return NacosConfigManager.getInstance(nacosConfigProperties); } @Bean public static NacosAnnotationProcessor nacosAnnotationProcessor() { return new NacosAnnotationProcessor(); } @Bean public NacosContextRefresher nacosContextRefresher(NacosConfigManager nacosConfigManager, NacosRefreshHistory nacosRefreshHistory) { // Consider that it is not necessary to be compatible with the previous // configuration // and use the new configuration if necessary. return new NacosContextRefresher(nacosConfigManager, nacosRefreshHistory); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigEnabledCondition.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; /** * @author shiyiyue */ public class NacosConfigEnabledCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String prefix = NacosPropertiesPrefixer.getPrefix(context.getEnvironment()); return context.getEnvironment().getProperty(prefix + ".config.enabled", Boolean.class, true); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigManager.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import java.util.Objects; import com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureException; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author zkzlx */ public class NacosConfigManager { private static final Logger log = LoggerFactory.getLogger(NacosConfigManager.class); private static ConfigService service; private static NacosConfigManager INSTANCE; private NacosConfigProperties nacosConfigProperties; public NacosConfigManager(NacosConfigProperties nacosConfigProperties) { this.nacosConfigProperties = nacosConfigProperties; } public static NacosConfigManager getInstance() { return INSTANCE; } public static NacosConfigManager getInstance(NacosConfigProperties properties) { if (INSTANCE != null) { return INSTANCE; } synchronized (NacosConfigManager.class) { if (INSTANCE == null) { INSTANCE = new NacosConfigManager(properties); INSTANCE.createConfigService(properties); } } return INSTANCE; } /** * Compatible with old design,It will be perfected in the future. */ private ConfigService createConfigService( NacosConfigProperties nacosConfigProperties) { try { if (Objects.isNull(service)) { service = NacosFactory.createConfigService( nacosConfigProperties.assembleConfigServiceProperties()); } } catch (NacosException e) { log.error(e.getMessage()); throw new NacosConnectionFailureException( nacosConfigProperties.getServerAddr(), e.getMessage(), e); } return service; } public ConfigService getConfigService() { if (Objects.isNull(service)) { createConfigService(this.nacosConfigProperties); } return service; } public NacosConfigProperties getNacosConfigProperties() { return nacosConfigProperties; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import com.alibaba.cloud.nacos.utils.PropertySourcesUtils; import com.alibaba.cloud.nacos.utils.StringUtils; import com.alibaba.nacos.api.config.ConfigService; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import static com.alibaba.nacos.api.PropertyKeyConst.ACCESS_KEY; import static com.alibaba.nacos.api.PropertyKeyConst.CLUSTER_NAME; import static com.alibaba.nacos.api.PropertyKeyConst.CONFIG_LONG_POLL_TIMEOUT; import static com.alibaba.nacos.api.PropertyKeyConst.CONFIG_RETRY_TIME; import static com.alibaba.nacos.api.PropertyKeyConst.ENABLE_REMOTE_SYNC_CONFIG; import static com.alibaba.nacos.api.PropertyKeyConst.ENCODE; import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT; import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT_PORT; import static com.alibaba.nacos.api.PropertyKeyConst.MAX_RETRY; import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE; import static com.alibaba.nacos.api.PropertyKeyConst.PASSWORD; import static com.alibaba.nacos.api.PropertyKeyConst.RAM_ROLE_NAME; import static com.alibaba.nacos.api.PropertyKeyConst.SECRET_KEY; import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR; import static com.alibaba.nacos.api.PropertyKeyConst.USERNAME; /** * Nacos properties. * * @author leijuan * @author xiaojing * @author pbting * @author lyuzb */ public class NacosConfigProperties { /** * COMMAS , . */ public static final String COMMAS = ","; /** * SEPARATOR , . */ public static final String SEPARATOR = "[,]"; /** * Nacos default namespace . */ public static final String DEFAULT_NAMESPACE = "public"; /** * Nacos default server and port. */ public static final String DEFAULT_ADDRESS = "127.0.0.1:8848"; private static final Pattern PATTERN = Pattern.compile("-(\\w)"); private static final Logger log = LoggerFactory .getLogger(NacosConfigProperties.class); @Autowired @JsonIgnore private Environment environment; /** * nacos config server address. */ private String serverAddr; /** * the nacos authentication username. */ private String username; /** * the nacos authentication password. */ private String password; /** * encode for nacos config content. */ private String encode; /** * nacos config group, group is config data meta info. */ private String group = "DEFAULT_GROUP"; /** * nacos config dataId prefix. */ private String prefix; /** * the suffix of nacos config dataId, also the file extension of config content. */ private String fileExtension = "properties"; /** * timeout for get config from nacos. */ private int timeout = 3000; /** * nacos maximum number of tolerable server reconnection errors. */ private String maxRetry; /** * nacos get config long poll timeout. */ private String configLongPollTimeout; /** * nacos get config failure retry time. */ private String configRetryTime; /** * If you want to pull it yourself when the program starts to get the configuration * for the first time, and the registered Listener is used for future configuration * updates, you can keep the original code unchanged, just add the system parameter: * enableRemoteSyncConfig = "true" ( But there is network overhead); therefore we * recommend that you use {@link ConfigService#getConfigAndSignListener} directly. */ private boolean enableRemoteSyncConfig = false; /** * endpoint for Nacos, the domain name of a service, through which the server address * can be dynamically obtained. */ private String endpoint; /** * namespace, separation configuration of different environments. */ private String namespace; /** * access key for namespace. */ private String accessKey; /** * secret key for namespace. */ private String secretKey; /** * role name for aliyun ram. */ private String ramRoleName; /** * context path for nacos config server. */ private String contextPath; /** * nacos config cluster name. */ private String clusterName; /** * nacos config dataId name. */ private String name; /** * a set of shared configurations .e.g: * spring.cloud.nacos.config.shared-configs[0]=xxx . */ private List sharedConfigs; /** * a set of extensional configurations .e.g: * spring.cloud.nacos.config.extension-configs[0]=xxx . */ private List extensionConfigs; /** * the master switch for refresh configuration, it default opened(true). */ private boolean refreshEnabled = true; @PostConstruct public void init() { this.overrideFromEnv(); } private void overrideFromEnv() { if (environment == null) { return; } String prefix = NacosPropertiesPrefixer.getPrefix(environment); if (StringUtils.isEmpty(this.getServerAddr())) { String serverAddr = environment .resolvePlaceholders("${" + prefix + ".config.server-addr:}"); if (StringUtils.isEmpty(serverAddr)) { serverAddr = environment.resolvePlaceholders( "${" + prefix + ".server-addr:127.0.0.1:8848}"); } this.setServerAddr(serverAddr); } if (StringUtils.isEmpty(this.getUsername())) { this.setUsername( environment.resolvePlaceholders("${" + prefix + ".username:}")); } if (StringUtils.isEmpty(this.getPassword())) { this.setPassword( environment.resolvePlaceholders("${" + prefix + ".password:}")); } } // todo sts support public String getServerAddr() { return serverAddr; } public void setServerAddr(String serverAddr) { this.serverAddr = serverAddr; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getFileExtension() { return fileExtension; } public void setFileExtension(String fileExtension) { this.fileExtension = fileExtension; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } public String getMaxRetry() { return maxRetry; } public void setMaxRetry(String maxRetry) { this.maxRetry = maxRetry; } public String getConfigLongPollTimeout() { return configLongPollTimeout; } public void setConfigLongPollTimeout(String configLongPollTimeout) { this.configLongPollTimeout = configLongPollTimeout; } public String getConfigRetryTime() { return configRetryTime; } public void setConfigRetryTime(String configRetryTime) { this.configRetryTime = configRetryTime; } public Boolean getEnableRemoteSyncConfig() { return enableRemoteSyncConfig; } public void setEnableRemoteSyncConfig(Boolean enableRemoteSyncConfig) { this.enableRemoteSyncConfig = enableRemoteSyncConfig; } public String getEndpoint() { return endpoint; } public void setEndpoint(String endpoint) { this.endpoint = endpoint; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } public String getRamRoleName() { return ramRoleName; } public void setRamRoleName(String ramRoleName) { this.ramRoleName = ramRoleName; } public String getEncode() { return encode; } public void setEncode(String encode) { this.encode = encode; } public String getContextPath() { return contextPath; } public void setContextPath(String contextPath) { this.contextPath = contextPath; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Environment getEnvironment() { return environment; } public void setEnvironment(Environment environment) { this.environment = environment; } public List getSharedConfigs() { return sharedConfigs; } public void setSharedConfigs(List sharedConfigs) { this.sharedConfigs = sharedConfigs; } public List getExtensionConfigs() { return extensionConfigs; } public void setExtensionConfigs(List extensionConfigs) { this.extensionConfigs = extensionConfigs; } public boolean isRefreshEnabled() { return refreshEnabled; } public void setRefreshEnabled(boolean refreshEnabled) { this.refreshEnabled = refreshEnabled; } /** * recommend to use {@link NacosConfigProperties#sharedConfigs} . * @return string */ @Deprecated @DeprecatedConfigurationProperty(reason = "use spring.config.import instead") public String getSharedDataids() { return null == getSharedConfigs() ? null : getSharedConfigs().stream().map(Config::getDataId) .collect(Collectors.joining(COMMAS)); } /** * recommend to use {@link NacosConfigProperties#sharedConfigs} and not use it at the * same time . * @param sharedDataids the dataids for configurable multiple shared configurations , * multiple separated by commas . */ @Deprecated public void setSharedDataids(String sharedDataids) { if (null != sharedDataids && sharedDataids.trim().length() > 0) { List list = new ArrayList<>(); Stream.of(sharedDataids.split(SEPARATOR)) .forEach(dataId -> list.add(new Config(dataId.trim()))); this.compatibleSharedConfigs(list); } } /** * Not providing support,the need to refresh is specified by the respective refresh * configuration and not use it at the same time . * @return string */ @Deprecated public String getRefreshableDataids() { return null == getSharedConfigs() ? null : getSharedConfigs().stream().filter(Config::isRefresh) .map(Config::getDataId).collect(Collectors.joining(COMMAS)); } /** * Not providing support,the need to refresh is specified by the respective refresh * configuration and not use it at the same time . * @param refreshableDataids refreshable dataids ,multiple separated by commas . */ @Deprecated public void setRefreshableDataids(String refreshableDataids) { if (null != refreshableDataids && refreshableDataids.trim().length() > 0) { List list = new ArrayList<>(); Stream.of(refreshableDataids.split(SEPARATOR)).forEach( dataId -> list.add(new Config(dataId.trim()).setRefresh(true))); this.compatibleSharedConfigs(list); } } private void compatibleSharedConfigs(List configList) { if (null != this.getSharedConfigs()) { configList.addAll(this.getSharedConfigs()); } List result = new ArrayList<>(); configList.stream() .collect(Collectors.groupingBy(cfg -> (cfg.getGroup() + cfg.getDataId()), LinkedHashMap::new, Collectors.toList())) .forEach((key, list) -> { list.stream() .reduce((a, b) -> new Config(a.getDataId(), a.getGroup(), a.isRefresh() || (b != null && b.isRefresh()))) .ifPresent(result::add); }); this.setSharedConfigs(result); } /** * recommend to use * {@link com.alibaba.cloud.nacos.NacosConfigProperties#extensionConfigs} and not use * it at the same time . * @return extensionConfigs */ @Deprecated @DeprecatedConfigurationProperty(reason = "use spring.config.import instead") public List getExtConfig() { return this.getExtensionConfigs(); } @Deprecated public void setExtConfig(List extConfig) { this.setExtensionConfigs(extConfig); } /** * recommend to use {@link NacosConfigManager#getConfigService()}. * @return ConfigService */ @Deprecated public ConfigService configServiceInstance() { // The following code will be migrated return NacosConfigManager.getInstance(this).getConfigService(); } /** * recommend to use {@link NacosConfigProperties#assembleConfigServiceProperties()}. * @return ConfigServiceProperties */ @Deprecated public Properties getConfigServiceProperties() { return this.assembleConfigServiceProperties(); } /** * assemble properties for configService. (cause by rename : Remove the interference * of auto prompts when writing,because autocue is based on get method. * @return properties */ public Properties assembleConfigServiceProperties() { Properties properties = new Properties(); properties.put(SERVER_ADDR, Objects.toString(this.serverAddr, "")); properties.put(USERNAME, Objects.toString(this.username, "")); properties.put(PASSWORD, Objects.toString(this.password, "")); properties.put(ENCODE, Objects.toString(this.encode, "")); properties.put(NAMESPACE, this.resolveNamespace()); properties.put(ACCESS_KEY, Objects.toString(this.accessKey, "")); properties.put(SECRET_KEY, Objects.toString(this.secretKey, "")); properties.put(RAM_ROLE_NAME, Objects.toString(this.ramRoleName, "")); properties.put(CLUSTER_NAME, Objects.toString(this.clusterName, "")); properties.put(MAX_RETRY, Objects.toString(this.maxRetry, "")); properties.put(CONFIG_LONG_POLL_TIMEOUT, Objects.toString(this.configLongPollTimeout, "")); properties.put(CONFIG_RETRY_TIME, Objects.toString(this.configRetryTime, "")); properties.put(ENABLE_REMOTE_SYNC_CONFIG, Objects.toString(this.enableRemoteSyncConfig, "")); String endpoint = Objects.toString(this.endpoint, ""); if (endpoint.contains(":")) { int index = endpoint.indexOf(":"); properties.put(ENDPOINT, endpoint.substring(0, index)); properties.put(ENDPOINT_PORT, endpoint.substring(index + 1)); } else { properties.put(ENDPOINT, endpoint); } enrichNacosConfigProperties(properties); // set default value when serverAddr and endpoint is empty if (StringUtils.isEmpty(this.serverAddr) && StringUtils.isEmpty(this.endpoint)) { properties.put(SERVER_ADDR, DEFAULT_ADDRESS); } return properties; } /** * refer * https://github.com/alibaba/spring-cloud-alibaba/issues/2872 * https://github.com/alibaba/spring-cloud-alibaba/issues/2869 . */ private String resolveNamespace() { if (DEFAULT_NAMESPACE.equals(this.namespace)) { log.info("set nacos config namespace 'public' to ''"); return ""; } else { return Objects.toString(this.namespace, ""); } } protected void enrichNacosConfigProperties(Properties nacosConfigProperties) { if (environment == null) { return; } String prefix = NacosPropertiesPrefixer.getPrefix(environment); Map properties = PropertySourcesUtils .getSubProperties((ConfigurableEnvironment) environment, prefix + ".config"); properties.forEach((k, v) -> nacosConfigProperties.putIfAbsent(resolveKey(k), String.valueOf(v))); } protected String resolveKey(String key) { Matcher matcher = PATTERN.matcher(key); StringBuffer sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, matcher.group(1).toUpperCase(Locale.ROOT)); } matcher.appendTail(sb); return sb.toString(); } /** * refer * https://github.com/alibaba/spring-cloud-alibaba/issues/4242 * Mask sensitive fields in logs to avoid credential leakage. */ private static String mask(String value) { return (value == null || value.isEmpty()) ? value : "******"; } @Override public String toString() { return "NacosConfigProperties{" + "serverAddr='" + serverAddr + '\'' + ", encode='" + encode + '\'' + ", group='" + group + '\'' + ", prefix='" + prefix + '\'' + ", fileExtension='" + fileExtension + '\'' + ", timeout=" + timeout + ", maxRetry='" + maxRetry + '\'' + ", configLongPollTimeout='" + configLongPollTimeout + '\'' + ", configRetryTime='" + configRetryTime + '\'' + ", enableRemoteSyncConfig=" + enableRemoteSyncConfig + ", endpoint='" + endpoint + '\'' + ", namespace='" + namespace + '\'' + ", accessKey='" + mask(accessKey) + '\'' + ", secretKey='" + mask(secretKey) + '\'' + ", ramRoleName='" + ramRoleName + '\'' + ", contextPath='" + contextPath + '\'' + ", clusterName='" + clusterName + '\'' + ", name='" + name + '\'' + ", shares=" + sharedConfigs + ", extensions=" + extensionConfigs + ", refreshEnabled=" + refreshEnabled + '}'; } public static class Config { /** * the data id of extended configuration. */ private String dataId; /** * the group of extended configuration, the default value is DEFAULT_GROUP. */ private String group = "DEFAULT_GROUP"; /** * whether to support dynamic refresh, the default does not support . */ private boolean refresh = false; public Config() { } public Config(String dataId) { this.dataId = dataId; } public Config(String dataId, String group) { this(dataId); this.group = group; } public Config(String dataId, boolean refresh) { this(dataId); this.refresh = refresh; } public Config(String dataId, String group, boolean refresh) { this(dataId, group); this.refresh = refresh; } public String getDataId() { return dataId; } public Config setDataId(String dataId) { this.dataId = dataId; return this; } public String getGroup() { return group; } public Config setGroup(String group) { this.group = group; return this; } public boolean isRefresh() { return refresh; } public Config setRefresh(boolean refresh) { this.refresh = refresh; return this; } @Override public String toString() { return "Config{" + "dataId='" + dataId + '\'' + ", group='" + group + '\'' + ", refresh=" + refresh + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Config config = (Config) o; return refresh == config.refresh && Objects.equals(dataId, config.dataId) && Objects.equals(group, config.group); } @Override public int hashCode() { return Objects.hash(dataId, group, refresh); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosPropertiesPrefixProvider.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; /** * @author shiyiyue */ public interface NacosPropertiesPrefixProvider { String getPrefix(); } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosPropertiesPrefixer.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import java.util.ServiceLoader; import com.alibaba.cloud.nacos.utils.StringUtils; import org.springframework.boot.context.properties.bind.BindResult; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.core.env.Environment; /** * @author shiyiyue */ public final class NacosPropertiesPrefixer { /** * prefix from spi provider. */ public static final String PREFIX = getPrefixFromSpi(); private NacosPropertiesPrefixer() { } private static String getPrefixFromSpi() { ServiceLoader load = ServiceLoader.load(NacosPropertiesPrefixProvider.class); for (NacosPropertiesPrefixProvider provider : load) { return provider.getPrefix(); } return ""; } public static String getPrefix(Environment environment) { String prefix = "spring.nacos"; String prefixFromProperties = environment.getProperty("spring.nacos.properties.prefix"); if (StringUtils.isBlank(prefixFromProperties)) { if (StringUtils.isNotBlank(NacosPropertiesPrefixer.PREFIX)) { prefix = NacosPropertiesPrefixer.PREFIX; } } else { prefix = prefixFromProperties; } if (StringUtils.isNotBlank(prefix) && prefix.endsWith(".")) { prefix = prefix.substring(0, prefix.length() - 1); } return prefix; } public static String getPrefix(Binder binder) { String prefix = "spring.nacos"; BindResult bind = binder.bind("spring.nacos.properties.prefix", String.class); if (!bind.isBound()) { if (StringUtils.isNotBlank(NacosPropertiesPrefixer.PREFIX)) { prefix = NacosPropertiesPrefixer.PREFIX; } } else { prefix = bind.get(); } if (StringUtils.isNotBlank(prefix) && prefix.endsWith(".")) { prefix = prefix.substring(0, prefix.length() - 1); } return prefix; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosPropertySourceRepository.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.cloud.nacos.client.NacosPropertySource; /** * @author xiaojing * @author pbting */ public final class NacosPropertySourceRepository { private final static ConcurrentHashMap NACOS_PROPERTY_SOURCE_REPOSITORY = new ConcurrentHashMap<>(); private NacosPropertySourceRepository() { } /** * @return all nacos properties from application context. */ public static List getAll() { return new ArrayList<>(NACOS_PROPERTY_SOURCE_REPOSITORY.values()); } /** * recommend to use {@link NacosPropertySourceRepository#collectNacosPropertySource}. * @param nacosPropertySource nacosPropertySource */ @Deprecated public static void collectNacosPropertySources( NacosPropertySource nacosPropertySource) { NACOS_PROPERTY_SOURCE_REPOSITORY.putIfAbsent(nacosPropertySource.getDataId(), nacosPropertySource); } /** * recommend to use * {@link NacosPropertySourceRepository#getNacosPropertySource(java.lang.String, java.lang.String)}. * @param dataId dataId * @return NacosPropertySource */ @Deprecated public static NacosPropertySource getNacosPropertySource(String dataId) { return NACOS_PROPERTY_SOURCE_REPOSITORY.get(dataId); } public static void collectNacosPropertySource( NacosPropertySource nacosPropertySource) { NACOS_PROPERTY_SOURCE_REPOSITORY .putIfAbsent(getMapKey(nacosPropertySource.getDataId(), nacosPropertySource.getGroup()), nacosPropertySource); } public static NacosPropertySource getNacosPropertySource(String dataId, String group) { return NACOS_PROPERTY_SOURCE_REPOSITORY.get(getMapKey(dataId, group)); } public static String getMapKey(String dataId, String group) { return String.join(NacosConfigProperties.COMMAS, String.valueOf(dataId), String.valueOf(group)); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/AbstractConfigChangeListener.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.util.Map; import com.alibaba.nacos.api.config.ConfigChangeEvent; import com.alibaba.nacos.api.config.ConfigChangeItem; import com.alibaba.nacos.api.config.listener.AbstractSharedListener; import com.alibaba.nacos.client.config.impl.ConfigChangeHandler; public abstract class AbstractConfigChangeListener extends AbstractSharedListener implements TargetRefreshable { String lastContent; Object target; @Override public Object getTarget() { return target; } @Override public void setTarget(Object target) { this.target = target; } public AbstractConfigChangeListener(Object target) { this.target = target; } protected void setLastContent(String lastContent) { this.lastContent = lastContent; } @Override public void innerReceive(String dataId, String group, String configInfo) { Map data = null; try { data = ConfigChangeHandler.getInstance().parseChangeData(lastContent, configInfo, type(dataId)); } catch (Exception e) { throw new RuntimeException(e); } ConfigChangeEvent event = new ConfigChangeEvent(data); receiveConfigChange(event); lastContent = configInfo; } private String type(String dataId) { if (dataId.endsWith(".yml") || dataId.endsWith(".yaml")) { return "yaml"; } return "properties"; } abstract void receiveConfigChange(ConfigChangeEvent event); } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/CustomDateDeserializer.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; public class CustomDateDeserializer extends JsonDeserializer { private static final long serialVersionUID = 1L; private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public CustomDateDeserializer() { super(); } @Override public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { JsonNode node = jsonParser.getCodec().readTree(jsonParser); String date = node.textValue(); try { return dateFormat.parse(date); } catch (Exception e) { throw new IOException("Invalid date format"); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/JsonUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.io.IOException; import java.lang.reflect.Type; import com.alibaba.nacos.api.exception.runtime.NacosDeserializationException; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; final class JsonUtils { private JsonUtils() { } static ObjectMapper mapper = new ObjectMapper(); static { mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); } /** * Json string deserialize to Object. * * @param json json string * @param cls class of object * @param General type * @return object * @throws NacosDeserializationException if deserialize failed */ public static T toObj(String json, Class cls) { try { return mapper.readValue(json, cls); } catch (IOException e) { throw new NacosDeserializationException(cls, e); } } public static T toObj(String json, Type type) { try { return mapper.readValue(json, TypeFactory.defaultInstance().constructType(type)); } catch (IOException e) { throw new NacosDeserializationException(type, e); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosAnnotationProcessor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.nacos.api.config.ConfigChangeEvent; import com.alibaba.nacos.api.config.ConfigChangeItem; import com.alibaba.nacos.api.config.listener.AbstractListener; import com.alibaba.nacos.client.config.common.GroupKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.BeansException; import org.springframework.beans.NotReadablePropertyException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.PriorityOrdered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.type.MethodMetadata; import org.springframework.util.ReflectionUtils; public class NacosAnnotationProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware { private NacosConfigManager nacosConfigManager; private ApplicationContext applicationContext; private final static Logger log = LoggerFactory .getLogger(NacosAnnotationProcessor.class); @Override public int getOrder() { return 0; } private Map targetListenerMap = new ConcurrentHashMap<>(); private Map> groupKeyCache = new ConcurrentHashMap<>(); private String getGroupKeyContent(String dataId, String group, boolean refreshed) throws Exception { if (groupKeyCache.containsKey(GroupKey.getKey(dataId, group))) { return groupKeyCache.get(GroupKey.getKey(dataId, group)).get(); } synchronized (this) { if (!groupKeyCache.containsKey(GroupKey.getKey(dataId, group))) { String content = getNacosConfigManager().getConfigService().getConfig(dataId, group, 5000); groupKeyCache.put(GroupKey.getKey(dataId, group), new AtomicReference<>(content)); if (!refreshed) { log.info("[Nacos Config] refreshed is set to false, not listening config for annotation: dataId={}, group={}", dataId, group); return content; } log.info("[Nacos Config] Listening config for annotation: dataId={}, group={}", dataId, group); getNacosConfigManager().getConfigService().addListener(dataId, group, new AbstractListener() { @Override public void receiveConfigInfo(String s) { groupKeyCache.get(GroupKey.getKey(dataId, group)).set(s); } @Override public String toString() { return String.format("sca nacos config annotation cache config listener"); } }); } return groupKeyCache.get(GroupKey.getKey(dataId, group)).get(); } } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); Class clazz = bean.getClass(); NacosConfig annotationBean = AnnotationUtils.findAnnotation(clazz, NacosConfig.class); if (annotationBean != null) { handleBeanNacosConfigAnnotation(annotationBean.dataId(), annotationBean.group(), annotationBean.key(), annotationBean.refreshed(), beanName, bean, annotationBean.defaultValue()); return bean; } for (Field field : getBeanFields(clazz)) { handleFiledAnnotation(bean, beanName, field); } for (Method method : getBeanMethods(clazz)) { handleMethodAnnotation(bean, beanName, method); } return bean; } private List getBeanFields(Class clazz) { List res = new ArrayList<>(); ReflectionUtils.doWithFields(clazz, field -> res.add(field)); return res; } private List getBeanMethods(Class clazz) { List res = new ArrayList<>(); ReflectionUtils.doWithMethods(clazz, method -> res.add(method)); return res; } private void handleFiledAnnotation(Object bean, String beanName, Field field) { NacosConfig annotation = AnnotationUtils.getAnnotation(field, NacosConfig.class); if (annotation != null) { handleFiledNacosConfigAnnotation(annotation, beanName, bean, field); } } private void handleBeanNacosConfigAnnotation(String dataId, String group, String key, boolean refreshed, String beanName, Object bean, String defaultValue) { try { String config = getDestContent(getGroupKeyContent(dataId, group, refreshed), key); if (!org.springframework.util.StringUtils.hasText(config)) { config = defaultValue; } //Init bean properties. if (org.springframework.util.StringUtils.hasText(config)) { Object targetObject = convertContentToTargetType(config, bean.getClass()); //yaml and json to object BeanUtils.copyProperties(targetObject, bean, getNullPropertyNames(targetObject)); } String refreshTargetKey = beanName + "#instance#"; if (!refreshed) { log.info("[Nacos Config] refresh is set to false,do not register listener for {} to bean {} ", refreshTargetKey, bean); return; } TargetRefreshable currentTarget = targetListenerMap.get(refreshTargetKey); if (currentTarget != null) { log.info("[Nacos Config] reset {} listener from {} to {} ", refreshTargetKey, currentTarget.getTarget(), bean); targetListenerMap.get(refreshTargetKey).setTarget(bean); return; } log.info("[Nacos Config] register {} listener on {} ", refreshTargetKey, bean); TargetRefreshable listener = null; if (org.springframework.util.StringUtils.hasText(key)) { listener = new NacosPropertiesKeyListener(bean, wrapArrayToSet(key)) { @Override public void configChanged(ConfigChangeEvent event) { try { ConfigChangeItem changeItem = event.getChangeItem(key); String newConfig = changeItem == null ? null : changeItem.getNewValue(); if (!org.springframework.util.StringUtils.hasText(newConfig)) { newConfig = defaultValue; } if (org.springframework.util.StringUtils.hasText(newConfig)) { Object targetObject = convertContentToTargetType(newConfig, getTarget().getClass()); //yaml and json to object BeanUtils.copyProperties(targetObject, getTarget(), getNullPropertyNames(targetObject)); } } catch (Exception e) { throw new RuntimeException(e); } } @Override public String toString() { return String.format("[spring cloud alibaba nacos config instance key listener , key %s , target %s ] ", key, bean); } }; } else { listener = new NacosConfigRefreshableListener(bean) { @Override public void receiveConfigInfo(String configInfo) { if (!org.springframework.util.StringUtils.hasText(configInfo)) { configInfo = defaultValue; } if (org.springframework.util.StringUtils.hasText(configInfo)) { Object targetObject = convertContentToTargetType(configInfo, bean.getClass()); //yaml and json to object BeanUtils.copyProperties(targetObject, getTarget(), getNullPropertyNames(targetObject)); } } @Override public String toString() { return String.format("[spring cloud alibaba nacos config instance listener , key %s , target %s ] ", key, bean); } }; } getNacosConfigManager().getConfigService() .addListener(dataId, group, listener); targetListenerMap.put(refreshTargetKey, listener); } catch (Exception e) { throw new RuntimeException(e); } } private void handleMethodNacosConfigKeysChangeListener(NacosConfigKeysListener annotation, String beanName, Object bean, Method method) { String dataId = annotation.dataId(); String group = annotation.group(); try { Class[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length != 1 || !ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0])) { throw new RuntimeException( "NacosConfigKeysChangeListener must be marked as a single parameter with ConfigChangeEvent"); } String refreshTargetKey = beanName + "#method#" + methodSignature(method); TargetRefreshable currentTarget = targetListenerMap.get(refreshTargetKey); if (currentTarget != null) { log.info("[Nacos Config] reset {} listener from {} to {} ", refreshTargetKey, currentTarget.getTarget(), bean); targetListenerMap.get(refreshTargetKey).setTarget(bean); return; } log.info("[Nacos Config] register {} listener on {} ", refreshTargetKey, bean); // annotation on string. NacosPropertiesKeyListener nacosPropertiesKeyListener = new NacosPropertiesKeyListener(bean, wrapArrayToSet(annotation.interestedKeys()), wrapArrayToSet(annotation.interestedKeyPrefixes())) { @Override public void configChanged(ConfigChangeEvent event) { ReflectionUtils.invokeMethod(method, this.getTarget(), event); } @Override public String toString() { return String.format("sca nacos config listener on bean method %s", bean + "#" + methodSignature(method)); } }; nacosPropertiesKeyListener.setLastContent(getGroupKeyContent(dataId, group, true)); getNacosConfigManager().getConfigService().addListener(dataId, group, nacosPropertiesKeyListener); targetListenerMap.put(refreshTargetKey, nacosPropertiesKeyListener); } catch (Throwable e) { throw new RuntimeException(e); } } private Set wrapArrayToSet(String... arrayKeys) { return new HashSet<>(Arrays.asList(arrayKeys)); } private String methodSignature(Method method) { StringBuilder signature = new StringBuilder(method.getName() + "("); Class[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { signature.append(parameterTypes[i].getSimpleName()); if (i < parameterTypes.length - 1) { signature.append(", "); } } signature.append(")"); return signature.toString(); } private void handleMethodNacosConfigListener(NacosConfigListener annotation, String beanName, Object bean, Method method) { String dataId = annotation.dataId(); String group = annotation.group(); String key = annotation.key(); try { Type[] parameterTypes = method.getGenericParameterTypes(); if (parameterTypes.length != 1) { throw new RuntimeException( "@NacosConfigListener must be over a method with a single parameter"); } String configInfo = getGroupKeyContent(dataId, group, true); String refreshTargetKey = beanName + "#method#" + methodSignature(method); TargetRefreshable currentTarget = targetListenerMap.get(refreshTargetKey); if (currentTarget != null) { log.info("[Nacos Config] reset {} listener from {} to {} ", refreshTargetKey, currentTarget.getTarget(), bean); targetListenerMap.get(refreshTargetKey).setTarget(bean); return; } log.info("[Nacos Config] register {} listener on {} ", refreshTargetKey, bean); TargetRefreshable listener = null; if (org.springframework.util.StringUtils.hasText(key)) { listener = new NacosPropertiesKeyListener(bean, wrapArrayToSet(key)) { @Override public void configChanged(ConfigChangeEvent event) { try { ConfigChangeItem changeItem = event.getChangeItem(key); String newConfig = changeItem == null ? null : changeItem.getNewValue(); if (org.springframework.util.StringUtils.hasText(newConfig)) { if (invokePrimitiveMethod(method, getTarget(), newConfig)) { return; } Object targetObject = convertContentToTargetType(newConfig, parameterTypes[0]); ReflectionUtils.invokeMethod(method, getTarget(), targetObject); } } catch (Exception e) { throw new RuntimeException(e); } } @Override public String toString() { return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + methodSignature(method)); } }; ((AbstractConfigChangeListener) listener).fillContext(dataId, group); if (!annotation.initNotify()) { ((AbstractConfigChangeListener) listener).setLastContent(configInfo); } } else { listener = new NacosConfigRefreshableListener(bean) { @Override public void receiveConfigInfo(String configInfo) { if (org.springframework.util.StringUtils.hasText(configInfo)) { try { if (invokePrimitiveMethod(method, getTarget(), configInfo)) { return; } Object targetObject = convertContentToTargetType(configInfo, parameterTypes[0]); ReflectionUtils.invokeMethod(method, getTarget(), targetObject); } catch (Exception e) { throw new RuntimeException(e); } } } @Override public String toString() { return String.format("[spring cloud alibaba nacos config listener , target %s ] ", bean + "#" + methodSignature(method)); } }; } getNacosConfigManager().getConfigService().addListener(dataId, group, listener); targetListenerMap.put(refreshTargetKey, listener); if (annotation.initNotify() && org.springframework.util.StringUtils.hasText(configInfo)) { try { log.info("[Nacos Config] init notify listener of {} on {} start...", refreshTargetKey, bean); listener.receiveConfigInfo(configInfo); log.info("[Nacos Config] init notify listener of {} on {} finished ", refreshTargetKey, bean); } catch (Throwable throwable) { log.warn("[Nacos Config] init notify listener error", throwable); throw throwable; } } } catch (Throwable e) { throw new RuntimeException(e); } } Object convertContentToTargetType(String rawContent, Type type) { if (String.class.getCanonicalName().equals(type.getTypeName())) { return rawContent; } if (Properties.class.getCanonicalName().equals(type.getTypeName())) { //properties and yaml config to properties. Properties properties = new Properties(); try { if (org.springframework.util.StringUtils.hasText(rawContent)) { properties = PropertiesUtils.convertToProperties(rawContent); } } catch (Throwable throwable) { throw new RuntimeException(throwable); } return properties; } return ObjectUtils.convertToObject(rawContent, type); } private void handleFiledNacosConfigAnnotation(NacosConfig annotation, String beanName, Object bean, Field field) { String dataId = annotation.dataId(); String group = annotation.group(); String key = annotation.key(); try { ReflectionUtils.makeAccessible(field); handleFiledNacosConfigAnnotationInner(dataId, group, key, annotation.refreshed(), beanName, bean, field, annotation.defaultValue()); } catch (Exception e) { throw new RuntimeException(e); } } private void handleFiledNacosConfigAnnotationInner(String dataId, String group, String key, boolean refreshed, String beanName, Object bean, Field field, String defaultValue) { try { String config = getDestContent(getGroupKeyContent(dataId, group, refreshed), key); if (!org.springframework.util.StringUtils.hasText(config)) { config = defaultValue; } //primitive type if (handPrimitiveFiled(field, dataId, group, config, key, defaultValue, refreshed, beanName, bean)) { return; } //for other type. if (org.springframework.util.StringUtils.hasText(config)) { Object targetObject = convertContentToTargetType(config, field.getGenericType()); //yaml and json to object ReflectionUtils.setField(field, bean, targetObject); } String refreshTargetKey = beanName + "#filed#" + field.getName(); if (!refreshed) { log.info("[Nacos Config] refresh is set to false,do not register listener for {} to bean {} ", refreshTargetKey, bean); return; } TargetRefreshable currentTarget = targetListenerMap.get(refreshTargetKey); if (currentTarget != null) { log.info("[Nacos Config] reset {} listener from {} to {} ", refreshTargetKey, currentTarget.getTarget(), bean); targetListenerMap.get(refreshTargetKey).setTarget(bean); return; } log.info("[Nacos Config] register {} listener on {} ", refreshTargetKey, bean); TargetRefreshable listener = null; if (org.springframework.util.StringUtils.hasText(key)) { listener = new NacosPropertiesKeyListener(bean, wrapArrayToSet(key)) { @Override public void configChanged(ConfigChangeEvent event) { try { ConfigChangeItem changeItem = event.getChangeItem(key); String newConfig = changeItem == null ? null : changeItem.getNewValue(); if (!org.springframework.util.StringUtils.hasText(newConfig)) { newConfig = defaultValue; } if (org.springframework.util.StringUtils.hasText(newConfig)) { Object targetObject = convertContentToTargetType(newConfig, field.getGenericType()); ReflectionUtils.setField(field, getTarget(), targetObject); } } catch (Exception e) { throw new RuntimeException(e); } } @Override public String toString() { return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + field.getName()); } }; } else { listener = new NacosConfigRefreshableListener(bean) { @Override public void receiveConfigInfo(String configInfo) { if (!org.springframework.util.StringUtils.hasText(configInfo)) { configInfo = defaultValue; } if (org.springframework.util.StringUtils.hasText(configInfo)) { Object targetObject = convertContentToTargetType(configInfo, field.getGenericType()); ReflectionUtils.setField(field, getTarget(), targetObject); } } @Override public String toString() { return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + field.getName()); } }; } getNacosConfigManager().getConfigService() .addListener(dataId, group, listener); targetListenerMap.put(refreshTargetKey, listener); } catch (Exception e) { throw new RuntimeException(e); } } private boolean handPrimitiveFiled(Field field, String dataId, String group, String config, String key, String defaultValue, boolean refreshed, String beanName, Object bean) throws Exception { if (field.getType().isPrimitive()) { if (org.springframework.util.StringUtils.hasText(config)) { try { setPrimitiveFiled(field, bean, config); } catch (Throwable throwable) { throw new RuntimeException(throwable); } } String refreshTargetKey = beanName + "#filed#" + field.getName(); if (!refreshed) { log.info("[Nacos Config] refresh is set to false,do not register listener for {} to bean {} ", refreshTargetKey, bean); return true; } TargetRefreshable currentTarget = targetListenerMap.get(refreshTargetKey); if (currentTarget != null) { log.info("[Nacos Config] reset {} listener from {} to {} ", refreshTargetKey, currentTarget.getTarget(), bean); targetListenerMap.get(refreshTargetKey).setTarget(bean); return true; } log.info("[Nacos Config] register {} listener on {} ", refreshTargetKey, bean); TargetRefreshable listener = null; if (org.springframework.util.StringUtils.hasText(key)) { listener = new NacosPropertiesKeyListener(bean, wrapArrayToSet(key)) { @Override public void configChanged(ConfigChangeEvent event) { try { ConfigChangeItem changeItem = event.getChangeItem(key); String newConfig = changeItem == null ? null : changeItem.getNewValue(); if (!org.springframework.util.StringUtils.hasText(newConfig)) { newConfig = defaultValue; } if (org.springframework.util.StringUtils.hasText(newConfig)) { setPrimitiveFiled(field, getTarget(), newConfig); } } catch (Exception e) { throw new RuntimeException(e); } } @Override public String toString() { return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + field.getName()); } }; } else { listener = new NacosConfigRefreshableListener(bean) { @Override public void receiveConfigInfo(String configInfo) { if (!org.springframework.util.StringUtils.hasText(configInfo)) { configInfo = defaultValue; } if (org.springframework.util.StringUtils.hasText(configInfo)) { try { setPrimitiveFiled(field, getTarget(), configInfo); } catch (Exception e) { throw new RuntimeException(e); } } } @Override public String toString() { return String.format("[spring cloud alibaba nacos config key listener , key %s , target %s ] ", key, bean + "#" + field.getName()); } }; } getNacosConfigManager().getConfigService() .addListener(dataId, group, listener); targetListenerMap.put(refreshTargetKey, listener); return true; } return false; } private boolean setPrimitiveFiled(Field filed, Object bean, String value) throws Exception { if (filed.getType() == int.class) { filed.setInt(bean, Integer.parseInt(value)); } else if (filed.getType() == Integer.class) { ReflectionUtils.setField(filed, bean, Integer.valueOf(value)); } else if (filed.getType() == long.class) { filed.setLong(bean, Long.parseLong(value)); } else if (filed.getType() == Long.class) { ReflectionUtils.setField(filed, bean, Long.valueOf(value)); } else if (filed.getType() == boolean.class) { filed.setBoolean(bean, Boolean.parseBoolean(value)); } else if (filed.getType() == Boolean.class) { ReflectionUtils.setField(filed, bean, Boolean.valueOf(value)); } else if (filed.getType() == double.class) { filed.setDouble(bean, Double.parseDouble(value)); } else if (filed.getType() == Double.class) { ReflectionUtils.setField(filed, bean, Double.valueOf(value)); } else if (filed.getType() == float.class) { filed.setFloat(bean, Float.parseFloat(value)); } else if (filed.getType() == Float.class) { ReflectionUtils.setField(filed, bean, Float.valueOf(value)); } else { return false; } return true; } private boolean invokePrimitiveMethod(Method method, Object bean, String value) throws Exception { Class parameterType = method.getParameterTypes()[0]; if (parameterType == int.class) { ReflectionUtils.invokeMethod(method, bean, Integer.parseInt(value)); } else if (parameterType == Integer.class) { ReflectionUtils.invokeMethod(method, bean, Integer.valueOf(value)); } else if (parameterType == long.class) { ReflectionUtils.invokeMethod(method, bean, Long.parseLong(value)); } else if (parameterType == Long.class) { ReflectionUtils.invokeMethod(method, bean, Long.valueOf(value)); } else if (parameterType == boolean.class) { ReflectionUtils.invokeMethod(method, bean, Boolean.parseBoolean(value)); } else if (parameterType == Boolean.class) { ReflectionUtils.invokeMethod(method, bean, Boolean.valueOf(value)); } else if (parameterType == double.class) { ReflectionUtils.invokeMethod(method, bean, Double.parseDouble(value)); } else if (parameterType == Double.class) { ReflectionUtils.invokeMethod(method, bean, Double.valueOf(value)); } else if (parameterType == float.class) { ReflectionUtils.invokeMethod(method, bean, Float.parseFloat(value)); } else if (parameterType == Float.class) { ReflectionUtils.invokeMethod(method, bean, Float.valueOf(value)); } else { return false; } return true; } private String getDestContent(String content, String key) throws Exception { if (org.springframework.util.StringUtils.hasText(key)) { Properties properties = PropertiesUtils.convertToProperties(content); return properties.getProperty(key); } else { return content; } } private void handleMethodAnnotation(final Object bean, String beanName, final Method method) { NacosConfigKeysListener keysAnnotation = AnnotationUtils.getAnnotation(method, NacosConfigKeysListener.class); if (keysAnnotation != null) { ReflectionUtils.makeAccessible(method); handleMethodNacosConfigKeysChangeListener(keysAnnotation, beanName, bean, method); return; } NacosConfigListener configAnnotation = AnnotationUtils.getAnnotation(method, NacosConfigListener.class); if (configAnnotation != null) { ReflectionUtils.makeAccessible(method); handleMethodNacosConfigListener(configAnnotation, beanName, bean, method); return; } if (!applicationContext.containsBeanDefinition(beanName)) { return; } BeanDefinition beanDefinition = ((GenericApplicationContext) applicationContext).getBeanDefinition(beanName); if (beanDefinition instanceof AnnotatedBeanDefinition) { MethodMetadata factoryMethodMetadata = (((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata()); if (factoryMethodMetadata != null) { MergedAnnotations annotations = factoryMethodMetadata.getAnnotations(); if (annotations != null && annotations.isPresent(NacosConfig.class)) { MergedAnnotation nacosConfigMergedAnnotation = annotations.get(NacosConfig.class); Map stringObjectMap = nacosConfigMergedAnnotation.asMap(); String dataId = (String) stringObjectMap.get("dataId"); String group = (String) stringObjectMap.get("group"); String key = (String) stringObjectMap.get("key"); String defaultValue = (String) stringObjectMap.get("defaultValue"); handleBeanNacosConfigAnnotation(dataId, group, key, true, beanName, bean, defaultValue); } } } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } private NacosConfigManager getNacosConfigManager() { if (this.nacosConfigManager == null) { nacosConfigManager = this.applicationContext.getBean(NacosConfigManager.class); } return nacosConfigManager; } private static String[] getNullPropertyNames(Object source) { final BeanWrapper src = new BeanWrapperImpl(source); PropertyDescriptor[] pds = src.getPropertyDescriptors(); Set nullPropertyNames = new HashSet<>(); for (PropertyDescriptor pd : pds) { String propertyName = pd.getName(); try { Object propertyValue = src.getPropertyValue(propertyName); if (propertyValue == null) { nullPropertyNames.add(propertyName); } } catch (NotReadablePropertyException e) { //ignore nullPropertyNames.add(propertyName); } } return nullPropertyNames.toArray(new String[0]); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosConfig.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Nacos Config annotation. * * @author shiyiyue1102 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD}) @Documented public @interface NacosConfig { String group(); String dataId(); String key() default ""; String defaultValue() default ""; boolean refreshed() default true; } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosConfigKeysListener.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ConfigChangeEvent */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface NacosConfigKeysListener { String dataId(); String group(); String[] interestedKeys() default {}; String[] interestedKeyPrefixes() default {}; } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosConfigListener.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface NacosConfigListener { String dataId(); String group(); String key() default ""; boolean initNotify() default false; } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosConfigRefreshableListener.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import com.alibaba.nacos.api.config.listener.AbstractListener; public abstract class NacosConfigRefreshableListener extends AbstractListener implements TargetRefreshable { Object target; NacosConfigRefreshableListener(Object target) { this.target = target; } public Object getTarget() { return target; } @Override public void setTarget(Object target) { this.target = target; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/NacosPropertiesKeyListener.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.util.Set; import com.alibaba.nacos.api.config.ConfigChangeEvent; import com.alibaba.nacos.api.config.ConfigChangeItem; import com.alibaba.nacos.common.utils.CollectionUtils; public abstract class NacosPropertiesKeyListener extends AbstractConfigChangeListener { Set interestedKeys; Set interestedKeyPrefixes; NacosPropertiesKeyListener(Object target) { super(target); } NacosPropertiesKeyListener(Object target, Set interestedKeys) { this(target); this.interestedKeys = interestedKeys; } public NacosPropertiesKeyListener(Object target, Set interestedKeys, Set interestedKeyPrefixes) { this(target); this.interestedKeys = interestedKeys; this.interestedKeyPrefixes = interestedKeyPrefixes; } @Override public final void receiveConfigChange(ConfigChangeEvent event) { if (CollectionUtils.isNotEmpty(interestedKeys) || CollectionUtils.isNotEmpty(interestedKeyPrefixes)) { boolean foundInterested = false; for (ConfigChangeItem changeItem : event.getChangeItems()) { if (interestedKeys != null && matchesInterestedKey(changeItem.getKey(), interestedKeys)) { foundInterested = true; break; } if (interestedKeyPrefixes != null) { for (String prefix : interestedKeyPrefixes) { if (changeItem.getKey().startsWith(prefix)) { foundInterested = true; break; } } } } if (!foundInterested) { return; } } configChanged(event); } /** * Check whether the changed key matches any of the interested keys. * * For YAML array/list configurations, the config change parser flattens keys * with array indices (e.g. {@code myList[0]}, {@code myList[1]}), but users * typically register interest in the base key ({@code myList}). This method * handles both exact matches and array-indexed key matches by stripping the * first {@code [index]} suffix and checking against the interested keys. * * @param changedKey the key from the {@link ConfigChangeItem} * @param interestedKeys the set of keys the listener is interested in * @return {@code true} if the changed key matches any interested key */ private boolean matchesInterestedKey(String changedKey, Set interestedKeys) { if (interestedKeys.contains(changedKey)) { return true; } // Handle array-indexed keys: "key[0]" or "key[0].nested" should match "key" int bracketIndex = changedKey.indexOf('['); if (bracketIndex > 0) { String baseKey = changedKey.substring(0, bracketIndex); return interestedKeys.contains(baseKey); } return false; } @Override public String toString() { return "NacosPropertiesKeyListener{" + "interestedKeys=" + interestedKeys + ", interestedKeyPrefixes=" + interestedKeyPrefixes + '}' + "@" + hashCode(); } public abstract void configChanged(ConfigChangeEvent event); } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/ObjectUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.lang.reflect.Type; import org.springframework.util.StringUtils; final class ObjectUtils { private ObjectUtils() { } public static Object convertToObject(String content, Type clazz) { if (!StringUtils.hasText(content)) { return null; } return convertFormJsonContent(content, clazz); } private static Object convertFormJsonContent(String content, Type clazz) { return JsonUtils.toObj(content, clazz); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/PropertiesUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.io.StringReader; import java.util.Map; import java.util.Properties; import com.alibaba.nacos.common.utils.StringUtils; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; final class PropertiesUtils { private PropertiesUtils() { } public static Properties convertToProperties(String content) throws Exception { if (StringUtils.isBlank(content)) { return new Properties(); } try { return convertFormYamlContent(content); } catch (Exception e) { return convertFormPropertiesContent(content); } } private static Properties convertFormPropertiesContent(String content) throws Exception { Properties properties = new Properties(); properties.load(new StringReader(content)); return properties; } private static Properties convertFormYamlContent(String content) { Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); Map yamlMap = yaml.load(content); Properties properties = new Properties(); flattenMap("", yamlMap, properties); return properties; } private static void flattenMap(String prefix, Map map, Properties properties) { for (Map.Entry entry : map.entrySet()) { String key = prefix.isEmpty() ? String.valueOf(entry.getKey()) : prefix + "." + String.valueOf(entry.getKey()); if (entry.getValue() instanceof Map) { flattenMap(key, (Map) entry.getValue(), properties); } else { properties.setProperty(key, entry.getValue().toString()); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/ScaYamlConfigChangeParser.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import com.alibaba.nacos.api.config.ConfigChangeItem; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; import com.alibaba.nacos.client.config.impl.YmlChangeParser; import com.alibaba.nacos.common.utils.StringUtils; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.composer.ComposerException; import org.yaml.snakeyaml.constructor.SafeConstructor; import org.yaml.snakeyaml.error.MarkedYAMLException; public class ScaYamlConfigChangeParser extends YmlChangeParser { private static final String INVALID_CONSTRUCTOR_ERROR_INFO = "could not determine a constructor for the tag"; public ScaYamlConfigChangeParser() { super(); } @Override public Map doParse(String oldContent, String newContent, String type) { Map oldMap = Collections.emptyMap(); Map newMap = Collections.emptyMap(); try { Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); if (StringUtils.isNotBlank(oldContent)) { oldMap = yaml.load(oldContent); oldMap = getFlattenedMap(oldMap); } if (StringUtils.isNotBlank(newContent)) { newMap = yaml.load(newContent); newMap = getFlattenedMap(newMap); } } catch (MarkedYAMLException e) { handleYamlException(e); } return filterChangeData(oldMap, newMap); } private void handleYamlException(MarkedYAMLException e) { if (e.getMessage().startsWith(INVALID_CONSTRUCTOR_ERROR_INFO) || e instanceof ComposerException) { throw new NacosRuntimeException(NacosException.INVALID_PARAM, "AbstractConfigChangeListener only support basic java data type for yaml. If you want to listen " + "key changes for custom classes, please use `Listener` to listener whole yaml configuration and parse it by yourself.", e); } throw e; } private Map getFlattenedMap(Map source) { Map result = new LinkedHashMap<>(128); buildFlattenedMap(result, source, null); return result; } private void buildFlattenedMap(Map result, Map source, String path) { for (Iterator> itr = source.entrySet().iterator(); itr.hasNext(); ) { Map.Entry e = itr.next(); String key = String.valueOf(e.getKey()); if (StringUtils.isNotBlank(path)) { if (key.startsWith("[")) { key = path + key; } else { key = path + '.' + key; } } if (e.getValue() instanceof String) { result.put(key, e.getValue()); } else if (e.getValue() instanceof Map) { @SuppressWarnings("unchecked") Map map = (Map) e.getValue(); buildFlattenedMap(result, map, key); } else if (e.getValue() instanceof Collection) { @SuppressWarnings("unchecked") Collection collection = (Collection) e.getValue(); if (collection.isEmpty()) { result.put(key, ""); } else { int count = 0; for (Object object : collection) { buildFlattenedMap(result, Collections.singletonMap("[" + (count++) + "]", object), key); } } } else { result.put(key, (e.getValue() != null ? e.getValue() : "")); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/annotation/TargetRefreshable.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import com.alibaba.nacos.api.config.listener.Listener; interface TargetRefreshable extends Listener { Object getTarget(); void setTarget(Object target); } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySource.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.client; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.alibaba.cloud.nacos.NacosConfigProperties; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.util.CollectionUtils; /** * @author xiaojing * @author pbting */ public class NacosPropertySource extends MapPropertySource { /** * Nacos Group. */ private final String group; /** * Nacos dataID. */ private final String dataId; /** * timestamp the property get. */ private final Date timestamp; /** * Whether to support dynamic refresh for this Property Source. */ private final boolean isRefreshable; NacosPropertySource(String group, String dataId, Map source, Date timestamp, boolean isRefreshable) { super(String.join(NacosConfigProperties.COMMAS, dataId, group), source); this.group = group; this.dataId = dataId; this.timestamp = timestamp; this.isRefreshable = isRefreshable; } public NacosPropertySource(List> propertySources, String group, String dataId, Date timestamp, boolean isRefreshable) { this(group, dataId, getSourceMap(group, dataId, propertySources), timestamp, isRefreshable); } private static Map getSourceMap(String group, String dataId, List> propertySources) { if (CollectionUtils.isEmpty(propertySources)) { return Collections.emptyMap(); } // If only one, return the internal element, otherwise wrap it. if (propertySources.size() == 1) { PropertySource propertySource = propertySources.get(0); if (propertySource != null && propertySource.getSource() instanceof Map source) { return source; } } Map sourceMap = new LinkedHashMap<>(); List> otherTypePropertySources = new ArrayList<>(); for (PropertySource propertySource : propertySources) { if (propertySource == null) { continue; } if (propertySource instanceof MapPropertySource mapPropertySource) { // If the Nacos configuration file uses "---" to separate property name, // propertySources will be multiple documents, and every document is a // map. // see org.springframework.boot.env.YamlPropertySourceLoader#load Map source = mapPropertySource.getSource(); sourceMap.putAll(source); } else { otherTypePropertySources.add(propertySource); } } // Other property sources which is not instanceof MapPropertySource will be put as // it is, // and the internal elements cannot be directly retrieved, // so the user needs to implement the retrieval logic by himself if (!otherTypePropertySources.isEmpty()) { sourceMap.put(String.join(NacosConfigProperties.COMMAS, dataId, group), otherTypePropertySources); } return sourceMap; } public String getGroup() { return this.group; } public String getDataId() { return dataId; } public Date getTimestamp() { return timestamp; } public boolean isRefreshable() { return isRefreshable; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceBuilder.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.client; import java.util.Collections; import java.util.Date; import java.util.List; import com.alibaba.cloud.nacos.NacosPropertySourceRepository; import com.alibaba.cloud.nacos.parser.NacosDataParserHandler; import com.alibaba.cloud.nacos.refresh.NacosSnapshotConfigManager; import com.alibaba.cloud.nacos.utils.StringUtils; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.PropertySource; /** * @author xiaojing * @author pbting */ public class NacosPropertySourceBuilder { private static final Logger log = LoggerFactory .getLogger(NacosPropertySourceBuilder.class); private ConfigService configService; private long timeout; public NacosPropertySourceBuilder(ConfigService configService, long timeout) { this.configService = configService; this.timeout = timeout; } public long getTimeout() { return timeout; } public void setTimeout(long timeout) { this.timeout = timeout; } public ConfigService getConfigService() { return configService; } public void setConfigService(ConfigService configService) { this.configService = configService; } /** * @param dataId Nacos dataId * @param group Nacos group */ public NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) { List> propertySources = loadNacosData(dataId, group, fileExtension); NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources, group, dataId, new Date(), isRefreshable); NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource); return nacosPropertySource; } private List> loadNacosData(String dataId, String group, String fileExtension) { String data = null; try { String configSnapshot = NacosSnapshotConfigManager.getAndRemoveConfigSnapshot(dataId, group); if (StringUtils.isEmpty(configSnapshot)) { log.debug("get config from nacos, dataId: {}, group: {}", dataId, group); data = configService.getConfig(dataId, group, timeout); } else { log.debug("get config from memory snapshot, dataId: {}, group: {}", dataId, group); data = configSnapshot; } if (StringUtils.isEmpty(data)) { log.warn( "Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]", dataId, group); return Collections.emptyList(); } if (log.isDebugEnabled()) { log.debug(String.format( "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId, group, data)); } return NacosDataParserHandler.getInstance().parseNacosData(dataId, data, fileExtension); } catch (NacosException e) { log.error("get data from Nacos error,dataId:{} ", dataId, e); } catch (Exception e) { log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e); } return Collections.emptyList(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/ConfigPreference.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.configdata; /** * Config preference. *

* When configured profile specific configuration, local config will override the remote, * because the local config is profile specific, it has higher priority. *

* So give remote config a chance to "win", we treat remote config as profile specific, it * should be included after profile specific sibling imports. Eventually, it will override * the local profile specific config. * * @author freeman * @since 2021.0.4.0 */ public enum ConfigPreference { /** * Prefer local configuration. */ LOCAL, /** * Prefer remote configuration. */ REMOTE } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLoadProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.configdata; import java.util.HashMap; import java.util.Map; import java.util.Properties; import com.alibaba.cloud.nacos.NacosConfigProperties; /** * Used for initialization of Nacos ConfigService. */ public class NacosConfigDataLoadProperties extends NacosConfigProperties { private Map config = new HashMap<>(); @Override protected void enrichNacosConfigProperties(Properties nacosConfigProperties) { config.forEach((k, v) -> nacosConfigProperties.putIfAbsent(resolveKey(k), String.valueOf(v))); } Map getConfig() { return config; } void setConfig(Map config) { this.config = config; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLoader.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.configdata; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.cloud.nacos.NacosConfigProperties; import com.alibaba.cloud.nacos.NacosPropertiesPrefixer; import com.alibaba.cloud.nacos.NacosPropertySourceRepository; import com.alibaba.cloud.nacos.client.NacosPropertySource; import com.alibaba.cloud.nacos.parser.NacosDataParserHandler; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; import org.apache.commons.logging.Log; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataLoader; import org.springframework.boot.context.config.ConfigDataLoaderContext; import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.PropertySource; import static com.alibaba.cloud.nacos.configdata.ConfigPreference.LOCAL; import static com.alibaba.cloud.nacos.configdata.ConfigPreference.REMOTE; import static com.alibaba.cloud.nacos.configdata.NacosConfigDataResource.NacosItemConfig; import static org.springframework.boot.context.config.ConfigData.Option; /** * Implementation of {@link ConfigDataLoader}. * *

* Load {@link ConfigData} via {@link NacosConfigDataResource} * * @author freeman * @since 2021.0.1.0 */ public class NacosConfigDataLoader implements ConfigDataLoader { private final Log log; public NacosConfigDataLoader(DeferredLogFactory logFactory) { this.log = logFactory.getLog(getClass()); } @Override public ConfigData load(ConfigDataLoaderContext context, NacosConfigDataResource resource) { return doLoad(context, resource); } public ConfigData doLoad(ConfigDataLoaderContext context, NacosConfigDataResource resource) { try { ConfigService configService = getBean(context, NacosConfigManager.class) .getConfigService(); NacosConfigProperties properties = getBean(context, NacosConfigProperties.class); NacosItemConfig config = resource.getConfig(); // pull config from nacos List> propertySources = pullConfig(configService, config.getGroup(), config.getDataId(), config.getSuffix(), properties.getTimeout()); NacosPropertySource propertySource = new NacosPropertySource(propertySources, config.getGroup(), config.getDataId(), new Date(), config.isRefreshEnabled()); NacosPropertySourceRepository.collectNacosPropertySource(propertySource); return new ConfigData(propertySources, getOptions(context, resource)); } catch (Exception e) { log.error("Error getting properties from nacos: " + resource, e); if (!resource.isOptional()) { throw new ConfigDataResourceNotFoundException(resource, e); } } return null; } private Option[] getOptions(ConfigDataLoaderContext context, NacosConfigDataResource resource) { List

* 1.inherit {@link AbstractPropertySourceLoader};
* 2. define the file{@code spring.factories} and append * {@code org.springframework.boot.env.PropertySourceLoader=..};
* 3.the last step validate. *

* Notice the use of {@link NacosByteArrayResource} . * * @author zkz */ public abstract class AbstractPropertySourceLoader implements PropertySourceLoader { /** * symbol: dot. */ static final String DOT = "."; /** * Prevent interference with other loaders.Nacos-specific loader, unless the reload * changes it. * @param name the root name of the property source. If multiple documents are loaded * an additional suffix should be added to the name for each source loaded. * @param resource the resource to load * @return if the resource can be loaded */ protected boolean canLoad(String name, Resource resource) { return resource instanceof NacosByteArrayResource; } /** * Load the resource into one or more property sources. Implementations may either * return a list containing a single source, or in the case of a multi-document format * such as yaml a source for each document in the resource. * @param name the root name of the property source. If multiple documents are loaded * an additional suffix should be added to the name for each source loaded. * @param resource the resource to load * @return a list property sources * @throws IOException if the source cannot be loaded */ @Override public List> load(String name, Resource resource) throws IOException { if (!canLoad(name, resource)) { return Collections.emptyList(); } return this.doLoad(name, resource); } /** * Load the resource into one or more property sources. Implementations may either * return a list containing a single source, or in the case of a multi-document format * such as yaml a source for each document in the resource. * @param name the root name of the property source. If multiple documents are loaded * an additional suffix should be added to the name for each source loaded. * @param resource the resource to load * @return a list property sources * @throws IOException if the source cannot be loaded */ protected abstract List> doLoad(String name, Resource resource) throws IOException; protected void flattenedMap(Map result, Map dataMap, String parentKey) { if (dataMap == null || dataMap.isEmpty()) { return; } Set> entries = dataMap.entrySet(); for (Iterator> iterator = entries.iterator(); iterator .hasNext();) { Map.Entry entry = iterator.next(); String key = entry.getKey(); Object value = entry.getValue(); String fullKey = StringUtils.isEmpty(parentKey) ? key : key.startsWith("[") ? parentKey.concat(key) : parentKey.concat(DOT).concat(key); if (value instanceof Map map) { flattenedMap(result, map, fullKey); continue; } else if (value instanceof Collection collection) { int count = 0; for (Object object : collection) { flattenedMap(result, Collections.singletonMap("[" + (count++) + "]", object), fullKey); } continue; } result.put(fullKey, value); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosByteArrayResource.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.parser; import org.springframework.core.io.ByteArrayResource; /** * Nacos-specific resource. * * @author zkz */ public class NacosByteArrayResource extends ByteArrayResource { private String filename; /** * Create a new {@code ByteArrayResource}. * @param byteArray the byte array to wrap */ public NacosByteArrayResource(byte[] byteArray) { super(byteArray); } /** * Create a new {@code ByteArrayResource} with a description. * @param byteArray the byte array to wrap * @param description where the byte array comes from */ public NacosByteArrayResource(byte[] byteArray, String description) { super(byteArray, description); } public void setFilename(String filename) { this.filename = filename; } /** * This implementation always returns {@code null}, assuming that this resource type * does not have a filename. */ @Override public String getFilename() { return null == this.filename ? this.getDescription() : this.filename; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataParserHandler.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.parser; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import com.alibaba.cloud.nacos.utils.NacosConfigUtils; import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.boot.env.PropertiesPropertySourceLoader; import org.springframework.boot.env.PropertySourceLoader; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import static com.alibaba.cloud.nacos.parser.AbstractPropertySourceLoader.DOT; /** * @author zkz */ public final class NacosDataParserHandler { /** * default extension. */ private static final String DEFAULT_EXTENSION = "properties"; private static List propertySourceLoaders; private NacosDataParserHandler() { propertySourceLoaders = SpringFactoriesLoader .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); } /** * Parsing nacos configuration content. * @param configName name of nacos-config * @param configValue value from nacos-config * @param extension identifies the type of configValue * @return result of Map * @throws IOException thrown if there is a problem parsing config. */ public List> parseNacosData(String configName, String configValue, String extension) throws IOException { if (!StringUtils.hasLength(configValue)) { return Collections.emptyList(); } if (!StringUtils.hasLength(extension)) { extension = this.getFileExtension(configName); } for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { if (!canLoadFileExtension(propertySourceLoader, extension)) { continue; } NacosByteArrayResource nacosByteArrayResource; if (propertySourceLoader instanceof PropertiesPropertySourceLoader) { // PropertiesPropertySourceLoader internal is to use the ISO_8859_1, // the Chinese will be garbled, needs to transform into unicode. nacosByteArrayResource = new NacosByteArrayResource( NacosConfigUtils.selectiveConvertUnicode(configValue).getBytes(), configName); } else { nacosByteArrayResource = new NacosByteArrayResource( configValue.getBytes(), configName); } nacosByteArrayResource.setFilename(getFileName(configName, extension)); List> propertySourceList = propertySourceLoader .load(configName, nacosByteArrayResource); if (CollectionUtils.isEmpty(propertySourceList)) { return Collections.emptyList(); } return propertySourceList.stream().filter(Objects::nonNull) .map(propertySource -> { if (propertySource instanceof EnumerablePropertySource enumerablePropertySource) { String[] propertyNames = enumerablePropertySource .getPropertyNames(); if (propertyNames != null && propertyNames.length > 0) { Map map = new LinkedHashMap<>(); Arrays.stream(propertyNames).forEach(name -> { map.put(name, propertySource.getProperty(name)); }); return new OriginTrackedMapPropertySource( propertySource.getName(), map, true); } } return propertySource; }).collect(Collectors.toList()); } return Collections.emptyList(); } /** * check the current extension can be processed. * @param loader the propertySourceLoader * @param extension file extension * @return if can match extension */ private boolean canLoadFileExtension(PropertySourceLoader loader, String extension) { return Arrays.stream(loader.getFileExtensions()) .anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(extension, fileExtension)); } /** * @param name filename * @return file extension, default {@code DEFAULT_EXTENSION} if don't get */ public String getFileExtension(String name) { if (!StringUtils.hasLength(name)) { return DEFAULT_EXTENSION; } int idx = name.lastIndexOf(DOT); if (idx > 0 && idx < name.length() - 1) { return name.substring(idx + 1); } return DEFAULT_EXTENSION; } private String getFileName(String name, String extension) { if (!StringUtils.hasLength(extension)) { return name; } if (!StringUtils.hasLength(name)) { return extension; } int idx = name.lastIndexOf(DOT); if (idx > 0 && idx < name.length() - 1) { String ext = name.substring(idx + 1); if (extension.equalsIgnoreCase(ext)) { return name; } } return name + DOT + extension; } public static NacosDataParserHandler getInstance() { return ParserHandler.HANDLER; } private static final class ParserHandler { private static final NacosDataParserHandler HANDLER = new NacosDataParserHandler(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosJsonPropertySourceLoader.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.parser; import java.io.IOException; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; /** * @author zkz */ public class NacosJsonPropertySourceLoader extends AbstractPropertySourceLoader { /** * constant. */ private static final String VALUE = "value"; /** * Returns the file extensions that the loader supports (excluding the '.'). * @return the file extensions */ @Override public String[] getFileExtensions() { return new String[] { "json" }; } /** * Load the resource into one or more property sources. Implementations may either * return a list containing a single source, or in the case of a multi-document format * such as yaml a source for each document in the resource. * @param name the root name of the property source. If multiple documents are loaded * an additional suffix should be added to the name for each source loaded. * @param resource the resource to load * @return a list property sources * @throws IOException if the source cannot be loaded */ @Override protected List> doLoad(String name, Resource resource) throws IOException { Map result = new LinkedHashMap<>(32); ObjectMapper mapper = new ObjectMapper(); // [fix issue #3043] support comment in json config mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); Map nacosDataMap = mapper.readValue(resource.getInputStream(), LinkedHashMap.class); flattenedMap(result, nacosDataMap, null); return Collections.singletonList( new OriginTrackedMapPropertySource(name, this.reloadMap(result), true)); } /** * Reload the key ending in `value` if need. */ protected Map reloadMap(Map map) { if (map == null || map.isEmpty()) { return null; } Map result = new LinkedHashMap<>(map); for (Map.Entry entry : map.entrySet()) { String key = entry.getKey(); if (key.contains(AbstractPropertySourceLoader.DOT)) { int idx = key.lastIndexOf(AbstractPropertySourceLoader.DOT); String suffix = key.substring(idx + 1); if (VALUE.equalsIgnoreCase(suffix)) { result.put(key.substring(0, idx), entry.getValue()); } } } return result; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosXmlPropertySourceLoader.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.parser; import java.io.IOException; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import com.alibaba.cloud.nacos.utils.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.boot.env.PropertiesPropertySourceLoader; import org.springframework.core.Ordered; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; /** * Parsing for XML requires overwriting the default * {@link PropertiesPropertySourceLoader}, because it internally rigorously validates * ({@code DOCTYPE}) THE XML in a way that makes it difficult to customize the * configuration; at finally, make sure it's in the first place. * * @author zkz */ public class NacosXmlPropertySourceLoader extends AbstractPropertySourceLoader implements Ordered { /** * Get the order value of this object. *

* Higher values are interpreted as lower priority. As a consequence, the object with * the lowest value has the highest priority (somewhat analogous to Servlet * {@code load-on-startup} values). *

* Same order values will result in arbitrary sort positions for the affected objects. * @return the order value * @see #HIGHEST_PRECEDENCE * @see #LOWEST_PRECEDENCE */ @Override public int getOrder() { return Integer.MIN_VALUE; } /** * Returns the file extensions that the loader supports (excluding the '.'). * @return the file extensions */ @Override public String[] getFileExtensions() { return new String[] { "xml" }; } /** * Load the resource into one or more property sources. Implementations may either * return a list containing a single source, or in the case of a multi-document format * such as yaml a source for each document in the resource. * @param name the root name of the property source. If multiple documents are loaded * an additional suffix should be added to the name for each source loaded. * @param resource the resource to load * @return a list property sources * @throws IOException if the source cannot be loaded */ @Override protected List> doLoad(String name, Resource resource) throws IOException { Map nacosDataMap = parseXml2Map(resource); return Collections.singletonList( new OriginTrackedMapPropertySource(name, nacosDataMap, true)); } private Map parseXml2Map(Resource resource) throws IOException { Map map = new LinkedHashMap<>(32); try { DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance() .newDocumentBuilder(); Document document = documentBuilder.parse(resource.getInputStream()); if (null == document) { return null; } parseNodeList(document.getChildNodes(), map, ""); } catch (Exception e) { throw new IOException("The xml content parse error.", e.getCause()); } return map; } private void parseNodeList(NodeList nodeList, Map map, String parentKey) { if (nodeList == null || nodeList.getLength() < 1) { return; } parentKey = parentKey == null ? "" : parentKey; for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); String value = node.getNodeValue(); value = value == null ? "" : value.trim(); String name = node.getNodeName(); name = name == null ? "" : name.trim(); if (StringUtils.isEmpty(name)) { continue; } String key = StringUtils.isEmpty(parentKey) ? name : parentKey + AbstractPropertySourceLoader.DOT + name; NamedNodeMap nodeMap = node.getAttributes(); parseNodeAttr(nodeMap, map, key); if (node.getNodeType() == Node.ELEMENT_NODE && node.hasChildNodes()) { parseNodeList(node.getChildNodes(), map, key); continue; } if (value.length() < 1) { continue; } map.put(parentKey, value); } } private void parseNodeAttr(NamedNodeMap nodeMap, Map map, String parentKey) { if (null == nodeMap || nodeMap.getLength() < 1) { return; } for (int i = 0; i < nodeMap.getLength(); i++) { Node node = nodeMap.item(i); if (null == node) { continue; } if (node.getNodeType() == Node.ATTRIBUTE_NODE) { if (StringUtils.isEmpty(node.getNodeName())) { continue; } if (StringUtils.isEmpty(node.getNodeValue())) { continue; } map.put(String.join(AbstractPropertySourceLoader.DOT, parentKey, node.getNodeName()), node.getNodeValue()); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/proxy/druid/NacosDruidConfigFilter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.proxy.druid; import java.io.StringReader; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.cloud.nacos.utils.StringUtils; import com.alibaba.druid.filter.FilterAdapter; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.proxy.jdbc.DataSourceProxy; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.listener.AbstractListener; import com.alibaba.nacos.api.exception.NacosException; public class NacosDruidConfigFilter extends FilterAdapter { private final static String groupMatched = "nacos-datasource"; private final Set idempotentControl = new HashSet<>(); private final String proxyDataId; public NacosDruidConfigFilter(String proxyDataId) { this.proxyDataId = proxyDataId; } @Override public void init(final DataSourceProxy dataSourceProxy) { if (!(dataSourceProxy instanceof DruidDataSource)) { return; } String name = StringUtils.isNotBlank(dataSourceProxy.getName()) ? dataSourceProxy.getName() : String.valueOf(dataSourceProxy.hashCode()); if (idempotentControl.contains(name)) { return; } DruidDataSource druidDataSource = ((DruidDataSource) dataSourceProxy); ConfigService configService = NacosConfigManager.getInstance().getConfigService(); try { String druidProperties = configService.getConfig(proxyDataId, groupMatched, 3000L); Properties propertiesNew = convert(druidProperties); druidDataSource.configFromProperties(propertiesNew); } catch (Exception e) { throw new RuntimeException(e); } //register listeners try { configService.addListener(proxyDataId, groupMatched, new AbstractListener() { @Override public void receiveConfigInfo(String configInfo) { try { Properties propertiesNew = convert(configInfo); //refresh ((DruidDataSource) dataSourceProxy).configFromProperties(propertiesNew); } catch (Exception e) { throw new RuntimeException(e); } } }); } catch (NacosException e) { throw new RuntimeException(e); } idempotentControl.add(name); } private static final String druidPrefix = "spring.datasource.druid."; private static final String datasourcePrefix = "spring.datasource."; private static Properties convert(String config) throws Exception { Properties properties = new Properties(); properties.load(new StringReader(config)); Properties propertiesNew = new Properties(); Iterator> iterator = properties.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); String key = entry.getKey().toString(); String value = entry.getValue().toString(); if (key.startsWith(druidPrefix)) { propertiesNew.put(key.replace(druidPrefix, "druid."), value); } else if (key.startsWith(datasourcePrefix)) { propertiesNew.put(key.replace(datasourcePrefix, "druid."), value); } } return propertiesNew; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/proxy/druid/NacosDruidFilterConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.proxy.druid; import com.alibaba.cloud.nacos.NacosConfigManager; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @Configuration public class NacosDruidFilterConfiguration { @Bean @ConditionalOnProperty(value = "spring.nacos.config.proxy.druid.enabled", havingValue = "true") @ConditionalOnBean(NacosConfigManager.class) public NacosDruidConfigFilter nacosDruidFilter(Environment environment) { String proxyDataId = environment.getProperty("spring.nacos.config.proxy.druid.data-id"); return new NacosDruidConfigFilter(proxyDataId); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/NacosConfigRefreshEvent.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.refresh; import org.springframework.context.ApplicationEvent; public class NacosConfigRefreshEvent extends ApplicationEvent { String dataId; String group; private Object event; private String eventDesc; public NacosConfigRefreshEvent(Object source, Object event, String eventDesc) { super(source); this.event = event; this.eventDesc = eventDesc; } public Object getEvent() { return this.event; } public String getEventDesc() { return this.eventDesc; } public String getDataId() { return dataId; } void setDataId(String dataId) { this.dataId = dataId; } public String getGroup() { return group; } void setGroup(String group) { this.group = group; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/NacosContextRefresher.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.refresh; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.cloud.nacos.NacosConfigProperties; import com.alibaba.cloud.nacos.NacosPropertySourceRepository; import com.alibaba.cloud.nacos.client.NacosPropertySource; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.listener.AbstractSharedListener; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.exception.NacosException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; /** * On application start up, NacosContextRefresher add nacos listeners to all application * level dataIds, when there is a change in the data, listeners will refresh * configurations. * * @author juven.xuxb * @author pbting * @author freeman */ public class NacosContextRefresher implements ApplicationListener, ApplicationContextAware { private final static Logger log = LoggerFactory .getLogger(NacosContextRefresher.class); private static final AtomicLong REFRESH_COUNT = new AtomicLong(0); private final boolean isRefreshEnabled; private final NacosRefreshHistory nacosRefreshHistory; private NacosConfigProperties nacosConfigProperties; private ConfigService configService; private NacosConfigManager configManager; private ApplicationContext applicationContext; private AtomicBoolean ready = new AtomicBoolean(false); private Map listenerMap = new ConcurrentHashMap<>(16); public NacosContextRefresher(NacosConfigManager nacosConfigManager, NacosRefreshHistory refreshHistory) { this.configManager = nacosConfigManager; this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties(); this.nacosRefreshHistory = refreshHistory; this.isRefreshEnabled = this.nacosConfigProperties.isRefreshEnabled(); } public static long getRefreshCount() { return REFRESH_COUNT.get(); } public static void refreshCountIncrement() { REFRESH_COUNT.incrementAndGet(); } @Override public void onApplicationEvent(ApplicationReadyEvent event) { // many Spring context if (this.ready.compareAndSet(false, true)) { this.registerNacosListenersForApplications(); } } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } /** * register Nacos Listeners. */ private void registerNacosListenersForApplications() { if (isRefreshEnabled()) { for (NacosPropertySource propertySource : NacosPropertySourceRepository .getAll()) { if (!propertySource.isRefreshable()) { continue; } String dataId = propertySource.getDataId(); registerNacosListener(propertySource.getGroup(), dataId); } } } private void registerNacosListener(final String groupKey, final String dataKey) { String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey); Listener listener = listenerMap.computeIfAbsent(key, lst -> new AbstractSharedListener() { @Override public void innerReceive(String dataId, String group, String configInfo) { log.info("[Nacos Config] Receive Nacos config change: dataId={}, group={}", dataKey, groupKey); refreshCountIncrement(); nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo); NacosSnapshotConfigManager.putConfigSnapshot(dataId, group, configInfo); NacosConfigRefreshEvent event = new NacosConfigRefreshEvent(this, null, "Refresh Nacos config"); event.setDataId(dataId); event.setGroup(group); applicationContext.publishEvent( event); if (log.isDebugEnabled()) { log.debug(String.format( "Publish Nacos config Refresh Event group=%s,dataId=%s,configInfo=%s", group, dataId, configInfo)); } } }); try { if (configService == null && configManager != null) { configService = configManager.getConfigService(); } configService.addListener(dataKey, groupKey, listener); log.info("[Nacos Config] Listening config: dataId={}, group={}", dataKey, groupKey); } catch (NacosException e) { log.warn(String.format( "register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey, groupKey), e); } } public NacosConfigProperties getNacosConfigProperties() { return nacosConfigProperties; } public NacosContextRefresher setNacosConfigProperties( NacosConfigProperties nacosConfigProperties) { this.nacosConfigProperties = nacosConfigProperties; return this; } public boolean isRefreshEnabled() { if (null == nacosConfigProperties) { return isRefreshEnabled; } // Compatible with older configurations if (nacosConfigProperties.isRefreshEnabled() && !isRefreshEnabled) { return false; } return isRefreshEnabled; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/NacosPropertySourceRefreshListener.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.refresh; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.cloud.nacos.NacosConfigProperties; import com.alibaba.cloud.nacos.client.NacosPropertySource; import com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.properties.ConfigurationPropertiesBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.SmartApplicationListener; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; public class NacosPropertySourceRefreshListener implements BeanPostProcessor, SmartApplicationListener, ApplicationContextAware { private final static Logger log = LoggerFactory .getLogger(NacosPropertySourceRefreshListener.class); private Map beans = new HashMap<>(); private ApplicationContext applicationContext; private AtomicBoolean ready = new AtomicBoolean(false); NacosConfigManager nacosConfigManager; public NacosPropertySourceRefreshListener(NacosConfigManager nacosConfigManager) { this.nacosConfigManager = nacosConfigManager; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName); if (propertiesBean != null) { this.beans.put(beanName, propertiesBean); } return bean; } @Override public boolean supportsEventType(Class eventType) { return ApplicationReadyEvent.class.isAssignableFrom(eventType) || NacosConfigRefreshEvent.class.isAssignableFrom(eventType); } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public void handle(ApplicationReadyEvent event) { this.ready.compareAndSet(false, true); } @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationReadyEvent) { handle((ApplicationReadyEvent) event); } else if (event instanceof NacosConfigRefreshEvent) { handle((NacosConfigRefreshEvent) event); } } public void handle(NacosConfigRefreshEvent event) { if (this.ready.get()) { // don't handle events before app is ready if (!applicationContext.containsBean("nacosConfigSpringCloudRefreshEventListener")) { log.info("Event received " + event.getEventDesc()); NacosPropertySourceBuilder nacosPropertySourceBuilder = new NacosPropertySourceBuilder(nacosConfigManager.getConfigService(), nacosConfigManager.getNacosConfigProperties() .getTimeout()); String sourceName = String.join(NacosConfigProperties.COMMAS, event.dataId, event.group); ConfigurableEnvironment environment = ((ConfigurableApplicationContext) applicationContext).getEnvironment(); MutablePropertySources target = environment.getPropertySources(); PropertySource prevpropertySource = target.get(sourceName); if (prevpropertySource instanceof NacosPropertySource) { NacosPropertySource newProperSource = nacosPropertySourceBuilder.build(event.getDataId(), event.getGroup(), "properties", ((NacosPropertySource) prevpropertySource).isRefreshable()); target.replace(sourceName, newProperSource); log.info("Replace Nacos Property Source : " + sourceName); } } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/NacosRefreshHistory.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.refresh; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedList; import java.util.Map; import com.alibaba.cloud.nacos.utils.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class NacosRefreshHistory { private final static Logger log = LoggerFactory.getLogger(NacosRefreshHistory.class); private static final int MAX_SIZE = 20; private final LinkedList records = new LinkedList<>(); private final ThreadLocal DATE_FORMAT = ThreadLocal .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); private MessageDigest md; public NacosRefreshHistory() { try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { log.error("failed to initialize MessageDigest : ", e); } } /** * recommend to use * {@link NacosRefreshHistory#addRefreshRecord(java.lang.String, java.lang.String, java.lang.String)}. * @param dataId dataId * @param md5 md5 */ @Deprecated public void add(String dataId, String md5) { records.addFirst( new Record(DATE_FORMAT.get().format(new Date()), dataId, "", md5, null)); if (records.size() > MAX_SIZE) { records.removeLast(); } } public void addRefreshRecord(String dataId, String group, String data) { records.addFirst(new Record(DATE_FORMAT.get().format(new Date()), dataId, group, md5(data), null)); if (records.size() > MAX_SIZE) { records.removeLast(); } } public LinkedList getRecords() { return records; } private String md5(String data) { if (StringUtils.isEmpty(data)) { return null; } if (null == md) { try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException ignored) { return "unable to get md5"; } } return new BigInteger(1, md.digest(data.getBytes(StandardCharsets.UTF_8))) .toString(16); } static class Record { private final String timestamp; private final String dataId; private final String group; private final String md5; Record(String timestamp, String dataId, String group, String md5, Map last) { this.timestamp = timestamp; this.dataId = dataId; this.group = group; this.md5 = md5; } public String getTimestamp() { return timestamp; } public String getDataId() { return dataId; } public String getGroup() { return group; } public String getMd5() { return md5; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/NacosSnapshotConfigManager.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.refresh; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author: ruansheng * @date: 2024-01-22 */ public final class NacosSnapshotConfigManager { private NacosSnapshotConfigManager() { } private static final Logger log = LoggerFactory .getLogger(NacosSnapshotConfigManager.class); private static final Map CONFIG_INFO_SNAPSHOT_MAP = new ConcurrentHashMap<>( 8); private static final int MAX_SNAPSHOT_COUNT = 100; private static String formatConfigSnapshotKey(String dataId, String group) { return dataId + "@" + group; } public static String getAndRemoveConfigSnapshot(String dataId, String group) { String configInfo = CONFIG_INFO_SNAPSHOT_MAP .get(formatConfigSnapshotKey(dataId, group)); removeConfigSnapshot(dataId, group); return configInfo; } public static void putConfigSnapshot(String dataId, String group, String configInfo) { try { // Theoretically, the capacity limit restriction will never be triggered. // This portion of the code serves as an additional fault tolerance layer. if (CONFIG_INFO_SNAPSHOT_MAP.size() > MAX_SNAPSHOT_COUNT) { Iterator> iterator = CONFIG_INFO_SNAPSHOT_MAP .entrySet().iterator(); iterator.next(); iterator.remove(); } String snapshotKey = formatConfigSnapshotKey(dataId, group); if (configInfo == null) { CONFIG_INFO_SNAPSHOT_MAP.remove(snapshotKey); } else { CONFIG_INFO_SNAPSHOT_MAP.put(snapshotKey, configInfo); } } catch (Exception e) { log.warn("remove nacos config snapshot error", e); } } public static void removeConfigSnapshot(String dataId, String group) { CONFIG_INFO_SNAPSHOT_MAP.remove(formatConfigSnapshotKey(dataId, group)); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/utils/NacosConfigUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.utils; /** * @author zkzlx */ public final class NacosConfigUtils { private NacosConfigUtils() { } /** * Convert Chinese characters to Unicode. * @param configValue value of config * @return new string */ public static String selectiveConvertUnicode(String configValue) { StringBuilder sb = new StringBuilder(); char[] chars = configValue.toCharArray(); for (char aChar : chars) { if (isBaseLetter(aChar)) { sb.append(aChar); } else { sb.append(String.format("\\u%04x", (int) aChar)); } } return sb.toString(); } /** * char is base latin or whitespace? * @param ch a character * @return true or false */ public static boolean isBaseLetter(char ch) { Character.UnicodeBlock ub = Character.UnicodeBlock.of(ch); return ub == Character.UnicodeBlock.BASIC_LATIN || Character.isWhitespace(ch); } /** * char is chinese? * @param c a character * @return true or false */ public static boolean isChinese(char c) { Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/utils/PropertySourcesUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.utils; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertyResolver; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySourcesPropertyResolver; import static java.util.Collections.unmodifiableMap; /** * @author Mercy * @author yuluo * {@link PropertySources} Utilities * {@see PropertySources} */ public final class PropertySourcesUtils { private PropertySourcesUtils() { } /** * Empty String array. */ public static final String[] EMPTY_STRING_ARRAY = {}; /** * Get Sub {@link Properties}. * * @param propertySources {@link PropertySource} Iterable. * @param prefix the prefix of property name. * @return Map * @see Properties */ public static Map getSubProperties(Iterable> propertySources, String prefix) { MutablePropertySources mutablePropertySources = new MutablePropertySources(); for (PropertySource source : propertySources) { mutablePropertySources.addLast(source); } return getSubProperties(mutablePropertySources, prefix); } /** * Get Sub {@link Properties}. * * @param environment {@link ConfigurableEnvironment}. * @param prefix the prefix of property name. * @return Map * @see Properties */ public static Map getSubProperties(ConfigurableEnvironment environment, String prefix) { return getSubProperties(environment.getPropertySources(), environment, prefix); } /** * Normalize the prefix. * * @param prefix the prefix. * @return the prefix. */ public static String normalizePrefix(String prefix) { return prefix.endsWith(".") ? prefix : prefix + "."; } /** * Get prefixed {@link Properties}. * * @param propertySources {@link PropertySources}. * @param prefix the prefix of property name. * @return Map * @see Properties */ public static Map getSubProperties(PropertySources propertySources, String prefix) { return getSubProperties(propertySources, new PropertySourcesPropertyResolver(propertySources), prefix); } /** * Get prefixed {@link Properties}. * * @param propertySources {@link PropertySources}. * @param propertyResolver {@link PropertyResolver} to resolve the placeholder if present. * @param prefix the prefix of property name. * @return Map * @see Properties */ public static Map getSubProperties(PropertySources propertySources, PropertyResolver propertyResolver, String prefix) { Map subProperties = new LinkedHashMap(); String normalizedPrefix = normalizePrefix(prefix); for (PropertySource source : propertySources) { for (String name : getPropertyNames(source)) { if (!subProperties.containsKey(name) && name.startsWith(normalizedPrefix)) { String subName = name.substring(normalizedPrefix.length()); if (!subProperties.containsKey(subName)) { // take first one Object value = source.getProperty(name); if (value instanceof String) { // Resolve placeholder value = propertyResolver.resolvePlaceholders((String) value); } subProperties.put(subName, value); } } } } return unmodifiableMap(subProperties); } /** * Get the property names as the array from the specified {@link PropertySource} instance. * * @param propertySource {@link PropertySource} instance. * @return non-null */ public static String[] getPropertyNames(PropertySource propertySource) { String[] propertyNames = propertySource instanceof EnumerablePropertySource ? ((EnumerablePropertySource) propertySource).getPropertyNames() : null; if (propertyNames == null) { propertyNames = EMPTY_STRING_ARRAY; } return propertyNames; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/utils/StringUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.utils; /** * StringUtils. copy from apache common-lang3. * * @author theonefx */ public final class StringUtils { /** * The empty String {@code ""}. * * @since 2.0 */ public static final String EMPTY = ""; /** * Represents a failed index search. * @since 2.1 */ public static final int INDEX_NOT_FOUND = -1; private StringUtils() { } /** *

* Checks if a CharSequence is empty ("") or null. *

* *
	 * StringUtils.isEmpty(null)      = true
	 * StringUtils.isEmpty("")        = true
	 * StringUtils.isEmpty(" ")       = false
	 * StringUtils.isEmpty("bob")     = false
	 * StringUtils.isEmpty("  bob  ") = false
	 * 
* *

* NOTE: This method changed in Lang version 2.0. It no longer trims the CharSequence. * That functionality is available in isBlank(). *

* @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is empty or null * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence) */ public static boolean isEmpty(final CharSequence cs) { return cs == null || cs.length() == 0; } /** *

* Checks if a CharSequence is not empty ("") and not null. *

* *
	 * StringUtils.isNotEmpty(null)      = false
	 * StringUtils.isNotEmpty("")        = false
	 * StringUtils.isNotEmpty(" ")       = true
	 * StringUtils.isNotEmpty("bob")     = true
	 * StringUtils.isNotEmpty("  bob  ") = true
	 * 
* @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is not empty and not null * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence) */ public static boolean isNotEmpty(final CharSequence cs) { return !isEmpty(cs); } /** *

* Checks if a CharSequence is whitespace, empty ("") or null. *

* *
	 * StringUtils.isBlank(null)      = true
	 * StringUtils.isBlank("")        = true
	 * StringUtils.isBlank(" ")       = true
	 * StringUtils.isBlank("bob")     = false
	 * StringUtils.isBlank("  bob  ") = false
	 * 
* @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is null, empty or whitespace */ public static boolean isBlank(final CharSequence cs) { if (cs == null || cs.length() == 0) { return true; } int strLen = cs.length(); for (int i = 0; i < strLen; i++) { if (!Character.isWhitespace(cs.charAt(i))) { return false; } } return true; } /** *

* Checks if a CharSequence is not empty (""), not null and not whitespace only. *

* *

* Whitespace is defined by {@link Character#isWhitespace(char)}. *

* *
	 * StringUtils.isNotBlank(null)      = false
	 * StringUtils.isNotBlank("")        = false
	 * StringUtils.isNotBlank(" ")       = false
	 * StringUtils.isNotBlank("bob")     = true
	 * StringUtils.isNotBlank("  bob  ") = true
	 * 
* @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is not empty and not null and not * whitespace only * @since 2.0 * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) */ public static boolean isNotBlank(final CharSequence cs) { return !isBlank(cs); } // Trim // ----------------------------------------------------------------------- /** *

* Removes control characters (char <= 32) from both ends of this String, handling * {@code null} by returning {@code null}. *

* *

* The String is trimmed using {@link String#trim()}. Trim removes start and end * characters <= 32. *

* *
	 * StringUtils.trim(null)          = null
	 * StringUtils.trim("")            = ""
	 * StringUtils.trim("     ")       = ""
	 * StringUtils.trim("abc")         = "abc"
	 * StringUtils.trim("    abc    ") = "abc"
	 * 
* @param str the String to be trimmed, may be null * @return the trimmed string, {@code null} if null String input */ public static String trim(final String str) { return str == null ? null : str.trim(); } // Equals // ----------------------------------------------------------------------- /** *

* Compares two CharSequences, returning {@code true} if they represent equal * sequences of characters. *

* *

* {@code null}s are handled without exceptions. Two {@code null} references are * considered to be equal. The comparison is case sensitive. *

* *
	 * StringUtils.equals(null, null)   = true
	 * StringUtils.equals(null, "abc")  = false
	 * StringUtils.equals("abc", null)  = false
	 * StringUtils.equals("abc", "abc") = true
	 * StringUtils.equals("abc", "ABC") = false
	 * 
* @param cs1 the first CharSequence, may be {@code null} * @param cs2 the second CharSequence, may be {@code null} * @return {@code true} if the CharSequences are equal (case-sensitive), or both * {@code null} * @see Object#equals(Object) */ public static boolean equals(final CharSequence cs1, final CharSequence cs2) { if (cs1 == cs2) { return true; } if (cs1 == null || cs2 == null) { return false; } if (cs1 instanceof String && cs2 instanceof String) { return cs1.equals(cs2); } return StringUtils.regionMatches(cs1, false, 0, cs2, 0, Math.max(cs1.length(), cs2.length())); } /** * Green implementation of regionMatches. * @param cs the {@code CharSequence} to be processed * @param ignoreCase whether or not to be case insensitive * @param thisStart the index to start on the {@code cs} CharSequence * @param substring the {@code CharSequence} to be looked for * @param start the index to start on the {@code substring} CharSequence * @param length character length of the region * @return whether the region matched */ public static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, final CharSequence substring, final int start, final int length) { if (cs instanceof String && substring instanceof String) { return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length); } int index1 = thisStart; int index2 = start; int tmpLen = length; while (tmpLen-- > 0) { final char c1 = cs.charAt(index1++); final char c2 = substring.charAt(index2++); if (c1 == c2) { continue; } if (!ignoreCase) { return false; } // The same check as in String.regionMatches(): if (Character.toUpperCase(c1) != Character.toUpperCase(c2) && Character.toLowerCase(c1) != Character.toLowerCase(c2)) { return false; } } return true; } /** *

* Gets the substring after the first occurrence of a separator. The separator is not * returned. *

* *

* A null string input will return null. An empty ("") * string input will return the empty string. A null separator will * return the empty string if the input string is not null. *

* *

* If nothing is found, the empty string is returned. *

* *
	 * StringUtils.substringAfter(null, *)      = null
	 * StringUtils.substringAfter("", *)        = ""
	 * StringUtils.substringAfter(*, null)      = ""
	 * StringUtils.substringAfter("abc", "a")   = "bc"
	 * StringUtils.substringAfter("abcba", "b") = "cba"
	 * StringUtils.substringAfter("abc", "c")   = ""
	 * StringUtils.substringAfter("abc", "d")   = ""
	 * StringUtils.substringAfter("abc", "")    = "abc"
	 * 
* @param str the String to get a substring from, may be null * @param separator the String to search for, may be null * @return the substring after the first occurrence of the separator, * null if null String input * @since 2.0 */ public static String substringAfter(String str, String separator) { if (isEmpty(str)) { return str; } if (separator == null) { return EMPTY; } int pos = str.indexOf(separator); if (pos == INDEX_NOT_FOUND) { return EMPTY; } return str.substring(pos + separator.length()); } // Substring between // ----------------------------------------------------------------------- /** *

* Gets the String that is nested in between two instances of the same String. *

* *

* A null input String returns null. A null tag * returns null. *

* *
	 * StringUtils.substringBetween(null, *)            = null
	 * StringUtils.substringBetween("", "")             = ""
	 * StringUtils.substringBetween("", "tag")          = null
	 * StringUtils.substringBetween("tagabctag", null)  = null
	 * StringUtils.substringBetween("tagabctag", "")    = ""
	 * StringUtils.substringBetween("tagabctag", "tag") = "abc"
	 * 
* @param str the String containing the substring, may be null * @param tag the String before and after the substring, may be null * @return the substring, null if no match * @since 2.0 */ public static String substringBetween(String str, String tag) { return substringBetween(str, tag, tag); } /** *

* Gets the String that is nested in between two Strings. Only the first match is * returned. *

* *

* A null input String returns null. A null * open/close returns null (no match). An empty ("") open and close * returns an empty string. *

* *
	 * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
	 * StringUtils.substringBetween(null, *, *)          = null
	 * StringUtils.substringBetween(*, null, *)          = null
	 * StringUtils.substringBetween(*, *, null)          = null
	 * StringUtils.substringBetween("", "", "")          = ""
	 * StringUtils.substringBetween("", "", "]")         = null
	 * StringUtils.substringBetween("", "[", "]")        = null
	 * StringUtils.substringBetween("yabcz", "", "")     = ""
	 * StringUtils.substringBetween("yabcz", "y", "z")   = "abc"
	 * StringUtils.substringBetween("yabczyabcz", "y", "z")   = "abc"
	 * 
* @param str the String containing the substring, may be null * @param open the String before the substring, may be null * @param close the String after the substring, may be null * @return the substring, null if no match * @since 2.0 */ public static String substringBetween(String str, String open, String close) { if (str == null || open == null || close == null) { return null; } int start = str.indexOf(open); if (start != INDEX_NOT_FOUND) { int end = str.indexOf(close, start + open.length()); if (end != INDEX_NOT_FOUND) { return str.substring(start + open.length(), end); } } return null; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json ================================================ { "properties": [ { "name": "spring.nacos.server-addr", "type": "java.lang.String", "defaultValue": "127.0.0.1:8848", "description": "nacos server address." }, { "name": "spring.nacos.config.server-addr", "type": "java.lang.String", "defaultValue": "${spring.nacos.server-addr}", "description": "nacos config server address." }, { "name": "spring.nacos.config.encode", "type": "java.lang.String", "defaultValue": "UTF-8", "description": "default encode for nacos config content." }, { "name": "spring.nacos.config.prefix", "type": "java.lang.String", "defaultValue": "${spring.application.name}", "description": "the prefix of dataId, nacos config data meta info. dataId = prefix + '-' + ${spring.active.profile} + `.` + ${spring.cloud.nacos.config.file-extension}." }, { "name": "spring.nacos.config.file-extension", "type": "java.lang.String", "defaultValue": "properties", "description": "the suffix of nacos config dataId, also the file extension of config content, only support properties now." }, { "name": "spring.nacos.config.shared-dataids", "type": "java.lang.String", "description": "the dataids for configurable multiple shared configurations , multiple separated by commas ." }, { "name": "spring.nacos.config.shared-configs", "type": "java.util.List", "description": "a set of shared configurations .e.g: spring.cloud.nacos.config.shared-configs[0]=xxx ." }, { "name": "spring.nacos.config.refreshable-dataids", "type": "java.lang.String", "description": "refreshable dataids , multiple separated by commas .Not providing support,the need to refresh is specified by the respective refresh configuration." }, { "name": "spring.nacos.config.ext-config", "type": "java.util.List", "description": "a set of extended configurations ." }, { "name": "spring.nacos.config.extension-configs", "type": "java.util.List", "description": "a set of extensional configurations .e.g: spring.cloud.nacos.config.extension-configs[0]=xxx ." }, { "name": "spring.nacos.config.refresh-enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "the master switch for refresh configuration, it default opened(true)." }, { "name": "spring.nacos.config.enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "enable nacos config or not." }, { "name": "spring.nacos.config.username", "type": "java.lang.String", "defaultValue": "${spring.nacos.username}", "description": "nacos config service's userName to authenticate." }, { "name": "spring.nacos.config.import-check.enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "Whether to enable import-check." }, { "name": "spring.nacos.config.password", "type": "java.lang.String", "defaultValue": "${spring.nacos.password}", "description": "nacos config service's password to authenticate." }, { "name": "spring.nacos.username", "type": "java.lang.String", "description": "nacos userName to authenticate." }, { "name": "spring.cloud.nacos.password", "type": "java.lang.String", "description": "nacos password to authenticate." }, { "name": "spring.nacos.config.preference", "type": "com.alibaba.cloud.nacos.configdata.ConfigPreference", "defaultValue": "local", "description": "Config preference." }, { "name": "spring.nacos.config.health-indicator.enabled", "type": "java.lang.Boolean", "defaultValue": false, "description": "whether to enable nacos-config health indicator." } ] } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/resources/META-INF/native-image/reflect-config.json ================================================ [ { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.ability.ClientAbilities", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "getConfigAbility", "parameterTypes": [] }, { "name": "getNamingAbility", "parameterTypes": [] }, { "name": "getRemoteAbility", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.config.ability.ClientConfigAbility", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "isSupportRemoteMetrics", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "name": "com.alibaba.nacos.api.config.remote.request.AbstractConfigRequest", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.config.remote.request.AbstractConfigRequest", "methods": [ { "name": "getModule", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.gson.Gson" }, "name": "com.alibaba.nacos.api.config.remote.request.AbstractConfigRequest", "allDeclaredFields": true }, { "condition": { "typeReachable": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener" }, "name": "com.alibaba.nacos.api.config.remote.request.AbstractConfigRequest", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "name": "com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "getConfigListenContexts", "parameterTypes": [] }, { "name": "isListen", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.gson.internal.bind.CollectionTypeAdapterFactory" }, "name": "com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest$ConfigListenContext", "allDeclaredFields": true, "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.SerializerProvider" }, "name": "com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest$ConfigListenContext", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer" }, "name": "com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest$ConfigListenContext", "methods": [ { "name": "getDataId", "parameterTypes": [] }, { "name": "getGroup", "parameterTypes": [] }, { "name": "getMd5", "parameterTypes": [] }, { "name": "getTenant", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.config.remote.request.ConfigChangeNotifyRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setDataId", "parameterTypes": [ "java.lang.String" ] }, { "name": "setGroup", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "name": "com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest", "allDeclaredFields": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "getDataId", "parameterTypes": [] }, { "name": "getGroup", "parameterTypes": [] }, { "name": "getTag", "parameterTypes": [] }, { "name": "getTenant", "parameterTypes": [] }, { "name": "isNotify", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener" }, "name": "com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest", "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "name": "com.alibaba.nacos.api.config.remote.response.ConfigChangeBatchListenResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setChangedConfigs", "parameterTypes": [ "java.util.List" ] } ] }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.DeserializationContext" }, "name": "com.alibaba.nacos.api.config.remote.response.ConfigChangeBatchListenResponse$ConfigContext", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.deser.std.CollectionDeserializer" }, "name": "com.alibaba.nacos.api.config.remote.response.ConfigChangeBatchListenResponse$ConfigContext", "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setDataId", "parameterTypes": [ "java.lang.String" ] }, { "name": "setGroup", "parameterTypes": [ "java.lang.String" ] }, { "name": "setTenant", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcUtils" }, "name": "com.alibaba.nacos.api.config.remote.response.ConfigChangeNotifyResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "name": "com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse", "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setBeta", "parameterTypes": [ "boolean" ] }, { "name": "setContent", "parameterTypes": [ "java.lang.String" ] }, { "name": "setContentType", "parameterTypes": [ "java.lang.String" ] }, { "name": "setLastModified", "parameterTypes": [ "long" ] }, { "name": "setMd5", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener" }, "name": "com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.api.grpc.auto.Metadata" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata", "methods": [ { "name": "getClientIp", "parameterTypes": [] }, { "name": "getClientIpBytes", "parameterTypes": [] }, { "name": "getType", "parameterTypes": [] }, { "name": "getTypeBytes", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$MapFieldAccessor" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata", "methods": [ { "name": "getDefaultInstance", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularMessageFieldAccessor" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata", "methods": [ { "name": "newBuilder", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.TextFormat$Printer" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata", "methods": [ { "name": "getClientIp", "parameterTypes": [] }, { "name": "getType", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.api.grpc.auto.Metadata" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata$Builder", "methods": [ { "name": "clearClientIp", "parameterTypes": [] }, { "name": "clearType", "parameterTypes": [] }, { "name": "getClientIp", "parameterTypes": [] }, { "name": "getClientIpBytes", "parameterTypes": [] }, { "name": "getType", "parameterTypes": [] }, { "name": "getTypeBytes", "parameterTypes": [] }, { "name": "setClientIp", "parameterTypes": [ "java.lang.String" ] }, { "name": "setClientIpBytes", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.ByteString" ] }, { "name": "setType", "parameterTypes": [ "java.lang.String" ] }, { "name": "setTypeBytes", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.ByteString" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularFieldAccessor$ReflectionInvoker" }, "name": "com.alibaba.nacos.api.grpc.auto.Payload", "methods": [ { "name": "hasBody", "parameterTypes": [] }, { "name": "hasMetadata", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularMessageFieldAccessor" }, "name": "com.alibaba.nacos.api.grpc.auto.Payload", "methods": [ { "name": "getBody", "parameterTypes": [] }, { "name": "getMetadata", "parameterTypes": [] }, { "name": "hasBody", "parameterTypes": [] }, { "name": "hasMetadata", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.TextFormat$Printer" }, "name": "com.alibaba.nacos.api.grpc.auto.Payload", "methods": [ { "name": "getBody", "parameterTypes": [] }, { "name": "getMetadata", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularMessageFieldAccessor" }, "name": "com.alibaba.nacos.api.grpc.auto.Payload$Builder", "methods": [ { "name": "clearBody", "parameterTypes": [] }, { "name": "clearMetadata", "parameterTypes": [] }, { "name": "getBody", "parameterTypes": [] }, { "name": "getBodyBuilder", "parameterTypes": [] }, { "name": "getMetadata", "parameterTypes": [] }, { "name": "getMetadataBuilder", "parameterTypes": [] }, { "name": "hasBody", "parameterTypes": [] }, { "name": "hasMetadata", "parameterTypes": [] }, { "name": "setBody", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.Any" ] }, { "name": "setMetadata", "parameterTypes": [ "com.alibaba.nacos.api.grpc.auto.Metadata" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.naming.ability.ClientNamingAbility", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "isSupportDeltaPush", "parameterTypes": [] }, { "name": "isSupportRemoteMetric", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcUtils" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.DeserializationContext" }, "name": "com.alibaba.nacos.api.remote.Payload" }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.ObjectMapper" }, "name": "com.alibaba.nacos.api.remote.Payload" }, { "condition": { "typeReachable": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.ability.ClientRemoteAbility", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "isSupportRemoteConnection", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.request.ConnectionSetupRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "getAbilities", "parameterTypes": [] }, { "name": "getClientVersion", "parameterTypes": [] }, { "name": "getLabels", "parameterTypes": [] }, { "name": "getTenant", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.request.HealthCheckRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.request.InternalRequest", "queryAllDeclaredMethods": true, "methods": [ { "name": "getModule", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.request.InternalRequest", "queryAllDeclaredMethods": true, "methods": [ { "name": "getModule", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.request.InternalRequest", "queryAllDeclaredMethods": true, "methods": [ { "name": "getModule", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "name": "com.alibaba.nacos.api.remote.request.Request", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.request.Request", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.request.Request", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.remote.request.Request", "queryAllDeclaredMethods": true, "methods": [ { "name": "setRequestId", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.request.Request", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcUtils" }, "name": "com.alibaba.nacos.api.remote.request.Request", "methods": [ { "name": "getHeaders", "parameterTypes": [] }, { "name": "getRequestId", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.gson.Gson" }, "name": "com.alibaba.nacos.api.remote.request.Request", "allDeclaredFields": true }, { "condition": { "typeReachable": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener" }, "name": "com.alibaba.nacos.api.remote.request.Request", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.request.ServerCheckRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.remote.request.ServerRequest", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.response.HealthCheckResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "name": "com.alibaba.nacos.api.remote.response.Response", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.response.Response", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.response.Response", "queryAllDeclaredMethods": true, "methods": [ { "name": "setErrorCode", "parameterTypes": [ "int" ] }, { "name": "setResultCode", "parameterTypes": [ "int" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.response.Response", "methods": [ { "name": "setErrorCode", "parameterTypes": [ "int" ] }, { "name": "setResultCode", "parameterTypes": [ "int" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcUtils" }, "name": "com.alibaba.nacos.api.remote.response.Response", "queryAllDeclaredMethods": true, "methods": [ { "name": "getErrorCode", "parameterTypes": [] }, { "name": "getMessage", "parameterTypes": [] }, { "name": "getRequestId", "parameterTypes": [] }, { "name": "getResultCode", "parameterTypes": [] }, { "name": "isSuccess", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener" }, "name": "com.alibaba.nacos.api.remote.response.Response", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.response.ServerCheckResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setConnectionId", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.api.config.ConfigFactory" }, "name": "com.alibaba.nacos.client.config.NacosConfigService", "methods": [ { "name": "", "parameterTypes": [ "java.util.Properties" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.spi.NacosServiceLoader" }, "name": "com.alibaba.nacos.client.logging.logback.NacosLogbackConfiguratorAdapterV1", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.ObjectMapper" }, "name": "com.alibaba.nacos.common.remote.TlsConfig", "queryAllDeclaredMethods": true, "methods": [ { "name": "getCertChainFile", "parameterTypes": [] }, { "name": "getCertPrivateKey", "parameterTypes": [] }, { "name": "getCertPrivateKeyPassword", "parameterTypes": [] }, { "name": "getCiphers", "parameterTypes": [] }, { "name": "getEnableTls", "parameterTypes": [] }, { "name": "getMutualAuthEnable", "parameterTypes": [] }, { "name": "getProtocols", "parameterTypes": [] }, { "name": "getSslProvider", "parameterTypes": [] }, { "name": "getTrustAll", "parameterTypes": [] }, { "name": "getTrustCollectionCertFile", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.ObjectMapper" }, "name": "com.alibaba.nacos.common.remote.client.RpcClientTlsConfig", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper" }, "name": "com.alibaba.nacos.shaded.com.google.common.util.concurrent.AbstractFuture", "fields": [ { "name": "listeners" }, { "name": "value" }, { "name": "waiters" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper" }, "name": "com.alibaba.nacos.shaded.com.google.common.util.concurrent.AbstractFuture$Waiter", "fields": [ { "name": "next" }, { "name": "thread" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.Any" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.Any", "methods": [ { "name": "getTypeUrl", "parameterTypes": [] }, { "name": "getTypeUrlBytes", "parameterTypes": [] }, { "name": "getValue", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularMessageFieldAccessor" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.Any", "methods": [ { "name": "newBuilder", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.TextFormat$Printer" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.Any", "methods": [ { "name": "getTypeUrl", "parameterTypes": [] }, { "name": "getValue", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.Any" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.Any$Builder", "methods": [ { "name": "clearTypeUrl", "parameterTypes": [] }, { "name": "clearValue", "parameterTypes": [] }, { "name": "getTypeUrl", "parameterTypes": [] }, { "name": "getTypeUrlBytes", "parameterTypes": [] }, { "name": "getValue", "parameterTypes": [] }, { "name": "setTypeUrl", "parameterTypes": [ "java.lang.String" ] }, { "name": "setTypeUrlBytes", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.ByteString" ] }, { "name": "setValue", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.ByteString" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.ExtensionRegistryFactory" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.ExtensionRegistry", "methods": [ { "name": "getEmptyRegistry", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.NameResolverRegistry" }, "name": "com.alibaba.nacos.shaded.io.grpc.internal.DnsNameResolverProvider" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.LoadBalancerRegistry" }, "name": "com.alibaba.nacos.shaded.io.grpc.internal.PickFirstLoadBalancerProvider" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiators$GrpcNegotiationHandler" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.AbstractNettyHandler", "methods": [ { "name": "channelActive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "exceptionCaught", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Throwable" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiators$GrpcNegotiationHandler" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler", "methods": [ { "name": "channelInactive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "close", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "write", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiators$ProtocolNegotiationHandler" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiators$GrpcNegotiationHandler", "methods": [ { "name": "userEventTriggered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiators$ProtocolNegotiationHandler", "methods": [ { "name": "userEventTriggered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiators$WaitUntilActiveHandler", "methods": [ { "name": "channelActive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.WriteBufferingAndExceptionHandler", "methods": [ { "name": "channelInactive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelRead", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] }, { "name": "close", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "connect", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.net.SocketAddress", "java.net.SocketAddress", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "exceptionCaught", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Throwable" ] }, { "name": "flush", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "write", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.buffer.AbstractByteBufAllocator" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.buffer.AbstractByteBufAllocator", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.ReferenceCountUpdater" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.buffer.AbstractReferenceCountedByteBuf", "fields": [ { "name": "refCnt" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelDuplexHandler", "methods": [ { "name": "bind", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.net.SocketAddress", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "close", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "connect", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.net.SocketAddress", "java.net.SocketAddress", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "deregister", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "disconnect", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "flush", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "read", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "write", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiators$ProtocolNegotiationHandler" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelInboundHandlerAdapter", "methods": [ { "name": "channelActive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelInactive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelRead", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] }, { "name": "channelReadComplete", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelWritabilityChanged", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "exceptionCaught", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Throwable" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelHandlerContext" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelInboundHandlerAdapter", "methods": [ { "name": "channelRegistered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelUnregistered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelInboundHandlerAdapter", "methods": [ { "name": "channelActive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelInactive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelRead", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] }, { "name": "channelReadComplete", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelWritabilityChanged", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "exceptionCaught", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Throwable" ] }, { "name": "userEventTriggered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext", "methods": [ { "name": "bind", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.net.SocketAddress", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "channelActive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelInactive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelRead", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] }, { "name": "channelReadComplete", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelRegistered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelUnregistered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelWritabilityChanged", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "close", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "connect", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.net.SocketAddress", "java.net.SocketAddress", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "deregister", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "disconnect", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "exceptionCaught", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Throwable" ] }, { "name": "flush", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "read", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "userEventTriggered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] }, { "name": "write", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$TailContext" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$TailContext", "methods": [ { "name": "channelActive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelInactive", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelRead", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] }, { "name": "channelReadComplete", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelRegistered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelUnregistered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelWritabilityChanged", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "exceptionCaught", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Throwable" ] }, { "name": "userEventTriggered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ReflectiveChannelFactory" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.socket.nio.NioSocketChannel", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiators$GrpcNegotiationHandler" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder", "methods": [ { "name": "channelRead", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] }, { "name": "userEventTriggered", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiators$GrpcNegotiationHandler" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler", "methods": [ { "name": "bind", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.net.SocketAddress", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "channelReadComplete", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelWritabilityChanged", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "connect", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "java.net.SocketAddress", "java.net.SocketAddress", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "deregister", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "disconnect", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext", "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelPromise" ] }, { "name": "flush", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] }, { "name": "read", "parameterTypes": [ "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.ReferenceCountUtil" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.ReferenceCountUtil", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueColdProducerFields" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueColdProducerFields", "fields": [ { "name": "producerLimit" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueConsumerFields" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueConsumerFields", "fields": [ { "name": "consumerIndex" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueProducerFields" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueProducerFields", "fields": [ { "name": "producerIndex" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueConsumerIndexField" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueConsumerIndexField", "fields": [ { "name": "consumerIndex" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueProducerIndexField" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueProducerIndexField", "fields": [ { "name": "producerIndex" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueProducerLimitField" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueProducerLimitField", "fields": [ { "name": "producerLimit" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.LoadBalancerRegistry" }, "name": "com.alibaba.nacos.shaded.io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider" }, { "name": "com.alibaba.nacos.api.naming.pojo.Instance", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "getClusterName", "parameterTypes": [] }, { "name": "getInstanceHeartBeatInterval", "parameterTypes": [] }, { "name": "getInstanceHeartBeatTimeOut", "parameterTypes": [] }, { "name": "getInstanceId", "parameterTypes": [] }, { "name": "getInstanceIdGenerator", "parameterTypes": [] }, { "name": "getIp", "parameterTypes": [] }, { "name": "getIpDeleteTimeout", "parameterTypes": [] }, { "name": "getMetadata", "parameterTypes": [] }, { "name": "getPort", "parameterTypes": [] }, { "name": "getServiceName", "parameterTypes": [] }, { "name": "getWeight", "parameterTypes": [] }, { "name": "isEnabled", "parameterTypes": [] }, { "name": "isEphemeral", "parameterTypes": [] }, { "name": "isHealthy", "parameterTypes": [] }, { "name": "setClusterName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setEnabled", "parameterTypes": [ "boolean" ] }, { "name": "setEphemeral", "parameterTypes": [ "boolean" ] }, { "name": "setHealthy", "parameterTypes": [ "boolean" ] }, { "name": "setInstanceId", "parameterTypes": [ "java.lang.String" ] }, { "name": "setIp", "parameterTypes": [ "java.lang.String" ] }, { "name": "setMetadata", "parameterTypes": [ "java.util.Map" ] }, { "name": "setPort", "parameterTypes": [ "int" ] }, { "name": "setServiceName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setWeight", "parameterTypes": [ "double" ] } ] } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/resources/META-INF/native-image/resource-config.json ================================================ { "resources": { "includes": [ { "condition": { "typeReachable": "com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.api.config.filter.IConfigFilter\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.PayloadRegistry" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.api.remote.Payload\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.client.utils.LogUtils" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.common.log.NacosLogbackConfigurator\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.plugin.auth.spi.client.ClientAuthPluginManager" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.plugin.auth.spi.client.AbstractClientAuthService\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.LoadBalancerRegistry" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.shaded.io.grpc.LoadBalancerProvider\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.ManagedChannelRegistry" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.shaded.io.grpc.ManagedChannelProvider\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.NameResolverRegistry" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.shaded.io.grpc.NameResolverProvider\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.common.utils.VersionUtils" }, "pattern": "\\Qnacos-version.txt\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.common.utils.ResourceUtils" }, "pattern": "\\Qnacos_default_setting.properties\\E" } ] }, "bundles": [] } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/resources/META-INF/services/com.alibaba.nacos.api.config.listener.ConfigChangeParser ================================================ com.alibaba.cloud.nacos.annotation.ScaYamlConfigChangeParser ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.nacos.NacosConfigAutoConfiguration com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.diagnostics.FailureAnalyzer=\ com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer org.springframework.boot.env.PropertySourceLoader=\ com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\ com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ com.alibaba.cloud.nacos.configdata.NacosConfigDataLocationResolver # ConfigData Loaders org.springframework.boot.context.config.ConfigDataLoader=\ com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/annotation/NacosPropertiesKeyListenerTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.annotation; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.nacos.api.config.ConfigChangeEvent; import com.alibaba.nacos.api.config.ConfigChangeItem; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link NacosPropertiesKeyListener} array key matching. * * @see #4239 */ class NacosPropertiesKeyListenerTest { @Test void exactKeyMatch() { AtomicBoolean called = new AtomicBoolean(false); NacosPropertiesKeyListener listener = createListener(called, Set.of("myKey"), Set.of()); listener.receiveConfigChange(buildEvent("myKey")); assertThat(called.get()).isTrue(); } @Test void arrayIndexedKeyMatchesBaseKey() { AtomicBoolean called = new AtomicBoolean(false); NacosPropertiesKeyListener listener = createListener(called, Set.of("storeCodes"), Set.of()); listener.receiveConfigChange(buildEvent("storeCodes[3]")); assertThat(called.get()).isTrue(); } @Test void arrayIndexedKeyWithNestedPathMatchesBaseKey() { AtomicBoolean called = new AtomicBoolean(false); NacosPropertiesKeyListener listener = createListener(called, Set.of("config.items"), Set.of()); listener.receiveConfigChange(buildEvent("config.items[0].name")); assertThat(called.get()).isTrue(); } @Test void unrelatedKeyDoesNotMatch() { AtomicBoolean called = new AtomicBoolean(false); NacosPropertiesKeyListener listener = createListener(called, Set.of("myKey"), Set.of()); listener.receiveConfigChange(buildEvent("otherKey[0]")); assertThat(called.get()).isFalse(); } @Test void prefixMatchStillWorks() { AtomicBoolean called = new AtomicBoolean(false); NacosPropertiesKeyListener listener = createListener(called, Set.of(), Set.of("app.config")); listener.receiveConfigChange(buildEvent("app.config.items[0]")); assertThat(called.get()).isTrue(); } @Test void emptyInterestedKeysPassesAllChanges() { AtomicBoolean called = new AtomicBoolean(false); NacosPropertiesKeyListener listener = createListener(called, Set.of(), Set.of()); listener.receiveConfigChange(buildEvent("anyKey")); assertThat(called.get()).isTrue(); } private NacosPropertiesKeyListener createListener(AtomicBoolean called, Set keys, Set prefixes) { Set interestedKeys = keys.isEmpty() ? null : new HashSet<>(keys); Set interestedPrefixes = prefixes.isEmpty() ? null : new HashSet<>(prefixes); return new NacosPropertiesKeyListener(new Object(), interestedKeys, interestedPrefixes) { @Override public void configChanged(ConfigChangeEvent event) { called.set(true); } }; } private ConfigChangeEvent buildEvent(String key) { Map data = new HashMap<>(); ConfigChangeItem item = new ConfigChangeItem(key, null, "newValue"); data.put(key, item); return new ConfigChangeEvent(data); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/test/java/com.alibaba.cloud.nacos/NacosConfigPropertiesTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; /** * NacosConfigProperties Tester. */ public class NacosConfigPropertiesTest { @Test void testSensitivePropertiesMaskedInToString() { NacosConfigProperties properties = new NacosConfigProperties(); properties.setEndpoint("endpoint-test"); properties.setAccessKey("ak-test-123"); properties.setPassword("pwd-test-789"); properties.setSecretKey("sk-test-456"); properties.setServerAddr("127.0.0.1:8848"); String text = properties.toString(); Assertions.assertThat(text).contains("accessKey='******'"); Assertions.assertThat(text).contains("secretKey='******'"); Assertions.assertThat(text).doesNotContain("ak-test-123"); Assertions.assertThat(text).doesNotContain("sk-test-456"); Assertions.assertThat(text).contains("endpoint='endpoint-test'"); Assertions.assertThat(text).contains("serverAddr='127.0.0.1:8848'"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/test/java/com.alibaba.cloud.nacos/configdata/NacosConfigDataLocationResolverTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.configdata; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.alibaba.cloud.nacos.NacosConfigProperties; import com.alibaba.cloud.nacos.NacosPropertiesPrefixer; import com.alibaba.nacos.api.config.ConfigService; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.springframework.boot.bootstrap.ConfigurableBootstrapContext; import org.springframework.boot.context.config.ConfigDataLocation; import org.springframework.boot.context.config.ConfigDataLocationResolverContext; import org.springframework.boot.context.config.Profiles; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.logging.DeferredLogs; import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; /** * NacosConfigDataLocationResolver Tester. * * @author freeman */ public class NacosConfigDataLocationResolverTest { private NacosConfigDataLocationResolver resolver; private ConfigDataLocationResolverContext context = Mockito.mock( ConfigDataLocationResolverContext.class); private MockEnvironment environment; private Binder environmentBinder; private ConfigurableBootstrapContext bootstrapContext = Mockito.mock( ConfigurableBootstrapContext.class); @BeforeEach void setup() { this.environment = new MockEnvironment(); environment.setProperty("spring.config.import", "nacos:test.properties"); this.environmentBinder = Binder.get(this.environment); this.resolver = new NacosConfigDataLocationResolver(new DeferredLogs()); Mockito.when(bootstrapContext.isRegistered(ArgumentMatchers.eq(ConfigService.class))).thenReturn(true); Mockito.when(context.getBinder()).thenReturn(environmentBinder); Mockito.when(context.getBootstrapContext()).thenReturn(bootstrapContext); } @Test void testIsResolvable_givenIncorrectPrefix_thenReturnFalse() { assertThat( this.resolver.isResolvable(this.context, ConfigDataLocation.of("test:"))) .isFalse(); } @Test void testIsResolvable_givenCorrectPrefix_thenReturnTure() { assertThat( this.resolver.isResolvable(this.context, ConfigDataLocation.of("nacos:"))) .isTrue(); assertThat(this.resolver.isResolvable(this.context, ConfigDataLocation.of("optional:nacos:"))).isTrue(); } @Test void testIsResolvable_givenDisable_thenReturnFalse() { String prefix = NacosPropertiesPrefixer.getPrefix(environment); this.environment.setProperty(prefix + ".config.enabled", "false"); assertThat( this.resolver.isResolvable(this.context, ConfigDataLocation.of("nacos:"))) .isFalse(); } @Test void testResolveProfileSpecific_givenNothing_thenReturnDefaultProfile() { NacosConfigDataResource resource = testResolveProfileSpecific(); assertThat(resource.getProfiles()).isEqualTo("default"); } @Test void testStartWithASlashIsOK() { String locationUri = "nacos:/app"; List resources = testUri(locationUri); Assertions.assertThat(resources).hasSize(1); assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app"); locationUri = "nacos:app"; resources = testUri(locationUri); Assertions.assertThat(resources).hasSize(1); assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app"); } @Test void testDataIdMustBeSpecified() { String locationUri = "nacos:"; Assertions.assertThatThrownBy(() -> testUri(locationUri)) .hasMessage("dataId must be specified"); } @Test void testInvalidDataId() { String locationUri = "nacos:test/test.yml"; Assertions.assertThatThrownBy(() -> testUri(locationUri)).hasMessage("illegal dataId"); } @Test void whenCustomizeSuffix_thenOverrideDefault() { String locationUri = "nacos:app"; List resources = testUri(locationUri); Assertions.assertThat(resources).hasSize(1); assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app"); assertThat(resources.get(0).getConfig().getSuffix()).isEqualTo("properties"); environment.setProperty("spring.nacos.config.file-extension", "yml"); locationUri = "nacos:app"; resources = testUri(locationUri); Assertions.assertThat(resources).hasSize(1); assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app"); assertThat(resources.get(0).getConfig().getSuffix()).isEqualTo("yml"); locationUri = "nacos:app.json"; resources = testUri(locationUri); Assertions.assertThat(resources).hasSize(1); assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app.json"); assertThat(resources.get(0).getConfig().getSuffix()).isEqualTo("json"); } @Test void testUrisInLocationShouldOverridesProperty() { environment.setProperty("spring.nacos.config.group", "default"); environment.setProperty("spring.nacos.config.refreshEnabled", "true"); String locationUri = "nacos:test.yml?group=not_default&refreshEnabled=false"; List resources = testUri(locationUri); Assertions.assertThat(resources).hasSize(1); NacosConfigDataResource resource = resources.get(0); assertThat(resource.getConfig().getGroup()).isEqualTo("not_default"); assertThat(resource.getConfig().getSuffix()).isEqualTo("yml"); assertThat(resource.getConfig().isRefreshEnabled()).isFalse(); assertThat(resource.getConfig().getDataId()).isEqualTo("test.yml"); } @Test void testSetCommonPropertiesIsOK() { environment.setProperty("spring.nacos.username", "root"); environment.setProperty("spring.nacos.password", "root"); environment.setProperty("spring.nacos.server-addr", "127.0.0.1:8888"); String locationUri = "nacos:test.yml"; List resources = testUri(locationUri); Assertions.assertThat(resources).hasSize(1); NacosConfigDataResource resource = resources.get(0); assertThat(resource.getProperties().getUsername()).isEqualTo("root"); assertThat(resource.getProperties().getPassword()).isEqualTo("root"); assertThat(resource.getProperties().getServerAddr()).isEqualTo("127.0.0.1:8888"); } @Test void testCommonPropertiesHasLowerPriority() { environment.setProperty("spring.nacos.username", "root"); environment.setProperty("spring.nacos.password", "root"); environment.setProperty("spring.nacos.config.password", "not_root"); environment.setProperty("spring.nacos.server-addr", "127.0.0.1:8888"); environment.setProperty("spring.nacos.config.server-addr", "127.0.0.1:9999"); String locationUri = "nacos:test.yml"; List resources = testUri(locationUri); Assertions.assertThat(resources).hasSize(1); NacosConfigDataResource resource = resources.get(0); assertThat(resource.getProperties().getUsername()).isEqualTo("root"); assertThat(resource.getProperties().getPassword()).isEqualTo("not_root"); assertThat(resource.getProperties().getServerAddr()).isEqualTo("127.0.0.1:9999"); } private List testUri(String locationUri, String... activeProfiles) { Profiles profiles = Mockito.mock(Profiles.class); Mockito.when(profiles.getActive()).thenReturn(Arrays.asList(activeProfiles)); return this.resolver.resolveProfileSpecific(context, ConfigDataLocation.of(locationUri), profiles); } @Test void whenNoneInBootstrapContext_thenCreateNewConfigClientProperties() { Mockito.when(bootstrapContext.isRegistered(ArgumentMatchers.eq(NacosConfigProperties.class))) .thenReturn(false); Mockito.when(bootstrapContext.get(ArgumentMatchers.eq(NacosConfigProperties.class))) .thenReturn(new NacosConfigProperties()); List resources = this.resolver.resolveProfileSpecific( context, ConfigDataLocation.of("nacos:test.yml"), Mockito.mock(Profiles.class)); Assertions.assertThat(resources).hasSize(1); Mockito.verify(bootstrapContext, Mockito.times(0)).get(ArgumentMatchers.eq(NacosConfigProperties.class)); NacosConfigDataResource resource = resources.get(0); assertThat(resource.getConfig().getGroup()).isEqualTo("DEFAULT_GROUP"); assertThat(resource.getConfig().getDataId()).isEqualTo("test.yml"); } private NacosConfigDataResource testResolveProfileSpecific() { return testResolveProfileSpecific("default"); } private NacosConfigDataResource testResolveProfileSpecific(String activeProfile) { Profiles profiles = Mockito.mock(Profiles.class); if (activeProfile != null) { Mockito.when(profiles.getActive()) .thenReturn(Collections.singletonList(activeProfile)); Mockito.when(profiles.getAccepted()) .thenReturn(Collections.singletonList(activeProfile)); } List resources = this.resolver.resolveProfileSpecific( context, ConfigDataLocation.of("nacos:test.yml"), profiles); Assertions.assertThat(resources).hasSize(1); return resources.get(0); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-alibaba-nacos-config/src/test/java/com.alibaba.cloud.nacos/endpoint/NacosConfigEndpointTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.endpoint; import java.util.Map; import com.alibaba.cloud.nacos.NacosConfigAutoConfiguration; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.cloud.nacos.NacosConfigProperties; import com.alibaba.cloud.nacos.refresh.NacosRefreshHistory; import com.alibaba.nacos.client.config.NacosConfigService; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Configuration; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; /** * * @author xiaojing * @author freeman */ @SpringBootTest(classes = NacosConfigEndpointTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.NONE, properties = { "spring.application.name=test-name", "spring.nacos.config.server-addr=127.0.0.1:8848", "spring.nacos.config.file-extension=properties", "spring.config.import[0]=nacos:test-name.properties?refreshEnabled=true"}) public class NacosConfigEndpointTests { @Autowired private NacosConfigProperties properties; @Autowired private NacosRefreshHistory refreshHistory; static { try { NacosConfigService mockedNacosConfigService = Mockito .mock(NacosConfigService.class); Mockito.when(mockedNacosConfigService.getServerStatus()).thenReturn("UP"); ReflectionTestUtils.setField(NacosConfigManager.class, "service", mockedNacosConfigService); } catch (Exception ignore) { ignore.printStackTrace(); } } @Test public void contextLoads() throws Exception { checkoutEndpoint(); checkoutAcmHealthIndicator(); } private void checkoutAcmHealthIndicator() { try { Health.Builder builder = new Health.Builder(); NacosConfigHealthIndicator healthIndicator = new NacosConfigHealthIndicator( properties.configServiceInstance()); healthIndicator.doHealthCheck(builder); Health.Builder builder1 = new Health.Builder(); builder1.up(); Assertions.assertThat(builder.build()).isEqualTo(builder1.build()); } catch (Exception ignore) { } } private void checkoutEndpoint() throws Exception { NacosConfigEndpoint endpoint = new NacosConfigEndpoint(properties, refreshHistory); Map map = endpoint.invoke(); assertThat(properties).isEqualTo(map.get("NacosConfigProperties")); assertThat(refreshHistory.getRecords()).isEqualTo(map.get("RefreshHistory")); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ NacosConfigEndpointAutoConfiguration.class, NacosConfigAutoConfiguration.class}) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-commons/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml 4.0.0 spring-cloud-alibaba-commons Spring Cloud Alibaba Commons org.springframework spring-context true ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-commons/src/main/java/com/alibaba/cloud/commons/context/support/PropertySourcesUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.commons.context.support; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertyResolver; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySourcesPropertyResolver; import static java.util.Collections.unmodifiableMap; /** * @author Mercy * @author yuluo * {@link PropertySources} Utilities * {@see PropertySources} */ public final class PropertySourcesUtils { private PropertySourcesUtils() { } /** * Empty String array. */ public static final String[] EMPTY_STRING_ARRAY = {}; /** * Get Sub {@link Properties}. * * @param propertySources {@link PropertySource} Iterable. * @param prefix the prefix of property name. * @return Map * @see Properties */ public static Map getSubProperties(Iterable> propertySources, String prefix) { MutablePropertySources mutablePropertySources = new MutablePropertySources(); for (PropertySource source : propertySources) { mutablePropertySources.addLast(source); } return getSubProperties(mutablePropertySources, prefix); } /** * Get Sub {@link Properties}. * * @param environment {@link ConfigurableEnvironment}. * @param prefix the prefix of property name. * @return Map * @see Properties */ public static Map getSubProperties(ConfigurableEnvironment environment, String prefix) { return getSubProperties(environment.getPropertySources(), environment, prefix); } /** * Normalize the prefix. * * @param prefix the prefix. * @return the prefix. */ public static String normalizePrefix(String prefix) { return prefix.endsWith(".") ? prefix : prefix + "."; } /** * Get prefixed {@link Properties}. * * @param propertySources {@link PropertySources}. * @param prefix the prefix of property name. * @return Map * @see Properties */ public static Map getSubProperties(PropertySources propertySources, String prefix) { return getSubProperties(propertySources, new PropertySourcesPropertyResolver(propertySources), prefix); } /** * Get prefixed {@link Properties}. * * @param propertySources {@link PropertySources}. * @param propertyResolver {@link PropertyResolver} to resolve the placeholder if present. * @param prefix the prefix of property name. * @return Map * @see Properties */ public static Map getSubProperties(PropertySources propertySources, PropertyResolver propertyResolver, String prefix) { Map subProperties = new LinkedHashMap(); String normalizedPrefix = normalizePrefix(prefix); for (PropertySource source : propertySources) { for (String name : getPropertyNames(source)) { if (!subProperties.containsKey(name) && name.startsWith(normalizedPrefix)) { String subName = name.substring(normalizedPrefix.length()); if (!subProperties.containsKey(subName)) { // take first one Object value = source.getProperty(name); if (value instanceof String) { // Resolve placeholder value = propertyResolver.resolvePlaceholders((String) value); } subProperties.put(subName, value); } } } } return unmodifiableMap(subProperties); } /** * Get the property names as the array from the specified {@link PropertySource} instance. * * @param propertySource {@link PropertySource} instance. * @return non-null */ public static String[] getPropertyNames(PropertySource propertySource) { String[] propertyNames = propertySource instanceof EnumerablePropertySource ? ((EnumerablePropertySource) propertySource).getPropertyNames() : null; if (propertyNames == null) { propertyNames = EMPTY_STRING_ARRAY; } return propertyNames; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-commons/src/main/java/com/alibaba/cloud/commons/io/Charsets.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.commons.io; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.SortedMap; import java.util.TreeMap; /** * The Charsets constants, copy from apache commons-io. * * @author theonefx */ public final class Charsets { private Charsets() { } /** * Constructs a sorted map from canonical charset names to charset objects required of * every implementation of the Java platform. *

* From the Java documentation * * Standard charsets: *

* * @return An immutable, case-insensitive map from canonical charset names to charset * objects. * @see Charset#availableCharsets() */ public static SortedMap requiredCharsets() { // maybe cache? final TreeMap m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); m.put(StandardCharsets.ISO_8859_1.name(), StandardCharsets.ISO_8859_1); m.put(StandardCharsets.US_ASCII.name(), StandardCharsets.US_ASCII); m.put(StandardCharsets.UTF_16.name(), StandardCharsets.UTF_16); m.put(StandardCharsets.UTF_16BE.name(), StandardCharsets.UTF_16BE); m.put(StandardCharsets.UTF_16LE.name(), StandardCharsets.UTF_16LE); m.put(StandardCharsets.UTF_8.name(), StandardCharsets.UTF_8); return Collections.unmodifiableSortedMap(m); } /** * Returns the given Charset or the default Charset if the given Charset is null. * * @param charset A charset or null. * @return the given Charset or the default Charset if the given Charset is null */ public static Charset toCharset(final Charset charset) { return charset == null ? Charset.defaultCharset() : charset; } /** * Returns a Charset for the named charset. If the name is null, return the default * Charset. * * @param charset The name of the requested charset, may be null. * @return a Charset for the named charset * @throws java.nio.charset.UnsupportedCharsetException If the named charset is unavailable */ public static Charset toCharset(final String charset) { return charset == null ? Charset.defaultCharset() : Charset.forName(charset); } /** * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. *

* Every implementation of the Java platform is required to support this character * encoding. *

* * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} * @see Standard * charsets */ @Deprecated public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; /** *

* Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of * the Unicode character set. *

*

* Every implementation of the Java platform is required to support this character * encoding. *

* * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} * @see Standard * charsets */ @Deprecated public static final Charset US_ASCII = StandardCharsets.US_ASCII; /** *

* Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory * initial byte-order mark (either order accepted on input, big-endian used on output) *

*

* Every implementation of the Java platform is required to support this character * encoding. *

* * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} * @see Standard * charsets */ @Deprecated public static final Charset UTF_16 = StandardCharsets.UTF_16; /** *

* Sixteen-bit Unicode Transformation Format, big-endian byte order. *

*

* Every implementation of the Java platform is required to support this character * encoding. *

* * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} * @see Standard * charsets */ @Deprecated public static final Charset UTF_16BE = StandardCharsets.UTF_16BE; /** *

* Sixteen-bit Unicode Transformation Format, little-endian byte order. *

*

* Every implementation of the Java platform is required to support this character * encoding. *

* * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} * @see Standard * charsets */ @Deprecated public static final Charset UTF_16LE = StandardCharsets.UTF_16LE; /** *

* Eight-bit Unicode Transformation Format. *

*

* Every implementation of the Java platform is required to support this character * encoding. *

* * @deprecated Use Java 7's {@link java.nio.charset.StandardCharsets} * @see Standard * charsets */ @Deprecated public static final Charset UTF_8 = StandardCharsets.UTF_8; } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-commons/src/main/java/com/alibaba/cloud/commons/io/FileUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.commons.io; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; /** * FileUtils. copy from apache commons-io. * * @author theonefx */ public final class FileUtils { private FileUtils() { } // ----------------------------------------------------------------------- /** * Opens a {@link java.io.FileInputStream} for the specified file, providing better * error messages than simply calling new FileInputStream(file). *

* At the end of the method either the stream will be successfully opened, or an * exception will have been thrown. *

* An exception is thrown if the file does not exist. An exception is thrown if the * file object exists but is a directory. An exception is thrown if the file exists * but cannot be read. * @param file the file to open for input, must not be {@code null} * @return a new {@link java.io.FileInputStream} for the specified file * @throws java.io.FileNotFoundException if the file does not exist * @throws IOException if the file object is a directory * @throws IOException if the file cannot be read * @since 1.3 */ public static FileInputStream openInputStream(final File file) throws IOException { if (file.exists()) { if (file.isDirectory()) { throw new IOException("File '" + file + "' exists but is a directory"); } if (!file.canRead()) { throw new IOException("File '" + file + "' cannot be read"); } } else { throw new FileNotFoundException("File '" + file + "' does not exist"); } return new FileInputStream(file); } // ----------------------------------------------------------------------- /** * Reads the contents of a file into a String. The file is always closed. * @param file the file to read, must not be {@code null} * @param encoding the encoding to use, {@code null} means platform default * @return the file contents, never {@code null} * @throws IOException in case of an I/O error */ public static String readFileToString(final File file, final Charset encoding) throws IOException { try (InputStream in = openInputStream(file)) { return IOUtils.toString(in, Charsets.toCharset(encoding)); } } /** * Reads the contents of a file into a String. The file is always closed. * @param file the file to read, must not be {@code null} * @param encoding the encoding to use, {@code null} means platform default * @return the file contents, never {@code null} * @throws java.io.IOException in case of an I/O error * @throws java.nio.charset.UnsupportedCharsetException thrown instead of * {@link java.io .UnsupportedEncodingException} in version 2.2 if the encoding is not * supported. */ public static String readFileToString(final File file, final String encoding) throws IOException { return readFileToString(file, Charsets.toCharset(encoding)); } /** * Reads the contents of a file into a String using the default encoding for the VM. * The file is always closed. * @param file the file to read, must not be {@code null} * @return the file contents, never {@code null} * @throws IOException in case of an I/O error * @deprecated 2.5 use {@link #readFileToString(File, String)} instead (and specify * the appropriate encoding) */ @Deprecated public static String readFileToString(final File file) throws IOException { return readFileToString(file, Charset.defaultCharset()); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-commons/src/main/java/com/alibaba/cloud/commons/io/IOUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.commons.io; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.nio.charset.Charset; /** * The IOUtils. copy from apache commons-io. * * @author theonefx */ public final class IOUtils { /** * Represents the end-of-file (or stream). * @since 2.5 (made public) */ public static final int EOF = -1; /** * The default buffer size ({@value}) to use for. * {@link #copyLarge(InputStream, java.io.OutputStream)} and * {@link #copyLarge(Reader, Writer)} */ private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; private IOUtils() { } /** * Gets the contents of an InputStream as a String using the specified * character encoding. *

* This method buffers the input internally, so there is no need to use a * BufferedInputStream. *

* @param input the InputStream to read from * @param encoding the encoding to use, null means platform default * @return the requested String * @throws NullPointerException if the input is null * @throws java.io.IOException if an I/O error occurs * @since 2.3 */ public static String toString(final InputStream input, final Charset encoding) throws IOException { try (StringBuilderWriter sw = new StringBuilderWriter()) { copy(input, sw, encoding); return sw.toString(); } } // copy from Reader // ----------------------------------------------------------------------- /** * Copies chars from a Reader to a Writer. *

* This method buffers the input internally, so there is no need to use a * BufferedReader. *

* Large streams (over 2GB) will return a chars copied value of -1 after * the copy has completed since the correct number of chars cannot be returned as an * int. For large streams use the copyLarge(Reader, Writer) method. * @param input the Reader to read from * @param output the Writer to write to * @return the number of characters copied, or -1 if > Integer.MAX_VALUE * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs * @since 1.1 */ public static int copy(final Reader input, final Writer output) throws IOException { final long count = copyLarge(input, output); if (count > Integer.MAX_VALUE) { return -1; } return (int) count; } /** * Copies bytes from an InputStream to chars on a Writer * using the specified character encoding. *

* This method buffers the input internally, so there is no need to use a * BufferedInputStream. *

* This method uses {@link java.io.InputStreamReader}. * @param input the InputStream to read from * @param output the Writer to write to * @param inputEncoding the encoding to use for the input stream, null means platform * default * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs * @since 2.3 */ public static void copy(final InputStream input, final Writer output, final Charset inputEncoding) throws IOException { final InputStreamReader in = new InputStreamReader(input, Charsets.toCharset(inputEncoding)); copy(in, output); } /** * Copies bytes from an InputStream to an OutputStream using * an internal buffer of the given size. *

* This method buffers the input internally, so there is no need to use a * BufferedInputStream. *

* @param input the InputStream to read from * @param output the OutputStream to write to * @param bufferSize the bufferSize used to copy from the input to the output * @return the number of bytes copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs * @since 2.5 */ public static long copy(final InputStream input, final OutputStream output, final int bufferSize) throws IOException { return copyLarge(input, output, new byte[bufferSize]); } /** * Copies chars from a large (over 2GB) Reader to a Writer. *

* This method buffers the input internally, so there is no need to use a * BufferedReader. *

* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. * @param input the Reader to read from * @param output the Writer to write to * @return the number of characters copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs * @since 1.3 */ public static long copyLarge(final Reader input, final Writer output) throws IOException { return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); } /** * Copies chars from a large (over 2GB) Reader to a Writer. *

* This method uses the provided buffer, so there is no need to use a * BufferedReader. *

* @param input the Reader to read from * @param output the Writer to write to * @param buffer the buffer to be used for the copy * @return the number of characters copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs * @since 2.2 */ public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException { long count = 0; int n; while (EOF != (n = input.read(buffer))) { output.write(buffer, 0, n); count += n; } return count; } /** * Copies bytes from a large (over 2GB) InputStream to an * OutputStream. *

* This method buffers the input internally, so there is no need to use a * BufferedInputStream. *

* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. * @param input the InputStream to read from * @param output the OutputStream to write to * @return the number of bytes copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs * @since 1.3 */ public static long copyLarge(final InputStream input, final OutputStream output) throws IOException { return copy(input, output, DEFAULT_BUFFER_SIZE); } /** * Copies bytes from a large (over 2GB) InputStream to an * OutputStream. *

* This method uses the provided buffer, so there is no need to use a * BufferedInputStream. *

* @param input the InputStream to read from * @param output the OutputStream to write to * @param buffer the buffer to use for the copy * @return the number of bytes copied * @throws NullPointerException if the input or output is null * @throws IOException if an I/O error occurs * @since 2.2 */ public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer) throws IOException { long count = 0; int n; while (EOF != (n = input.read(buffer))) { output.write(buffer, 0, n); count += n; } return count; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-commons/src/main/java/com/alibaba/cloud/commons/io/StringBuilderWriter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.commons.io; import java.io.Serializable; import java.io.Writer; /** * Copy from apache commons-io. * * @author theonefx */ public class StringBuilderWriter extends Writer implements Serializable { private static final long serialVersionUID = -146927496096066153L; private final StringBuilder builder; /** * Constructs a new {@link StringBuilder} instance with default capacity. */ public StringBuilderWriter() { this.builder = new StringBuilder(); } /** * Constructs a new {@link StringBuilder} instance with the specified capacity. * @param capacity The initial capacity of the underlying {@link StringBuilder} */ public StringBuilderWriter(final int capacity) { this.builder = new StringBuilder(capacity); } /** * Constructs a new instance with the specified {@link StringBuilder}. * *

* If {@code builder} is null a new instance with default capacity will be created. *

* @param builder The String builder. May be null. */ public StringBuilderWriter(final StringBuilder builder) { this.builder = builder != null ? builder : new StringBuilder(); } /** * Appends a single character to this Writer. * @param value The character to append * @return This writer instance */ @Override public Writer append(final char value) { builder.append(value); return this; } /** * Appends a character sequence to this Writer. * @param value The character to append * @return This writer instance */ @Override public Writer append(final CharSequence value) { builder.append(value); return this; } /** * Appends a portion of a character sequence to the {@link StringBuilder}. * @param value The character to append * @param start The index of the first character * @param end The index of the last character + 1 * @return This writer instance */ @Override public Writer append(final CharSequence value, final int start, final int end) { builder.append(value, start, end); return this; } /** * Closing this writer has no effect. */ @Override public void close() { // no-op } /** * Flushing this writer has no effect. */ @Override public void flush() { // no-op } /** * Writes a String to the {@link StringBuilder}. * @param value The value to write */ @Override public void write(final String value) { if (value != null) { builder.append(value); } } /** * Writes a portion of a character array to the {@link StringBuilder}. * @param value The value to write * @param offset The index of the first character * @param length The number of characters to write */ @Override public void write(final char[] value, final int offset, final int length) { if (value != null) { builder.append(value, offset, length); } } /** * Returns the underlying builder. * @return The underlying builder */ public StringBuilder getBuilder() { return builder; } /** * Returns {@link StringBuilder#toString()}. * @return The contents of the String builder. */ @Override public String toString() { return builder.toString(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-commons/src/main/java/com/alibaba/cloud/commons/lang/StringUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.commons.lang; /** * StringUtils. copy from apache common-lang3. * * @author theonefx */ public final class StringUtils { /** * The empty String {@code ""}. * * @since 2.0 */ public static final String EMPTY = ""; /** * Represents a failed index search. * @since 2.1 */ public static final int INDEX_NOT_FOUND = -1; private StringUtils() { } /** *

* Checks if a CharSequence is empty ("") or null. *

* *
	 * StringUtils.isEmpty(null)      = true
	 * StringUtils.isEmpty("")        = true
	 * StringUtils.isEmpty(" ")       = false
	 * StringUtils.isEmpty("bob")     = false
	 * StringUtils.isEmpty("  bob  ") = false
	 * 
* *

* NOTE: This method changed in Lang version 2.0. It no longer trims the CharSequence. * That functionality is available in isBlank(). *

* @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is empty or null * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence) */ public static boolean isEmpty(final CharSequence cs) { return cs == null || cs.length() == 0; } /** *

* Checks if a CharSequence is not empty ("") and not null. *

* *
	 * StringUtils.isNotEmpty(null)      = false
	 * StringUtils.isNotEmpty("")        = false
	 * StringUtils.isNotEmpty(" ")       = true
	 * StringUtils.isNotEmpty("bob")     = true
	 * StringUtils.isNotEmpty("  bob  ") = true
	 * 
* @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is not empty and not null * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence) */ public static boolean isNotEmpty(final CharSequence cs) { return !isEmpty(cs); } /** *

* Checks if a CharSequence is whitespace, empty ("") or null. *

* *
	 * StringUtils.isBlank(null)      = true
	 * StringUtils.isBlank("")        = true
	 * StringUtils.isBlank(" ")       = true
	 * StringUtils.isBlank("bob")     = false
	 * StringUtils.isBlank("  bob  ") = false
	 * 
* @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is null, empty or whitespace */ public static boolean isBlank(final CharSequence cs) { if (cs == null || cs.length() == 0) { return true; } int strLen = cs.length(); for (int i = 0; i < strLen; i++) { if (!Character.isWhitespace(cs.charAt(i))) { return false; } } return true; } /** *

* Checks if a CharSequence is not empty (""), not null and not whitespace only. *

* *

* Whitespace is defined by {@link Character#isWhitespace(char)}. *

* *
	 * StringUtils.isNotBlank(null)      = false
	 * StringUtils.isNotBlank("")        = false
	 * StringUtils.isNotBlank(" ")       = false
	 * StringUtils.isNotBlank("bob")     = true
	 * StringUtils.isNotBlank("  bob  ") = true
	 * 
* @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is not empty and not null and not * whitespace only * @since 2.0 * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) */ public static boolean isNotBlank(final CharSequence cs) { return !isBlank(cs); } // Trim // ----------------------------------------------------------------------- /** *

* Removes control characters (char <= 32) from both ends of this String, handling * {@code null} by returning {@code null}. *

* *

* The String is trimmed using {@link String#trim()}. Trim removes start and end * characters <= 32. *

* *
	 * StringUtils.trim(null)          = null
	 * StringUtils.trim("")            = ""
	 * StringUtils.trim("     ")       = ""
	 * StringUtils.trim("abc")         = "abc"
	 * StringUtils.trim("    abc    ") = "abc"
	 * 
* @param str the String to be trimmed, may be null * @return the trimmed string, {@code null} if null String input */ public static String trim(final String str) { return str == null ? null : str.trim(); } // Equals // ----------------------------------------------------------------------- /** *

* Compares two CharSequences, returning {@code true} if they represent equal * sequences of characters. *

* *

* {@code null}s are handled without exceptions. Two {@code null} references are * considered to be equal. The comparison is case sensitive. *

* *
	 * StringUtils.equals(null, null)   = true
	 * StringUtils.equals(null, "abc")  = false
	 * StringUtils.equals("abc", null)  = false
	 * StringUtils.equals("abc", "abc") = true
	 * StringUtils.equals("abc", "ABC") = false
	 * 
* @param cs1 the first CharSequence, may be {@code null} * @param cs2 the second CharSequence, may be {@code null} * @return {@code true} if the CharSequences are equal (case-sensitive), or both * {@code null} * @see Object#equals(Object) */ public static boolean equals(final CharSequence cs1, final CharSequence cs2) { if (cs1 == cs2) { return true; } if (cs1 == null || cs2 == null) { return false; } if (cs1 instanceof String && cs2 instanceof String) { return cs1.equals(cs2); } return StringUtils.regionMatches(cs1, false, 0, cs2, 0, Math.max(cs1.length(), cs2.length())); } /** * Green implementation of regionMatches. * @param cs the {@code CharSequence} to be processed * @param ignoreCase whether or not to be case insensitive * @param thisStart the index to start on the {@code cs} CharSequence * @param substring the {@code CharSequence} to be looked for * @param start the index to start on the {@code substring} CharSequence * @param length character length of the region * @return whether the region matched */ public static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, final CharSequence substring, final int start, final int length) { if (cs instanceof String && substring instanceof String) { return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length); } int index1 = thisStart; int index2 = start; int tmpLen = length; while (tmpLen-- > 0) { final char c1 = cs.charAt(index1++); final char c2 = substring.charAt(index2++); if (c1 == c2) { continue; } if (!ignoreCase) { return false; } // The same check as in String.regionMatches(): if (Character.toUpperCase(c1) != Character.toUpperCase(c2) && Character.toLowerCase(c1) != Character.toLowerCase(c2)) { return false; } } return true; } /** *

* Gets the substring after the first occurrence of a separator. The separator is not * returned. *

* *

* A null string input will return null. An empty ("") * string input will return the empty string. A null separator will * return the empty string if the input string is not null. *

* *

* If nothing is found, the empty string is returned. *

* *
	 * StringUtils.substringAfter(null, *)      = null
	 * StringUtils.substringAfter("", *)        = ""
	 * StringUtils.substringAfter(*, null)      = ""
	 * StringUtils.substringAfter("abc", "a")   = "bc"
	 * StringUtils.substringAfter("abcba", "b") = "cba"
	 * StringUtils.substringAfter("abc", "c")   = ""
	 * StringUtils.substringAfter("abc", "d")   = ""
	 * StringUtils.substringAfter("abc", "")    = "abc"
	 * 
* @param str the String to get a substring from, may be null * @param separator the String to search for, may be null * @return the substring after the first occurrence of the separator, * null if null String input * @since 2.0 */ public static String substringAfter(String str, String separator) { if (isEmpty(str)) { return str; } if (separator == null) { return EMPTY; } int pos = str.indexOf(separator); if (pos == INDEX_NOT_FOUND) { return EMPTY; } return str.substring(pos + separator.length()); } // Substring between // ----------------------------------------------------------------------- /** *

* Gets the String that is nested in between two instances of the same String. *

* *

* A null input String returns null. A null tag * returns null. *

* *
	 * StringUtils.substringBetween(null, *)            = null
	 * StringUtils.substringBetween("", "")             = ""
	 * StringUtils.substringBetween("", "tag")          = null
	 * StringUtils.substringBetween("tagabctag", null)  = null
	 * StringUtils.substringBetween("tagabctag", "")    = ""
	 * StringUtils.substringBetween("tagabctag", "tag") = "abc"
	 * 
* @param str the String containing the substring, may be null * @param tag the String before and after the substring, may be null * @return the substring, null if no match * @since 2.0 */ public static String substringBetween(String str, String tag) { return substringBetween(str, tag, tag); } /** *

* Gets the String that is nested in between two Strings. Only the first match is * returned. *

* *

* A null input String returns null. A null * open/close returns null (no match). An empty ("") open and close * returns an empty string. *

* *
	 * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
	 * StringUtils.substringBetween(null, *, *)          = null
	 * StringUtils.substringBetween(*, null, *)          = null
	 * StringUtils.substringBetween(*, *, null)          = null
	 * StringUtils.substringBetween("", "", "")          = ""
	 * StringUtils.substringBetween("", "", "]")         = null
	 * StringUtils.substringBetween("", "[", "]")        = null
	 * StringUtils.substringBetween("yabcz", "", "")     = ""
	 * StringUtils.substringBetween("yabcz", "y", "z")   = "abc"
	 * StringUtils.substringBetween("yabczyabcz", "y", "z")   = "abc"
	 * 
* @param str the String containing the substring, may be null * @param open the String before the substring, may be null * @param close the String after the substring, may be null * @return the substring, null if no match * @since 2.0 */ public static String substringBetween(String str, String open, String close) { if (str == null || open == null || close == null) { return null; } int start = str.indexOf(open); if (start != INDEX_NOT_FOUND) { int end = str.indexOf(close, start + open.length()); if (end != INDEX_NOT_FOUND) { return str.substring(start + open.length(), end); } } return null; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml 4.0.0 spring-cloud-alibaba-sentinel-datasource Spring Cloud Alibaba Sentinel DataSource com.alibaba.cloud spring-cloud-alibaba-commons org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot-autoconfigure true org.hibernate.validator hibernate-validator true com.alibaba.csp sentinel-datasource-extension true com.alibaba.csp sentinel-parameter-flow-control true com.alibaba.csp sentinel-api-gateway-adapter-common true com.alibaba.csp sentinel-datasource-nacos true com.alibaba.csp sentinel-datasource-zookeeper true com.alibaba.csp sentinel-datasource-apollo true com.alibaba.csp sentinel-datasource-redis true com.alibaba.csp sentinel-datasource-consul true tools.jackson.core jackson-databind true tools.jackson.dataformat jackson-dataformat-xml true org.springframework.boot spring-boot-starter-test test ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/RuleType.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import java.util.Arrays; import java.util.Optional; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.sentinel.datasource.config.AbstractDataSourceProperties; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; /** * Enum for {@link AbstractRule} class, using in * {@link AbstractDataSourceProperties#ruleType}. * * @author Jim */ public enum RuleType { /** * flow. */ FLOW("flow", FlowRule.class), /** * degrade. */ DEGRADE("degrade", DegradeRule.class), /** * param flow. */ PARAM_FLOW("param-flow", ParamFlowRule.class), /** * system. */ SYSTEM("system", SystemRule.class), /** * authority. */ AUTHORITY("authority", AuthorityRule.class), /** * gateway flow. */ GW_FLOW("gw-flow", "com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule"), /** * api. */ GW_API_GROUP("gw-api-group", "com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition"); /** * alias for {@link AbstractRule}. */ private final String name; /** * concrete {@link AbstractRule} class. */ private Class clazz; /** * concrete {@link AbstractRule} class name. */ private String clazzName; RuleType(String name, Class clazz) { this.name = name; this.clazz = clazz; } RuleType(String name, String clazzName) { this.name = name; this.clazzName = clazzName; } public String getName() { return name; } public Class getClazz() { if (clazz != null) { return clazz; } else { try { return Class.forName(clazzName); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } } public static Optional getByName(String name) { if (StringUtils.isEmpty(name)) { return Optional.empty(); } return Arrays.stream(RuleType.values()) .filter(ruleType -> name.equals(ruleType.getName())).findFirst(); } public static Optional getByClass(Class clazz) { return Arrays.stream(RuleType.values()) .filter(ruleType -> clazz == ruleType.getClazz()).findFirst(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/config/AbstractDataSourceProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.config; import com.alibaba.cloud.sentinel.datasource.RuleType; import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import org.springframework.core.env.Environment; /** * Abstract class Using by {@link DataSourcePropertiesConfiguration}. * * @author Jim */ public class AbstractDataSourceProperties { @NotEmpty private String dataType = "json"; @NotNull private RuleType ruleType; private String converterClass; @JsonIgnore private final String factoryBeanName; @JsonIgnore private Environment env; public AbstractDataSourceProperties(String factoryBeanName) { this.factoryBeanName = factoryBeanName; } public String getDataType() { return dataType; } public void setDataType(String dataType) { this.dataType = dataType; } public RuleType getRuleType() { return ruleType; } public void setRuleType(RuleType ruleType) { this.ruleType = ruleType; } public String getConverterClass() { return converterClass; } public void setConverterClass(String converterClass) { this.converterClass = converterClass; } public String getFactoryBeanName() { return factoryBeanName; } protected Environment getEnv() { return env; } public void setEnv(Environment env) { this.env = env; } public void preCheck(String dataSourceName) { } public void postRegister(AbstractDataSource dataSource) { switch (this.getRuleType()) { case FLOW -> FlowRuleManager.register2Property(dataSource.getProperty()); case DEGRADE -> DegradeRuleManager.register2Property(dataSource.getProperty()); case PARAM_FLOW -> ParamFlowRuleManager.register2Property(dataSource.getProperty()); case SYSTEM -> SystemRuleManager.register2Property(dataSource.getProperty()); case AUTHORITY -> AuthorityRuleManager.register2Property(dataSource.getProperty()); case GW_FLOW -> GatewayRuleManager.register2Property(dataSource.getProperty()); case GW_API_GROUP -> GatewayApiDefinitionManager.register2Property(dataSource.getProperty()); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/config/ApolloDataSourceProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.config; import com.alibaba.cloud.sentinel.datasource.factorybean.ApolloDataSourceFactoryBean; import jakarta.validation.constraints.NotEmpty; /** * Apollo Properties class Using by {@link DataSourcePropertiesConfiguration} and * {@link ApolloDataSourceFactoryBean}. * * @author Jim */ public class ApolloDataSourceProperties extends AbstractDataSourceProperties { @NotEmpty private String namespaceName; @NotEmpty private String flowRulesKey; private String defaultFlowRuleValue; public ApolloDataSourceProperties() { super(ApolloDataSourceFactoryBean.class.getName()); } public String getNamespaceName() { return namespaceName; } public void setNamespaceName(String namespaceName) { this.namespaceName = namespaceName; } public String getFlowRulesKey() { return flowRulesKey; } public void setFlowRulesKey(String flowRulesKey) { this.flowRulesKey = flowRulesKey; } public String getDefaultFlowRuleValue() { return defaultFlowRuleValue; } public void setDefaultFlowRuleValue(String defaultFlowRuleValue) { this.defaultFlowRuleValue = defaultFlowRuleValue; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/config/ConsulDataSourceProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.config; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.sentinel.datasource.factorybean.ConsulDataSourceFactoryBean; /** * Consul Properties class Using by {@link DataSourcePropertiesConfiguration} and * {@link ConsulDataSourceFactoryBean}. * * @author mengjin */ public class ConsulDataSourceProperties extends AbstractDataSourceProperties { public ConsulDataSourceProperties() { super(ConsulDataSourceFactoryBean.class.getName()); } /** * consul server host. */ private String host; /** * consul server port. */ private int port = 8500; /** * consul acl-token. */ private String token; /** * data key in Redis. */ private String ruleKey; /** * Request of query will hang until timeout (in second) or get updated value. */ private int waitTimeoutInSecond = 1; @Override public void preCheck(String dataSourceName) { if (StringUtils.isEmpty(host)) { throw new IllegalArgumentException("ConsulDataSource server-host is empty"); } if (StringUtils.isEmpty(ruleKey)) { throw new IllegalArgumentException( "ConsulDataSource ruleKey can not be empty"); } } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getRuleKey() { return ruleKey; } public void setRuleKey(String ruleKey) { this.ruleKey = ruleKey; } public int getWaitTimeoutInSecond() { return waitTimeoutInSecond; } public void setWaitTimeoutInSecond(int waitTimeoutInSecond) { this.waitTimeoutInSecond = waitTimeoutInSecond; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/config/DataSourcePropertiesConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.config; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.util.ObjectUtils; /** * Using By ConfigurationProperties. * * @author Jim * @see NacosDataSourceProperties * @see ApolloDataSourceProperties * @see ZookeeperDataSourceProperties * @see FileDataSourceProperties * @see RedisDataSourceProperties * @see ConsulDataSourceProperties */ public class DataSourcePropertiesConfiguration { private FileDataSourceProperties file; private NacosDataSourceProperties nacos; private ZookeeperDataSourceProperties zk; private ApolloDataSourceProperties apollo; private RedisDataSourceProperties redis; private ConsulDataSourceProperties consul; public DataSourcePropertiesConfiguration() { } public DataSourcePropertiesConfiguration(ConsulDataSourceProperties consul) { this.consul = consul; } public ConsulDataSourceProperties getConsul() { return consul; } public void setConsul(ConsulDataSourceProperties consul) { this.consul = consul; } public DataSourcePropertiesConfiguration(FileDataSourceProperties file) { this.file = file; } public DataSourcePropertiesConfiguration(NacosDataSourceProperties nacos) { this.nacos = nacos; } public DataSourcePropertiesConfiguration(ZookeeperDataSourceProperties zk) { this.zk = zk; } public DataSourcePropertiesConfiguration(ApolloDataSourceProperties apollo) { this.apollo = apollo; } public DataSourcePropertiesConfiguration(RedisDataSourceProperties redis) { this.redis = redis; } public FileDataSourceProperties getFile() { return file; } public void setFile(FileDataSourceProperties file) { this.file = file; } public NacosDataSourceProperties getNacos() { return nacos; } public void setNacos(NacosDataSourceProperties nacos) { this.nacos = nacos; } public ZookeeperDataSourceProperties getZk() { return zk; } public void setZk(ZookeeperDataSourceProperties zk) { this.zk = zk; } public ApolloDataSourceProperties getApollo() { return apollo; } public void setApollo(ApolloDataSourceProperties apollo) { this.apollo = apollo; } public RedisDataSourceProperties getRedis() { return redis; } public void setRedis(RedisDataSourceProperties redis) { this.redis = redis; } @JsonIgnore public List getValidField() { return Arrays .stream(this.getClass().getDeclaredFields()) .filter(field -> !field.isSynthetic()) .map(field -> { try { if (!ObjectUtils.isEmpty(field.get(this))) { return field.getName(); } } catch (IllegalAccessException e) { // won't happen } return null; }) .filter(Objects::nonNull) .collect(Collectors.toList()); } @JsonIgnore public AbstractDataSourceProperties getValidDataSourceProperties() { List invalidFields = getValidField(); if (invalidFields.size() == 1) { try { this.getClass().getDeclaredField(invalidFields.get(0)) .setAccessible(true); return (AbstractDataSourceProperties) this.getClass() .getDeclaredField(invalidFields.get(0)).get(this); } catch (IllegalAccessException e) { // won't happen } catch (NoSuchFieldException e) { // won't happen } } return null; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/config/FileDataSourceProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.config; import java.io.IOException; import com.alibaba.cloud.sentinel.datasource.factorybean.FileRefreshableDataSourceFactoryBean; import jakarta.validation.constraints.NotEmpty; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** * File Properties class Using by {@link DataSourcePropertiesConfiguration} and * {@link FileRefreshableDataSourceFactoryBean}. * * @author Jim */ public class FileDataSourceProperties extends AbstractDataSourceProperties { @NotEmpty private String file; private String charset = "utf-8"; private long recommendRefreshMs = 3000L; private int bufSize = 1024 * 1024; public FileDataSourceProperties() { super(FileRefreshableDataSourceFactoryBean.class.getName()); } public String getFile() { return file; } public void setFile(String file) { this.file = file; } public String getCharset() { return charset; } public void setCharset(String charset) { this.charset = charset; } public long getRecommendRefreshMs() { return recommendRefreshMs; } public void setRecommendRefreshMs(long recommendRefreshMs) { this.recommendRefreshMs = recommendRefreshMs; } public int getBufSize() { return bufSize; } public void setBufSize(int bufSize) { this.bufSize = bufSize; } @Override public void preCheck(String dataSourceName) { super.preCheck(dataSourceName); try { this.setFile( ResourceUtils.getFile(StringUtils.trimAllWhitespace(this.getFile())) .getAbsolutePath()); } catch (IOException e) { throw new RuntimeException("[Sentinel Starter] DataSource " + dataSourceName + " handle file [" + this.getFile() + "] error: " + e.getMessage(), e); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/config/NacosDataSourceProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.config; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.sentinel.datasource.factorybean.NacosDataSourceFactoryBean; import jakarta.validation.constraints.NotEmpty; /** * Nacos Properties class Using by {@link DataSourcePropertiesConfiguration} and * {@link NacosDataSourceFactoryBean}. * * @author Jim */ public class NacosDataSourceProperties extends AbstractDataSourceProperties { private String serverAddr; private String contextPath; private String username; private String password; @NotEmpty private String groupId = "DEFAULT_GROUP"; @NotEmpty private String dataId; private String endpoint; private String namespace; private String accessKey; private String secretKey; public NacosDataSourceProperties() { super(NacosDataSourceFactoryBean.class.getName()); } @Override public void preCheck(String dataSourceName) { if (StringUtils.isEmpty(serverAddr)) { serverAddr = this.getEnv().getProperty( "spring.cloud.sentinel.datasource.nacos.server-addr", "127.0.0.1:8848"); } } public String getServerAddr() { return serverAddr; } public void setServerAddr(String serverAddr) { this.serverAddr = serverAddr; } public String getContextPath() { return contextPath; } public void setContextPath(String contextPath) { this.contextPath = contextPath; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public String getDataId() { return dataId; } public void setDataId(String dataId) { this.dataId = dataId; } public String getEndpoint() { return endpoint; } public void setEndpoint(String endpoint) { this.endpoint = endpoint; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/config/RedisDataSourceProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.config; import java.time.Duration; import java.util.List; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.sentinel.datasource.factorybean.RedisDataSourceFactoryBean; /** * Redis Properties class Using by {@link DataSourcePropertiesConfiguration} and * {@link RedisDataSourceFactoryBean}. * * @author lengleng */ public class RedisDataSourceProperties extends AbstractDataSourceProperties { public RedisDataSourceProperties() { super(RedisDataSourceFactoryBean.class.getName()); } /** * redis server host. */ private String host = "localhost"; /** * redis server port. */ private int port = 6379; /** * redis server password. */ private String password; /** * redis server default select database. */ private int database; /** * redis server timeout. */ private Duration timeout; /** * Comma-separated list of "host:port" pairs. */ private List nodes; /** * data key in Redis. */ private String ruleKey; /** * channel to subscribe in Redis. */ private String channel; /** * redis sentinel model. */ private String masterId; @Override public void preCheck(String dataSourceName) { super.preCheck(dataSourceName); if (StringUtils.isEmpty(ruleKey)) { throw new IllegalArgumentException( "RedisDataSource ruleKey can not be empty"); } if (StringUtils.isEmpty(channel)) { throw new IllegalArgumentException( "RedisDataSource channel can not be empty"); } if (StringUtils.isEmpty(masterId)) { throw new IllegalArgumentException( "RedisDataSource sentinel model,masterId can not be empty"); } } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getRuleKey() { return ruleKey; } public void setRuleKey(String ruleKey) { this.ruleKey = ruleKey; } public String getChannel() { return channel; } public void setChannel(String channel) { this.channel = channel; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getDatabase() { return database; } public void setDatabase(int database) { this.database = database; } public Duration getTimeout() { return timeout; } public void setTimeout(Duration timeout) { this.timeout = timeout; } public List getNodes() { return nodes; } public void setNodes(List nodes) { this.nodes = nodes; } public String getMasterId() { return masterId; } public void setMasterId(String masterId) { this.masterId = masterId; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/config/ZookeeperDataSourceProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.config; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.sentinel.datasource.factorybean.ZookeeperDataSourceFactoryBean; /** * Zookeeper Properties class Using by {@link DataSourcePropertiesConfiguration} and * {@link ZookeeperDataSourceFactoryBean}. * * @author Jim */ public class ZookeeperDataSourceProperties extends AbstractDataSourceProperties { public ZookeeperDataSourceProperties() { super(ZookeeperDataSourceFactoryBean.class.getName()); } private String serverAddr = "localhost:2181"; private String path; private String groupId; private String dataId; @Override public void preCheck(String dataSourceName) { if (StringUtils.isEmpty(serverAddr)) { serverAddr = this.getEnv() .getProperty("spring.cloud.sentinel.datasource.zk.server-addr", ""); if (StringUtils.isEmpty(serverAddr)) { throw new IllegalArgumentException( "ZookeeperDataSource server-addr is empty"); } } } public String getServerAddr() { return serverAddr; } public void setServerAddr(String serverAddr) { this.serverAddr = serverAddr; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public String getDataId() { return dataId; } public void setDataId(String dataId) { this.dataId = dataId; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/converter/JsonConverter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.converter; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import tools.jackson.databind.ObjectMapper; /** * Convert sentinel rules for json array Using strict mode to parse json. * * @author Jim * @see FlowRule * @see DegradeRule * @see SystemRule * @see AuthorityRule * @see ParamFlowRule * @see ObjectMapper */ public class JsonConverter extends SentinelConverter { public JsonConverter(ObjectMapper objectMapper, Class ruleClass) { super(objectMapper, ruleClass); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/converter/SentinelConverter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.converter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Optional; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tools.jackson.core.JacksonException; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; /** * Convert sentinel rules for json or xml array Using strict mode to parse json or xml. * * @author Jim * @see FlowRule * @see DegradeRule * @see SystemRule * @see AuthorityRule * @see ParamFlowRule * @see ObjectMapper */ public abstract class SentinelConverter implements Converter> { private static final Logger log = LoggerFactory.getLogger(SentinelConverter.class); private final ObjectMapper objectMapper; private final Class ruleClass; public SentinelConverter(ObjectMapper objectMapper, Class ruleClass) { this.objectMapper = objectMapper; this.ruleClass = ruleClass; } @Override public Collection convert(String source) { Collection ruleCollection; // hard code if (ruleClass == FlowRule.class || ruleClass == DegradeRule.class || ruleClass == SystemRule.class || ruleClass == AuthorityRule.class || ruleClass == ParamFlowRule.class) { ruleCollection = new ArrayList<>(); } else { ruleCollection = new HashSet<>(); } if (StringUtils.isEmpty(source)) { log.info("converter can not convert rules because source is empty"); return ruleCollection; } try { List sourceArray = objectMapper.readValue(source, new TypeReference>() { }); for (Object obj : sourceArray) { try { String item = objectMapper.writeValueAsString(obj); Optional.ofNullable(convertRule(item)) .ifPresent(convertRule -> ruleCollection.add(convertRule)); } catch (JacksonException e) { log.error("sentinel rule convert error: " + e.getMessage(), e); throw new IllegalArgumentException( "sentinel rule convert error: " + e.getMessage(), e); } } } catch (Exception e) { if (e instanceof RuntimeException runtimeException) { throw runtimeException; } else { throw new RuntimeException("convert error: " + e.getMessage(), e); } } return ruleCollection; } private Object convertRule(String ruleStr) { return objectMapper.readValue(ruleStr, ruleClass); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/converter/XmlConverter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.converter; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import tools.jackson.databind.ObjectMapper; import tools.jackson.dataformat.xml.XmlMapper; /** * Convert sentinel rules for xml array Using strict mode to parse xml. * * @author Jim * @see FlowRule * @see DegradeRule * @see SystemRule * @see AuthorityRule * @see ParamFlowRule * @see ObjectMapper */ public class XmlConverter extends SentinelConverter { public XmlConverter(XmlMapper xmlMapper, Class ruleClass) { super(xmlMapper, ruleClass); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/factorybean/ApolloDataSourceFactoryBean.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.factorybean; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.apollo.ApolloDataSource; import org.springframework.beans.factory.FactoryBean; /** * A {@link FactoryBean} for creating {@link ApolloDataSource} instance. * * @author Jim * @see ApolloDataSource */ public class ApolloDataSourceFactoryBean implements FactoryBean { private String namespaceName; private String flowRulesKey; private String defaultFlowRuleValue; private Converter converter; @Override public ApolloDataSource getObject() throws Exception { return new ApolloDataSource(namespaceName, flowRulesKey, defaultFlowRuleValue, converter); } @Override public Class getObjectType() { return ApolloDataSource.class; } public String getNamespaceName() { return namespaceName; } public void setNamespaceName(String namespaceName) { this.namespaceName = namespaceName; } public String getFlowRulesKey() { return flowRulesKey; } public void setFlowRulesKey(String flowRulesKey) { this.flowRulesKey = flowRulesKey; } public String getDefaultFlowRuleValue() { return defaultFlowRuleValue; } public void setDefaultFlowRuleValue(String defaultFlowRuleValue) { this.defaultFlowRuleValue = defaultFlowRuleValue; } public Converter getConverter() { return converter; } public void setConverter(Converter converter) { this.converter = converter; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/factorybean/ConsulDataSourceFactoryBean.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.factorybean; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.consul.ConsulDataSource; import org.springframework.beans.factory.FactoryBean; /** * A {@link FactoryBean} for creating {@link ConsulDataSource} instance. * * @author mengjin * @see ConsulDataSource */ public class ConsulDataSourceFactoryBean implements FactoryBean { private String host; private int port; private String ruleKey; private String token; private int waitTimeoutInSecond; private Converter converter; @Override public ConsulDataSource getObject() throws Exception { return new ConsulDataSource(host, port, token, ruleKey, waitTimeoutInSecond, converter); } @Override public Class getObjectType() { return ConsulDataSource.class; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getRuleKey() { return ruleKey; } public void setRuleKey(String ruleKey) { this.ruleKey = ruleKey; } public int getWaitTimeoutInSecond() { return waitTimeoutInSecond; } public void setWaitTimeoutInSecond(int waitTimeoutInSecond) { this.waitTimeoutInSecond = waitTimeoutInSecond; } public Converter getConverter() { return converter; } public void setConverter(Converter converter) { this.converter = converter; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/factorybean/FileRefreshableDataSourceFactoryBean.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.factorybean; import java.io.File; import java.nio.charset.Charset; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; import org.springframework.beans.factory.FactoryBean; /** * A {@link FactoryBean} for creating {@link FileRefreshableDataSource} instance. * * @author Jim * @see FileRefreshableDataSource */ public class FileRefreshableDataSourceFactoryBean implements FactoryBean { private String file; private String charset; private long recommendRefreshMs; private int bufSize; private Converter converter; @Override public FileRefreshableDataSource getObject() throws Exception { return new FileRefreshableDataSource(new File(file), converter, recommendRefreshMs, bufSize, Charset.forName(charset)); } @Override public Class getObjectType() { return FileRefreshableDataSource.class; } public String getFile() { return file; } public void setFile(String file) { this.file = file; } public String getCharset() { return charset; } public void setCharset(String charset) { this.charset = charset; } public long getRecommendRefreshMs() { return recommendRefreshMs; } public void setRecommendRefreshMs(long recommendRefreshMs) { this.recommendRefreshMs = recommendRefreshMs; } public int getBufSize() { return bufSize; } public void setBufSize(int bufSize) { this.bufSize = bufSize; } public Converter getConverter() { return converter; } public void setConverter(Converter converter) { this.converter = converter; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/factorybean/NacosDataSourceFactoryBean.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.factorybean; import java.util.Properties; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; import com.alibaba.nacos.api.PropertyKeyConst; import org.springframework.beans.factory.FactoryBean; /** * A {@link FactoryBean} for creating {@link NacosDataSource} instance. * * @author Jim * @see NacosDataSource */ public class NacosDataSourceFactoryBean implements FactoryBean { private String serverAddr; private String contextPath; private String username; private String password; private String groupId; private String dataId; private Converter converter; private String endpoint; private String namespace; private String accessKey; private String secretKey; @Override public NacosDataSource getObject() throws Exception { Properties properties = new Properties(); if (!StringUtils.isEmpty(this.serverAddr)) { properties.setProperty(PropertyKeyConst.SERVER_ADDR, this.serverAddr); } else { properties.setProperty(PropertyKeyConst.ENDPOINT, this.endpoint); } if (!StringUtils.isEmpty(this.contextPath)) { properties.setProperty(PropertyKeyConst.CONTEXT_PATH, this.contextPath); } if (!StringUtils.isEmpty(this.accessKey)) { properties.setProperty(PropertyKeyConst.ACCESS_KEY, this.accessKey); } if (!StringUtils.isEmpty(this.secretKey)) { properties.setProperty(PropertyKeyConst.SECRET_KEY, this.secretKey); } if (!StringUtils.isEmpty(this.namespace)) { properties.setProperty(PropertyKeyConst.NAMESPACE, this.namespace); } if (!StringUtils.isEmpty(this.username)) { properties.setProperty(PropertyKeyConst.USERNAME, this.username); } if (!StringUtils.isEmpty(this.password)) { properties.setProperty(PropertyKeyConst.PASSWORD, this.password); } return new NacosDataSource(properties, groupId, dataId, converter); } @Override public Class getObjectType() { return NacosDataSource.class; } public String getServerAddr() { return serverAddr; } public void setServerAddr(String serverAddr) { this.serverAddr = serverAddr; } public String getContextPath() { return contextPath; } public void setContextPath(String contextPath) { this.contextPath = contextPath; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public String getDataId() { return dataId; } public void setDataId(String dataId) { this.dataId = dataId; } public Converter getConverter() { return converter; } public void setConverter(Converter converter) { this.converter = converter; } public String getEndpoint() { return endpoint; } public void setEndpoint(String endpoint) { this.endpoint = endpoint; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/factorybean/RedisDataSourceFactoryBean.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.factorybean; import java.time.Duration; import java.util.List; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.redis.RedisDataSource; import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import org.springframework.beans.factory.FactoryBean; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * A {@link FactoryBean} for creating {@link RedisDataSource} instance. * * @author lengleng * @see RedisDataSource */ public class RedisDataSourceFactoryBean implements FactoryBean { private String host; private int port; private int database; private Duration timeout; /** * Comma-separated list of "host:port" pairs. */ private List nodes; private Converter converter; /** * data key in Redis. */ private String ruleKey; /** * channel to subscribe in Redis. */ private String channel; /** * redis server password. */ private String password; private String masterId; @Override public RedisDataSource getObject() { RedisConnectionConfig.Builder builder = RedisConnectionConfig.builder(); if (nodes == null || nodes.isEmpty()) { builder.withHost(host).withPort(port).withDatabase(database); } else { nodes.forEach(node -> { try { String[] parts = StringUtils.split(node, ":"); Assert.state(parts.length == 2, "Must be defined as 'host:port'"); builder.withRedisSentinel(parts[0], Integer.parseInt(parts[1])); } catch (RuntimeException ex) { throw new IllegalStateException( "Invalid redis sentinel property " + node, ex); } }); builder.withSentinelMasterId(masterId); } if (timeout != null) { builder.withTimeout(timeout.toMillis()); } if (StringUtils.hasText(password)) { builder.withPassword(password); } return new RedisDataSource>(builder.build(), ruleKey, channel, converter); } @Override public Class getObjectType() { return RedisDataSource.class; } public Converter getConverter() { return converter; } public void setConverter(Converter converter) { this.converter = converter; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getRuleKey() { return ruleKey; } public void setRuleKey(String ruleKey) { this.ruleKey = ruleKey; } public String getChannel() { return channel; } public void setChannel(String channel) { this.channel = channel; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getDatabase() { return database; } public void setDatabase(int database) { this.database = database; } public Duration getTimeout() { return timeout; } public void setTimeout(Duration timeout) { this.timeout = timeout; } public List getNodes() { return nodes; } public void setNodes(List nodes) { this.nodes = nodes; } public String getMasterId() { return masterId; } public void setMasterId(String masterId) { this.masterId = masterId; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/java/com/alibaba/cloud/sentinel/datasource/factorybean/ZookeeperDataSourceFactoryBean.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource.factorybean; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource; import org.springframework.beans.factory.FactoryBean; /** * A {@link FactoryBean} for creating {@link ZookeeperDataSource} instance. * * @author Jim * @see ZookeeperDataSource */ public class ZookeeperDataSourceFactoryBean implements FactoryBean { private String serverAddr; private String path; private String groupId; private String dataId; private Converter converter; @Override public ZookeeperDataSource getObject() throws Exception { if (StringUtils.isNotEmpty(groupId) && StringUtils.isNotEmpty(dataId)) { // the path will be /{groupId}/{dataId} return new ZookeeperDataSource(serverAddr, groupId, dataId, converter); } else { // using path directly return new ZookeeperDataSource(serverAddr, path, converter); } } @Override public Class getObjectType() { return ZookeeperDataSource.class; } public String getServerAddr() { return serverAddr; } public void setServerAddr(String serverAddr) { this.serverAddr = serverAddr; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public String getDataId() { return dataId; } public void setDataId(String dataId) { this.dataId = dataId; } public Converter getConverter() { return converter; } public void setConverter(Converter converter) { this.converter = converter; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/main/resources/META-INF/sentinel-datasource.properties ================================================ nacos = com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource file =com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource apollo = com.alibaba.csp.sentinel.datasource.apollo.ApolloDataSource zk = com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource redis = com.alibaba.csp.sentinel.datasource.redis.RedisDataSource consul = com.alibaba.csp.sentinel.datasource.consul.ConsulDataSource ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/java/com/alibaba/cloud/sentinel/datasource/ApolloDataSourceFactoryBeanTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter; import com.alibaba.cloud.sentinel.datasource.factorybean.ApolloDataSourceFactoryBean; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.apollo.ApolloDataSource; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; /** * @author Jim */ public class ApolloDataSourceFactoryBeanTests { private String flowRuleKey = "sentinel"; private String namespace = "namespace"; private String defaultFlowValue = "{}"; @Test public void testApolloFactoryBean() throws Exception { ApolloDataSourceFactoryBean factoryBean = spy(new ApolloDataSourceFactoryBean()); Converter converter = mock(JsonConverter.class); factoryBean.setDefaultFlowRuleValue(defaultFlowValue); factoryBean.setFlowRulesKey(flowRuleKey); factoryBean.setNamespaceName(namespace); factoryBean.setConverter(converter); ApolloDataSource apolloDataSource = mock(ApolloDataSource.class); when(apolloDataSource.readSource()).thenReturn("{}"); doReturn(apolloDataSource).when(factoryBean).getObject(); assertThat(factoryBean.getObject()).isEqualTo(apolloDataSource); assertThat(factoryBean.getObject().readSource()).isEqualTo("{}"); assertThat(factoryBean.getConverter()).isEqualTo(converter); assertThat(factoryBean.getFlowRulesKey()).isEqualTo(flowRuleKey); assertThat(factoryBean.getNamespaceName()).isEqualTo(namespace); assertThat(factoryBean.getDefaultFlowRuleValue()).isEqualTo(defaultFlowValue); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/java/com/alibaba/cloud/sentinel/datasource/DataSourcePropertiesConfigurationTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import java.util.List; import com.alibaba.cloud.sentinel.datasource.config.ApolloDataSourceProperties; import com.alibaba.cloud.sentinel.datasource.config.DataSourcePropertiesConfiguration; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Test cases for {@link DataSourcePropertiesConfiguration}. * * @author hnyyghk */ public class DataSourcePropertiesConfigurationTests { /** * Test cases for {@link DataSourcePropertiesConfiguration#getValidField()}. * * @see com.alibaba.cloud.sentinel.custom.SentinelDataSourceHandler#afterSingletonsInstantiated() */ @Test public void testGetValidField() { DataSourcePropertiesConfiguration configuration = new DataSourcePropertiesConfiguration(); ApolloDataSourceProperties apollo = new ApolloDataSourceProperties(); apollo.setNamespaceName("application"); apollo.setFlowRulesKey("test-flow-rules"); apollo.setDefaultFlowRuleValue("[]"); apollo.setDataType("json"); apollo.setRuleType(RuleType.FLOW); configuration.setApollo(apollo); //indicate which datasource active List validField = configuration.getValidField(); //not allowed multi datasource active, $jacocoData should not be included assertThat(validField.size()).isEqualTo(1); assertThat(validField).doesNotContain("$jacocoData"); assertThat(validField).contains("apollo"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/java/com/alibaba/cloud/sentinel/datasource/DataSourcePropertiesTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import java.util.List; import com.alibaba.cloud.sentinel.datasource.config.ApolloDataSourceProperties; import com.alibaba.cloud.sentinel.datasource.config.FileDataSourceProperties; import com.alibaba.cloud.sentinel.datasource.config.ZookeeperDataSourceProperties; import com.alibaba.cloud.sentinel.datasource.factorybean.ApolloDataSourceFactoryBean; import com.alibaba.cloud.sentinel.datasource.factorybean.FileRefreshableDataSourceFactoryBean; import com.alibaba.cloud.sentinel.datasource.factorybean.ZookeeperDataSourceFactoryBean; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.junit.jupiter.api.Test; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Jim */ public class DataSourcePropertiesTests { @Test public void testApollo() { ApolloDataSourceProperties apolloDataSourceProperties = new ApolloDataSourceProperties(); apolloDataSourceProperties.setFlowRulesKey("test-key"); apolloDataSourceProperties.setDefaultFlowRuleValue("dft-val"); apolloDataSourceProperties.setNamespaceName("namespace"); apolloDataSourceProperties.setRuleType(RuleType.DEGRADE); assertThat(apolloDataSourceProperties.getFlowRulesKey()).isEqualTo("test-key"); assertThat(apolloDataSourceProperties.getNamespaceName()).isEqualTo("namespace"); assertThat(apolloDataSourceProperties.getDataType()).isEqualTo("json"); assertThat(apolloDataSourceProperties.getRuleType()).isEqualTo(RuleType.DEGRADE); assertThat(apolloDataSourceProperties.getDefaultFlowRuleValue()) .isEqualTo("dft-val"); assertThat(apolloDataSourceProperties.getFactoryBeanName()) .isEqualTo(ApolloDataSourceFactoryBean.class.getName()); assertThat(apolloDataSourceProperties.getConverterClass()).isNull(); } @Test public void testZK() { ZookeeperDataSourceProperties zookeeperDataSourceProperties = new ZookeeperDataSourceProperties(); zookeeperDataSourceProperties.setServerAddr("localhost:2181"); zookeeperDataSourceProperties.setGroupId("groupId"); zookeeperDataSourceProperties.setDataId("dataId"); zookeeperDataSourceProperties.setPath("/path"); zookeeperDataSourceProperties.setConverterClass("test.ConverterClass"); zookeeperDataSourceProperties.setRuleType(RuleType.AUTHORITY); assertThat(zookeeperDataSourceProperties.getServerAddr()) .isEqualTo("localhost:2181"); assertThat(zookeeperDataSourceProperties.getGroupId()).isEqualTo("groupId"); assertThat(zookeeperDataSourceProperties.getDataId()).isEqualTo("dataId"); assertThat(zookeeperDataSourceProperties.getPath()).isEqualTo("/path"); assertThat(zookeeperDataSourceProperties.getFactoryBeanName()) .isEqualTo(ZookeeperDataSourceFactoryBean.class.getName()); assertThat(zookeeperDataSourceProperties.getConverterClass()) .isEqualTo("test.ConverterClass"); assertThat(zookeeperDataSourceProperties.getRuleType()) .isEqualTo(RuleType.AUTHORITY); } @Test public void testFileDefaultValue() { FileDataSourceProperties fileDataSourceProperties = new FileDataSourceProperties(); fileDataSourceProperties.setFile("/tmp/test.json"); fileDataSourceProperties.setRuleType(RuleType.PARAM_FLOW); assertThat(fileDataSourceProperties.getFile()).isEqualTo("/tmp/test.json"); assertThat(fileDataSourceProperties.getCharset()).isEqualTo("utf-8"); assertThat(fileDataSourceProperties.getRecommendRefreshMs()).isEqualTo(3000L); assertThat(fileDataSourceProperties.getBufSize()).isEqualTo(1024 * 1024); assertThat(fileDataSourceProperties.getFactoryBeanName()) .isEqualTo(FileRefreshableDataSourceFactoryBean.class.getName()); assertThat(fileDataSourceProperties.getRuleType()).isEqualTo(RuleType.PARAM_FLOW); } @Test public void testFileCustomValue() { FileDataSourceProperties fileDataSourceProperties = new FileDataSourceProperties(); fileDataSourceProperties.setFile("/tmp/test.json"); fileDataSourceProperties.setBufSize(1024); fileDataSourceProperties.setRecommendRefreshMs(2000); fileDataSourceProperties.setCharset("ISO8859-1"); assertThat(fileDataSourceProperties.getFile()).isEqualTo("/tmp/test.json"); assertThat(fileDataSourceProperties.getCharset()).isEqualTo("ISO8859-1"); assertThat(fileDataSourceProperties.getRecommendRefreshMs()).isEqualTo(2000L); assertThat(fileDataSourceProperties.getBufSize()).isEqualTo(1024); } @Test public void testFileException() { assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> { FileDataSourceProperties fileDataSourceProperties = new FileDataSourceProperties(); fileDataSourceProperties.setFile("classpath: 1.json"); fileDataSourceProperties.preCheck("test-ds"); }); } @Test public void testPostRegister() throws Exception { FileDataSourceProperties fileDataSourceProperties = new FileDataSourceProperties(); fileDataSourceProperties.setFile("classpath: flowrule.json"); fileDataSourceProperties.setRuleType(RuleType.FLOW); FileRefreshableDataSource fileRefreshableDataSource = new FileRefreshableDataSource( ResourceUtils .getFile(StringUtils .trimAllWhitespace(fileDataSourceProperties.getFile())) .getAbsolutePath(), new Converter>() { ObjectMapper objectMapper = new ObjectMapper(); @Override public List convert(String source) { return objectMapper.readValue(source, new TypeReference<>() { }); } }); fileDataSourceProperties.postRegister(fileRefreshableDataSource); assertThat(FlowRuleManager.getRules()) .isEqualTo(fileRefreshableDataSource.loadConfig()); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/java/com/alibaba/cloud/sentinel/datasource/FileRefreshableDataSourceFactoryBeanTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import java.io.FileNotFoundException; import java.util.List; import com.alibaba.cloud.sentinel.datasource.factorybean.FileRefreshableDataSourceFactoryBean; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import org.junit.jupiter.api.Test; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.ResourceUtils; import static org.assertj.core.api.Assertions.assertThat; /** * @author Jim */ public class FileRefreshableDataSourceFactoryBeanTests { @Test public void testFile() throws Exception { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext( TestConfig.class); assertThat(annotationConfigApplicationContext.getBean("fileBean")).isNotNull(); FileRefreshableDataSource fileRefreshableDataSource = annotationConfigApplicationContext .getBean("fileBean", FileRefreshableDataSource.class); assertThat(((List) fileRefreshableDataSource.loadConfig()).size()) .isEqualTo(1); FileRefreshableDataSourceFactoryBean factoryBean = annotationConfigApplicationContext .getBean("&fileBean", FileRefreshableDataSourceFactoryBean.class); assertThat(factoryBean.getBufSize()).isEqualTo(1024); assertThat(factoryBean.getCharset()).isEqualTo("utf-8"); assertThat(factoryBean.getRecommendRefreshMs()).isEqualTo(2000); assertThat(factoryBean.getFile()).isNotNull(); assertThat(factoryBean.getConverter()).isNotNull(); } @Configuration public static class TestConfig { @Bean public FileRefreshableDataSourceFactoryBean fileBean() { FileRefreshableDataSourceFactoryBean factoryBean = new FileRefreshableDataSourceFactoryBean(); factoryBean.setBufSize(1024); factoryBean.setCharset("utf-8"); factoryBean.setRecommendRefreshMs(2000); try { factoryBean.setFile(ResourceUtils.getFile("classpath:flowrule.json") .getAbsolutePath()); } catch (FileNotFoundException e) { // ignore } factoryBean.setConverter(buildConverter()); return factoryBean; } private Converter buildConverter() { return new Converter>() { ObjectMapper objectMapper = new ObjectMapper(); @Override public List convert(String source) { return objectMapper.readValue(source, new TypeReference<>() { }); } }; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/java/com/alibaba/cloud/sentinel/datasource/NacosDataSourceFactoryBeanTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import com.alibaba.cloud.sentinel.datasource.converter.SentinelConverter; import com.alibaba.cloud.sentinel.datasource.factorybean.NacosDataSourceFactoryBean; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; /** * @author Jim */ public class NacosDataSourceFactoryBeanTests { private String dataId = "sentinel"; private String groupId = "DEFAULT_GROUP"; private String serverAddr = "127.0.0.1:8848"; private String contextPath = "/my-nacos"; private String accessKey = "ak"; private String secretKey = "sk"; private String endpoint = "endpoint"; private String namespace = "namespace"; @Test public void testNacosFactoryBeanServerAddr() throws Exception { NacosDataSourceFactoryBean factoryBean = spy(new NacosDataSourceFactoryBean()); Converter converter = mock(SentinelConverter.class); factoryBean.setDataId(dataId); factoryBean.setGroupId(groupId); factoryBean.setServerAddr(serverAddr); factoryBean.setContextPath(contextPath); factoryBean.setConverter(converter); NacosDataSource nacosDataSource = mock(NacosDataSource.class); doReturn(nacosDataSource).when(factoryBean).getObject(); when(nacosDataSource.readSource()).thenReturn("{}"); assertThat(factoryBean.getObject()).isEqualTo(nacosDataSource); assertThat(factoryBean.getObject().readSource()).isEqualTo("{}"); assertThat(factoryBean.getConverter()).isEqualTo(converter); assertThat(factoryBean.getDataId()).isEqualTo(dataId); assertThat(factoryBean.getGroupId()).isEqualTo(groupId); assertThat(factoryBean.getServerAddr()).isEqualTo(serverAddr); assertThat(factoryBean.getContextPath()).isEqualTo(contextPath); } @Test public void testNacosFactoryBeanProperties() throws Exception { NacosDataSourceFactoryBean factoryBean = spy(new NacosDataSourceFactoryBean()); Converter converter = mock(SentinelConverter.class); factoryBean.setDataId(dataId); factoryBean.setGroupId(groupId); factoryBean.setAccessKey(accessKey); factoryBean.setSecretKey(secretKey); factoryBean.setEndpoint(endpoint); factoryBean.setNamespace(namespace); factoryBean.setConverter(converter); NacosDataSource nacosDataSource = mock(NacosDataSource.class); doReturn(nacosDataSource).when(factoryBean).getObject(); when(nacosDataSource.readSource()).thenReturn("{}"); assertThat(factoryBean.getObject()).isEqualTo(nacosDataSource); assertThat(factoryBean.getObject().readSource()).isEqualTo("{}"); assertThat(factoryBean.getConverter()).isEqualTo(converter); assertThat(factoryBean.getDataId()).isEqualTo(dataId); assertThat(factoryBean.getGroupId()).isEqualTo(groupId); assertThat(factoryBean.getNamespace()).isEqualTo(namespace); assertThat(factoryBean.getEndpoint()).isEqualTo(endpoint); assertThat(factoryBean.getAccessKey()).isEqualTo(accessKey); assertThat(factoryBean.getSecretKey()).isEqualTo(secretKey); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/java/com/alibaba/cloud/sentinel/datasource/NacosDataSourcePropertiesTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import com.alibaba.cloud.sentinel.datasource.config.NacosDataSourceProperties; import com.alibaba.cloud.sentinel.datasource.factorybean.NacosDataSourceFactoryBean; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Jim */ public class NacosDataSourcePropertiesTests { @Test public void testNacosWithAddr() { NacosDataSourceProperties nacosDataSourceProperties = new NacosDataSourceProperties(); nacosDataSourceProperties.setServerAddr("127.0.0.1:8848"); nacosDataSourceProperties.setContextPath("/my-nacos"); nacosDataSourceProperties.setRuleType(RuleType.FLOW); nacosDataSourceProperties.setDataId("sentinel"); nacosDataSourceProperties.setGroupId("custom-group"); nacosDataSourceProperties.setDataType("xml"); assertThat(nacosDataSourceProperties.getContextPath()).isEqualTo("/my-nacos"); assertThat(nacosDataSourceProperties.getGroupId()).isEqualTo("custom-group"); assertThat(nacosDataSourceProperties.getDataId()).isEqualTo("sentinel"); assertThat(nacosDataSourceProperties.getDataType()).isEqualTo("xml"); assertThat(nacosDataSourceProperties.getRuleType()).isEqualTo(RuleType.FLOW); assertThat(nacosDataSourceProperties.getFactoryBeanName()) .isEqualTo(NacosDataSourceFactoryBean.class.getName()); } @Test public void testNacosWithProperties() { NacosDataSourceProperties nacosDataSourceProperties = new NacosDataSourceProperties(); nacosDataSourceProperties.setAccessKey("ak"); nacosDataSourceProperties.setSecretKey("sk"); nacosDataSourceProperties.setEndpoint("endpoint"); nacosDataSourceProperties.setNamespace("namespace"); nacosDataSourceProperties.setRuleType(RuleType.SYSTEM); assertThat(nacosDataSourceProperties.getAccessKey()).isEqualTo("ak"); assertThat(nacosDataSourceProperties.getSecretKey()).isEqualTo("sk"); assertThat(nacosDataSourceProperties.getEndpoint()).isEqualTo("endpoint"); assertThat(nacosDataSourceProperties.getNamespace()).isEqualTo("namespace"); assertThat(nacosDataSourceProperties.getRuleType()).isEqualTo(RuleType.SYSTEM); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/java/com/alibaba/cloud/sentinel/datasource/RuleTypeTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Jim */ public class RuleTypeTests { @Test public void testGetByName() { assertThat(RuleType.getByName("").isPresent()).isEqualTo(Boolean.FALSE); assertThat(RuleType.getByName("test").isPresent()).isEqualTo(Boolean.FALSE); assertThat(RuleType.getByName("param_flow").isPresent()).isEqualTo(Boolean.FALSE); assertThat(RuleType.getByName("param").isPresent()).isEqualTo(Boolean.FALSE); assertThat(RuleType.getByName("FLOW").isPresent()).isEqualTo(Boolean.FALSE); assertThat(RuleType.getByName("flow").isPresent()).isEqualTo(Boolean.TRUE); assertThat(RuleType.getByName("degrade").isPresent()).isEqualTo(Boolean.TRUE); assertThat(RuleType.getByName("param-flow").isPresent()).isEqualTo(Boolean.TRUE); assertThat(RuleType.getByName("system").isPresent()).isEqualTo(Boolean.TRUE); assertThat(RuleType.getByName("authority").isPresent()).isEqualTo(Boolean.TRUE); assertThat(RuleType.getByName("flow").get()).isEqualTo(RuleType.FLOW); assertThat(RuleType.getByName("degrade").get()).isEqualTo(RuleType.DEGRADE); assertThat(RuleType.getByName("param-flow").get()).isEqualTo(RuleType.PARAM_FLOW); assertThat(RuleType.getByName("system").get()).isEqualTo(RuleType.SYSTEM); assertThat(RuleType.getByName("authority").get()).isEqualTo(RuleType.AUTHORITY); } @Test public void testGetByClass() { assertThat(RuleType.getByClass(Object.class).isPresent()) .isEqualTo(Boolean.FALSE); assertThat(RuleType.getByClass(AbstractRule.class).isPresent()) .isEqualTo(Boolean.FALSE); assertThat(RuleType.getByClass(FlowRule.class).isPresent()) .isEqualTo(Boolean.TRUE); assertThat(RuleType.getByClass(DegradeRule.class).isPresent()) .isEqualTo(Boolean.TRUE); assertThat(RuleType.getByClass(ParamFlowRule.class).isPresent()) .isEqualTo(Boolean.TRUE); assertThat(RuleType.getByClass(SystemRule.class).isPresent()) .isEqualTo(Boolean.TRUE); assertThat(RuleType.getByClass(AuthorityRule.class).isPresent()) .isEqualTo(Boolean.TRUE); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/java/com/alibaba/cloud/sentinel/datasource/SentinelConverterTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import java.io.IOException; import java.nio.charset.Charset; import java.util.List; import com.alibaba.cloud.commons.io.FileUtils; import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter; import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import org.junit.jupiter.api.Test; import tools.jackson.databind.ObjectMapper; import tools.jackson.dataformat.xml.XmlMapper; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Jim */ public class SentinelConverterTests { private ObjectMapper objectMapper = new ObjectMapper(); private XmlMapper xmlMapper = new XmlMapper(); @Test public void testJsonConverter() { JsonConverter jsonConverter = new JsonConverter(objectMapper, FlowRule.class); List flowRules = (List) jsonConverter .convert(readFileContent("classpath: flowrule.json")); assertThat(flowRules.size()).isEqualTo(1); assertThat(flowRules.get(0).getResource()).isEqualTo("resource"); assertThat(flowRules.get(0).getLimitApp()).isEqualTo("default"); assertThat(String.valueOf(flowRules.get(0).getCount())).isEqualTo("1.0"); assertThat(flowRules.get(0).getControlBehavior()) .isEqualTo(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); assertThat(flowRules.get(0).getStrategy()) .isEqualTo(RuleConstant.STRATEGY_DIRECT); assertThat(flowRules.get(0).getGrade()).isEqualTo(RuleConstant.FLOW_GRADE_QPS); } @Test public void testConverterEmptyContent() { JsonConverter jsonConverter = new JsonConverter(objectMapper, FlowRule.class); List flowRules = (List) jsonConverter.convert(""); assertThat(flowRules.size()).isEqualTo(0); } @Test public void testConverterErrorFormat() { assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> { JsonConverter jsonConverter = new JsonConverter(objectMapper, FlowRule.class); jsonConverter .convert(readFileContent("classpath: flowrule-errorformat.json")); }); } @Test public void testConverterErrorContent() { // see https://github.com/FasterXML/jackson-databind/issues/493 assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> { JsonConverter jsonConverter = new JsonConverter(objectMapper, FlowRule.class); jsonConverter .convert(readFileContent("classpath: flowrule-errorcontent.json")); }); } @Test public void testXmlConverter() { XmlConverter jsonConverter = new XmlConverter(xmlMapper, FlowRule.class); List flowRules = (List) jsonConverter .convert(readFileContent("classpath: flowrule.xml")); assertThat(flowRules.size()).isEqualTo(2); assertThat(flowRules.get(0).getResource()).isEqualTo("resource"); assertThat(flowRules.get(0).getLimitApp()).isEqualTo("default"); assertThat(String.valueOf(flowRules.get(0).getCount())).isEqualTo("1.0"); assertThat(flowRules.get(0).getControlBehavior()) .isEqualTo(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); assertThat(flowRules.get(0).getStrategy()) .isEqualTo(RuleConstant.STRATEGY_DIRECT); assertThat(flowRules.get(0).getGrade()).isEqualTo(RuleConstant.FLOW_GRADE_QPS); assertThat(flowRules.get(1).getResource()).isEqualTo("test"); assertThat(flowRules.get(1).getLimitApp()).isEqualTo("default"); assertThat(String.valueOf(flowRules.get(1).getCount())).isEqualTo("1.0"); assertThat(flowRules.get(1).getControlBehavior()) .isEqualTo(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); assertThat(flowRules.get(1).getStrategy()) .isEqualTo(RuleConstant.STRATEGY_DIRECT); assertThat(flowRules.get(1).getGrade()).isEqualTo(RuleConstant.FLOW_GRADE_QPS); } private String readFileContent(String file) { try { return FileUtils.readFileToString( ResourceUtils.getFile(StringUtils.trimAllWhitespace(file)), Charset.defaultCharset()); } catch (IOException e) { return ""; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/java/com/alibaba/cloud/sentinel/datasource/ZookeeperDataSourceFactoryBeanTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.datasource; import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter; import com.alibaba.cloud.sentinel.datasource.factorybean.ZookeeperDataSourceFactoryBean; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; /** * @author Jim */ public class ZookeeperDataSourceFactoryBeanTests { private String dataId = "dataId"; private String groupId = "groupId"; private String serverAddr = "localhost:2181"; private String path = "/sentinel"; @Test public void testZKWithoutPathFactoryBean() throws Exception { ZookeeperDataSourceFactoryBean factoryBean = spy( ZookeeperDataSourceFactoryBean.class); Converter converter = mock(XmlConverter.class); ZookeeperDataSource zookeeperDataSource = mock(ZookeeperDataSource.class); factoryBean.setConverter(converter); factoryBean.setDataId(dataId); factoryBean.setGroupId(groupId); factoryBean.setServerAddr(serverAddr); when(zookeeperDataSource.readSource()).thenReturn("{}"); doReturn(zookeeperDataSource).when(factoryBean).getObject(); assertThat(factoryBean.getObject()).isEqualTo(zookeeperDataSource); assertThat(factoryBean.getObject().readSource()).isEqualTo("{}"); assertThat(factoryBean.getDataId()).isEqualTo(dataId); assertThat(factoryBean.getConverter()).isEqualTo(converter); assertThat(factoryBean.getGroupId()).isEqualTo(groupId); assertThat(factoryBean.getServerAddr()).isEqualTo(serverAddr); } @Test public void testZKWithPathFactoryBean() throws Exception { ZookeeperDataSourceFactoryBean factoryBean = spy( ZookeeperDataSourceFactoryBean.class); Converter converter = mock(XmlConverter.class); ZookeeperDataSource zookeeperDataSource = mock(ZookeeperDataSource.class); factoryBean.setConverter(converter); factoryBean.setPath(path); factoryBean.setServerAddr(serverAddr); when(zookeeperDataSource.readSource()).thenReturn("{}"); doReturn(zookeeperDataSource).when(factoryBean).getObject(); assertThat(factoryBean.getObject()).isEqualTo(zookeeperDataSource); assertThat(factoryBean.getObject().readSource()).isEqualTo("{}"); assertThat(factoryBean.getConverter()).isEqualTo(converter); assertThat(factoryBean.getPath()).isEqualTo(path); assertThat(factoryBean.getServerAddr()).isEqualTo(serverAddr); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/resources/flowrule-errorcontent.json ================================================ [ { "controlBehavior": "test", "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/resources/flowrule-errorformat.json ================================================ [ { "resource": "resource", "controlBehavior": 0, "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 }== ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/resources/flowrule.json ================================================ [ { "resource": "resource", "controlBehavior": 0, "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-datasource/src/test/resources/flowrule.xml ================================================ resource 0 1 1 default 0 test 0 1 1 default 0 ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/README.md ================================================ # Sentinel Spring Cloud Zuul Adapter Zuul does not provide rateLimit function, If you use default `SentinelRibbonFilter` route filter. it wrapped by Hystrix Command. so only provide Service level circuit protection. Sentinel can provide `ServiceId` level and `API Path` level flow control for spring cloud zuul gateway service. *Note*: this project is for zuul 1. ## How to use 1. Add maven dependency ```xml com.alibaba.cloud spring-cloud-alibaba-sentinel-gateway x.y.z ``` 2. Set application.property ``` // default value is false spring.cloud.sentinel.zuul.enabled=true ``` ## How it works As Zuul run as per thread connection block model, we add filters around `route Filter` to trace sentinel statistics. - `SentinelPreFilter`: Get an entry of resource,the first order is **ServiceId**, then **API Path**. - `SentinelPostFilter`: When success response,exit entry. - `SentinelErrorFilter`: When get an `Exception`, trace the exception and exit context. the order of Filter can be changed by configuration: ``` spring.cloud.sentinel.zuul.order.post=0 spring.cloud.sentinel.zuul.order.pre=10000 spring.cloud.sentinel.zuul.order.error=-1 ``` Filters create structure like: ```bash EntranceNode: machine-root(t:3 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) -EntranceNode: coke(t:2 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) --coke(t:2 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) ---/coke/uri(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) -EntranceNode: sentinel_default_context(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) -EntranceNode: book(t:1 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) --book(t:1 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) ---/book/uri(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0) ``` `book` and `coke` are serviceId. `---/book/uri` is api path, the real uri is `/uri`. ## Integration with Sentinel DashBord Start [Sentinel DashBord](https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0). ## Rule config with dataSource Sentinel has full rule config features. see [Dynamic-Rule-Configuration](https://github.com/alibaba/Sentinel/wiki/Dynamic-Rule-Configuration) ## Custom Fallbacks Implements `SentinelFallbackProvider` to define your own Fallback Provider when Sentinel Block Exception throwing for different rout. the default Fallback Provider is `DefaultBlockFallbackProvider`. By default, fallback route is `ServiveId + URI PATH`, example `/book/coke`, first `book` is serviceId, `/uri` is URI PATH, so both can be needed. Here is an example: ```java // custom provider public class MyCokeServiceBlockFallbackProvider implements SentinelFallbackProvider { private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class); // you can define root as service level @Override public String getRoute() { return "/coke/uri"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { if (cause instanceof BlockException) { logger.info("get in fallback block exception:{}", cause); return response(HttpStatus.TOO_MANY_REQUESTS, route); } else { return response(HttpStatus.INTERNAL_SERVER_ERROR, route); } } } ``` ## Custom Request Origin Parser By default, this adapter use `DefaultRequestOriginParser` to parse sentinel origin. ```java public class CustomRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest request) { // do custom logic. return ""; } } ``` ## Custom UrlCleaner By default this adapter use `DefaultUrlCleaner` to define uri resource. ```java public class CustomUrlCleaner implements UrlCleaner { @Override public String clean(String originUrl) { // do custom logic. return originUrl; } } ``` ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml 4.0.0 spring-cloud-alibaba-sentinel-gateway Spring Cloud Alibaba Sentinel Gateway org.springframework.boot spring-boot-configuration-processor true com.alibaba.csp sentinel-api-gateway-adapter-common com.alibaba.csp sentinel-parameter-flow-control com.alibaba.csp sentinel-spring-cloud-gateway-v6x-adapter com.alibaba.cloud spring-cloud-alibaba-sentinel-datasource org.springframework.cloud spring-cloud-starter-gateway-server-webflux true tools.jackson.dataformat jackson-dataformat-xml true com.alibaba.csp sentinel-datasource-extension test junit junit test org.mockito mockito-core test ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/main/java/com/alibaba/cloud/sentinel/gateway/ConfigConstants.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway; import com.alibaba.cloud.sentinel.gateway.scg.SentinelGatewayProperties; /** * @author Jim */ public final class ConfigConstants { /** * Spring Cloud Gateway type. */ public static final String APP_TYPE_SCG_GATEWAY = "11"; /** * ConfigurationProperties for {@link SentinelGatewayProperties}. */ public static final String GATEWAY_PREFIX = "spring.cloud.sentinel.scg"; /** * Response type for Spring Cloud Gateway fallback. */ public static final String FALLBACK_MSG_RESPONSE = "response"; /** * Redirect type for Spring Cloud Gateway fallback. */ public static final String FALLBACK_REDIRECT = "redirect"; private ConfigConstants() { throw new AssertionError("Must not instantiate constant utility class"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/main/java/com/alibaba/cloud/sentinel/gateway/FallbackProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; /** * @author Jim */ public class FallbackProperties { /** * The fallback mode for sentinel spring-cloud-gateway. choose `redirect` or * `response`. */ private String mode; /** * Redirect Url for `redirect` mode. */ private String redirect; /** * Response Body for `response` mode. */ private String responseBody; /** * Response Status for `response` mode. */ private Integer responseStatus = HttpStatus.TOO_MANY_REQUESTS.value(); /** * Content-Type for `response` mode. */ private String contentType = MediaType.APPLICATION_JSON.toString(); public String getMode() { return mode; } public FallbackProperties setMode(String mode) { this.mode = mode; return this; } public String getRedirect() { return redirect; } public FallbackProperties setRedirect(String redirect) { this.redirect = redirect; return this; } public String getResponseBody() { return responseBody; } public FallbackProperties setResponseBody(String responseBody) { this.responseBody = responseBody; return this; } public Integer getResponseStatus() { return responseStatus; } public FallbackProperties setResponseStatus(Integer responseStatus) { this.responseStatus = responseStatus; return this; } public String getContentType() { return contentType; } public FallbackProperties setContentType(String contentType) { this.contentType = contentType; return this; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/main/java/com/alibaba/cloud/sentinel/gateway/GatewayEnvironmentPostProcessor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway; import java.util.HashMap; import java.util.Map; import org.springframework.boot.EnvironmentPostProcessor; import org.springframework.boot.SpringApplication; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; /** * @author zhuhonghan */ public class GatewayEnvironmentPostProcessor implements EnvironmentPostProcessor { private final static String SENTINEL_FILTER_ENABLED = "spring.cloud.sentinel.filter.enabled"; private final static String PROPERTY_SOURCE_NAME = "defaultProperties"; @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication springApplication) { addDefaultPropertySource(environment); } private void addDefaultPropertySource(ConfigurableEnvironment environment) { Map map = new HashMap(); configureDefaultProperties(map); addOrReplace(environment.getPropertySources(), map); } private void configureDefaultProperties(Map source) { // Required Properties source.put(SENTINEL_FILTER_ENABLED, "false"); } private void addOrReplace(MutablePropertySources propertySources, Map map) { MapPropertySource target = null; if (propertySources.contains(PROPERTY_SOURCE_NAME)) { PropertySource source = propertySources.get(PROPERTY_SOURCE_NAME); if (source instanceof MapPropertySource mapPropertySource) { target = mapPropertySource; for (String key : map.keySet()) { if (!target.containsProperty(key)) { target.getSource().put(key, map.get(key)); } } } } if (target == null) { target = new MapPropertySource(PROPERTY_SOURCE_NAME, map); } if (!propertySources.contains(PROPERTY_SOURCE_NAME)) { propertySources.addLast(target); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/main/java/com/alibaba/cloud/sentinel/gateway/SentinelGatewayAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway; import java.util.HashMap; import java.util.Map; import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter; import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateGroupItem; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import tools.jackson.core.JsonParser; import tools.jackson.core.Version; import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.deser.std.StdDeserializer; import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.module.SimpleModule; import tools.jackson.dataformat.xml.XmlMapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Jim */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) public class SentinelGatewayAutoConfiguration { @ConditionalOnClass(ObjectMapper.class) @Configuration(proxyBeanMethods = false) protected static class SentinelConverterConfiguration { static class ApiPredicateItemDeserializer extends StdDeserializer { private Map> registry = new HashMap>(); ApiPredicateItemDeserializer() { super(ApiPredicateItem.class); } void registerApiPredicateItem(String uniqueAttribute, Class apiPredicateItemClass) { registry.put(uniqueAttribute, apiPredicateItemClass); } @Override public ApiPredicateItem deserialize(JsonParser jp, DeserializationContext ctxt) { JsonNode root = ctxt.readTree(jp); Class apiPredicateItemClass = null; for (String name : root.propertyNames()) { if (registry.containsKey(name)) { apiPredicateItemClass = registry.get(name); break; } } if (apiPredicateItemClass == null) { return null; } return ctxt.readTreeAsValue(root, apiPredicateItemClass); } } @Configuration(proxyBeanMethods = false) protected static class SentinelJsonConfiguration { private final ObjectMapper objectMapper; public SentinelJsonConfiguration() { ApiPredicateItemDeserializer deserializer = new ApiPredicateItemDeserializer(); deserializer.registerApiPredicateItem("pattern", ApiPathPredicateItem.class); deserializer.registerApiPredicateItem("items", ApiPredicateGroupItem.class); SimpleModule module = new SimpleModule( "PolymorphicApiPredicateItemDeserializerModule", new Version(1, 0, 0, null, null, null)); module.addDeserializer(ApiPredicateItem.class, deserializer); this.objectMapper = JsonMapper.builder() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .addModule(module) .build(); } @Bean("sentinel-json-gw-flow-converter") public JsonConverter jsonGatewayFlowConverter() { return new JsonConverter(objectMapper, GatewayFlowRule.class); } @Bean("sentinel-json-gw-api-group-converter") public JsonConverter jsonApiConverter() { return new JsonConverter(objectMapper, ApiDefinition.class); } } @ConditionalOnClass(XmlMapper.class) @Configuration(proxyBeanMethods = false) protected static class SentinelXmlConfiguration { private final XmlMapper xmlMapper; public SentinelXmlConfiguration() { ApiPredicateItemDeserializer deserializer = new ApiPredicateItemDeserializer(); deserializer.registerApiPredicateItem("pattern", ApiPathPredicateItem.class); deserializer.registerApiPredicateItem("items", ApiPredicateGroupItem.class); SimpleModule module = new SimpleModule( "PolymorphicGatewayDeserializerModule", new Version(1, 0, 0, null, null, null)); module.addDeserializer(ApiPredicateItem.class, deserializer); this.xmlMapper = XmlMapper.builder() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .addModule(module) .build(); } @Bean("sentinel-xml-gw-flow-converter") public XmlConverter xmlGatewayFlowConverter() { return new XmlConverter(xmlMapper, GatewayFlowRule.class); } @Bean("sentinel-xml-gw-api-group-converter") public XmlConverter xmlApiConverter() { return new XmlConverter(xmlMapper, ApiDefinition.class); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/main/java/com/alibaba/cloud/sentinel/gateway/scg/SentinelGatewayProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway.scg; import com.alibaba.cloud.sentinel.gateway.ConfigConstants; import com.alibaba.cloud.sentinel.gateway.FallbackProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.core.Ordered; /** * @author Jim */ @ConfigurationProperties(prefix = ConfigConstants.GATEWAY_PREFIX) public class SentinelGatewayProperties { @NestedConfigurationProperty private FallbackProperties fallback; private Integer order = Ordered.HIGHEST_PRECEDENCE; public FallbackProperties getFallback() { return fallback; } public SentinelGatewayProperties setFallback(FallbackProperties fallback) { this.fallback = fallback; return this; } public Integer getOrder() { return order; } public void setOrder(Integer order) { this.order = order; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/main/java/com/alibaba/cloud/sentinel/gateway/scg/SentinelSCGAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway.scg; import java.util.Collections; import java.util.List; import java.util.Optional; import com.alibaba.cloud.sentinel.gateway.ConfigConstants; import com.alibaba.cloud.sentinel.gateway.FallbackProperties; import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.RedirectBlockRequestHandler; import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.util.StringUtil; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import static org.springframework.web.reactive.function.BodyInserters.fromValue; /** * @author Jim */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(GlobalFilter.class) @ConditionalOnProperty(prefix = ConfigConstants.GATEWAY_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties(SentinelGatewayProperties.class) public class SentinelSCGAutoConfiguration { private static final Logger logger = LoggerFactory .getLogger(SentinelSCGAutoConfiguration.class); private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; @Autowired private Optional blockRequestHandlerOptional; @Autowired private SentinelGatewayProperties gatewayProperties; @PostConstruct public void init() { // blockRequestHandlerOptional has low priority blockRequestHandlerOptional.ifPresent(GatewayCallbackManager::setBlockHandler); initAppType(); initFallback(); } public SentinelSCGAutoConfiguration( ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } private void initAppType() { System.setProperty(SentinelConfig.APP_TYPE_PROP_KEY, ConfigConstants.APP_TYPE_SCG_GATEWAY); } private void initFallback() { FallbackProperties fallbackProperties = gatewayProperties.getFallback(); if (fallbackProperties == null || StringUtil.isBlank(fallbackProperties.getMode())) { return; } if (ConfigConstants.FALLBACK_MSG_RESPONSE.equals(fallbackProperties.getMode())) { if (StringUtil.isNotBlank(fallbackProperties.getResponseBody())) { GatewayCallbackManager.setBlockHandler((exchange, t) -> ServerResponse .status(fallbackProperties.getResponseStatus()) .contentType( MediaType.valueOf(fallbackProperties.getContentType())) .body(fromValue(fallbackProperties.getResponseBody()))); logger.info( "[Sentinel SpringCloudGateway] using AnonymousBlockRequestHandler, responseStatus: " + fallbackProperties.getResponseStatus() + ", responseBody: " + fallbackProperties.getResponseBody()); } } String redirectUrl = fallbackProperties.getRedirect(); if (ConfigConstants.FALLBACK_REDIRECT.equals(fallbackProperties.getMode()) && StringUtil.isNotBlank(redirectUrl)) { GatewayCallbackManager .setBlockHandler(new RedirectBlockRequestHandler(redirectUrl)); logger.info( "[Sentinel SpringCloudGateway] using RedirectBlockRequestHandler, redirectUrl: " + redirectUrl); } } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnMissingBean public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // Register the block exception handler for Spring Cloud Gateway. logger.info( "[Sentinel SpringCloudGateway] register SentinelGatewayBlockExceptionHandler"); return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) @ConditionalOnMissingBean public SentinelGatewayFilter sentinelGatewayFilter() { logger.info( "[Sentinel SpringCloudGateway] register SentinelGatewayFilter with order: {}", gatewayProperties.getOrder()); return new SentinelGatewayFilter(gatewayProperties.getOrder()); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.sentinel.gateway.scg.SentinelSCGAutoConfiguration com.alibaba.cloud.sentinel.gateway.SentinelGatewayAutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.EnvironmentPostProcessor=com.alibaba.cloud.sentinel.gateway.GatewayEnvironmentPostProcessor ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/java/com/alibaba/cloud/sentinel/gateway/ConfigConstantsTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway; import junit.framework.Assert; import org.junit.Test; public class ConfigConstantsTest { @Test public void testConfigConstants() { Assert.assertEquals("11", ConfigConstants.APP_TYPE_SCG_GATEWAY); Assert.assertEquals("spring.cloud.sentinel.scg", ConfigConstants.GATEWAY_PREFIX); Assert.assertEquals("response", ConfigConstants.FALLBACK_MSG_RESPONSE); Assert.assertEquals("redirect", ConfigConstants.FALLBACK_REDIRECT); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/java/com/alibaba/cloud/sentinel/gateway/FallbackPropertiesTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway; import junit.framework.Assert; import org.junit.Test; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; public class FallbackPropertiesTest { /** * Tests the correct setting and retrieval of fallback properties. * This test case verifies that the FallbackProperties class correctly sets and retrieves * various properties for fallback responses, including the response mode, redirect URL, * response body content, HTTP status code, and content type. */ @Test public void testFallbackProperties() { FallbackProperties properties = new FallbackProperties() .setMode("response") .setRedirect("http://example.com") .setResponseBody("{'message': 'Fallback response'}") .setResponseStatus(HttpStatus.TOO_EARLY.value()) .setContentType("application/json"); Assert.assertEquals("response", properties.getMode()); Assert.assertEquals("http://example.com", properties.getRedirect()); Assert.assertEquals("{'message': 'Fallback response'}", properties.getResponseBody()); Assert.assertEquals(HttpStatus.TOO_EARLY.value(), properties.getResponseStatus().intValue()); Assert.assertEquals("application/json", properties.getContentType()); } /** * This test method checks the default values of a FallbackProperties object. * It verifies that certain properties are not set (null) and others have default values. */ @Test public void testDefaultValues() { FallbackProperties properties = new FallbackProperties(); Assert.assertNull(properties.getMode()); Assert.assertNull(properties.getRedirect()); Assert.assertNull(properties.getResponseBody()); Assert.assertEquals(HttpStatus.TOO_MANY_REQUESTS.value(), properties.getResponseStatus().intValue()); Assert.assertEquals(MediaType.APPLICATION_JSON.toString(), properties.getContentType()); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/java/com/alibaba/cloud/sentinel/gateway/GatewayEnvironmentPostProcessorTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway; import java.util.HashMap; import java.util.Map; import junit.framework.Assert; import org.junit.Test; import org.springframework.boot.SpringApplication; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class GatewayEnvironmentPostProcessorTest { /** * Tests the custom property source processing logic. * This test case verifies whether the custom property source is correctly added during the environment post-processing. * Specifically, it checks if the configuration property "spring.cloud.sentinel.filter.enabled" is properly added. */ @Test public void testPostProcessEnvironment() { ConfigurableEnvironment environment = mock(ConfigurableEnvironment.class); MutablePropertySources propertySources = new MutablePropertySources(); when(environment.getPropertySources()).thenReturn(propertySources); GatewayEnvironmentPostProcessor postProcessor = new GatewayEnvironmentPostProcessor(); postProcessor.postProcessEnvironment(environment, mock(SpringApplication.class)); PropertySource propertySource = propertySources.get("defaultProperties"); Assert.assertNotNull(propertySource); Assert.assertNotNull(propertySource.getProperty("spring.cloud.sentinel.filter.enabled")); } /** * Tests the logic of processing an environment that already contains a property source. * This test case simulates an environment with an existing property source and checks if the post-processor can interact correctly with these property sources. */ @Test public void testPostProcessEnvironmentWithExistingPropertySource() { ConfigurableEnvironment environment = mock(ConfigurableEnvironment.class); MutablePropertySources propertySources = new MutablePropertySources(); when(environment.getPropertySources()).thenReturn(propertySources); Map existingProperties = new HashMap<>(); existingProperties.put("existing.property", "value"); MapPropertySource existingPropertySource = new MapPropertySource("defaultProperties", existingProperties); propertySources.addFirst(existingPropertySource); GatewayEnvironmentPostProcessor postProcessor = new GatewayEnvironmentPostProcessor(); postProcessor.postProcessEnvironment(environment, mock(SpringApplication.class)); PropertySource propertySource = propertySources.get("defaultProperties"); Assert.assertNotNull(propertySource); Assert.assertEquals("value", propertySource.getProperty("existing.property")); Assert.assertEquals("false", propertySource.getProperty("spring.cloud.sentinel.filter.enabled")); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/java/com/alibaba/cloud/sentinel/gateway/SentinelGatewayAutoConfigurationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import com.alibaba.cloud.commons.io.FileUtils; import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter; import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter; import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; public class SentinelGatewayAutoConfigurationTest { private SentinelGatewayAutoConfiguration.SentinelConverterConfiguration.SentinelJsonConfiguration json; private SentinelGatewayAutoConfiguration.SentinelConverterConfiguration.SentinelXmlConfiguration xml; /** * Setup method to initialize test configurations. */ @Before public void setup() { json = new SentinelGatewayAutoConfiguration.SentinelConverterConfiguration.SentinelJsonConfiguration(); xml = new SentinelGatewayAutoConfiguration.SentinelConverterConfiguration.SentinelXmlConfiguration(); } /** * Tests the JSON gateway flow rule converter. * Reads JSON content from a file, converts it to GatewayFlowRule collection, and validates the result. */ @Test public void testJsonGatewayFlowConverter() { JsonConverter jsonGatewayFlowConverter = json.jsonGatewayFlowConverter(); Collection gatewayFlowRules = jsonGatewayFlowConverter.convert(readFileContent("classpath: gatewayflowrule.json")); Assert.assertEquals(1, gatewayFlowRules.size()); Assert.assertEquals("test", new ArrayList<>(gatewayFlowRules).get(0).getResource()); } /** * Tests the JSON API definition converter. * Reads JSON content from a file, converts it to ApiDefinition collection, and validates the result. */ @Test public void testJsonApiConverter() { JsonConverter jsonApiConverter = json.jsonApiConverter(); Collection apiDefinitions = jsonApiConverter.convert(readFileContent("classpath: apidefinition.json")); Assert.assertEquals(1, apiDefinitions.size()); Assert.assertEquals("test", new ArrayList<>(apiDefinitions).get(0).getApiName()); } /** * Tests the XML gateway flow rule converter. * Reads XML content from a file, converts it to GatewayFlowRule collection, and validates the result. */ @Test public void testXmlGatewayFlowConverter() { XmlConverter xmlGatewayFlowConverter = xml.xmlGatewayFlowConverter(); Collection gatewayFlowRules = xmlGatewayFlowConverter.convert(readFileContent("classpath: gatewayflowrule.xml")); Assert.assertEquals(1, gatewayFlowRules.size()); Assert.assertEquals("test", new ArrayList<>(gatewayFlowRules).get(0).getResource()); } /** * Tests the XML API definition converter. * Reads XML content from a file, converts it to ApiDefinition collection, and validates the result. */ @Test public void testSentinelXmlConfiguration() { XmlConverter xmlApiConverter = xml.xmlApiConverter(); Collection apiDefinitions = xmlApiConverter.convert(readFileContent("classpath: apidefinition.xml")); Assert.assertEquals(1, apiDefinitions.size()); Assert.assertEquals("test", new ArrayList<>(apiDefinitions).get(0).getApiName()); } /** * Reads the content of a file. * * @param file the classpath location of the file * @return the content of the file as a string */ private String readFileContent(String file) { try { return FileUtils.readFileToString( ResourceUtils.getFile(StringUtils.trimAllWhitespace(file)), Charset.defaultCharset()); } catch (IOException e) { return ""; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/java/com/alibaba/cloud/sentinel/gateway/scg/SentinelGatewayPropertiesTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway.scg; import com.alibaba.cloud.sentinel.gateway.FallbackProperties; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.core.Ordered; public class SentinelGatewayPropertiesTest { private SentinelGatewayProperties properties; @Before public void setUp() { properties = new SentinelGatewayProperties(); } @Test public void testDefaultOrder() { Assert.assertEquals(Ordered.HIGHEST_PRECEDENCE, properties.getOrder().intValue()); } @Test public void testSetOrder() { int newOrder = 100; properties.setOrder(newOrder); Assert.assertEquals(newOrder, properties.getOrder().intValue()); } @Test public void testFallbackPropertiesInitialization() { Assert.assertNull(properties.getFallback()); } @Test public void testSetFallbackProperties() { FallbackProperties newFallback = new FallbackProperties(); properties.setFallback(newFallback); Assert.assertSame(newFallback, properties.getFallback()); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/java/com/alibaba/cloud/sentinel/gateway/scg/SentinelSCGAutoConfigurationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.gateway.scg; import java.lang.reflect.Field; import java.util.List; import java.util.Objects; import java.util.Optional; import com.alibaba.cloud.sentinel.gateway.ConfigConstants; import com.alibaba.cloud.sentinel.gateway.FallbackProperties; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import reactor.core.publisher.Mono; import org.springframework.beans.factory.ObjectProvider; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class SentinelSCGAutoConfigurationTest { @Mock private ObjectProvider> viewResolversProvider; @Mock private ServerCodecConfigurer serverCodecConfigurer; @Mock private SentinelGatewayProperties gatewayProperties; private SentinelSCGAutoConfiguration config; @Before public void setup() throws Exception { MockitoAnnotations.openMocks(this).close(); config = new SentinelSCGAutoConfiguration(viewResolversProvider, serverCodecConfigurer); Field optional = SentinelSCGAutoConfiguration.class.getDeclaredField("blockRequestHandlerOptional"); optional.setAccessible(true); optional.set(config, Optional.empty()); Field properties = SentinelSCGAutoConfiguration.class.getDeclaredField("gatewayProperties"); properties.setAccessible(true); properties.set(config, this.gatewayProperties); } /** * Tests the initialization method to ensure that fallback properties are fetched * and the block exception handler and gateway filter are properly configured. */ @Test public void testInit() { config.init(); verify(gatewayProperties).getFallback(); // Check if fallback properties are fetched Assert.assertNotNull(config.sentinelGatewayBlockExceptionHandler()); Assert.assertNotNull(config.sentinelGatewayFilter()); } /** * Tests the initialization method when the fallback mode is set to return a custom response message. * Verifies that the response status, content type, and body match the expected values. */ @Test public void testInitWithFallbackMsgResponse() { FallbackProperties fallbackProperties = mock(FallbackProperties.class); when(gatewayProperties.getFallback()).thenReturn(fallbackProperties); when(fallbackProperties.getMode()).thenReturn(ConfigConstants.FALLBACK_MSG_RESPONSE); when(fallbackProperties.getResponseStatus()).thenReturn(200); when(fallbackProperties.getContentType()).thenReturn(MediaType.APPLICATION_JSON.toString()); when(fallbackProperties.getResponseBody()).thenReturn("test"); config.init(); Mono responseMono = GatewayCallbackManager.getBlockHandler() .handleRequest(mock(ServerWebExchange.class), null); Assert.assertEquals(200, Objects.requireNonNull(responseMono.block()).statusCode().value()); } /** * Tests the initialization method when the fallback mode is set to redirect to another URL. * Verifies that the response contains the correct redirect location header. */ @Test public void testInitWithFallbackRedirect() { FallbackProperties fallbackProperties = mock(FallbackProperties.class); when(gatewayProperties.getFallback()).thenReturn(fallbackProperties); when(fallbackProperties.getMode()).thenReturn(ConfigConstants.FALLBACK_REDIRECT); when(fallbackProperties.getRedirect()).thenReturn("/test"); config.init(); Mono responseMono = GatewayCallbackManager.getBlockHandler() .handleRequest(mock(ServerWebExchange.class), null); HttpHeaders headers = Objects.requireNonNull(responseMono.block()).headers(); List location = headers.get("Location"); Assert.assertNotNull(location); Assert.assertEquals("/test", location.get(0)); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/resources/apidefinition.json ================================================ [ { "apiName": "test" } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/resources/apidefinition.xml ================================================ test ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/resources/gatewayflowrule.json ================================================ [ { "resource": "test", "resourceMode": 0, "grade": 1, "count": 1, "intervalSec": 1, "controlBehavior": 1, "burst": 1, "maxQueueingTimeoutMs": 100 } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-alibaba-sentinel-gateway/src/test/resources/gatewayflowrule.xml ================================================ test ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml 4.0.0 spring-cloud-circuitbreaker-sentinel Spring Cloud Circuit Breaker Sentinel org.springframework.cloud spring-cloud-commons org.springframework.cloud spring-cloud-context provided com.alibaba.csp sentinel-core com.alibaba.csp sentinel-reactor-adapter org.springframework.boot spring-boot-starter-web true io.projectreactor reactor-core true org.springframework.cloud spring-cloud-starter-openfeign true com.alibaba.csp sentinel-datasource-extension provided org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot-starter-webflux test org.springframework.boot spring-boot-starter-test test io.projectreactor reactor-test test org.springframework.boot spring-boot-restclient test org.springframework.boot spring-boot-resttestclient test ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreaker.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Function; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.reactor.EntryConfig; import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; import org.springframework.util.Assert; /** * Sentinel implementation of {@link ReactiveCircuitBreaker}. * * @author Eric Zhao */ public class ReactiveSentinelCircuitBreaker implements ReactiveCircuitBreaker { private final String resourceName; private final EntryType entryType; private final List rules; public ReactiveSentinelCircuitBreaker(String resourceName, EntryType entryType, List rules) { Assert.hasText(resourceName, "resourceName cannot be blank"); Assert.notNull(rules, "rules should not be null"); this.resourceName = resourceName; this.entryType = entryType; this.rules = Collections.unmodifiableList(rules); applyToSentinelRuleManager(); } public ReactiveSentinelCircuitBreaker(String resourceName, List rules) { this(resourceName, EntryType.OUT, rules); } public ReactiveSentinelCircuitBreaker(String resourceName) { this(resourceName, EntryType.OUT, Collections.emptyList()); } private void applyToSentinelRuleManager() { if (this.rules == null || this.rules.isEmpty()) { return; } Set ruleSet = new HashSet<>(DegradeRuleManager.getRules()); for (DegradeRule rule : this.rules) { if (rule == null) { continue; } rule.setResource(resourceName); ruleSet.add(rule); } DegradeRuleManager.loadRules(new ArrayList<>(ruleSet)); } @Override public Mono run(Mono toRun, Function> fallback) { Mono toReturn = toRun.transform(new SentinelReactorTransformer<>( new EntryConfig(resourceName, entryType))); if (fallback != null) { toReturn = toReturn.onErrorResume(fallback); } return toReturn; } @Override public Flux run(Flux toRun, Function> fallback) { Flux toReturn = toRun.transform(new SentinelReactorTransformer<>( new EntryConfig(resourceName, entryType))); if (fallback != null) { toReturn = toReturn.onErrorResume(fallback); } return toReturn; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Eric Zhao * @author freeman */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass( name = { "reactor.core.publisher.Mono", "reactor.core.publisher.Flux" }) @ConditionalOnProperty(name = "spring.cloud.circuitbreaker.sentinel.enabled", havingValue = "true", matchIfMissing = true) public class ReactiveSentinelCircuitBreakerAutoConfiguration { @Autowired(required = false) private List> customizers = new ArrayList<>(); @Bean @ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class) public ReactiveCircuitBreakerFactory reactiveSentinelCircuitBreakerFactory() { ReactiveSentinelCircuitBreakerFactory factory = new ReactiveSentinelCircuitBreakerFactory(); customizers.forEach(customizer -> customizer.customize(factory)); return factory; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerFactory.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.ArrayList; import java.util.function.Function; import com.alibaba.cloud.circuitbreaker.sentinel.SentinelConfigBuilder.SentinelCircuitBreakerConfiguration; import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; import org.springframework.util.Assert; /** * Factory for {@link ReactiveSentinelCircuitBreaker}. * * @author Eric Zhao */ public class ReactiveSentinelCircuitBreakerFactory extends ReactiveCircuitBreakerFactory { private Function defaultConfiguration = id -> new SentinelConfigBuilder() .resourceName(id).rules(new ArrayList<>()).build(); @Override public ReactiveCircuitBreaker create(String id) { Assert.hasText(id, "A CircuitBreaker must have an id."); SentinelConfigBuilder.SentinelCircuitBreakerConfiguration conf = getConfigurations() .computeIfAbsent(id, defaultConfiguration); return new ReactiveSentinelCircuitBreaker(id, conf.getEntryType(), conf.getRules()); } @Override protected SentinelConfigBuilder configBuilder(String id) { return new SentinelConfigBuilder(id); } @Override public void configureDefault( Function defaultConfiguration) { this.defaultConfiguration = defaultConfiguration; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/SentinelCircuitBreaker.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; import org.springframework.util.Assert; /** * Sentinel implementation of {@link CircuitBreaker}. * * @author Eric Zhao */ public class SentinelCircuitBreaker implements CircuitBreaker { private final String resourceName; private final EntryType entryType; private final List rules; public SentinelCircuitBreaker(String resourceName, EntryType entryType, List rules) { Assert.hasText(resourceName, "resourceName cannot be blank"); Assert.notNull(rules, "rules should not be null"); this.resourceName = resourceName; this.entryType = entryType; this.rules = Collections.unmodifiableList(rules); applyToSentinelRuleManager(); } public SentinelCircuitBreaker(String resourceName, List rules) { this(resourceName, EntryType.OUT, rules); } public SentinelCircuitBreaker(String resourceName) { this(resourceName, EntryType.OUT, Collections.emptyList()); } private void applyToSentinelRuleManager() { if (this.rules == null || this.rules.isEmpty()) { return; } Set ruleSet = new HashSet<>(DegradeRuleManager.getRules()); for (DegradeRule rule : this.rules) { if (rule == null) { continue; } rule.setResource(resourceName); ruleSet.add(rule); } DegradeRuleManager.loadRules(new ArrayList<>(ruleSet)); } @Override public T run(Supplier toRun, Function fallback) { Entry entry = null; try { entry = SphU.entry(resourceName, entryType); // If the SphU.entry() does not throw `BlockException`, it means that the // request can pass. return toRun.get(); } catch (BlockException ex) { // SphU.entry() may throw BlockException which indicates that // the request was rejected (flow control or circuit breaking triggered). // So it should not be counted as the business exception. return fallback.apply(ex); } catch (Exception ex) { // For other kinds of exceptions, we'll trace the exception count via // Tracer.trace(ex). Tracer.trace(ex); return fallback.apply(ex); } finally { // Guarantee the invocation has been completed. if (entry != null) { entry.exit(); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.ArrayList; import java.util.List; import com.alibaba.csp.sentinel.SphU; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Auto configuration for {@link SentinelCircuitBreaker}. * * @author Eric Zhao * @author freeman */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ SphU.class }) @ConditionalOnProperty(name = "spring.cloud.circuitbreaker.sentinel.enabled", havingValue = "true", matchIfMissing = true) public class SentinelCircuitBreakerAutoConfiguration { @Autowired(required = false) private List> customizers = new ArrayList<>(); @Bean @ConditionalOnMissingBean(CircuitBreakerFactory.class) public CircuitBreakerFactory sentinelCircuitBreakerFactory() { SentinelCircuitBreakerFactory factory = new SentinelCircuitBreakerFactory(); customizers.forEach(customizer -> customizer.customize(factory)); return factory; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerFactory.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.ArrayList; import java.util.function.Function; import com.alibaba.cloud.circuitbreaker.sentinel.SentinelConfigBuilder.SentinelCircuitBreakerConfiguration; import com.alibaba.csp.sentinel.EntryType; import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.util.Assert; /** * @author Eric Zhao */ public class SentinelCircuitBreakerFactory extends CircuitBreakerFactory { private Function defaultConfiguration = id -> new SentinelConfigBuilder() .resourceName(id).entryType(EntryType.OUT).rules(new ArrayList<>()).build(); @Override public CircuitBreaker create(String id) { Assert.hasText(id, "A CircuitBreaker must have an id."); SentinelConfigBuilder.SentinelCircuitBreakerConfiguration conf = getConfigurations() .computeIfAbsent(id, defaultConfiguration); return new SentinelCircuitBreaker(id, conf.getEntryType(), conf.getRules()); } @Override protected SentinelConfigBuilder configBuilder(String id) { return new SentinelConfigBuilder(id); } @Override public void configureDefault( Function defaultConfiguration) { this.defaultConfiguration = defaultConfiguration; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/SentinelConfigBuilder.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.ArrayList; import java.util.List; import java.util.Optional; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import org.springframework.cloud.client.circuitbreaker.ConfigBuilder; import org.springframework.util.Assert; /** * @author Eric Zhao */ public class SentinelConfigBuilder implements ConfigBuilder { private String resourceName; private EntryType entryType; private List rules; public SentinelConfigBuilder() { } public SentinelConfigBuilder(String resourceName) { this.resourceName = resourceName; } public SentinelConfigBuilder resourceName(String resourceName) { this.resourceName = resourceName; return this; } public SentinelConfigBuilder entryType(EntryType entryType) { this.entryType = entryType; return this; } public SentinelConfigBuilder rules(List rules) { this.rules = rules; return this; } @Override public SentinelCircuitBreakerConfiguration build() { Assert.hasText(resourceName, "resourceName cannot be empty"); List rules = Optional.ofNullable(this.rules) .orElse(new ArrayList<>()); EntryType entryType = Optional.ofNullable(this.entryType).orElse(EntryType.OUT); return new SentinelCircuitBreakerConfiguration() .setResourceName(this.resourceName).setEntryType(entryType) .setRules(rules); } public static class SentinelCircuitBreakerConfiguration { private String resourceName; private EntryType entryType; private List rules; public String getResourceName() { return resourceName; } public SentinelCircuitBreakerConfiguration setResourceName(String resourceName) { this.resourceName = resourceName; return this; } public EntryType getEntryType() { return entryType; } public SentinelCircuitBreakerConfiguration setEntryType(EntryType entryType) { this.entryType = entryType; return this; } public List getRules() { return rules; } public SentinelCircuitBreakerConfiguration setRules(List rules) { this.rules = rules; return this; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/CircuitBreakerRuleChangeListener.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel.feign; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import com.alibaba.cloud.circuitbreaker.sentinel.SentinelConfigBuilder; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.json.JsonMapper; import org.springframework.beans.BeansException; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.cloud.client.circuitbreaker.AbstractCircuitBreakerFactory; import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.core.annotation.AnnotationUtils; /** * Sentinel circuit breaker config change listener. * * @author freeman * @since 2021.0.1.0 */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class CircuitBreakerRuleChangeListener implements ApplicationContextAware, ApplicationListener, SmartInitializingSingleton { private static final Logger LOGGER = LoggerFactory .getLogger(CircuitBreakerRuleChangeListener.class); private SentinelFeignClientProperties properties; /** * properties backup, prevent rules from being updated every time the container is * refreshed. */ private SentinelFeignClientProperties propertiesBackup; private AbstractCircuitBreakerFactory circuitBreakerFactory; private ApplicationContext applicationContext; @Override public void onApplicationEvent(RefreshScopeRefreshedEvent event) { ensureReady(); // No need to update the rules if (Objects.equals(properties, propertiesBackup)) { return; } clearRules(); // rebind configureDefault(); configureCustom(); updateBackup(); LOGGER.info("Sentinel circuit beaker rules refreshed: \n" + prettyPrint(properties.getRules())); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void afterSingletonsInstantiated() { this.propertiesBackup = applicationContext .getBean(SentinelFeignClientProperties.class).copy(); } private void ensureReady() { // Do not inject these beans directly, // as it will cause the bean to be initialized prematurely, // and we don't want to change the initialization order of the beans if (circuitBreakerFactory == null) { String[] names = applicationContext .getBeanNamesForType(AbstractCircuitBreakerFactory.class); if (names.length >= 1) { this.circuitBreakerFactory = applicationContext.getBean(names[0], AbstractCircuitBreakerFactory.class); } } if (properties == null) { this.properties = applicationContext .getBean(SentinelFeignClientProperties.class); } } private void clearRules() { clearCircuitBreakerFactory(); clearFeignClientRulesInDegradeManager(); } private void configureDefault() { configureDefault(properties, circuitBreakerFactory); } private void configureCustom() { configureCustom(properties, circuitBreakerFactory); } private void clearCircuitBreakerFactory() { Optional.ofNullable(getConfigurations(circuitBreakerFactory)) .ifPresent(Map::clear); } private void clearFeignClientRulesInDegradeManager() { // first, clear all manually configured feign clients and methods. propertiesBackup.getRules().keySet().stream() .filter(key -> !Objects.equals(key, propertiesBackup.getDefaultRule())) .forEach(resource -> Optional .ofNullable(DegradeRuleManager.getRulesOfResource(resource)) .ifPresent(Set::clear)); // Find all feign clients, clear the corresponding rules // NOTE: feign client name cannot be the same as the general resource name !!! Arrays.stream(applicationContext.getBeanNamesForAnnotation(FeignClient.class)) // A little trick, FeignClient bean name is full class name. // Simple exclusions, such as its subclass. .filter(beanName -> beanName.contains(".")).map(beanName -> { try { return Class.forName(beanName); } catch (ClassNotFoundException ignore) { // definitely not a feign client, just ignore return null; } }).filter(Objects::nonNull).forEach(clazz -> { FeignClient anno = clazz.getAnnotation(FeignClient.class); if (anno == null || AnnotationUtils.getValue(anno) == null) { return; } String feignClientName = AnnotationUtils.getValue(anno).toString(); Optional.ofNullable( DegradeRuleManager.getRulesOfResource(feignClientName)) .ifPresent(Set::clear); }); } private void updateBackup() { this.propertiesBackup = this.properties.copy(); } private String prettyPrint(Object o) { try { return JsonMapper.builder().configure(SerializationFeature.INDENT_OUTPUT, true).build() .writeValueAsString(o); } catch (Exception e) { LOGGER.error("JSON serialization err.", e); return "__JSON format err__"; } } // static method public static void configureCustom(SentinelFeignClientProperties properties, AbstractCircuitBreakerFactory factory) { properties.getRules().forEach((resourceName, degradeRules) -> { if (!Objects.equals(properties.getDefaultRule(), resourceName)) { factory.configure(builder -> ((SentinelConfigBuilder) builder) .rules(properties.getRules().getOrDefault(resourceName, new ArrayList<>())), resourceName); } }); } public static void configureDefault(SentinelFeignClientProperties properties, AbstractCircuitBreakerFactory factory) { List defaultConfigurations = properties.getRules() .getOrDefault(properties.getDefaultRule(), new ArrayList<>()); factory.configureDefault( resourceName -> new SentinelConfigBuilder(resourceName.toString()) .entryType(EntryType.OUT).rules(defaultConfigurations).build()); } public static Map getConfigurations( AbstractCircuitBreakerFactory circuitBreakerFactory) { try { Method method = AbstractCircuitBreakerFactory.class .getDeclaredMethod("getConfigurations"); method.setAccessible(true); return (Map) method.invoke(circuitBreakerFactory); } catch (Exception ignored) { } return Collections.emptyMap(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitNameResolver.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel.feign; import java.lang.reflect.Method; import java.util.Map; import feign.Feign; import feign.Target; import org.springframework.cloud.client.circuitbreaker.AbstractCircuitBreakerFactory; import org.springframework.cloud.openfeign.CircuitBreakerNameResolver; import static com.alibaba.cloud.circuitbreaker.sentinel.feign.CircuitBreakerRuleChangeListener.getConfigurations; /** * Feign client circuit breaker name resolver. * * @author freeman * @since 2021.0.1.0 * @see CircuitBreakerNameResolver */ @SuppressWarnings("rawtypes") public class FeignClientCircuitNameResolver implements CircuitBreakerNameResolver { private final Map configurations; public FeignClientCircuitNameResolver(AbstractCircuitBreakerFactory factory) { configurations = getConfigurations(factory); } @Override public String resolveCircuitBreakerName(String feignClientName, Target target, Method method) { String key = getKey(feignClientName, target, method); if (configurations != null && configurations.containsKey(key)) { return key; } return feignClientName; } private String getKey(String feignClientName, Target target, Method method) { String key = Feign.configKey(target.type(), method); return feignClientName + key.substring(key.indexOf('#')); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel.feign; import java.util.Collections; import java.util.List; import com.alibaba.cloud.circuitbreaker.sentinel.ReactiveSentinelCircuitBreakerFactory; import com.alibaba.cloud.circuitbreaker.sentinel.SentinelCircuitBreakerFactory; import feign.Feign; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.circuitbreaker.AbstractCircuitBreakerFactory; import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.cloud.openfeign.CircuitBreakerNameResolver; import org.springframework.cloud.openfeign.FeignClientFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static com.alibaba.cloud.circuitbreaker.sentinel.feign.CircuitBreakerRuleChangeListener.configureCustom; import static com.alibaba.cloud.circuitbreaker.sentinel.feign.CircuitBreakerRuleChangeListener.configureDefault; /** * Auto configuration for feign client circuit breaker rules. * * @author freeman * @since 2021.0.1.0 */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Feign.class, FeignClientFactoryBean.class }) @ConditionalOnProperty(name = "spring.cloud.circuitbreaker.sentinel.enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties(SentinelFeignClientProperties.class) public class SentinelFeignClientAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "feign.sentinel.enable-refresh-rules", havingValue = "true", matchIfMissing = true) public static class CircuitBreakerListenerConfiguration { @Bean public CircuitBreakerRuleChangeListener circuitBreakerRuleChangeListener() { return new CircuitBreakerRuleChangeListener(); } } @Configuration(proxyBeanMethods = false) public static class CircuitBreakerNameResolverConfiguration { @Bean @ConditionalOnMissingBean(CircuitBreakerNameResolver.class) public CircuitBreakerNameResolver feignClientCircuitNameResolver( ObjectProvider> provider) { List factories = provider .getIfAvailable(Collections::emptyList); if (factories.size() >= 1) { return new FeignClientCircuitNameResolver(factories.get(0)); } throw new IllegalArgumentException( "need one CircuitBreakerFactory/ReactiveCircuitBreakerFactory, but 0 found."); } } @Configuration(proxyBeanMethods = false) public static class SentinelCustomizerConfiguration { @Bean public Customizer configureRulesCustomizer( SentinelFeignClientProperties properties) { return factory -> { configureDefault(properties, factory); configureCustom(properties, factory); }; } } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = { "reactor.core.publisher.Mono", "reactor.core.publisher.Flux" }) public static class ReactiveSentinelCustomizerConfiguration { @Bean public Customizer reactiveConfigureRulesCustomizer( SentinelFeignClientProperties properties) { return factory -> { configureDefault(properties, factory); configureCustom(properties, factory); }; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel.feign; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import tools.jackson.databind.ObjectMapper; import org.springframework.boot.context.properties.ConfigurationProperties; /** * Sentinel feign client properties. * * @author freeman * @since 2021.0.1.0 */ @ConfigurationProperties("feign.sentinel") public class SentinelFeignClientProperties { /** * default rule name. */ private String defaultRule = "default"; /** * enable refresh circuit breaker rules from config center. */ private boolean enableRefreshRules = true; private Map> rules = new HashMap<>(); public String getDefaultRule() { return defaultRule; } public void setDefaultRule(String defaultRule) { this.defaultRule = defaultRule; } public boolean isEnableRefreshRules() { return enableRefreshRules; } public void setEnableRefreshRules(boolean enableRefreshRules) { this.enableRefreshRules = enableRefreshRules; } public Map> getRules() { return rules; } public void setRules(Map> rules) { this.rules = rules; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SentinelFeignClientProperties that = (SentinelFeignClientProperties) o; return enableRefreshRules == that.enableRefreshRules && Objects.equals(defaultRule, that.defaultRule) && Objects.equals(rules, that.rules); } @Override public int hashCode() { return Objects.hash(defaultRule, enableRefreshRules, rules); } public SentinelFeignClientProperties copy() { try { ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(this); return objectMapper.readValue(json, this.getClass()); } catch (Exception ignored) { } return new SentinelFeignClientProperties(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json ================================================ {"properties": [ { "name": "spring.cloud.circuitbreaker.sentinel.enabled", "type": "java.lang.Boolean", "description": "enable sentinel circuitbreaker or not." } ]} ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.circuitbreaker.sentinel.SentinelCircuitBreakerAutoConfiguration com.alibaba.cloud.circuitbreaker.sentinel.ReactiveSentinelCircuitBreakerAutoConfiguration com.alibaba.cloud.circuitbreaker.sentinel.feign.SentinelFeignClientAutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerIntegrationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.time.Duration; import java.util.Collections; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.ParameterizedTypeReference; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.client.WebClient; import static com.alibaba.cloud.circuitbreaker.sentinel.ReactiveSentinelCircuitBreakerIntegrationTest.Application; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** * @author Ryan Baxter */ @SpringBootTest(webEnvironment = RANDOM_PORT, classes = Application.class, properties = { "spring.cloud.discovery.client.health-indicator.enabled=false" }) public class ReactiveSentinelCircuitBreakerIntegrationTest { @LocalServerPort private int port = 0; @Autowired private Application.DemoControllerService service; @BeforeEach public void setup() { service.setPort(port); } @Test public void test() throws Exception { StepVerifier.create(service.normal()).expectNext("normal").verifyComplete(); StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); // Then in the next 5s, the fallback method should be called. for (int i = 0; i < 5; i++) { StepVerifier.create(service.slow()).expectNext("fallback").verifyComplete(); Thread.sleep(900); } Thread.sleep(500); // Half-open recovery (will re-open the circuit breaker). StepVerifier.create(service.slow()).expectNext("slow").verifyComplete(); StepVerifier.create(service.normalFlux()).expectNext("normalflux") .verifyComplete(); StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); // Then in the next 5s, the fallback method should be called. for (int i = 0; i < 5; i++) { StepVerifier.create(service.slowFlux()).expectNext("flux_fallback") .verifyComplete(); Thread.sleep(900); } Thread.sleep(500); // Half-open recovery (will re-open the circuit breaker). StepVerifier.create(service.slowFlux()).expectNext("slowflux").verifyComplete(); } @Configuration @EnableAutoConfiguration @RestController protected static class Application { @GetMapping("/slow") public Mono slow() { return Mono.just("slow").delayElement(Duration.ofMillis(80)); } @GetMapping("/normal") public Mono normal() { return Mono.just("normal"); } @GetMapping("/slow_flux") public Flux slowFlux() { return Flux.just("slow", "flux").delayElements(Duration.ofMillis(80)); } @GetMapping("normal_flux") public Flux normalFlux() { return Flux.just("normal", "flux"); } @Bean public Customizer slowCustomizer() { return factory -> { factory.configure( builder -> builder.rules(Collections .singletonList(new DegradeRule("slow_mono").setCount(50) .setSlowRatioThreshold(0.7).setMinRequestAmount(5) .setStatIntervalMs(30000).setTimeWindow(5))), "slow_mono"); factory.configure( builder -> builder.rules(Collections .singletonList(new DegradeRule("slow_mono").setCount(50) .setSlowRatioThreshold(0.7).setMinRequestAmount(5) .setStatIntervalMs(30000).setTimeWindow(5))), "slow_flux"); factory.configureDefault(id -> new SentinelConfigBuilder() .resourceName(id) .rules(Collections.singletonList(new DegradeRule(id) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) .setCount(5).setTimeWindow(10))) .build()); }; } @Service public static class DemoControllerService { private int port = 0; private ReactiveCircuitBreakerFactory cbFactory; DemoControllerService(ReactiveCircuitBreakerFactory cbFactory) { this.cbFactory = cbFactory; } public Mono slow() { return WebClient.builder().baseUrl("http://localhost:" + port).build() .get().uri("/slow").retrieve().bodyToMono(String.class) .transform(it -> cbFactory.create("slow_mono").run(it, t -> { t.printStackTrace(); return Mono.just("fallback"); })); } public Mono normal() { return WebClient.builder().baseUrl("http://localhost:" + port).build() .get().uri("/normal").retrieve().bodyToMono(String.class) .transform(it -> cbFactory.create("normal_mono").run(it, t -> { t.printStackTrace(); return Mono.just("fallback"); })); } public Flux slowFlux() { return WebClient.builder().baseUrl("http://localhost:" + port).build() .get().uri("/slow_flux").retrieve() .bodyToFlux(new ParameterizedTypeReference() { }).transform(it -> cbFactory.create("slow_flux").run(it, t -> { t.printStackTrace(); return Flux.just("flux_fallback"); })); } public Flux normalFlux() { return WebClient.builder().baseUrl("http://localhost:" + port).build() .get().uri("/normal_flux").retrieve().bodyToFlux(String.class) .transform(it -> cbFactory.create("normal_flux").run(it, t -> { t.printStackTrace(); return Flux.just("flux_fallback"); })); } public void setPort(int port) { this.port = port; } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/ReactiveSentinelCircuitBreakerTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.Arrays; import java.util.Collections; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; import static org.assertj.core.api.Assertions.assertThat; /** * @author Eric Zhao */ public class ReactiveSentinelCircuitBreakerTest { @Test public void testCreateWithNullRule() { String id = "testCreateReactiveCbWithNullRule"; ReactiveSentinelCircuitBreaker cb = new ReactiveSentinelCircuitBreaker(id, Collections.singletonList(null)); assertThat(Mono.just("foobar").transform(it -> cb.run(it)).block()) .isEqualTo("foobar"); assertThat(DegradeRuleManager.hasConfig(id)).isFalse(); } @Test public void runMono() { ReactiveCircuitBreaker cb = new ReactiveSentinelCircuitBreakerFactory() .create("foo"); assertThat(Mono.just("foobar").transform(it -> cb.run(it)).block()) .isEqualTo("foobar"); } @Test public void runMonoWithFallback() { ReactiveCircuitBreaker cb = new ReactiveSentinelCircuitBreakerFactory() .create("foo"); assertThat(Mono.error(new RuntimeException("boom")) .transform(it -> cb.run(it, t -> Mono.just("fallback"))).block()) .isEqualTo("fallback"); } @Test public void runFlux() { ReactiveCircuitBreaker cb = new ReactiveSentinelCircuitBreakerFactory() .create("foo"); assertThat(Flux.just("foobar", "hello world").transform(it -> cb.run(it)) .collectList().block()).isEqualTo(Arrays.asList("foobar", "hello world")); } @Test public void runFluxWithFallback() { ReactiveCircuitBreaker cb = new ReactiveSentinelCircuitBreakerFactory() .create("foo"); assertThat(Flux.error(new RuntimeException("boom")) .transform(it -> cb.run(it, t -> Flux.just("fallback"))).collectList() .block()).isEqualTo(Arrays.asList("fallback")); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerIntegrationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.resttestclient.TestRestTemplate; import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** * @author Eric Zhao */ @AutoConfigureTestRestTemplate @SpringBootTest(webEnvironment = RANDOM_PORT, classes = SentinelCircuitBreakerIntegrationTest.Application.class, properties = { "spring.cloud.discovery.client.health-indicator.enabled=false" }) public class SentinelCircuitBreakerIntegrationTest { @Autowired private Application.DemoControllerService service; @Test public void testSlow() throws Exception { assertThat(service.slow(true)).isEqualTo("slow"); assertThat(service.slow(true)).isEqualTo("slow"); assertThat(service.slow(true)).isEqualTo("slow"); assertThat(service.slow(false)).isEqualTo("slow"); assertThat(service.slow(false)).isEqualTo("slow"); assertThat(service.slow(true)).isEqualTo("slow"); assertThat(service.slow(true)).isEqualTo("slow"); // Then in the next 10s, the fallback method should be called. for (int i = 0; i < 5; i++) { assertThat(service.slow(true)).isEqualTo("fallback"); Thread.sleep(1000); } // Try a normal request. assertThat(service.slow(false)).isEqualTo("slow"); // Recovered. assertThat(service.slow(true)).isEqualTo("slow"); } @Test public void testNormal() { assertThat(service.normal()).isEqualTo("normal"); } @BeforeEach public void setUp() { DegradeRuleManager.loadRules(new ArrayList<>()); } @BeforeEach public void tearDown() { DegradeRuleManager.loadRules(new ArrayList<>()); } @Configuration @EnableAutoConfiguration @RestController protected static class Application { @GetMapping("/slow") public String slow(@RequestParam(required = false) Boolean slow) throws InterruptedException { if (slow == null || slow) { Thread.sleep(80); } return "slow"; } @GetMapping("/normal") public String normal() { return "normal"; } @Bean public Customizer slowCustomizer() { String slowId = "slow"; List rules = Collections.singletonList(new DegradeRule(slowId) .setGrade(RuleConstant.DEGRADE_GRADE_RT).setCount(50) .setSlowRatioThreshold(0.7).setMinRequestAmount(5) .setStatIntervalMs(30000).setTimeWindow(5)); return factory -> { factory.configure(builder -> builder.rules(rules), slowId); factory.configureDefault(id -> new SentinelConfigBuilder() .resourceName(id) .rules(Collections.singletonList(new DegradeRule(id) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) .setCount(10).setStatIntervalMs(10000).setTimeWindow(10))) .build()); }; } @Service public static class DemoControllerService { private TestRestTemplate rest; private CircuitBreakerFactory cbFactory; DemoControllerService(TestRestTemplate rest, CircuitBreakerFactory cbFactory) { this.rest = rest; this.cbFactory = cbFactory; } public String slow(boolean slow) { return cbFactory.create("slow").run( () -> rest.getForObject("/slow?slow=" + slow, String.class), t -> "fallback"); } public String normal() { return cbFactory.create("normal").run( () -> rest.getForObject("/normal", String.class), t -> "fallback"); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/SentinelCircuitBreakerTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel; import java.util.ArrayList; import java.util.Collections; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; import static org.assertj.core.api.Assertions.assertThat; /** * @author Eric Zhao */ public class SentinelCircuitBreakerTest { @AfterEach public void tearDown() { // Clear the rules. DegradeRuleManager.loadRules(new ArrayList<>()); } @Test public void testCreateDirectlyThenRun() { // Create a circuit breaker without any circuit breaking rules. CircuitBreaker cb = new SentinelCircuitBreaker( "testSentinelCreateDirectlyThenRunA"); assertThat(cb.run(() -> "Sentinel")).isEqualTo("Sentinel"); assertThat(DegradeRuleManager.hasConfig("testSentinelCreateDirectlyThenRunA")) .isFalse(); CircuitBreaker cb2 = new SentinelCircuitBreaker( "testSentinelCreateDirectlyThenRunB", Collections.singletonList( new DegradeRule("testSentinelCreateDirectlyThenRunB") .setCount(100).setTimeWindow(10))); assertThat(cb2.run(() -> "Sentinel")).isEqualTo("Sentinel"); assertThat(DegradeRuleManager.hasConfig("testSentinelCreateDirectlyThenRunB")) .isTrue(); } @Test public void testCreateWithNullRule() { String id = "testCreateCbWithNullRule"; CircuitBreaker cb = new SentinelCircuitBreaker(id, Collections.singletonList(null)); assertThat(cb.run(() -> "Sentinel")).isEqualTo("Sentinel"); assertThat(DegradeRuleManager.hasConfig(id)).isFalse(); } @Test public void testCreateFromFactoryThenRun() { CircuitBreaker cb = new SentinelCircuitBreakerFactory().create("testSentinelRun"); assertThat(cb.run(() -> "foobar")).isEqualTo("foobar"); } @Test public void testRunWithFallback() { CircuitBreaker cb = new SentinelCircuitBreakerFactory() .create("testSentinelRunWithFallback"); assertThat(cb.run(() -> { throw new RuntimeException("boom"); }, t -> "fallback")).isEqualTo("fallback"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitBreakerRuleIntegrationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.circuitbreaker.sentinel.feign; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import static com.alibaba.cloud.circuitbreaker.sentinel.feign.FeignClientCircuitBreakerRuleIntegrationTest.Application; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; /** * @author freeman */ @SpringBootTest(webEnvironment = DEFINED_PORT, classes = Application.class, properties = { "server.port=10101", "spring.cloud.openfeign.circuitbreaker.enabled=true", "feign.sentinel.default-rule=default", "feign.sentinel.rules.default[0].grade=2", "feign.sentinel.rules.default[0].count=2", "feign.sentinel.rules.default[0].timeWindow=1", "feign.sentinel.rules.default[0].statIntervalMs=30000", "feign.sentinel.rules.default[0].minRequestAmount=5", "feign.sentinel.rules.user[0].grade=2", "feign.sentinel.rules.user[0].count=2", "feign.sentinel.rules.user[0].timeWindow=1", "feign.sentinel.rules.user[0].statIntervalMs=30000", "feign.sentinel.rules.user[0].minRequestAmount=5", "feign.sentinel.rules.[user#specificFeignMethod(boolean)][0].grade=2", "feign.sentinel.rules.[user#specificFeignMethod(boolean)][0].count=1", "feign.sentinel.rules.[user#specificFeignMethod(boolean)][0].timeWindow=1", "feign.sentinel.rules.[user#specificFeignMethod(boolean)][0].statIntervalMs=30000", "feign.sentinel.rules.[user#specificFeignMethod(boolean)][0].minRequestAmount=5" }) public class FeignClientCircuitBreakerRuleIntegrationTest { @Autowired private Application.UserClient userClient; @Autowired private Application.OrderClient orderClient; @Test public void testDefaultRule() throws Exception { // test default configuration is working // ok assertThat(orderClient.defaultConfig(true)).isEqualTo("ok"); assertThat(orderClient.defaultConfig(true)).isEqualTo("ok"); assertThat(orderClient.defaultConfig(true)).isEqualTo("ok"); assertThat(orderClient.defaultConfig(true)).isEqualTo("ok"); assertThat(orderClient.defaultConfig(true)).isEqualTo("ok"); // occur exception assertThat(orderClient.defaultConfig(false)).isEqualTo("fallback"); assertThat(orderClient.defaultConfig(false)).isEqualTo("fallback"); // the 3rd exception, circuit breaker open assertThat(orderClient.defaultConfig(false)).isEqualTo("fallback"); // sleep 300, ensure circuit breaker status is open. Thread.sleep(300); // test circuit breaker open assertThat(orderClient.defaultConfig(true)).isEqualTo("fallback"); assertThat(orderClient.defaultConfig(false)).isEqualTo("fallback"); // longer than timeWindow, circuit breaker half open Thread.sleep(1200L); // let circuit breaker close assertThat(orderClient.defaultConfig(true)).isEqualTo("ok"); assertThat(orderClient.defaultConfig(false)).isEqualTo("fallback"); } @Test public void testSpecificFeignRule() throws Exception { // test specific Feign client configuration is working // ok assertThat(userClient.specificFeign(true)).isEqualTo("ok"); assertThat(userClient.specificFeign(true)).isEqualTo("ok"); assertThat(userClient.specificFeign(true)).isEqualTo("ok"); assertThat(userClient.specificFeign(true)).isEqualTo("ok"); assertThat(userClient.specificFeign(true)).isEqualTo("ok"); // occur exception assertThat(userClient.specificFeign(false)).isEqualTo("fallback"); assertThat(userClient.specificFeign(false)).isEqualTo("fallback"); // the 3rd exception, circuit breaker open assertThat(userClient.specificFeign(false)).isEqualTo("fallback"); Thread.sleep(300); // test circuit breaker open assertThat(userClient.specificFeign(true)).isEqualTo("fallback"); assertThat(userClient.specificFeign(false)).isEqualTo("fallback"); // longer than timeWindow, circuit breaker half open Thread.sleep(1200L); // let circuit breaker close assertThat(userClient.specificFeign(true)).isEqualTo("ok"); assertThat(userClient.specificFeign(false)).isEqualTo("fallback"); } @Test public void testSpecificFeignMethodRule() throws Exception { // test specific Feign client method configuration is working // ok assertThat(userClient.specificFeignMethod(true)).isEqualTo("ok"); assertThat(userClient.specificFeignMethod(true)).isEqualTo("ok"); assertThat(userClient.specificFeignMethod(true)).isEqualTo("ok"); assertThat(userClient.specificFeignMethod(true)).isEqualTo("ok"); assertThat(userClient.specificFeignMethod(true)).isEqualTo("ok"); // occur exception assertThat(userClient.specificFeignMethod(false)).isEqualTo("fallback"); // occur the 2nd exception, circuit breaker open assertThat(userClient.specificFeignMethod(false)).isEqualTo("fallback"); Thread.sleep(300); // test circuit breaker is open assertThat(userClient.specificFeignMethod(true)).isEqualTo("fallback"); assertThat(userClient.specificFeignMethod(false)).isEqualTo("fallback"); // longer than timeWindow, circuit breaker half open Thread.sleep(1200L); // let circuit breaker close assertThat(userClient.specificFeignMethod(true)).isEqualTo("ok"); assertThat(userClient.specificFeignMethod(false)).isEqualTo("fallback"); } @Configuration @EnableAutoConfiguration @RestController @EnableFeignClients protected static class Application { @FeignClient(value = "user", url = "http://localhost:${server.port}", fallback = UserClientFallback.class) interface UserClient { @GetMapping("/specificFeign/{success}") String specificFeign(@PathVariable boolean success); @GetMapping("/specificFeignMethod/{success}") String specificFeignMethod(@PathVariable boolean success); } @FeignClient(value = "order", url = "http://localhost:${server.port}", fallback = OrderClientFallback.class) interface OrderClient { @GetMapping("/defaultConfig/{success}") String defaultConfig(@PathVariable boolean success); } @Component static class UserClientFallback implements UserClient { @Override public String specificFeign(boolean success) { return "fallback"; } @Override public String specificFeignMethod(boolean success) { return "fallback"; } } @Component static class OrderClientFallback implements OrderClient { @Override public String defaultConfig(boolean success) { return "fallback"; } } @RestController static class TestController { @GetMapping("/specificFeign/{success}") public String specificFeign(@PathVariable boolean success) { if (success) { return "ok"; } throw new RuntimeException("failed"); } @GetMapping("/defaultConfig/{success}") String defaultConfig(@PathVariable boolean success) { if (success) { return "ok"; } throw new RuntimeException("failed"); } @GetMapping("/specificFeignMethod/{success}") String specificFeignMethod(@PathVariable boolean success) { if (success) { return "ok"; } throw new RuntimeException("failed"); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/pom.xml ================================================ 4.0.0 com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml spring-cloud-starter-alibaba-nacos-config Spring Cloud Starter Alibaba Nacos Config com.alibaba.cloud spring-cloud-alibaba-commons com.alibaba.cloud spring-alibaba-nacos-config org.springframework.boot spring-boot-actuator-autoconfigure true org.springframework.boot spring-boot-configuration-processor true com.alibaba.nacos nacos-client org.springframework.cloud spring-cloud-commons org.springframework.cloud spring-cloud-context org.springframework.boot spring-boot-starter-web test org.springframework.boot spring-boot-starter-test test org.slf4j slf4j-api jakarta.annotation jakarta.annotation-api ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigSpringCloudAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import com.alibaba.cloud.nacos.configdata.NacosConfigRefreshEventListener; import com.alibaba.cloud.nacos.refresh.SmartConfigurationPropertiesRebinder; import com.alibaba.cloud.nacos.refresh.condition.ConditionalOnNonDefaultBehavior; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** * @author juven.xuxb * @author freeman */ @Configuration(proxyBeanMethods = false) @Conditional(NacosConfigEnabledCondition.class) public class NacosConfigSpringCloudAutoConfiguration { @Bean @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) @ConditionalOnNonDefaultBehavior public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) { // If using default behavior, not use SmartConfigurationPropertiesRebinder. // Minimize te possibility of making mistakes. return new SmartConfigurationPropertiesRebinder(beans); } @Bean(name = "nacosConfigSpringCloudRefreshEventListener") public NacosConfigRefreshEventListener nacosConfigRefreshEventListener() { return new NacosConfigRefreshEventListener(); } @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.nacos.config.enable-check-bootstrap", matchIfMissing = true) @ConditionalOnClass(name = "org.springframework.cloud.bootstrap.marker.Marker") static class BootstrapDetectionConfiguration { BootstrapDetectionConfiguration() { LoggerFactory.getLogger(BootstrapDetectionConfiguration.class) .warn("Including 'org.springframework.cloud:spring-cloud-starter-bootstrap' will prevent Nacos Config from working properly." + "Please remove this dependency. For details, please refer to: https://github.com/alibaba/spring-cloud-alibaba/issues/4259"); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/SpringCloudNacosPropertiesPrefixProvider.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; public class SpringCloudNacosPropertiesPrefixProvider implements NacosPropertiesPrefixProvider { @Override public String getPrefix() { return "spring.cloud.nacos"; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceLocator.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.client; import java.util.List; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.cloud.nacos.NacosConfigProperties; import com.alibaba.cloud.nacos.NacosPropertySourceRepository; import com.alibaba.cloud.nacos.parser.NacosDataParserHandler; import com.alibaba.cloud.nacos.refresh.NacosContextRefresher; import com.alibaba.nacos.api.config.ConfigService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.bootstrap.config.PropertySourceLocator; import org.springframework.core.annotation.Order; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; /** * @author xiaojing * @author pbting */ @Order(0) @Deprecated public class NacosPropertySourceLocator implements PropertySourceLocator { private static final Logger log = LoggerFactory .getLogger(NacosPropertySourceLocator.class); private static final String NACOS_PROPERTY_SOURCE_NAME = "NACOS"; private static final String SEP1 = "-"; private static final String DOT = "."; private NacosPropertySourceBuilder nacosPropertySourceBuilder; private NacosConfigProperties nacosConfigProperties; private NacosConfigManager nacosConfigManager; /** * recommend to use * {@link NacosPropertySourceLocator#NacosPropertySourceLocator(com.alibaba.cloud.nacos.NacosConfigManager)}. * @param nacosConfigProperties nacosConfigProperties */ @Deprecated public NacosPropertySourceLocator(NacosConfigProperties nacosConfigProperties) { this.nacosConfigProperties = nacosConfigProperties; } public NacosPropertySourceLocator(NacosConfigManager nacosConfigManager) { this.nacosConfigManager = nacosConfigManager; this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties(); } @Override public PropertySource locate(Environment env) { ConfigService configService = nacosConfigManager.getConfigService(); if (null == configService) { log.warn("no instance of config service found, can't load config from nacos"); return null; } long timeout = nacosConfigProperties.getTimeout(); nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout); String name = nacosConfigProperties.getName(); String dataIdPrefix = nacosConfigProperties.getPrefix(); if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = name; } if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = env.getProperty("spring.application.name"); } CompositePropertySource composite = new CompositePropertySource( NACOS_PROPERTY_SOURCE_NAME); loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env); return composite; } /** * load configuration of application. */ private void loadApplicationConfiguration( CompositePropertySource compositePropertySource, String dataIdPrefix, NacosConfigProperties properties, Environment environment) { String fileExtension = properties.getFileExtension(); String nacosGroup = properties.getGroup(); // load directly once by default loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup, fileExtension, true); // load with suffix, which have a higher priority than the default loadNacosDataIfPresent(compositePropertySource, dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true); // Loaded with profile, which have a higher priority than the suffix for (String profile : environment.getActiveProfiles()) { String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension; loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup, fileExtension, true); } } private void loadNacosConfiguration(final CompositePropertySource composite, List configs) { for (NacosConfigProperties.Config config : configs) { loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(), NacosDataParserHandler.getInstance() .getFileExtension(config.getDataId()), config.isRefresh()); } } private void checkConfiguration(List configs, String tips) { for (int i = 0; i < configs.size(); i++) { String dataId = configs.get(i).getDataId(); if (dataId == null || dataId.trim().length() == 0) { throw new IllegalStateException(String.format( "the [ spring.cloud.nacos.config.%s[%s] ] must give a dataId", tips, i)); } } } private void loadNacosDataIfPresent(final CompositePropertySource composite, final String dataId, final String group, String fileExtension, boolean isRefreshable) { if (null == dataId || dataId.trim().length() < 1) { return; } if (null == group || group.trim().length() < 1) { return; } NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group, fileExtension, isRefreshable); this.addFirstPropertySource(composite, propertySource, false); } private NacosPropertySource loadNacosPropertySource(final String dataId, final String group, String fileExtension, boolean isRefreshable) { if (NacosContextRefresher.getRefreshCount() != 0) { if (!isRefreshable) { return NacosPropertySourceRepository.getNacosPropertySource(dataId, group); } } return nacosPropertySourceBuilder.build(dataId, group, fileExtension, isRefreshable); } /** * Add the nacos configuration to the first place and maybe ignore the empty * configuration. */ private void addFirstPropertySource(final CompositePropertySource composite, NacosPropertySource nacosPropertySource, boolean ignoreEmpty) { if (null == nacosPropertySource || null == composite) { return; } if (ignoreEmpty && nacosPropertySource.getSource().isEmpty()) { return; } composite.addFirstPropertySource(nacosPropertySource); } public void setNacosConfigManager(NacosConfigManager nacosConfigManager) { this.nacosConfigManager = nacosConfigManager; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataMissingEnvironmentPostProcessor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.configdata; import com.alibaba.cloud.nacos.NacosPropertiesPrefixer; import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.cloud.commons.ConfigDataMissingEnvironmentPostProcessor; import org.springframework.cloud.util.PropertyUtils; import org.springframework.core.env.Environment; import static com.alibaba.cloud.nacos.configdata.NacosConfigDataLocationResolver.PREFIX; /** * * @author freeman * @since 2021.0.1.0 */ public class NacosConfigDataMissingEnvironmentPostProcessor extends ConfigDataMissingEnvironmentPostProcessor { /** * after {@link ConfigDataEnvironmentPostProcessor}. */ public static final int ORDER = ConfigDataEnvironmentPostProcessor.ORDER + 1000; @Override public int getOrder() { return ORDER; } @Override protected boolean shouldProcessEnvironment(Environment environment) { // don't run if using bootstrap or legacy processing if (PropertyUtils.bootstrapEnabled(environment) || PropertyUtils.useLegacyProcessing(environment)) { return false; } String prefix = NacosPropertiesPrefixer.getPrefix(environment); String configPrefix = prefix + ".config"; boolean configEnabled = environment.getProperty( configPrefix + ".enabled", Boolean.class, true); boolean importCheckEnabled = environment.getProperty( configPrefix + ".import-check.enabled", Boolean.class, true); return configEnabled && importCheckEnabled; } @Override protected String getPrefix() { return PREFIX; } static class ImportExceptionFailureAnalyzer extends AbstractFailureAnalyzer { @Override protected FailureAnalysis analyze(Throwable rootFailure, ImportException cause) { String description; if (cause.missingPrefix) { description = "The spring.config.import property is missing a " + PREFIX + " entry"; } else { description = "No spring.config.import property has been defined"; } String action = "Add a spring.config.import=nacos: property to your configuration.\n" + "\tIf configuration is not required add spring.config.import=optional:nacos: instead.\n" + "\tTo disable this check, set spring.cloud.nacos.config.import-check.enabled=false."; return new FailureAnalysis(description, action, cause); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigRefreshEventListener.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.configdata; import com.alibaba.cloud.nacos.refresh.NacosConfigRefreshEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.endpoint.event.RefreshEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.SmartApplicationListener; /** * * @author shiyiyue * @since 2024.10.17 */ public class NacosConfigRefreshEventListener implements SmartApplicationListener, ApplicationContextAware { private final static Logger log = LoggerFactory.getLogger(NacosConfigRefreshEventListener.class); private ApplicationContext applicationContext; @Override public boolean supportsEventType(Class eventType) { return NacosConfigRefreshEvent.class.isAssignableFrom(eventType); } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Override public void onApplicationEvent(ApplicationEvent event) { applicationContext.publishEvent(new RefreshEvent(event.getSource(), null, "Refresh Nacos config")); if (log.isDebugEnabled()) { log.debug(String.format("Refresh Nacos config group=%s,dataId=%s", ((NacosConfigRefreshEvent) event).getGroup(), ((NacosConfigRefreshEvent) event).getDataId())); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/RefreshBehavior.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.refresh; import org.springframework.boot.context.properties.ConfigurationPropertiesBean; /** * Refresh behavior. * * @author freeman * @since 2021.0.1.1 */ public enum RefreshBehavior { /** * Refresh all {@link ConfigurationPropertiesBean}s. */ ALL_BEANS, /** * Refresh specific {@link ConfigurationPropertiesBean} base on change key. */ SPECIFIC_BEAN, } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/SmartConfigurationPropertiesRebinder.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.refresh; import java.lang.reflect.Field; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import org.springframework.beans.BeansException; import org.springframework.boot.context.properties.ConfigurationPropertiesBean; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ReflectionUtils; /** * Extend {@link ConfigurationPropertiesRebinder}. *

* Spring team doesn't seem to support single {@link ConfigurationPropertiesBean} refresh. *

* SmartConfigurationPropertiesRebinder can refresh specific * {@link ConfigurationPropertiesBean} base on the change keys. *

* NOTE: We still use Spring's default behavior (full refresh) as default * behavior, This feature can be considered an advanced feature, it may not be as stable * as the default behavior. * * @author freeman * @since 2021.0.1.1 */ public class SmartConfigurationPropertiesRebinder extends ConfigurationPropertiesRebinder { private Map beanMap; private ApplicationContext applicationContext; private RefreshBehavior refreshBehavior; public SmartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) { super(beans); fillBeanMap(beans); } @SuppressWarnings("unchecked") private void fillBeanMap(ConfigurationPropertiesBeans beans) { this.beanMap = new HashMap<>(); Field field = ReflectionUtils.findField(beans.getClass(), "beans"); if (field != null) { field.setAccessible(true); this.beanMap.putAll((Map) Optional .ofNullable(ReflectionUtils.getField(field, beans)) .orElse(Collections.emptyMap())); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { super.setApplicationContext(applicationContext); this.applicationContext = applicationContext; this.refreshBehavior = this.applicationContext.getEnvironment().getProperty( "spring.cloud.nacos.config.refresh-behavior", RefreshBehavior.class, RefreshBehavior.ALL_BEANS); } @Override public void onApplicationEvent(EnvironmentChangeEvent event) { if (this.applicationContext.equals(event.getSource()) // Backwards compatible || event.getKeys().equals(event.getSource())) { switch (refreshBehavior) { case SPECIFIC_BEAN -> rebindSpecificBean(event); default -> rebind(); } } } private void rebindSpecificBean(EnvironmentChangeEvent event) { Set refreshedSet = new HashSet<>(); beanMap.forEach((name, bean) -> event.getKeys().forEach(changeKey -> { String prefix = AnnotationUtils.getValue(bean.getAnnotation()).toString(); // prevent multiple refresh one ConfigurationPropertiesBean. if (changeKey.startsWith(prefix) && refreshedSet.add(name)) { rebind(name); } })); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/condition/ConditionalOnNonDefaultBehavior.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.refresh.condition; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.alibaba.cloud.nacos.refresh.RefreshBehavior; import org.springframework.context.annotation.Conditional; /** * Condition on {@link RefreshBehavior} is NOT default. * * @author freeman * @since 2021.0.1.1 */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(com.alibaba.cloud.nacos.refresh.condition.NonDefaultBehaviorCondition.class) public @interface ConditionalOnNonDefaultBehavior { } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/condition/NonDefaultBehaviorCondition.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.refresh.condition; import com.alibaba.cloud.nacos.NacosPropertiesPrefixer; import com.alibaba.cloud.nacos.refresh.RefreshBehavior; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; /** * @author freeman * @since 2021.0.1.1 */ public class NonDefaultBehaviorCondition extends SpringBootCondition { private static final RefreshBehavior DEFAULT_REFRESH_BEHAVIOR = RefreshBehavior.ALL_BEANS; @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { String prefix = NacosPropertiesPrefixer.getPrefix(context.getEnvironment()); RefreshBehavior behavior = context.getEnvironment().getProperty( prefix + ".config.refresh-behavior", RefreshBehavior.class, DEFAULT_REFRESH_BEHAVIOR); if (DEFAULT_REFRESH_BEHAVIOR == behavior) { return ConditionOutcome.noMatch("no matched"); } return ConditionOutcome.match("matched"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json ================================================ { "properties": [ { "name": "spring.cloud.nacos.server-addr", "type": "java.lang.String", "defaultValue": "127.0.0.1:8848", "description": "nacos server address." }, { "name": "spring.cloud.nacos.config.server-addr", "type": "java.lang.String", "defaultValue": "${spring.cloud.nacos.server-addr}", "description": "nacos config server address." }, { "name": "spring.cloud.nacos.config.encode", "type": "java.lang.String", "defaultValue": "UTF-8", "description": "default encode for nacos config content." }, { "name": "spring.cloud.nacos.config.prefix", "type": "java.lang.String", "defaultValue": "${spring.application.name}", "description": "the prefix of dataId, nacos config data meta info. dataId = prefix + '-' + ${spring.active.profile} + `.` + ${spring.cloud.nacos.config.file-extension}." }, { "name": "spring.cloud.nacos.config.file-extension", "type": "java.lang.String", "defaultValue": "properties", "description": "the suffix of nacos config dataId, also the file extension of config content, only support properties now." }, { "name": "spring.cloud.nacos.config.shared-dataids", "type": "java.lang.String", "description": "the dataids for configurable multiple shared configurations , multiple separated by commas ." }, { "name": "spring.cloud.nacos.config.shared-configs", "type": "java.util.List", "description": "a set of shared configurations .e.g: spring.cloud.nacos.config.shared-configs[0]=xxx ." }, { "name": "spring.cloud.nacos.config.refreshable-dataids", "type": "java.lang.String", "description": "refreshable dataids , multiple separated by commas .Not providing support,the need to refresh is specified by the respective refresh configuration." }, { "name": "spring.cloud.nacos.config.ext-config", "type": "java.util.List", "description": "a set of extended configurations ." }, { "name": "spring.cloud.nacos.config.extension-configs", "type": "java.util.List", "description": "a set of extensional configurations .e.g: spring.cloud.nacos.config.extension-configs[0]=xxx ." }, { "name": "spring.cloud.nacos.config.refresh-enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "the master switch for refresh configuration, it default opened(true)." }, { "name": "spring.cloud.nacos.config.enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "enable nacos config or not." }, { "name": "spring.cloud.nacos.config.username", "type": "java.lang.String", "defaultValue": "${spring.cloud.nacos.username}", "description": "nacos config service's userName to authenticate." }, { "name": "spring.cloud.nacos.config.import-check.enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "Whether to enable import-check." }, { "name": "spring.cloud.nacos.config.password", "type": "java.lang.String", "defaultValue": "${spring.cloud.nacos.password}", "description": "nacos config service's password to authenticate." }, { "name": "spring.cloud.nacos.username", "type": "java.lang.String", "description": "nacos userName to authenticate." }, { "name": "spring.cloud.nacos.password", "type": "java.lang.String", "description": "nacos password to authenticate." }, { "name": "spring.cloud.nacos.config.refresh-behavior", "type": "com.alibaba.cloud.nacos.refresh.RefreshBehavior", "defaultValue": "all_beans", "description": "ConfigurationPropertiesBean refresh behavior." }, { "name": "spring.cloud.nacos.config.preference", "type": "com.alibaba.cloud.nacos.configdata.ConfigPreference", "defaultValue": "local", "description": "Config preference." }, { "name": "spring.cloud.nacos.config.group", "type": "java.lang.String", "defaultValue": "DEFAULT_GROUP", "description": "Config group,group is config data meta info." }, { "name": "spring.cloud.nacos.config.timeout", "type": "java.lang.Integer", "defaultValue": "3000", "description": "Config timeout." }, { "name": "spring.cloud.nacos.config.max-retry", "type": "java.lang.String", "defaultValue": "", "description": "Config maximum number of tolerable server reconnection errors." }, { "name": "spring.cloud.nacos.config.config-long-poll-timeout", "type": "java.lang.String", "defaultValue": "", "description": "Config long poll timeout." }, { "name": "spring.cloud.nacos.config.config-retry-time", "type": "java.lang.String", "defaultValue": "", "description": "Config failure retry time." }, { "name": "spring.cloud.nacos.config.enable-remote-sync-config", "type": "java.lang.Boolean", "defaultValue": "false", "description": "Config enable remote sync config." }, { "name": "spring.cloud.nacos.config.endpoint", "type": "java.lang.String", "defaultValue": "", "description": "Config endpoint." }, { "name": "spring.cloud.nacos.config.namespace", "type": "java.lang.String", "defaultValue": "", "description": "Config namespace." }, { "name": "spring.cloud.nacos.config.access-key", "type": "java.lang.String", "defaultValue": "", "description": "Config access key for namespace." }, { "name": "spring.cloud.nacos.config.secret-key", "type": "java.lang.String", "defaultValue": "", "description": "Config secret key for namespace." }, { "name": "spring.cloud.nacos.config.ram-role-name", "type": "java.lang.String", "defaultValue": "", "description": "Config ole name for aliyun ram." }, { "name": "spring.cloud.nacos.config.context-path", "type": "java.lang.String", "defaultValue": "", "description": "Config context path." }, { "name": "spring.cloud.nacos.config.cluster-name", "type": "java.lang.String", "defaultValue": "", "description": "Config cluster name." }, { "name": "spring.cloud.nacos.name", "type": "java.lang.String", "defaultValue": "", "description": "Config dataId name." } ] } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/services/com.alibaba.cloud.nacos.NacosPropertiesPrefixProvider ================================================ com.alibaba.cloud.nacos.SpringCloudNacosPropertiesPrefixProvider ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.nacos.NacosConfigSpringCloudAutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.diagnostics.FailureAnalyzer=\ com.alibaba.cloud.nacos.configdata.NacosConfigDataMissingEnvironmentPostProcessor.ImportExceptionFailureAnalyzer org.springframework.boot.EnvironmentPostProcessor=\ com.alibaba.cloud.nacos.configdata.NacosConfigDataMissingEnvironmentPostProcessor ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/SmartConfigurationPropertiesRebinderIntegrationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import com.alibaba.cloud.nacos.refresh.RefreshBehavior; import com.alibaba.cloud.nacos.refresh.SmartConfigurationPropertiesRebinder; import org.junit.jupiter.api.Test; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; /** * {@link SmartConfigurationPropertiesRebinder} tester. * * @author freeman */ public class SmartConfigurationPropertiesRebinderIntegrationTest { ConfigurableApplicationContext context; @Test public void testUsingSmartConfigurationPropertiesRebinder_whenBehaviorIsNotDefault() { context = new SpringApplicationBuilder(RebinderConfiguration.class) .web(WebApplicationType.NONE) .properties("spring.cloud.nacos.config.refresh-behavior=specific_bean") .properties("spring.cloud.nacos.server-addr=123.123.123.123:8848") .properties("spring.cloud.nacos.config.import-check.enabled=false") .properties("spring.config.import=nacos:test.properties").run(); ConfigurationPropertiesRebinder rebinder = context .getBean(ConfigurationPropertiesRebinder.class); assertThat(rebinder.getClass()) .isEqualTo(SmartConfigurationPropertiesRebinder.class); RefreshBehavior refreshBehavior = (RefreshBehavior) ReflectionTestUtils .getField(rebinder, "refreshBehavior"); assertThat(refreshBehavior).isEqualTo(RefreshBehavior.SPECIFIC_BEAN); } @Test public void testUsingConfigurationPropertiesRebinder_whenBehaviorIsDefault() { context = new SpringApplicationBuilder(RebinderConfiguration.class) .web(WebApplicationType.NONE) .properties("spring.cloud.nacos.server-addr=123.123.123.123:8848") .properties("spring.cloud.nacos.config.import-check.enabled=false") .properties("spring.config.import=nacos:test.properties").run(); ConfigurationPropertiesRebinder rebinder = context .getBean(ConfigurationPropertiesRebinder.class); assertThat(rebinder.getClass()).isEqualTo(ConfigurationPropertiesRebinder.class); } @Configuration @ImportAutoConfiguration({ NacosConfigSpringCloudAutoConfiguration.class }) @EnableAutoConfiguration public static class RebinderConfiguration { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataMissingEnvironmentPostProcessorTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.configdata; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.boot.SpringApplication; import org.springframework.mock.env.MockEnvironment; /** * * @author Ryan Baxter * @author freeman */ class NacosConfigDataMissingEnvironmentPostProcessorTest { @Test void noSpringConfigImport() { MockEnvironment environment = new MockEnvironment(); SpringApplication app = Mockito.mock(SpringApplication.class); NacosConfigDataMissingEnvironmentPostProcessor processor = new NacosConfigDataMissingEnvironmentPostProcessor(); Assertions.assertThatThrownBy(() -> processor.postProcessEnvironment(environment, app)) .isInstanceOf( NacosConfigDataMissingEnvironmentPostProcessor.ImportException.class); } @Test void boostrap() { MockEnvironment environment = new MockEnvironment(); environment.setProperty("spring.cloud.bootstrap.enabled", "true"); SpringApplication app = Mockito.mock(SpringApplication.class); NacosConfigDataMissingEnvironmentPostProcessor processor = new NacosConfigDataMissingEnvironmentPostProcessor(); Assertions.assertThatCode(() -> processor.postProcessEnvironment(environment, app)) .doesNotThrowAnyException(); } @Test void legacy() { MockEnvironment environment = new MockEnvironment(); environment.setProperty("spring.config.use-legacy-processing", "true"); SpringApplication app = Mockito.mock(SpringApplication.class); NacosConfigDataMissingEnvironmentPostProcessor processor = new NacosConfigDataMissingEnvironmentPostProcessor(); Assertions.assertThatCode(() -> processor.postProcessEnvironment(environment, app)) .doesNotThrowAnyException(); } @Test void configNotEnabled() { MockEnvironment environment = new MockEnvironment(); environment.setProperty("spring.cloud.nacos.config.enabled", "false"); SpringApplication app = Mockito.mock(SpringApplication.class); NacosConfigDataMissingEnvironmentPostProcessor processor = new NacosConfigDataMissingEnvironmentPostProcessor(); Assertions.assertThatCode(() -> processor.postProcessEnvironment(environment, app)) .doesNotThrowAnyException(); } @Test void importCheckNotEnabled() { MockEnvironment environment = new MockEnvironment(); environment.setProperty("spring.cloud.nacos.config.import-check.enabled", "false"); SpringApplication app = Mockito.mock(SpringApplication.class); NacosConfigDataMissingEnvironmentPostProcessor processor = new NacosConfigDataMissingEnvironmentPostProcessor(); Assertions.assertThatCode(() -> processor.postProcessEnvironment(environment, app)) .doesNotThrowAnyException(); } @Test void importSinglePropertySource() { MockEnvironment environment = new MockEnvironment(); environment.setProperty("spring.config.import", "nacos:test.yml"); SpringApplication app = Mockito.mock(SpringApplication.class); NacosConfigDataMissingEnvironmentPostProcessor processor = new NacosConfigDataMissingEnvironmentPostProcessor(); Assertions.assertThatCode(() -> processor.postProcessEnvironment(environment, app)) .doesNotThrowAnyException(); } @Test void importMultiplePropertySource() { MockEnvironment environment = new MockEnvironment(); environment.setProperty("spring.config.import", "nacos:test.yml"); SpringApplication app = Mockito.mock(SpringApplication.class); NacosConfigDataMissingEnvironmentPostProcessor processor = new NacosConfigDataMissingEnvironmentPostProcessor(); Assertions.assertThatCode(() -> processor.postProcessEnvironment(environment, app)) .doesNotThrowAnyException(); } @Test void importMultiplePropertySourceAsList() { MockEnvironment environment = new MockEnvironment(); environment.setProperty("spring.config.import[0]", "nacos:test.yml"); environment.setProperty("spring.config.import[1]", "file:./app.properties"); SpringApplication app = Mockito.mock(SpringApplication.class); NacosConfigDataMissingEnvironmentPostProcessor processor = new NacosConfigDataMissingEnvironmentPostProcessor(); Assertions.assertThatCode(() -> processor.postProcessEnvironment(environment, app)) .doesNotThrowAnyException(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/pom.xml ================================================ 4.0.0 com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml spring-cloud-starter-alibaba-nacos-discovery Spring Cloud Starter Alibaba Nacos Discovery org.springframework.boot spring-boot-health provided com.alibaba.cloud spring-cloud-alibaba-commons org.springframework.boot spring-boot-actuator true org.springframework.boot spring-boot-actuator-autoconfigure true org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot-starter-webflux true com.alibaba.nacos nacos-client com.alibaba.nacos nacos-logback-adapter-12 org.springframework.cloud spring-cloud-commons org.springframework.cloud spring-cloud-context org.springframework.cloud spring-cloud-config-client true org.springframework.cloud spring-cloud-config-server true org.springframework.cloud spring-cloud-loadbalancer true org.springframework.boot spring-boot-starter-web test org.springframework.boot spring-boot-starter-test test io.projectreactor reactor-test test ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/ConditionalOnNacosDiscoveryEnabled.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled", matchIfMissing = true) public @interface ConditionalOnNacosDiscoveryEnabled { } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/NacosDiscoveryProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.alibaba.cloud.commons.context.support.PropertySourcesUtils; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.nacos.event.NacosDiscoveryInfoChangedEvent; import com.alibaba.cloud.nacos.util.InetIPv6Utils; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.PreservedMetadataKeys; import com.alibaba.nacos.client.naming.utils.UtilAndComs; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import static com.alibaba.nacos.api.PropertyKeyConst.ACCESS_KEY; import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT; import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT_PORT; import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE; import static com.alibaba.nacos.api.PropertyKeyConst.NAMING_LOAD_CACHE_AT_START; import static com.alibaba.nacos.api.PropertyKeyConst.PASSWORD; import static com.alibaba.nacos.api.PropertyKeyConst.SECRET_KEY; import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR; import static com.alibaba.nacos.api.PropertyKeyConst.USERNAME; /** * @author dungu.zpf * @author xiaojing * @author Mercy * @author lyuzb * @author eshun * @author freeman */ @ConfigurationProperties("spring.cloud.nacos.discovery") public class NacosDiscoveryProperties { /** * Prefix of {@link NacosDiscoveryProperties}. */ public static final String PREFIX = "spring.cloud.nacos.discovery"; private static final Logger log = LoggerFactory .getLogger(NacosDiscoveryProperties.class); private static final Pattern PATTERN = Pattern.compile("-(\\w)"); private static final String IPV4 = "IPv4"; private static final String IPV6 = "IPv6"; /** * nacos discovery server address. */ private String serverAddr; /** * the nacos authentication username. */ private String username; /** * the nacos authentication password. */ private String password; /** * the domain name of a service, through which the server address can be dynamically * obtained. */ private String endpoint; /** * namespace, separation registry of different environments. */ private String namespace; /** * watch delay,duration to pull new service from nacos server. */ private long watchDelay = 30000; /** * nacos naming log file name. */ private String logName; /** * service name to registry. */ @Value("${spring.cloud.nacos.discovery.service:${spring.application.name:}}") private String service; /** * weight for service instance, the larger the value, the larger the weight. */ private float weight = 1; /** * cluster name for nacos . */ private String clusterName; /** * group name for nacos. */ private String group = "DEFAULT_GROUP"; /** * naming load from local cache at application start. true is load. */ private String namingLoadCacheAtStart = "false"; /** * extra metadata to register. */ private Map metadata = new HashMap<>(); /** * if you just want to subscribe, but don't want to register your service, set it to * false. */ private boolean registerEnabled = true; /** * The ip address your want to register for your service instance, needn't to set it * if the auto detect ip works well. */ private String ip; /** * which network interface's ip you want to register. */ private String networkInterface = ""; /** * choose IPv4 or IPv6,if you don't set it will choose IPv4. * When IPv6 is chosen but no IPv6 can be found, system will automatically find IPv4 to ensure there is an * available service address. */ private String ipType; /** * The port your want to register for your service instance, needn't to set it if the * auto detect port works well. */ private int port = -1; /** * whether your service is a https service. */ private boolean secure = false; /** * access key for namespace. */ private String accessKey; /** * secret key for namespace. */ private String secretKey; /** * Heart beat interval. Time unit: millisecond. */ private Integer heartBeatInterval; /** * Heart beat timeout. Time unit: millisecond. */ private Integer heartBeatTimeout; /** * Ip delete timeout. Time unit: millisecond. */ private Integer ipDeleteTimeout; /** * If instance is enabled to accept request. The default value is true. */ private boolean instanceEnabled = true; /** * If instance is ephemeral.The default value is true. */ private boolean ephemeral = true; /** * Whether to enable nacos failure tolerance. If enabled, nacos will return cached * values when exceptions occur. */ private boolean failureToleranceEnabled; /** * Throw exceptions during service registration if true, otherwise, log error * (defaults to true). */ private boolean failFast = true; /** * graceful shutdown wait time. Time unit: millisecond. * default is 10s * When the Springboot shutdown hook is executed, remove the Nacos service, wait for 10 seconds, and then Tomcat rejects the request */ private Integer gracefulShutdownWaitTime = 10 * 1000; @Autowired private InetIPv6Utils inetIPv6Utils; @Autowired private InetUtils inetUtils; @Autowired private Environment environment; @Autowired private NacosServiceManager nacosServiceManager; @Autowired private ApplicationEventPublisher applicationEventPublisher; @PostConstruct public void init() throws Exception { metadata.put(PreservedMetadataKeys.REGISTER_SOURCE, "SPRING_CLOUD"); if (secure) { metadata.put("secure", "true"); } serverAddr = Objects.toString(serverAddr, ""); if (serverAddr.endsWith("/")) { serverAddr = serverAddr.substring(0, serverAddr.length() - 1); } endpoint = Objects.toString(endpoint, ""); namespace = Objects.toString(namespace, ""); logName = Objects.toString(logName, ""); if (StringUtils.isEmpty(ip)) { // traversing network interfaces if didn't specify an interface if (StringUtils.isEmpty(networkInterface)) { if (ipType == null) { ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); String ipv6Addr = inetIPv6Utils.findIPv6Address(); metadata.put(IPV6, ipv6Addr); if (ipv6Addr != null) { metadata.put(IPV6, ipv6Addr); } } else if (IPV4.equalsIgnoreCase(ipType)) { ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); } else if (IPV6.equalsIgnoreCase(ipType)) { ip = inetIPv6Utils.findIPv6Address(); if (StringUtils.isEmpty(ip)) { log.warn("There is no available IPv6 found. Spring Cloud Alibaba will automatically find IPv4."); ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); } } else { throw new IllegalArgumentException( "please checking the type of IP " + ipType); } } else { NetworkInterface netInterface = NetworkInterface .getByName(networkInterface); if (null == netInterface) { throw new IllegalArgumentException( "no such interface " + networkInterface); } Enumeration inetAddress = netInterface.getInetAddresses(); while (inetAddress.hasMoreElements()) { InetAddress currentAddress = inetAddress.nextElement(); if (currentAddress instanceof Inet4Address || currentAddress instanceof Inet6Address && !currentAddress.isLoopbackAddress()) { ip = currentAddress.getHostAddress(); break; } } if (StringUtils.isEmpty(ip)) { throw new RuntimeException("cannot find available ip from" + " network interface " + networkInterface); } } } this.overrideFromEnv(environment); if (nacosServiceManager.isNacosDiscoveryInfoChanged(this)) { applicationEventPublisher .publishEvent(new NacosDiscoveryInfoChangedEvent(this)); } nacosServiceManager.setNacosDiscoveryProperties(this); } /** * recommend to use {@link NacosServiceManager#getNamingService()}. * @return NamingService */ @Deprecated public NamingService namingServiceInstance() { return nacosServiceManager.getNamingService(); } public String getEndpoint() { return endpoint; } public void setEndpoint(String endpoint) { this.endpoint = endpoint; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getLogName() { return logName; } public void setLogName(String logName) { this.logName = logName; } public void setInetUtils(InetUtils inetUtils) { this.inetUtils = inetUtils; } public float getWeight() { return weight; } public void setWeight(float weight) { this.weight = weight; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public String getService() { return service; } public void setService(String service) { this.service = service; } public boolean isRegisterEnabled() { return registerEnabled; } public void setRegisterEnabled(boolean registerEnabled) { this.registerEnabled = registerEnabled; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getIpType() { return ipType; } public void setIpType(String ipType) { this.ipType = ipType; } public String getNetworkInterface() { return networkInterface; } public void setNetworkInterface(String networkInterface) { this.networkInterface = networkInterface; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public boolean isSecure() { return secure; } public void setSecure(boolean secure) { this.secure = secure; } public Map getMetadata() { return metadata; } public void setMetadata(Map metadata) { this.metadata = metadata; } public String getServerAddr() { return serverAddr; } public void setServerAddr(String serverAddr) { this.serverAddr = serverAddr; } public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } public Integer getHeartBeatInterval() { return heartBeatInterval; } public void setHeartBeatInterval(Integer heartBeatInterval) { this.heartBeatInterval = heartBeatInterval; } public Integer getHeartBeatTimeout() { return heartBeatTimeout; } public void setHeartBeatTimeout(Integer heartBeatTimeout) { this.heartBeatTimeout = heartBeatTimeout; } public Integer getIpDeleteTimeout() { return ipDeleteTimeout; } public void setIpDeleteTimeout(Integer ipDeleteTimeout) { this.ipDeleteTimeout = ipDeleteTimeout; } public String getNamingLoadCacheAtStart() { return namingLoadCacheAtStart; } public void setNamingLoadCacheAtStart(String namingLoadCacheAtStart) { this.namingLoadCacheAtStart = namingLoadCacheAtStart; } public long getWatchDelay() { return watchDelay; } public void setWatchDelay(long watchDelay) { this.watchDelay = watchDelay; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isInstanceEnabled() { return instanceEnabled; } public void setInstanceEnabled(boolean instanceEnabled) { this.instanceEnabled = instanceEnabled; } public boolean isEphemeral() { return ephemeral; } public void setEphemeral(boolean ephemeral) { this.ephemeral = ephemeral; } public boolean isFailureToleranceEnabled() { return failureToleranceEnabled; } public void setFailureToleranceEnabled(boolean failureToleranceEnabled) { this.failureToleranceEnabled = failureToleranceEnabled; } public boolean isFailFast() { return failFast; } public void setFailFast(boolean failFast) { this.failFast = failFast; } public Integer getGracefulShutdownWaitTime() { return gracefulShutdownWaitTime; } public void setGracefulShutdownWaitTime(Integer gracefulShutdownWaitTime) { this.gracefulShutdownWaitTime = gracefulShutdownWaitTime; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } NacosDiscoveryProperties that = (NacosDiscoveryProperties) o; return watchDelay == that.watchDelay && Float.compare(that.weight, weight) == 0 && registerEnabled == that.registerEnabled && port == that.port && secure == that.secure && instanceEnabled == that.instanceEnabled && ephemeral == that.ephemeral && failureToleranceEnabled == that.failureToleranceEnabled && Objects.equals(serverAddr, that.serverAddr) && Objects.equals(username, that.username) && Objects.equals(password, that.password) && Objects.equals(endpoint, that.endpoint) && Objects.equals(namespace, that.namespace) && Objects.equals(logName, that.logName) && Objects.equals(service, that.service) && Objects.equals(clusterName, that.clusterName) && Objects.equals(group, that.group) && Objects.equals(namingLoadCacheAtStart, that.namingLoadCacheAtStart) && Objects.equals(metadata, that.metadata) && Objects.equals(ip, that.ip) && Objects.equals(networkInterface, that.networkInterface) && Objects.equals(accessKey, that.accessKey) && Objects.equals(secretKey, that.secretKey) && Objects.equals(heartBeatInterval, that.heartBeatInterval) && Objects.equals(heartBeatTimeout, that.heartBeatTimeout) && Objects.equals(failFast, that.failFast) && Objects.equals(ipDeleteTimeout, that.ipDeleteTimeout); } @Override public int hashCode() { return Objects.hash(serverAddr, username, password, endpoint, namespace, watchDelay, logName, service, weight, clusterName, group, namingLoadCacheAtStart, metadata, registerEnabled, ip, networkInterface, port, secure, accessKey, secretKey, heartBeatInterval, heartBeatTimeout, ipDeleteTimeout, instanceEnabled, ephemeral, failureToleranceEnabled, failFast); } @Override public String toString() { return "NacosDiscoveryProperties{" + "serverAddr='" + serverAddr + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + ", endpoint='" + endpoint + '\'' + ", namespace='" + namespace + '\'' + ", watchDelay=" + watchDelay + ", logName='" + logName + '\'' + ", service='" + service + '\'' + ", weight=" + weight + ", clusterName='" + clusterName + '\'' + ", group='" + group + '\'' + ", namingLoadCacheAtStart='" + namingLoadCacheAtStart + '\'' + ", metadata=" + metadata + ", registerEnabled=" + registerEnabled + ", ip='" + ip + '\'' + ", networkInterface='" + networkInterface + '\'' + ", port=" + port + ", secure=" + secure + ", accessKey='" + accessKey + '\'' + ", secretKey='" + secretKey + '\'' + ", heartBeatInterval=" + heartBeatInterval + ", heartBeatTimeout=" + heartBeatTimeout + ", ipDeleteTimeout=" + ipDeleteTimeout + ", instanceEnabled=" + instanceEnabled + ", ephemeral=" + ephemeral + ", failureToleranceEnabled=" + failureToleranceEnabled + '}' + ", ipDeleteTimeout=" + ipDeleteTimeout + ", failFast=" + failFast + '}'; } public void overrideFromEnv(Environment env) { if (StringUtils.isEmpty(this.getServerAddr())) { String serverAddr = env .resolvePlaceholders("${spring.cloud.nacos.discovery.server-addr:}"); if (StringUtils.isEmpty(serverAddr)) { serverAddr = env.resolvePlaceholders( "${spring.cloud.nacos.server-addr:127.0.0.1:8848}"); } this.setServerAddr(serverAddr); } if (StringUtils.isEmpty(this.getNamespace())) { this.setNamespace(env .resolvePlaceholders("${spring.cloud.nacos.discovery.namespace:}")); } if (StringUtils.isEmpty(this.getAccessKey())) { this.setAccessKey(env .resolvePlaceholders("${spring.cloud.nacos.discovery.access-key:}")); } if (StringUtils.isEmpty(this.getSecretKey())) { this.setSecretKey(env .resolvePlaceholders("${spring.cloud.nacos.discovery.secret-key:}")); } if (StringUtils.isEmpty(this.getLogName())) { this.setLogName( env.resolvePlaceholders("${spring.cloud.nacos.discovery.log-name:}")); } if (StringUtils.isEmpty(this.getClusterName())) { this.setClusterName(env.resolvePlaceholders( "${spring.cloud.nacos.discovery.cluster-name:}")); } if (StringUtils.isEmpty(this.getEndpoint())) { this.setEndpoint( env.resolvePlaceholders("${spring.cloud.nacos.discovery.endpoint:}")); } if (StringUtils.isEmpty(this.getGroup())) { this.setGroup( env.resolvePlaceholders("${spring.cloud.nacos.discovery.group:}")); } if (StringUtils.isEmpty(this.getUsername())) { this.setUsername(env.resolvePlaceholders("${spring.cloud.nacos.username:}")); } if (StringUtils.isEmpty(this.getPassword())) { this.setPassword(env.resolvePlaceholders("${spring.cloud.nacos.password:}")); } } public Properties getNacosProperties() { Properties properties = new Properties(); properties.put(SERVER_ADDR, serverAddr); properties.put(USERNAME, Objects.toString(username, "")); properties.put(PASSWORD, Objects.toString(password, "")); properties.put(NAMESPACE, namespace); properties.put(UtilAndComs.NACOS_NAMING_LOG_NAME, logName); if (endpoint.contains(":")) { int index = endpoint.indexOf(":"); properties.put(ENDPOINT, endpoint.substring(0, index)); properties.put(ENDPOINT_PORT, endpoint.substring(index + 1)); } else { properties.put(ENDPOINT, endpoint); } properties.put(ACCESS_KEY, accessKey); properties.put(SECRET_KEY, secretKey); // only used for instance.setClusterName() // properties.put(CLUSTER_NAME, clusterName); properties.put(NAMING_LOAD_CACHE_AT_START, namingLoadCacheAtStart); enrichNacosDiscoveryProperties(properties); return properties; } private void enrichNacosDiscoveryProperties(Properties nacosDiscoveryProperties) { Map properties = PropertySourcesUtils .getSubProperties((ConfigurableEnvironment) environment, PREFIX); properties.forEach((k, v) -> nacosDiscoveryProperties.putIfAbsent(resolveKey(k), String.valueOf(v))); } private String resolveKey(String key) { Matcher matcher = PATTERN.matcher(key); StringBuffer sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, matcher.group(1).toUpperCase(Locale.ROOT)); } matcher.appendTail(sb); return sb.toString(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/NacosServiceAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author yuhuangbin */ @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled @ConditionalOnNacosDiscoveryEnabled public class NacosServiceAutoConfiguration { @Bean public NacosServiceManager nacosServiceManager() { return new NacosServiceManager(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/NacosServiceInstance.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import java.net.URI; import java.util.Map; import java.util.Objects; import org.springframework.cloud.client.ServiceInstance; /** * @author xiaojing * @author changjin wei(魏昌进) */ public class NacosServiceInstance implements ServiceInstance { private String serviceId; private String instanceId; private String host; private int port; private boolean secure; private Map metadata; @Override public String getServiceId() { return serviceId; } @Override public String getInstanceId() { return instanceId; } @Override public String getHost() { return host; } @Override public int getPort() { return port; } @Override public boolean isSecure() { return secure; } @Override public URI getUri() { return ServiceInstance.createUri(this); } @Override public Map getMetadata() { return metadata; } @Override public String getScheme() { return getUri().getScheme(); } public void setServiceId(String serviceId) { this.serviceId = serviceId; } public void setInstanceId(String instanceId) { this.instanceId = instanceId; } public void setHost(String host) { this.host = host; } public void setPort(int port) { this.port = port; } public void setSecure(boolean secure) { this.secure = secure; } public void setMetadata(Map metadata) { this.metadata = metadata; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } NacosServiceInstance that = (NacosServiceInstance) o; return Objects.equals(this.serviceId, that.serviceId) && Objects.equals(this.instanceId, that.instanceId) && Objects.equals(this.host, that.host) && this.port == that.port && this.secure == that.secure && Objects.equals(this.metadata, that.metadata); } @Override public int hashCode() { return (instanceId == null) ? 31 : (instanceId.hashCode() + 31); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/NacosServiceManager.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import java.util.Objects; import java.util.Properties; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingMaintainService; import com.alibaba.nacos.api.naming.NamingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.alibaba.nacos.api.NacosFactory.createMaintainService; import static com.alibaba.nacos.api.NacosFactory.createNamingService; /** * @author yuhuangbin */ public class NacosServiceManager { private static final Logger log = LoggerFactory.getLogger(NacosServiceManager.class); private NacosDiscoveryProperties nacosDiscoveryProperties; private volatile NamingService namingService; private volatile NamingMaintainService namingMaintainService; public NamingService getNamingService() { if (Objects.isNull(this.namingService)) { buildNamingService(nacosDiscoveryProperties.getNacosProperties()); } return namingService; } @Deprecated public NamingService getNamingService(Properties properties) { if (Objects.isNull(this.namingService)) { buildNamingService(properties); } return namingService; } public NamingMaintainService getNamingMaintainService(Properties properties) { if (Objects.isNull(namingMaintainService)) { buildNamingMaintainService(properties); } return namingMaintainService; } public boolean isNacosDiscoveryInfoChanged( NacosDiscoveryProperties currentNacosDiscoveryPropertiesCache) { if (Objects.isNull(this.nacosDiscoveryProperties) || this.nacosDiscoveryProperties.equals(currentNacosDiscoveryPropertiesCache)) { return false; } return true; } private NamingMaintainService buildNamingMaintainService(Properties properties) { if (Objects.isNull(namingMaintainService)) { synchronized (NacosServiceManager.class) { if (Objects.isNull(namingMaintainService)) { namingMaintainService = createNamingMaintainService(properties); } } } return namingMaintainService; } private NamingService buildNamingService(Properties properties) { if (Objects.isNull(namingService)) { synchronized (NacosServiceManager.class) { if (Objects.isNull(namingService)) { namingService = createNewNamingService(properties); } } } return namingService; } private NamingService createNewNamingService(Properties properties) { try { return createNamingService(properties); } catch (NacosException e) { throw new RuntimeException(e); } } private NamingMaintainService createNamingMaintainService(Properties properties) { try { return createMaintainService(properties); } catch (NacosException e) { throw new RuntimeException(e); } } public void nacosServiceShutDown() throws NacosException { if (Objects.nonNull(this.namingService)) { this.namingService.shutDown(); this.namingService = null; } if (Objects.nonNull(this.namingMaintainService)) { this.namingMaintainService.shutDown(); this.namingMaintainService = null; } } public void setNacosDiscoveryProperties(NacosDiscoveryProperties nacosDiscoveryProperties) { this.nacosDiscoveryProperties = nacosDiscoveryProperties; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/balancer/NacosBalancer.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.balancer; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.nacos.NacosServiceInstance; import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.client.naming.core.Balancer; import org.springframework.cloud.client.ServiceInstance; /** * @author itmuch.com XuDaojie * @since 2021.1 */ public class NacosBalancer extends Balancer { private static final String IPV4_REGEX = "((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}"; private static final String IPV6_KEY = "IPv6"; /** * Choose instance by weight. * @param instances Instance List * @return the chosen instance */ public static Instance getHostByRandomWeight2(List instances) { return getHostByRandomWeight(instances); } /** * Spring Cloud LoadBalancer Choose instance by weight. * @param serviceInstances Instance List * @return the chosen instance */ public static ServiceInstance getHostByRandomWeight3( List serviceInstances) { Map instanceMap = new HashMap<>(); List nacosInstance = serviceInstances.stream().map(serviceInstance -> { Map metadata = serviceInstance.getMetadata(); // see // com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery.hostToServiceInstance() Instance instance = new Instance(); instance.setIp(serviceInstance.getHost()); instance.setPort(serviceInstance.getPort()); instance.setWeight(Double.parseDouble(metadata.get("nacos.weight"))); instance.setHealthy(Boolean.parseBoolean(metadata.get("nacos.healthy"))); instanceMap.put(instance, serviceInstance); return instance; }).collect(Collectors.toList()); Instance instance = getHostByRandomWeight2(nacosInstance); NacosServiceInstance nacosServiceInstance = (NacosServiceInstance) instanceMap.get(instance); // When local support IPv6 address stack, referred to use IPv6 address. if (StringUtils.isNotEmpty(NacosLoadBalancer.ipv6)) { convertIPv4ToIPv6(nacosServiceInstance); } return nacosServiceInstance; } /** * There is two type Ip,using IPv6 should use IPv6 in metadata to replace IPv4 in IP * field. */ private static void convertIPv4ToIPv6(NacosServiceInstance instance) { if (Pattern.matches(IPV4_REGEX, instance.getHost())) { String ip = instance.getMetadata().get(IPV6_KEY); if (StringUtils.isNotEmpty(ip)) { instance.setHost(ip); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author echooymxq **/ @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled @ConditionalOnNacosDiscoveryEnabled public class NacosDiscoveryAutoConfiguration { @Bean @ConditionalOnMissingBean public NacosDiscoveryProperties nacosProperties() { return new NacosDiscoveryProperties(); } @Bean @ConditionalOnMissingBean public NacosServiceDiscovery nacosServiceDiscovery( NacosDiscoveryProperties discoveryProperties, NacosServiceManager nacosServiceManager) { return new NacosServiceDiscovery(discoveryProperties, nacosServiceManager); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import java.util.Collections; import java.util.List; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; /** * @author xiaojing * @author renhaojun * @author echooymxq * @author freeman */ public class NacosDiscoveryClient implements DiscoveryClient { private static final Logger log = LoggerFactory.getLogger(NacosDiscoveryClient.class); /** * Nacos Discovery Client Description. */ public static final String DESCRIPTION = "Spring Cloud Nacos Discovery Client"; private NacosServiceDiscovery serviceDiscovery; @Value("${spring.cloud.nacos.discovery.failure-tolerance-enabled:false}") private boolean failureToleranceEnabled; public NacosDiscoveryClient(NacosServiceDiscovery nacosServiceDiscovery) { this.serviceDiscovery = nacosServiceDiscovery; } @Override public String description() { return DESCRIPTION; } @Override public List getInstances(String serviceId) { try { return Optional.of(serviceDiscovery.getInstances(serviceId)) .map(instances -> { ServiceCache.setInstances(serviceId, instances); return instances; }).get(); } catch (Exception e) { if (failureToleranceEnabled) { return ServiceCache.getInstances(serviceId); } throw new RuntimeException( "Can not get hosts from nacos server. serviceId: " + serviceId, e); } } @Override public List getServices() { try { return Optional.of(serviceDiscovery.getServices()).map(services -> { ServiceCache.setServiceIds(services); return services; }).get(); } catch (Exception e) { log.error("get service name from nacos server failed.", e); return failureToleranceEnabled ? ServiceCache.getServiceIds() : Collections.emptyList(); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryClientConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.CommonsClientAutoConfiguration; import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author xiaojing * @author echooymxq * @author ruansheng */ @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled @ConditionalOnBlockingDiscoveryEnabled @ConditionalOnNacosDiscoveryEnabled @AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class }) @AutoConfigureAfter(NacosDiscoveryAutoConfiguration.class) public class NacosDiscoveryClientConfiguration { @Bean public DiscoveryClient nacosDiscoveryClient( NacosServiceDiscovery nacosServiceDiscovery) { return new NacosDiscoveryClient(nacosServiceDiscovery); } /** * NacosWatch is no longer enabled by default . * see https://github.com/alibaba/spring-cloud-alibaba/issues/2868 */ @Bean @ConditionalOnMissingBean @ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled", matchIfMissing = false) public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) { return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryHeartBeatConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** * @author xiaojing * @author echooymxq * @author ruansheng * @author zhangbin */ @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled @ConditionalOnBlockingDiscoveryEnabled @ConditionalOnNacosDiscoveryEnabled @AutoConfigureAfter(value = NacosDiscoveryAutoConfiguration.class, name = "de.codecentric.boot.admin.server.cloud.config.AdminServerDiscoveryAutoConfiguration") public class NacosDiscoveryHeartBeatConfiguration { /** * Nacos HeartBeat is no longer enabled by default . * publish an event every 30 seconds * see https://github.com/alibaba/spring-cloud-alibaba/issues/2868 * see https://github.com/alibaba/spring-cloud-alibaba/issues/3258 */ @Bean @ConditionalOnMissingBean @Conditional(NacosDiscoveryHeartBeatCondition.class) public NacosDiscoveryHeartBeatPublisher nacosDiscoveryHeartBeatPublisher(NacosDiscoveryProperties nacosDiscoveryProperties) { return new NacosDiscoveryHeartBeatPublisher(nacosDiscoveryProperties); } private static class NacosDiscoveryHeartBeatCondition extends AnyNestedCondition { NacosDiscoveryHeartBeatCondition() { super(ConfigurationPhase.REGISTER_BEAN); } /** * Spring Cloud Gateway HeartBeat . */ @ConditionalOnProperty(value = "spring.cloud.gateway.server.webflux.discovery.locator.enabled", matchIfMissing = false) static class GatewayLocatorHeartBeatEnabled { } /** * Spring Boot Admin HeartBeat . */ @ConditionalOnBean(type = "de.codecentric.boot.admin.server.cloud.discovery.InstanceDiscoveryListener") static class SpringBootAdminHeartBeatEnabled { } /** * Nacos HeartBeat . */ @ConditionalOnProperty(value = "spring.cloud.nacos.discovery.heart-beat.enabled", matchIfMissing = false) static class NacosDiscoveryHeartBeatEnabled { } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryHeartBeatPublisher.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import java.time.Duration; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.client.discovery.event.HeartbeatEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.SmartLifecycle; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; /** * @author yuhuangbin * @author ruansheng */ public class NacosDiscoveryHeartBeatPublisher implements ApplicationEventPublisherAware, SmartLifecycle { private static final Logger log = LoggerFactory.getLogger(NacosDiscoveryHeartBeatPublisher.class); private final NacosDiscoveryProperties nacosDiscoveryProperties; private final ThreadPoolTaskScheduler taskScheduler; private final AtomicLong nacosHeartBeatIndex = new AtomicLong(0); private final AtomicBoolean running = new AtomicBoolean(false); private ApplicationEventPublisher publisher; private ScheduledFuture heartBeatFuture; public NacosDiscoveryHeartBeatPublisher(NacosDiscoveryProperties nacosDiscoveryProperties) { this.nacosDiscoveryProperties = nacosDiscoveryProperties; this.taskScheduler = getTaskScheduler(); } private static ThreadPoolTaskScheduler getTaskScheduler() { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.setBeanName("HeartBeat-Task-Scheduler"); taskScheduler.initialize(); return taskScheduler; } @Override public void start() { if (this.running.compareAndSet(false, true)) { log.info("Start nacos heartBeat task scheduler."); this.heartBeatFuture = this.taskScheduler.scheduleWithFixedDelay( this::publishHeartBeat, Duration.ofMillis(this.nacosDiscoveryProperties.getWatchDelay())); } } @Override public void stop() { if (this.running.compareAndSet(true, false)) { if (this.heartBeatFuture != null) { // shutdown current user-thread, // then the other daemon-threads will terminate automatic. this.taskScheduler.shutdown(); this.heartBeatFuture.cancel(true); } } } @Override public boolean isAutoStartup() { return true; } @Override public boolean isRunning() { return this.running.get(); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } /** * nacos doesn't support watch now , publish an event every 30 seconds. */ public void publishHeartBeat() { HeartbeatEvent event = new HeartbeatEvent(this, nacosHeartBeatIndex.getAndIncrement()); this.publisher.publishEvent(event); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/NacosServiceDiscovery.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceInstance; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import org.springframework.cloud.client.ServiceInstance; /** * @author echooymxq * @author changjin wei(魏昌进) **/ public class NacosServiceDiscovery { private NacosDiscoveryProperties discoveryProperties; private NacosServiceManager nacosServiceManager; public NacosServiceDiscovery(NacosDiscoveryProperties discoveryProperties, NacosServiceManager nacosServiceManager) { this.discoveryProperties = discoveryProperties; this.nacosServiceManager = nacosServiceManager; } /** * Return all instances for the given service. * @param serviceId id of service * @return list of instances * @throws NacosException nacosException */ public List getInstances(String serviceId) throws NacosException { String group = discoveryProperties.getGroup(); List instances = namingService().selectInstances(serviceId, group, true); return hostToServiceInstanceList(instances, serviceId); } /** * Return the names of all services. * @return list of service names * @throws NacosException nacosException */ public List getServices() throws NacosException { String group = discoveryProperties.getGroup(); ListView services = namingService().getServicesOfServer(1, Integer.MAX_VALUE, group); return services.getData(); } public static List hostToServiceInstanceList( List instances, String serviceId) { List result = new ArrayList<>(instances.size()); for (Instance instance : instances) { ServiceInstance serviceInstance = hostToServiceInstance(instance, serviceId); if (serviceInstance != null) { result.add(serviceInstance); } } return result; } public static ServiceInstance hostToServiceInstance(Instance instance, String serviceId) { if (instance == null || !instance.isEnabled() || !instance.isHealthy()) { return null; } NacosServiceInstance nacosServiceInstance = new NacosServiceInstance(); nacosServiceInstance.setHost(instance.getIp()); nacosServiceInstance.setPort(instance.getPort()); nacosServiceInstance.setServiceId(serviceId); nacosServiceInstance.setInstanceId(instance.getInstanceId()); Map metadata = new HashMap<>(); metadata.put("nacos.instanceId", instance.getInstanceId()); metadata.put("nacos.weight", instance.getWeight() + ""); metadata.put("nacos.healthy", instance.isHealthy() + ""); metadata.put("nacos.cluster", instance.getClusterName() + ""); if (instance.getMetadata() != null) { metadata.putAll(instance.getMetadata()); } metadata.put("nacos.ephemeral", String.valueOf(instance.isEphemeral())); nacosServiceInstance.setMetadata(metadata); if (metadata.containsKey("secure")) { boolean secure = Boolean.parseBoolean(metadata.get("secure")); nacosServiceInstance.setSecure(secure); } return nacosServiceInstance; } private NamingService namingService() { return nacosServiceManager.getNamingService(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/NacosWatch.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.listener.Event; import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.listener.NamingEvent; import com.alibaba.nacos.api.naming.pojo.Instance; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.SmartLifecycle; /** * @author xiaojing * @author yuhuangbin * @author pengfei.lu * @author ruansheng */ public class NacosWatch implements SmartLifecycle, DisposableBean { private static final Logger log = LoggerFactory.getLogger(NacosWatch.class); private final Map listenerMap = new ConcurrentHashMap<>(16); private final AtomicBoolean running = new AtomicBoolean(false); private final NacosServiceManager nacosServiceManager; private final NacosDiscoveryProperties properties; public NacosWatch(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties properties) { this.nacosServiceManager = nacosServiceManager; this.properties = properties; } @Override public boolean isAutoStartup() { return true; } @Override public void stop(Runnable callback) { this.stop(); callback.run(); } @Override public void start() { if (this.running.compareAndSet(false, true)) { EventListener eventListener = listenerMap.computeIfAbsent(buildKey(), event -> new EventListener() { @Override public void onEvent(Event event) { if (event instanceof NamingEvent namingEvent) { List instances = namingEvent.getInstances(); Optional instanceOptional = selectCurrentInstance( instances); instanceOptional.ifPresent(currentInstance -> { resetIfNeeded(currentInstance); }); } } }); NamingService namingService = nacosServiceManager.getNamingService(); try { namingService.subscribe(properties.getService(), properties.getGroup(), Arrays.asList(properties.getClusterName()), eventListener); } catch (Exception e) { log.error("namingService subscribe failed, properties:{}", properties, e); } } } private String buildKey() { return String.join(":", properties.getService(), properties.getGroup()); } private void resetIfNeeded(Instance instance) { if (!properties.getMetadata().equals(instance.getMetadata())) { properties.setMetadata(instance.getMetadata()); } } private Optional selectCurrentInstance(List instances) { return instances.stream() .filter(instance -> properties.getIp().equals(instance.getIp()) && properties.getPort() == instance.getPort()) .findFirst(); } @Override public void stop() { if (this.running.compareAndSet(true, false)) { EventListener eventListener = listenerMap.get(buildKey()); try { NamingService namingService = nacosServiceManager.getNamingService(); namingService.unsubscribe(properties.getService(), properties.getGroup(), Arrays.asList(properties.getClusterName()), eventListener); } catch (Exception e) { log.error("namingService unsubscribe failed, properties:{}", properties, e); } } } @Override public boolean isRunning() { return this.running.get(); } @Override public int getPhase() { return 0; } @Override public void destroy() { this.stop(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/ServiceCache.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClient; import org.springframework.cloud.client.ServiceInstance; /** * Service cache. *

* Cache serviceIds and corresponding instances in Nacos. *

* It's very useful to query services and instances on runtime, but it's not real-time, * depends on {@link NacosDiscoveryClient} or {@link NacosReactiveDiscoveryClient} * {@code getServices(), getInstances(..)} invoke. * * @author freeman * @since 2021.0.1.0 */ public final class ServiceCache { private ServiceCache() { } private static List services = Collections.emptyList(); private static Map> instancesMap = new ConcurrentHashMap<>(); /** * Set instances for specific service. * @param serviceId service id * @param instances service instances */ public static void setInstances(String serviceId, List instances) { instancesMap.put(serviceId, Collections.unmodifiableList(instances)); } /** * Get instances for specific service. * @param serviceId service id * @return service instances */ public static List getInstances(String serviceId) { return Optional.ofNullable(instancesMap.get(serviceId)) .orElse(Collections.emptyList()); } /** * Set all services. * @param serviceIds all services * @deprecated since 2021.0.1.1, use {@link #setServiceIds(List)} instead. */ @Deprecated public static void set(List serviceIds) { services = Collections.unmodifiableList(serviceIds); } /** * Set all services. * @param serviceIds all services * @since 2021.0.1.1 */ public static void setServiceIds(List serviceIds) { services = Collections.unmodifiableList(serviceIds); } /** * Get all services. * @return all services * @deprecated since 2021.0.1.1, use {@link #getServiceIds()} instead. */ @Deprecated public static List get() { return services; } /** * Get all services. * @return all services * @since 2021.0.1.1 */ public static List getServiceIds() { return services; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/actuate/health/NacosDiscoveryHealthIndicator.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery.actuate.health; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.nacos.api.naming.NamingService; import org.springframework.boot.health.contributor.AbstractHealthIndicator; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.HealthIndicator; /** * The {@link HealthIndicator} for Nacos Discovery. * * @author Mercy * @since 2.2.0 * @see HealthIndicator */ public class NacosDiscoveryHealthIndicator extends AbstractHealthIndicator { /** * status up. */ private static final String STATUS_UP = "UP"; /** * status down. */ private static final String STATUS_DOWN = "DOWN"; private NacosServiceManager nacosServiceManager; @Deprecated private NamingService namingService; public NacosDiscoveryHealthIndicator(NacosServiceManager nacosServiceManager) { this.nacosServiceManager = nacosServiceManager; } @Deprecated public NacosDiscoveryHealthIndicator(NamingService namingService) { this.namingService = namingService; } @Override protected void doHealthCheck(Health.Builder builder) throws Exception { // Just return "UP" or "DOWN" String status = nacosServiceManager.getNamingService().getServerStatus(); // Set the status to Builder builder.status(status); switch (status) { case STATUS_UP -> builder.up(); case STATUS_DOWN -> builder.down(); default -> builder.unknown(); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/configclient/NacosConfigServerAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery.configclient; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.config.server.config.ConfigServerProperties; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; /** * Extra configuration for config server if it happens to be registered with Nacos. * * @author JevonYang */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties @ConditionalOnClass({NacosDiscoveryProperties.class, ConfigServerProperties.class}) public class NacosConfigServerAutoConfiguration { @Autowired(required = false) private NacosDiscoveryProperties properties; @Autowired(required = false) private ConfigServerProperties server; @PostConstruct public void init() { if (this.properties == null || this.server == null) { return; } String prefix = this.server.getPrefix(); if (StringUtils.hasText(prefix) && !StringUtils .hasText(this.properties.getMetadata().get("configPath"))) { this.properties.getMetadata().put("configPath", prefix); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/configclient/NacosDiscoveryClientConfigServiceBootstrapConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery.configclient; import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration; import com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration; import com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator; import org.springframework.context.annotation.Configuration; /** * Helper for config client that wants to lookup the config server via discovery. * * @author JevonYang */ @ConditionalOnClass(ConfigServicePropertySourceLocator.class) @ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false) @Configuration(proxyBeanMethods = false) @ImportAutoConfiguration({ NacosDiscoveryAutoConfiguration.class, NacosServiceAutoConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosReactiveDiscoveryClientConfiguration.class, UtilIPv6AutoConfiguration.class }) public class NacosDiscoveryClientConfigServiceBootstrapConfiguration { } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/reactive/NacosReactiveDiscoveryClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery.reactive; import java.util.function.Function; import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery; import com.alibaba.cloud.nacos.discovery.ServiceCache; import com.alibaba.nacos.api.exception.NacosException; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; /** * @author echooymxq * @author freeman **/ public class NacosReactiveDiscoveryClient implements ReactiveDiscoveryClient { private static final Logger log = LoggerFactory .getLogger(NacosReactiveDiscoveryClient.class); private NacosServiceDiscovery serviceDiscovery; @Value("${spring.cloud.nacos.discovery.failure-tolerance-enabled:false}") private boolean failureToleranceEnabled; public NacosReactiveDiscoveryClient(NacosServiceDiscovery nacosServiceDiscovery) { this.serviceDiscovery = nacosServiceDiscovery; } @Override public String description() { return "Spring Cloud Nacos Reactive Discovery Client"; } @Override public Flux getInstances(String serviceId) { return Mono.justOrEmpty(serviceId).flatMapMany(loadInstancesFromNacos()) .subscribeOn(Schedulers.boundedElastic()); } private Function> loadInstancesFromNacos() { return serviceId -> { try { return Mono.justOrEmpty(serviceDiscovery.getInstances(serviceId)) .flatMapMany(instances -> { ServiceCache.setInstances(serviceId, instances); return Flux.fromIterable(instances); }); } catch (NacosException e) { log.error("get service instance[{}] from nacos error!", serviceId, e); return failureToleranceEnabled ? Flux.fromIterable(ServiceCache.getInstances(serviceId)) : Flux.empty(); } }; } @Override public Flux getServices() { return Flux.defer(() -> { try { return Mono.justOrEmpty(serviceDiscovery.getServices()) .flatMapMany(services -> { ServiceCache.setServiceIds(services); return Flux.fromIterable(services); }); } catch (Exception e) { log.error("get services from nacos server fail,", e); return failureToleranceEnabled ? Flux.fromIterable(ServiceCache.getServiceIds()) : Flux.empty(); } }).subscribeOn(Schedulers.boundedElastic()); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/discovery/reactive/NacosReactiveDiscoveryClientConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery.reactive; import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration; import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled; import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration; import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author echooymxq **/ @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled @ConditionalOnReactiveDiscoveryEnabled @ConditionalOnNacosDiscoveryEnabled @AutoConfigureAfter({ NacosDiscoveryAutoConfiguration.class, ReactiveCompositeDiscoveryClientAutoConfiguration.class }) @AutoConfigureBefore({ ReactiveCommonsClientAutoConfiguration.class }) public class NacosReactiveDiscoveryClientConfiguration { @Bean @ConditionalOnMissingBean public NacosReactiveDiscoveryClient nacosReactiveDiscoveryClient( NacosServiceDiscovery nacosServiceDiscovery) { return new NacosReactiveDiscoveryClient(nacosServiceDiscovery); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/endpoint/NacosDiscoveryEndpoint.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.endpoint; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; /** * Endpoint for nacos discovery, get nacos properties and subscribed services. * * @author xiaojing */ @Endpoint(id = "nacosdiscovery") public class NacosDiscoveryEndpoint { private static final Logger log = LoggerFactory .getLogger(NacosDiscoveryEndpoint.class); private NacosServiceManager nacosServiceManager; private NacosDiscoveryProperties nacosDiscoveryProperties; public NacosDiscoveryEndpoint(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) { this.nacosServiceManager = nacosServiceManager; this.nacosDiscoveryProperties = nacosDiscoveryProperties; } /** * @return nacos discovery endpoint */ @ReadOperation public Map nacosDiscovery() { Map result = new HashMap<>(); result.put("NacosDiscoveryProperties", nacosDiscoveryProperties); NamingService namingService = nacosServiceManager.getNamingService(); List subscribe = Collections.emptyList(); try { subscribe = namingService.getSubscribeServices(); for (ServiceInfo serviceInfo : subscribe) { List instances = namingService.getAllInstances( serviceInfo.getName(), serviceInfo.getGroupName()); serviceInfo.setHosts(instances); } } catch (Exception e) { log.error("get subscribe services from nacos fail,", e); } result.put("subscribe", subscribe); return result; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/endpoint/NacosDiscoveryEndpointAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.endpoint; import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.cloud.nacos.discovery.actuate.health.NacosDiscoveryHealthIndicator; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.health.contributor.HealthIndicator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * The AutoConfiguration class for Nacos Discovery's Endpoints. * * @author xiaojing * @author Mercy */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Endpoint.class) @ConditionalOnNacosDiscoveryEnabled public class NacosDiscoveryEndpointAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnAvailableEndpoint public NacosDiscoveryEndpoint nacosDiscoveryEndpoint( NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) { return new NacosDiscoveryEndpoint(nacosServiceManager, nacosDiscoveryProperties); } @Bean @ConditionalOnProperty(name = "spring.cloud.nacos.discovery.health-indicator.enabled", havingValue = "true", matchIfMissing = false) @ConditionalOnEnabledHealthIndicator("nacos-discovery") public HealthIndicator nacosDiscoveryHealthIndicator(NacosServiceManager nacosServiceManager) { return new NacosDiscoveryHealthIndicator(nacosServiceManager); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/event/NacosDiscoveryInfoChangedEvent.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.event; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import org.springframework.context.ApplicationEvent; /** * @author yuhuangbin */ public class NacosDiscoveryInfoChangedEvent extends ApplicationEvent { public NacosDiscoveryInfoChangedEvent( NacosDiscoveryProperties nacosDiscoveryProperties) { super(nacosDiscoveryProperties); } @Override public NacosDiscoveryProperties getSource() { return (NacosDiscoveryProperties) super.getSource(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/loadbalancer/ConditionalOnLoadBalancerNacos.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.loadbalancer; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @ConditionalOnProperty(value = "spring.cloud.loadbalancer.nacos.enabled", havingValue = "true") public @interface ConditionalOnLoadBalancerNacos { } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/loadbalancer/DefaultLoadBalancerAlgorithm.java ================================================ /* * Copyright 2023-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.loadbalancer; import java.util.List; import com.alibaba.cloud.nacos.balancer.NacosBalancer; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.core.Ordered; /** * This is a default implementation of load balancing algorithm. * use {@link com.alibaba.cloud.nacos.balancer.NacosBalancer} * * @author zhangbinhub */ public class DefaultLoadBalancerAlgorithm implements LoadBalancerAlgorithm { @Override public String getServiceId() { return LoadBalancerAlgorithm.DEFAULT_SERVICE_ID; } @Override public ServiceInstance getInstance(Request request, List serviceInstances) { return NacosBalancer.getHostByRandomWeight3(serviceInstances); } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/loadbalancer/LoadBalancerAlgorithm.java ================================================ /* * Copyright 2023-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.loadbalancer; import java.util.List; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.core.Ordered; /** * Load Balancer algorithm interface. * When expanding the load balancing algorithm, implement this interface and register it as a bean. * * @author zhangbinhub */ public interface LoadBalancerAlgorithm extends Ordered { /** * default service id. */ String DEFAULT_SERVICE_ID = "defaultServiceId"; String getServiceId(); ServiceInstance getInstance(Request request, List serviceInstances); } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/loadbalancer/LoadBalancerNacosAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.loadbalancer; import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} that sets up LoadBalancer for Nacos. */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties @ConditionalOnLoadBalancerNacos @ConditionalOnNacosDiscoveryEnabled @LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class) public class LoadBalancerNacosAutoConfiguration { @Bean public LoadBalancerAlgorithm defaultLoadBalancerAlgorithm() { return new DefaultLoadBalancerAlgorithm(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/loadbalancer/NacosLoadBalancer.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.loadbalancer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.util.InetIPv6Utils; import com.alibaba.nacos.common.utils.CollectionUtils; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.DefaultResponse; import org.springframework.cloud.client.loadbalancer.EmptyResponse; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; /** * see original. * {@link org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer} * * @author XuDaojie * @since 2021.1 */ public class NacosLoadBalancer implements ReactorServiceInstanceLoadBalancer { private static final Logger log = LoggerFactory.getLogger(NacosLoadBalancer.class); private final String serviceId; private final ObjectProvider serviceInstanceListSupplierProvider; private final NacosDiscoveryProperties nacosDiscoveryProperties; private static final String IPV4_REGEX = "((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}"; private static final String IPV6_KEY = "IPv6"; /** * Storage local valid IPv6 address, it's a flag whether local machine support IPv6 address stack. */ public static String ipv6; private final InetIPv6Utils inetIPv6Utils; private final List serviceInstanceFilters; private final Map loadBalancerAlgorithmMap; @PostConstruct public void init() { String ip = nacosDiscoveryProperties.getIp(); if (StringUtils.isNotEmpty(ip)) { ipv6 = Pattern.matches(IPV4_REGEX, ip) ? nacosDiscoveryProperties.getMetadata().get(IPV6_KEY) : ip; } else { ipv6 = inetIPv6Utils.findIPv6Address(); } } private List filterInstanceByIpType(List instances) { if (StringUtils.isNotEmpty(ipv6)) { List ipv6InstanceList = new ArrayList<>(); for (ServiceInstance instance : instances) { if (Pattern.matches(IPV4_REGEX, instance.getHost())) { if (StringUtils.isNotEmpty(instance.getMetadata().get(IPV6_KEY))) { ipv6InstanceList.add(instance); } } else { ipv6InstanceList.add(instance); } } // Provider has no IPv6, should use IPv4. if (ipv6InstanceList.isEmpty()) { return instances.stream() .filter(instance -> Pattern.matches(IPV4_REGEX, instance.getHost())) .collect(Collectors.toList()); } else { return ipv6InstanceList; } } return instances.stream() .filter(instance -> Pattern.matches(IPV4_REGEX, instance.getHost())) .collect(Collectors.toList()); } public NacosLoadBalancer( ObjectProvider serviceInstanceListSupplierProvider, String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties, InetIPv6Utils inetIPv6Utils, List serviceInstanceFilters, Map loadBalancerAlgorithmMap) { this.serviceId = serviceId; this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; this.nacosDiscoveryProperties = nacosDiscoveryProperties; this.inetIPv6Utils = inetIPv6Utils; this.serviceInstanceFilters = serviceInstanceFilters; this.loadBalancerAlgorithmMap = loadBalancerAlgorithmMap; } @Override public Mono> choose(Request request) { ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new); return supplier.get(request).next().map(serviceInstances -> getInstanceResponse(request, serviceInstances)); } private Response getInstanceResponse(Request request, List serviceInstances) { if (serviceInstances.isEmpty()) { log.warn("No servers available for service: {}", this.serviceId); return new EmptyResponse(); } try { String clusterName = this.nacosDiscoveryProperties.getClusterName(); List instancesToChoose = serviceInstances; if (StringUtils.isNotBlank(clusterName)) { List sameClusterInstances = serviceInstances.stream() .filter(serviceInstance -> { String cluster = serviceInstance.getMetadata() .get("nacos.cluster"); return StringUtils.equals(cluster, clusterName); }).collect(Collectors.toList()); if (!CollectionUtils.isEmpty(sameClusterInstances)) { instancesToChoose = sameClusterInstances; } } else { log.warn( "A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", serviceId, clusterName, serviceInstances); } instancesToChoose = this.filterInstanceByIpType(instancesToChoose); // Filter the service list sequentially based on the order number for (ServiceInstanceFilter filter : serviceInstanceFilters) { instancesToChoose = filter.filterInstance(request, instancesToChoose); } ServiceInstance instance; // Find the corresponding load balancing algorithm through the service ID and select the final service instance if (loadBalancerAlgorithmMap.containsKey(serviceId)) { instance = loadBalancerAlgorithmMap.get(serviceId).getInstance(request, instancesToChoose); } else { instance = loadBalancerAlgorithmMap.get(LoadBalancerAlgorithm.DEFAULT_SERVICE_ID) .getInstance(request, instancesToChoose); } return new DefaultResponse(instance); } catch (Exception e) { log.warn("NacosLoadBalancer error", e); return null; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/loadbalancer/NacosLoadBalancerClientConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.loadbalancer; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.util.InetIPv6Utils; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; /** * {@link ServiceInstanceListSupplier} don't use cache.
*
* 1. LoadBalancerCache causes information such as the weight of the service instance to * be changed without immediate effect.
* 2. Nacos itself supports caching. * * @author XuDaojie * @since 2021.1 */ @Configuration(proxyBeanMethods = false) @ConditionalOnLoadBalancerNacos @ConditionalOnDiscoveryEnabled public class NacosLoadBalancerClientConfiguration { private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 183827465; @Bean @ConditionalOnMissingBean public ReactorLoadBalancer nacosLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory, NacosDiscoveryProperties nacosDiscoveryProperties, InetIPv6Utils inetIPv6Utils, List serviceInstanceFilters, List loadBalancerAlgorithms) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); Map loadBalancerAlgorithmMap = new HashMap<>(); loadBalancerAlgorithms.forEach(loadBalancerAlgorithm -> { if (!loadBalancerAlgorithmMap.containsKey(loadBalancerAlgorithm.getServiceId())) { loadBalancerAlgorithmMap.put(loadBalancerAlgorithm.getServiceId(), loadBalancerAlgorithm); } }); return new NacosLoadBalancer( loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name, nacosDiscoveryProperties, inetIPv6Utils, serviceInstanceFilters, loadBalancerAlgorithmMap); } @Configuration(proxyBeanMethods = false) @ConditionalOnReactiveDiscoveryEnabled @Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER) public static class ReactiveSupportConfiguration { @Bean @ConditionalOnBean(ReactiveDiscoveryClient.class) @ConditionalOnMissingBean @ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "default", matchIfMissing = true) public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context) { return ServiceInstanceListSupplier.builder().withDiscoveryClient() .build(context); } @Bean @ConditionalOnBean(ReactiveDiscoveryClient.class) @ConditionalOnMissingBean @ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "zone-preference") public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context) { return ServiceInstanceListSupplier.builder().withDiscoveryClient() .withZonePreference().build(context); } } @Configuration(proxyBeanMethods = false) @ConditionalOnBlockingDiscoveryEnabled @Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER + 1) public static class BlockingSupportConfiguration { @Bean @ConditionalOnBean(DiscoveryClient.class) @ConditionalOnMissingBean @ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "default", matchIfMissing = true) public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context) { return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient() .build(context); } @Bean @ConditionalOnBean(DiscoveryClient.class) @ConditionalOnMissingBean @ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "zone-preference") public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context) { return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient() .withZonePreference().build(context); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/loadbalancer/ServiceInstanceFilter.java ================================================ /* * Copyright 2023-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.loadbalancer; import java.util.List; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.core.Ordered; /** * Service Instance Filter interface. * When custom service instance list filter, implement this interface and register it as a bean. * * @author zhangbinhub */ public interface ServiceInstanceFilter extends Ordered { List filterInstance(Request request, List serviceInstances); } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/registry/NacosAutoServiceRegistration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.nacos.event.NacosDiscoveryInfoChangedEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import org.springframework.context.ApplicationContext; import org.springframework.context.event.EventListener; import org.springframework.util.Assert; /** * @author xiaojing * @author Mercy */ public class NacosAutoServiceRegistration extends AbstractAutoServiceRegistration { private static final Logger log = LoggerFactory .getLogger(NacosAutoServiceRegistration.class); private NacosRegistration registration; public NacosAutoServiceRegistration(ApplicationContext context, ServiceRegistry serviceRegistry, AutoServiceRegistrationProperties autoServiceRegistrationProperties, NacosRegistration registration) { super(context, serviceRegistry, autoServiceRegistrationProperties); this.registration = registration; } @Deprecated public void setPort(int port) { getPort().set(port); } @Override protected NacosRegistration getRegistration() { if (this.registration.getPort() < 0 && this.getPort().get() > 0) { this.registration.setPort(this.getPort().get()); } Assert.isTrue(this.registration.getPort() > 0, "service.port has not been set"); return this.registration; } @Override protected NacosRegistration getManagementRegistration() { return null; } @Override protected void register() { if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) { log.debug("Registration disabled."); return; } if (this.registration.getPort() < 0) { this.registration.setPort(getPort().get()); } super.register(); } @Override protected void registerManagement() { if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) { return; } super.registerManagement(); } @Override protected Object getConfiguration() { return this.registration.getNacosDiscoveryProperties(); } @Override protected boolean isEnabled() { return this.registration.getNacosDiscoveryProperties().isRegisterEnabled(); } @Override @SuppressWarnings("deprecation") protected String getAppName() { String appName = registration.getNacosDiscoveryProperties().getService(); return StringUtils.isEmpty(appName) ? super.getAppName() : appName; } @EventListener public void onNacosDiscoveryInfoChangedEvent(NacosDiscoveryInfoChangedEvent event) { restart(); } private void restart() { this.stop(); this.start(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/registry/NacosGracefulShutdownDelegate.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.nacos.common.utils.ThreadUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.lang.NonNull; /** * @author uuuyuqi */ public class NacosGracefulShutdownDelegate implements ApplicationListener, ApplicationContextAware { private static final Logger log = LoggerFactory.getLogger(NacosGracefulShutdownDelegate.class); private final NacosAutoServiceRegistration autoServiceRegistration; private final NacosDiscoveryProperties nacosDiscoveryProperties; private ApplicationContext applicationContext; public NacosGracefulShutdownDelegate(NacosAutoServiceRegistration autoServiceRegistration, NacosDiscoveryProperties nacosDiscoveryProperties) { this.autoServiceRegistration = autoServiceRegistration; this.nacosDiscoveryProperties = nacosDiscoveryProperties; } @Override public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void onApplicationEvent(@NonNull ContextClosedEvent event) { // should NOT be executed if ContextClosedEvent published by sub contexts if (!applicationContext.equals(event.getApplicationContext())) { log.debug("Nacos client graceful shutdown will NOT be executed " + "for Spring context source: {}", event.getApplicationContext()); return; } doGracefulShutdown(); } protected void doGracefulShutdown() { try { autoServiceRegistration.stop(); Integer gracefulShutdownWaitTime = this.nacosDiscoveryProperties.getGracefulShutdownWaitTime(); if (gracefulShutdownWaitTime != null && gracefulShutdownWaitTime > 0) { ThreadUtils.sleep(gracefulShutdownWaitTime); } log.info("Nacos client graceful shutdown has been executed successfully. " + "Graceful shutdown wait time is {}", gracefulShutdownWaitTime); } catch (Throwable t) { log.error("Error occurred while performing Nacos client graceful shutdown", t); } } @Override public boolean supportsAsyncExecution() { // need wait for graceful shutdown return false; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/registry/NacosRegistration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.net.URI; import java.util.List; import java.util.Map; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.nacos.api.naming.PreservedMetadataKeys; import jakarta.annotation.PostConstruct; import org.springframework.beans.BeanUtils; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.ManagementServerPortUtils; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; /** * @author xiaojing * @author changjin wei(魏昌进) */ public class NacosRegistration implements Registration { /** * The metadata key of management port. */ public static final String MANAGEMENT_PORT = "management.port"; /** * The metadata key of management context-path. */ public static final String MANAGEMENT_CONTEXT_PATH = "management.context-path"; /** * The metadata key of management address. */ public static final String MANAGEMENT_ADDRESS = "management.address"; /** * The metadata key of management endpoints web base path. */ public static final String MANAGEMENT_ENDPOINT_BASE_PATH = "management.endpoints.web.base-path"; private List registrationCustomizers; private NacosDiscoveryProperties nacosDiscoveryProperties; private ApplicationContext context; public NacosRegistration(List registrationCustomizers, NacosDiscoveryProperties nacosDiscoveryProperties, ApplicationContext context) { this.registrationCustomizers = registrationCustomizers; this.nacosDiscoveryProperties = nacosDiscoveryProperties; this.context = context; } @PostConstruct public void init() { Map metadata = nacosDiscoveryProperties.getMetadata(); Environment env = context.getEnvironment(); String endpointBasePath = env.getProperty(MANAGEMENT_ENDPOINT_BASE_PATH); if (StringUtils.hasLength(endpointBasePath)) { metadata.put(MANAGEMENT_ENDPOINT_BASE_PATH, endpointBasePath); } Integer managementPort = ManagementServerPortUtils.getPort(context); if (null != managementPort) { metadata.put(MANAGEMENT_PORT, managementPort.toString()); String contextPath = env .getProperty("management.server.servlet.context-path"); String address = env.getProperty("management.server.address"); if (StringUtils.hasLength(contextPath)) { metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath); } if (StringUtils.hasLength(address)) { metadata.put(MANAGEMENT_ADDRESS, address); } } if (null != nacosDiscoveryProperties.getHeartBeatInterval()) { metadata.put(PreservedMetadataKeys.HEART_BEAT_INTERVAL, nacosDiscoveryProperties.getHeartBeatInterval().toString()); } if (null != nacosDiscoveryProperties.getHeartBeatTimeout()) { metadata.put(PreservedMetadataKeys.HEART_BEAT_TIMEOUT, nacosDiscoveryProperties.getHeartBeatTimeout().toString()); } if (null != nacosDiscoveryProperties.getIpDeleteTimeout()) { metadata.put(PreservedMetadataKeys.IP_DELETE_TIMEOUT, nacosDiscoveryProperties.getIpDeleteTimeout().toString()); } customize(registrationCustomizers); } protected void customize( List registrationCustomizers) { if (registrationCustomizers != null) { for (NacosRegistrationCustomizer customizer : registrationCustomizers) { customizer.customize(this); } } } @Override public String getServiceId() { return nacosDiscoveryProperties.getService(); } @Override public String getHost() { return nacosDiscoveryProperties.getIp(); } @Override public int getPort() { return nacosDiscoveryProperties.getPort(); } public void setPort(int port) { this.nacosDiscoveryProperties.setPort(port); } @Override public boolean isSecure() { return nacosDiscoveryProperties.isSecure(); } @Override public URI getUri() { return ServiceInstance.createUri(this); } @Override public Map getMetadata() { return nacosDiscoveryProperties.getMetadata(); } public boolean isRegisterEnabled() { return nacosDiscoveryProperties.isRegisterEnabled(); } public String getCluster() { return nacosDiscoveryProperties.getClusterName(); } public float getRegisterWeight() { return nacosDiscoveryProperties.getWeight(); } public NacosDiscoveryProperties getNacosDiscoveryProperties() { return nacosDiscoveryProperties; } @Override public String toString() { NacosDiscoveryProperties safeProp = new NacosDiscoveryProperties(); BeanUtils.copyProperties(nacosDiscoveryProperties, safeProp); safeProp.setUsername("******"); safeProp.setPassword("******"); return "NacosRegistration{" + "nacosDiscoveryProperties=" + safeProp + '}'; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/registry/NacosRegistrationCustomizer.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; /** * @author L.cm */ public interface NacosRegistrationCustomizer { /** * customize NacosRegistration. * @param registration NacosRegistration */ void customize(NacosRegistration registration); } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/registry/NacosServiceRegistry.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.util.List; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import static org.springframework.util.ReflectionUtils.rethrowRuntimeException; /** * @author xiaojing * @author Mercy * @author eshun * @author JAY */ public class NacosServiceRegistry implements ServiceRegistry { private static final String STATUS_UP = "UP"; private static final String STATUS_DOWN = "DOWN"; private static final Logger log = LoggerFactory.getLogger(NacosServiceRegistry.class); private final NacosDiscoveryProperties nacosDiscoveryProperties; private final NacosServiceManager nacosServiceManager; public NacosServiceRegistry(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) { this.nacosDiscoveryProperties = nacosDiscoveryProperties; this.nacosServiceManager = nacosServiceManager; } @Override public void register(Registration registration) { if (StringUtils.isEmpty(registration.getServiceId())) { log.warn("No service to register for nacos client..."); return; } NamingService namingService = namingService(); String serviceId = registration.getServiceId(); String group = nacosDiscoveryProperties.getGroup(); Instance instance = getNacosInstanceFromRegistration(registration); try { namingService.registerInstance(serviceId, group, instance); log.info("nacos registry, {} {} {}:{} register finished", group, serviceId, instance.getIp(), instance.getPort()); } catch (Exception e) { if (nacosDiscoveryProperties.isFailFast()) { log.error("nacos registry, {} register failed...{},", serviceId, registration.toString(), e); rethrowRuntimeException(e); } else { log.warn("Failfast is false. {} register failed...{},", serviceId, registration.toString(), e); } } } @Override public void deregister(Registration registration) { log.info("De-registering from Nacos Server now..."); if (StringUtils.isEmpty(registration.getServiceId())) { log.warn("No dom to de-register for nacos client..."); return; } NamingService namingService = namingService(); String serviceId = registration.getServiceId(); String group = nacosDiscoveryProperties.getGroup(); try { namingService.deregisterInstance(serviceId, group, registration.getHost(), registration.getPort(), nacosDiscoveryProperties.getClusterName()); } catch (Exception e) { log.error("ERR_NACOS_DEREGISTER, de-register failed...{},", registration.toString(), e); } log.info("De-registration finished."); } @Override public void close() { try { nacosServiceManager.nacosServiceShutDown(); } catch (NacosException e) { log.error("Nacos namingService shutDown failed", e); } } @Override public void setStatus(Registration registration, String status) { if (!STATUS_UP.equalsIgnoreCase(status) && !STATUS_DOWN.equalsIgnoreCase(status)) { log.warn("can't support status {},please choose UP or DOWN", status); return; } try { if (STATUS_DOWN.equalsIgnoreCase(status)) { deregister(registration); } else { register(registration); } } catch (Exception e) { throw new RuntimeException("update nacos instance status fail", e); } } @Override public Object getStatus(Registration registration) { String serviceName = registration.getServiceId(); String group = nacosDiscoveryProperties.getGroup(); try { List instances = namingService().getAllInstances(serviceName, group); for (Instance instance : instances) { if (instance.getIp().equalsIgnoreCase(nacosDiscoveryProperties.getIp()) && instance.getPort() == nacosDiscoveryProperties.getPort()) { return instance.isEnabled() ? STATUS_UP : STATUS_DOWN; } } } catch (Exception e) { log.error("get all instance of {} error,", serviceName, e); } return null; } private Instance getNacosInstanceFromRegistration(Registration registration) { Instance instance = new Instance(); instance.setIp(registration.getHost()); instance.setPort(registration.getPort()); instance.setWeight(nacosDiscoveryProperties.getWeight()); instance.setClusterName(nacosDiscoveryProperties.getClusterName()); instance.setEnabled(nacosDiscoveryProperties.isInstanceEnabled()); instance.setMetadata(registration.getMetadata()); instance.setEphemeral(nacosDiscoveryProperties.isEphemeral()); return instance; } private NamingService namingService() { return nacosServiceManager.getNamingService(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/registry/NacosServiceRegistryAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.util.List; import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author xiaojing * @author Mercy */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties @ConditionalOnNacosDiscoveryEnabled @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) @AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class, AutoServiceRegistrationAutoConfiguration.class, NacosDiscoveryAutoConfiguration.class }) public class NacosServiceRegistryAutoConfiguration { @Bean public NacosServiceRegistry nacosServiceRegistry( NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) { return new NacosServiceRegistry(nacosServiceManager, nacosDiscoveryProperties); } @Bean @ConditionalOnBean(AutoServiceRegistrationProperties.class) public NacosRegistration nacosRegistration( ObjectProvider> registrationCustomizers, NacosDiscoveryProperties nacosDiscoveryProperties, ApplicationContext context) { return new NacosRegistration(registrationCustomizers.getIfAvailable(), nacosDiscoveryProperties, context); } @Bean @ConditionalOnBean(AutoServiceRegistrationProperties.class) public NacosAutoServiceRegistration nacosAutoServiceRegistration( ApplicationContext context, NacosServiceRegistry registry, AutoServiceRegistrationProperties autoServiceRegistrationProperties, NacosRegistration registration) { return new NacosAutoServiceRegistration(context, registry, autoServiceRegistrationProperties, registration); } @Bean @ConditionalOnBean(NacosAutoServiceRegistration.class) public NacosGracefulShutdownDelegate nacosGracefulShutdownDelegate( NacosAutoServiceRegistration nacosAutoServiceRegistration, NacosDiscoveryProperties nacosDiscoveryProperties) { return new NacosGracefulShutdownDelegate(nacosAutoServiceRegistration, nacosDiscoveryProperties); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/util/InetIPv6Utils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.util; import java.io.IOException; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; import java.util.List; import com.alibaba.cloud.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.cloud.commons.util.InetUtilsProperties; /** * @author HH */ public class InetIPv6Utils { private final static Log log = LogFactory.getLog(InetIPv6Utils.class); private final InetUtilsProperties properties; public InetIPv6Utils(final InetUtilsProperties properties) { this.properties = properties; } private InetUtils.HostInfo findFirstValidHostInfo() { InetAddress address = this.findFirstValidIPv6Address(); return address != null ? this.getHostInfo(address) : null; } private InetAddress findFirstValidIPv6Address() { InetAddress address = null; try { for (Enumeration nics = NetworkInterface .getNetworkInterfaces(); nics.hasMoreElements(); ) { NetworkInterface ifc = nics.nextElement(); if (ifc.isUp() || !ifc.isVirtual() || !ifc.isLoopback()) { if (address != null) { break; } if (!ignoreInterface(ifc.getDisplayName())) { for (Enumeration addrs = ifc .getInetAddresses(); addrs.hasMoreElements(); ) { InetAddress inetAddress = addrs.nextElement(); if (inetAddress instanceof Inet6Address // filter ::1 && !inetAddress.isLoopbackAddress() // filter fe80::/10 && !inetAddress.isLinkLocalAddress() // filter ::/128 && !inetAddress.isAnyLocalAddress() // filter fec0::/10,which was discarded, but some // address may be deployed. && !inetAddress.isSiteLocalAddress() // filter fd00::/8 && !isUniqueLocalAddress(inetAddress) && isPreferredAddress(inetAddress)) { log.trace("Found non-loopback interface: " + ifc.getDisplayName()); address = inetAddress; break; } } } } } } catch (IOException e) { log.error("Cannot get first non-loopback address", e); } return address; } public String findIPv6Address() { InetUtils.HostInfo hostInfo = findFirstValidHostInfo(); return hostInfo != null ? normalizeIPv6(hostInfo.getIpAddress()) : null; } private String normalizeIPv6(String ip) { // Remove the suffix of network card in IPv6 address, such as // 2408:400a:8c:5400:6578:5c42:77b1:bc5d%eth0 int idx = ip.indexOf("%"); return idx != -1 ? "[" + ip.substring(0, idx) + "]" : "[" + ip + "]"; } private boolean isPreferredAddress(InetAddress address) { if (this.properties.isUseOnlySiteLocalInterfaces()) { final boolean siteLocalAddress = address.isSiteLocalAddress(); if (!siteLocalAddress) { log.trace("Ignoring address" + address.getHostAddress()); } return siteLocalAddress; } final List preferredNetworks = this.properties.getPreferredNetworks(); if (preferredNetworks.isEmpty()) { return true; } for (String regex : preferredNetworks) { final String hostAddress = address.getHostAddress(); if (hostAddress.matches(regex) || hostAddress.startsWith(regex)) { return true; } } return false; } boolean ignoreInterface(String interfaceName) { for (String regex : this.properties.getIgnoredInterfaces()) { if (interfaceName.matches(regex)) { return true; } } return false; } private InetUtils.HostInfo getHostInfo(final InetAddress address) { InetUtils.HostInfo hostInfo = new InetUtils.HostInfo(); String hostName = address.getHostName(); if (hostName == null) { hostName = "localhost"; } hostInfo.setHostname(hostName); if (StringUtils.isNotEmpty(address.getHostAddress())) { hostInfo.setIpAddress(address.getHostAddress()); } else { hostInfo.setIpAddress(StringUtils.EMPTY); } return hostInfo; } /** * If the address is Unique Local Address. * * @param inetAddress {@link InetAddress} * @return {@code true} if the address is Unique Local Address, otherwise {@code false} */ private boolean isUniqueLocalAddress(InetAddress inetAddress) { byte[] ip = inetAddress.getAddress(); return (ip[0] & 0xff) == 0xfd; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/util/UtilIPv6AutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.util; import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.commons.util.InetUtilsProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author HH */ @Configuration(proxyBeanMethods = false) @ConditionalOnDiscoveryEnabled @ConditionalOnNacosDiscoveryEnabled public class UtilIPv6AutoConfiguration { public UtilIPv6AutoConfiguration() { } @Bean @ConditionalOnMissingBean public InetIPv6Utils inetIPv6Utils(InetUtilsProperties properties) { return new InetIPv6Utils(properties); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/resources/META-INF/additional-spring-configuration-metadata.json ================================================ {"properties": [ { "name": "spring.cloud.nacos.server-addr", "type": "java.lang.String", "defaultValue": "127.0.0.1:8848", "description": "nacos server address." }, { "name": "spring.cloud.nacos.discovery.server-addr", "type": "java.lang.String", "defaultValue": "${spring.cloud.nacos.server-addr}", "description": "nacos discovery server address." }, { "name": "spring.cloud.nacos.discovery.service", "type": "java.lang.String", "defaultValue": "${spring.application.name}", "description": "the service name to register, default value is ${spring.application.name}." }, { "name": "spring.cloud.nacos.discovery.enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "enable nacos discovery or not." }, { "name": "spring.cloud.nacos.discovery.instance-enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "If instance is enabled to accept request. The default value is true." }, { "name": "spring.cloud.nacos.discovery.ephemeral", "type": "java.lang.Boolean", "defaultValue": true, "description": "If instance is ephemeral.The default value is true." }, { "name": "spring.cloud.nacos.discovery.namingLoadCacheAtStart", "type": "java.lang.Boolean", "defaultValue": "false", "description": "naming load from local cache at application start ." }, { "name": "spring.cloud.nacos.discovery.watch.enabled", "type": "java.lang.Boolean", "defaultValue": "false", "description": "enable nacos discovery watch or not ." }, { "name": "spring.cloud.nacos.discovery.heart-beat.enabled", "type": "java.lang.Boolean", "defaultValue": "false", "description": "enable nacos discovery heart beat or not ." }, { "name": "spring.cloud.nacos.discovery.username", "type": "java.lang.String", "defaultValue": "${spring.cloud.nacos.username}", "description": "nacos discovery service's username to authenticate." }, { "name": "spring.cloud.nacos.discovery.password", "type": "java.lang.String", "defaultValue": "${spring.cloud.nacos.password}", "description": "nacos discovery service's password to authenticate." }, { "name": "spring.cloud.nacos.username", "type": "java.lang.String", "description": "nacos userName to authenticate." }, { "name": "spring.cloud.nacos.password", "type": "java.lang.String", "description": "nacos password to authenticate." }, { "name": "spring.cloud.loadbalancer.nacos.enabled", "type": "java.lang.Boolean", "defaultValue": false, "description": "Integrate LoadBalancer or not." }, { "name": "spring.cloud.nacos.discovery.health-indicator.enabled", "type": "java.lang.Boolean", "defaultValue": false, "description": "whether to enable nacos-discovery health indicator." } ]} ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/resources/META-INF/native-image/reflect-config.json ================================================ [ { "condition": { "typeReachable": "com.alibaba.nacos.api.grpc.auto.Metadata" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata", "methods": [ { "name": "getClientIp", "parameterTypes": [] }, { "name": "getClientIpBytes", "parameterTypes": [] }, { "name": "getType", "parameterTypes": [] }, { "name": "getTypeBytes", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$MapFieldAccessor" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata", "methods": [ { "name": "getDefaultInstance", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularMessageFieldAccessor" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata", "methods": [ { "name": "newBuilder", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.TextFormat$Printer" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata", "methods": [ { "name": "getClientIp", "parameterTypes": [] }, { "name": "getType", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.api.grpc.auto.Metadata" }, "name": "com.alibaba.nacos.api.grpc.auto.Metadata$Builder", "methods": [ { "name": "clearClientIp", "parameterTypes": [] }, { "name": "clearType", "parameterTypes": [] }, { "name": "getClientIp", "parameterTypes": [] }, { "name": "getClientIpBytes", "parameterTypes": [] }, { "name": "getType", "parameterTypes": [] }, { "name": "getTypeBytes", "parameterTypes": [] }, { "name": "setClientIp", "parameterTypes": [ "java.lang.String" ] }, { "name": "setClientIpBytes", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.ByteString" ] }, { "name": "setType", "parameterTypes": [ "java.lang.String" ] }, { "name": "setTypeBytes", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.ByteString" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularFieldAccessor$ReflectionInvoker" }, "name": "com.alibaba.nacos.api.grpc.auto.Payload", "methods": [ { "name": "hasBody", "parameterTypes": [] }, { "name": "hasMetadata", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularMessageFieldAccessor" }, "name": "com.alibaba.nacos.api.grpc.auto.Payload", "methods": [ { "name": "getBody", "parameterTypes": [] }, { "name": "getMetadata", "parameterTypes": [] }, { "name": "hasBody", "parameterTypes": [] }, { "name": "hasMetadata", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.TextFormat$Printer" }, "name": "com.alibaba.nacos.api.grpc.auto.Payload", "methods": [ { "name": "getBody", "parameterTypes": [] }, { "name": "getMetadata", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularMessageFieldAccessor" }, "name": "com.alibaba.nacos.api.grpc.auto.Payload$Builder", "methods": [ { "name": "clearBody", "parameterTypes": [] }, { "name": "clearMetadata", "parameterTypes": [] }, { "name": "getBody", "parameterTypes": [] }, { "name": "getBodyBuilder", "parameterTypes": [] }, { "name": "getMetadata", "parameterTypes": [] }, { "name": "getMetadataBuilder", "parameterTypes": [] }, { "name": "hasBody", "parameterTypes": [] }, { "name": "hasMetadata", "parameterTypes": [] }, { "name": "setBody", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.Any" ] }, { "name": "setMetadata", "parameterTypes": [ "com.alibaba.nacos.api.grpc.auto.Metadata" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.cache.ServiceInfoHolder" }, "name": "com.alibaba.nacos.api.naming.pojo.Instance", "methods": [ { "name": "getClusterName", "parameterTypes": [] }, { "name": "getInstanceHeartBeatInterval", "parameterTypes": [] }, { "name": "getInstanceHeartBeatTimeOut", "parameterTypes": [] }, { "name": "getInstanceId", "parameterTypes": [] }, { "name": "getInstanceIdGenerator", "parameterTypes": [] }, { "name": "getIp", "parameterTypes": [] }, { "name": "getIpDeleteTimeout", "parameterTypes": [] }, { "name": "getMetadata", "parameterTypes": [] }, { "name": "getPort", "parameterTypes": [] }, { "name": "getServiceName", "parameterTypes": [] }, { "name": "getWeight", "parameterTypes": [] }, { "name": "isEnabled", "parameterTypes": [] }, { "name": "isEphemeral", "parameterTypes": [] }, { "name": "isHealthy", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.naming.pojo.Instance", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "getClusterName", "parameterTypes": [] }, { "name": "getInstanceHeartBeatInterval", "parameterTypes": [] }, { "name": "getInstanceHeartBeatTimeOut", "parameterTypes": [] }, { "name": "getInstanceId", "parameterTypes": [] }, { "name": "getInstanceIdGenerator", "parameterTypes": [] }, { "name": "getIp", "parameterTypes": [] }, { "name": "getIpDeleteTimeout", "parameterTypes": [] }, { "name": "getMetadata", "parameterTypes": [] }, { "name": "getPort", "parameterTypes": [] }, { "name": "getServiceName", "parameterTypes": [] }, { "name": "getWeight", "parameterTypes": [] }, { "name": "isEnabled", "parameterTypes": [] }, { "name": "isEphemeral", "parameterTypes": [] }, { "name": "isHealthy", "parameterTypes": [] }, { "name": "setClusterName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setEnabled", "parameterTypes": [ "boolean" ] }, { "name": "setEphemeral", "parameterTypes": [ "boolean" ] }, { "name": "setHealthy", "parameterTypes": [ "boolean" ] }, { "name": "setIp", "parameterTypes": [ "java.lang.String" ] }, { "name": "setMetadata", "parameterTypes": [ "java.util.Map" ] }, { "name": "setPort", "parameterTypes": [ "int" ] }, { "name": "setServiceName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setWeight", "parameterTypes": [ "double" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.naming.pojo.Instance", "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setClusterName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setEnabled", "parameterTypes": [ "boolean" ] }, { "name": "setEphemeral", "parameterTypes": [ "boolean" ] }, { "name": "setHealthy", "parameterTypes": [ "boolean" ] }, { "name": "setIp", "parameterTypes": [ "java.lang.String" ] }, { "name": "setMetadata", "parameterTypes": [ "java.util.Map" ] }, { "name": "setPort", "parameterTypes": [ "int" ] }, { "name": "setServiceName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setWeight", "parameterTypes": [ "double" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.cache.ServiceInfoHolder" }, "name": "com.alibaba.nacos.api.naming.pojo.ServiceInfo", "methods": [ { "name": "getCacheMillis", "parameterTypes": [] }, { "name": "getChecksum", "parameterTypes": [] }, { "name": "getClusters", "parameterTypes": [] }, { "name": "getGroupName", "parameterTypes": [] }, { "name": "getHosts", "parameterTypes": [] }, { "name": "getLastRefTime", "parameterTypes": [] }, { "name": "getName", "parameterTypes": [] }, { "name": "isAllIPs", "parameterTypes": [] }, { "name": "isReachProtectionThreshold", "parameterTypes": [] }, { "name": "isValid", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate" }, "name": "com.alibaba.nacos.api.naming.pojo.ServiceInfo", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.naming.pojo.ServiceInfo", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setAllIPs", "parameterTypes": [ "boolean" ] }, { "name": "setCacheMillis", "parameterTypes": [ "long" ] }, { "name": "setChecksum", "parameterTypes": [ "java.lang.String" ] }, { "name": "setClusters", "parameterTypes": [ "java.lang.String" ] }, { "name": "setGroupName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setHosts", "parameterTypes": [ "java.util.List" ] }, { "name": "setLastRefTime", "parameterTypes": [ "long" ] }, { "name": "setName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setReachProtectionThreshold", "parameterTypes": [ "boolean" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.naming.pojo.ServiceInfo", "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setAllIPs", "parameterTypes": [ "boolean" ] }, { "name": "setCacheMillis", "parameterTypes": [ "long" ] }, { "name": "setChecksum", "parameterTypes": [ "java.lang.String" ] }, { "name": "setClusters", "parameterTypes": [ "java.lang.String" ] }, { "name": "setGroupName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setHosts", "parameterTypes": [ "java.util.List" ] }, { "name": "setLastRefTime", "parameterTypes": [ "long" ] }, { "name": "setName", "parameterTypes": [ "java.lang.String" ] }, { "name": "setReachProtectionThreshold", "parameterTypes": [ "boolean" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.naming.remote.request.AbstractNamingRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.naming.remote.request.AbstractNamingRequest", "methods": [ { "name": "getGroupName", "parameterTypes": [] }, { "name": "getModule", "parameterTypes": [] }, { "name": "getNamespace", "parameterTypes": [] }, { "name": "getServiceName", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.naming.remote.request.InstanceRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "getInstance", "parameterTypes": [] }, { "name": "getType", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.naming.remote.request.NotifySubscriberRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setServiceInfo", "parameterTypes": [ "com.alibaba.nacos.api.naming.pojo.ServiceInfo" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.naming.remote.request.ServiceListRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "getPageNo", "parameterTypes": [] }, { "name": "getPageSize", "parameterTypes": [] }, { "name": "getSelector", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.naming.remote.request.SubscribeServiceRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "getClusters", "parameterTypes": [] }, { "name": "isSubscribe", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.naming.remote.response.InstanceResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setType", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcUtils" }, "name": "com.alibaba.nacos.api.naming.remote.response.NotifySubscriberResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.naming.remote.response.ServiceListResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setCount", "parameterTypes": [ "int" ] }, { "name": "setServiceNames", "parameterTypes": [ "java.util.List" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.naming.remote.response.SubscribeServiceResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setServiceInfo", "parameterTypes": [ "com.alibaba.nacos.api.naming.pojo.ServiceInfo" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcUtils" }, "name": "com.alibaba.nacos.api.remote.Payload", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.request.ConnectionSetupRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "getAbilities", "parameterTypes": [] }, { "name": "getClientVersion", "parameterTypes": [] }, { "name": "getLabels", "parameterTypes": [] }, { "name": "getTenant", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.request.HealthCheckRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.request.InternalRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "getModule", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.request.InternalRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "getModule", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.request.InternalRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "getModule", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.remote.request.Request", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.request.Request", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.request.Request", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.remote.request.Request", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "setRequestId", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.request.Request", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcUtils" }, "name": "com.alibaba.nacos.api.remote.request.Request", "methods": [ { "name": "getHeaders", "parameterTypes": [] }, { "name": "getRequestId", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.request.ServerCheckRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient$1" }, "name": "com.alibaba.nacos.api.remote.request.ServerRequest", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.response.HealthCheckResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy" }, "name": "com.alibaba.nacos.api.remote.response.Response", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "setMessage", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.RpcClient" }, "name": "com.alibaba.nacos.api.remote.response.Response", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.response.Response", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "setErrorCode", "parameterTypes": [ "int" ] }, { "name": "setResultCode", "parameterTypes": [ "int" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcConnection" }, "name": "com.alibaba.nacos.api.remote.response.Response", "methods": [ { "name": "setErrorCode", "parameterTypes": [ "int" ] }, { "name": "setResultCode", "parameterTypes": [ "int" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcUtils" }, "name": "com.alibaba.nacos.api.remote.response.Response", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "getErrorCode", "parameterTypes": [] }, { "name": "getMessage", "parameterTypes": [] }, { "name": "getRequestId", "parameterTypes": [] }, { "name": "getResultCode", "parameterTypes": [] }, { "name": "isSuccess", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.api.remote.response.ServerCheckResponse", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setConnectionId", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.spi.NacosServiceLoader" }, "name": "com.alibaba.nacos.client.logging.logback.NacosLogbackConfiguratorAdapterV1", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.api.naming.NamingFactory" }, "name": "com.alibaba.nacos.client.naming.NacosNamingService", "methods": [ { "name": "", "parameterTypes": [ "java.util.Properties" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.notify.NotifyCenter" }, "name": "com.alibaba.nacos.common.notify.DefaultPublisher", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.common.remote.TlsConfig", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "getCertChainFile", "parameterTypes": [] }, { "name": "getCertPrivateKey", "parameterTypes": [] }, { "name": "getCertPrivateKeyPassword", "parameterTypes": [] }, { "name": "getCiphers", "parameterTypes": [] }, { "name": "getEnableTls", "parameterTypes": [] }, { "name": "getMutualAuthEnable", "parameterTypes": [] }, { "name": "getProtocols", "parameterTypes": [] }, { "name": "getSslProvider", "parameterTypes": [] }, { "name": "getTrustAll", "parameterTypes": [] }, { "name": "getTrustCollectionCertFile", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.client.grpc.GrpcClient" }, "name": "com.alibaba.nacos.common.remote.client.RpcClientTlsConfig", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper" }, "name": "com.alibaba.nacos.shaded.com.google.common.util.concurrent.AbstractFuture", "fields": [ { "name": "listeners" }, { "name": "value" }, { "name": "waiters" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper" }, "name": "com.alibaba.nacos.shaded.com.google.common.util.concurrent.AbstractFuture$Waiter", "fields": [ { "name": "next" }, { "name": "thread" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.Any" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.Any", "methods": [ { "name": "getTypeUrl", "parameterTypes": [] }, { "name": "getTypeUrlBytes", "parameterTypes": [] }, { "name": "getValue", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.GeneratedMessageV3$FieldAccessorTable$SingularMessageFieldAccessor" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.Any", "methods": [ { "name": "newBuilder", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.TextFormat$Printer" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.Any", "methods": [ { "name": "getTypeUrl", "parameterTypes": [] }, { "name": "getValue", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.Any" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.Any$Builder", "methods": [ { "name": "clearTypeUrl", "parameterTypes": [] }, { "name": "clearValue", "parameterTypes": [] }, { "name": "getTypeUrl", "parameterTypes": [] }, { "name": "getTypeUrlBytes", "parameterTypes": [] }, { "name": "getValue", "parameterTypes": [] }, { "name": "setTypeUrl", "parameterTypes": [ "java.lang.String" ] }, { "name": "setTypeUrlBytes", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.ByteString" ] }, { "name": "setValue", "parameterTypes": [ "com.alibaba.nacos.shaded.com.google.protobuf.ByteString" ] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.protobuf.ExtensionRegistryFactory" }, "name": "com.alibaba.nacos.shaded.com.google.protobuf.ExtensionRegistry", "methods": [ { "name": "getEmptyRegistry", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.internal.ManagedChannelImplBuilder" }, "name": "com.alibaba.nacos.shaded.io.grpc.census.InternalCensusStatsAccessor" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.internal.ManagedChannelImplBuilder" }, "name": "com.alibaba.nacos.shaded.io.grpc.census.InternalCensusTracingAccessor" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.NameResolverRegistry" }, "name": "com.alibaba.nacos.shaded.io.grpc.internal.DnsNameResolverProvider" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.internal.DnsNameResolver" }, "name": "com.alibaba.nacos.shaded.io.grpc.internal.JndiResourceResolverFactory" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.LoadBalancerRegistry" }, "name": "com.alibaba.nacos.shaded.io.grpc.internal.PickFirstLoadBalancerProvider" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.ManagedChannelRegistry" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.NettyChannelProvider" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.ManagedChannelRegistry" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.UdsNettyChannelProvider" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.buffer.AbstractByteBufAllocator" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.buffer.AbstractByteBufAllocator", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.ReferenceCountUpdater" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.buffer.AbstractReferenceCountedByteBuf", "fields": [ { "name": "refCnt" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.grpc.netty.Utils" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.epoll.Epoll" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.ReflectiveChannelFactory" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.socket.nio.NioSocketChannel", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.ReferenceCountUtil" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.ReferenceCountUtil", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueColdProducerFields" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueColdProducerFields", "fields": [ { "name": "producerLimit" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueConsumerFields" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueConsumerFields", "fields": [ { "name": "consumerIndex" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueProducerFields" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueProducerFields", "fields": [ { "name": "producerIndex" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueConsumerIndexField" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueConsumerIndexField", "fields": [ { "name": "consumerIndex" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueProducerIndexField" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueProducerIndexField", "fields": [ { "name": "producerIndex" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueProducerLimitField" }, "name": "com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueueProducerLimitField", "fields": [ { "name": "producerLimit" } ] }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.ManagedChannelRegistry" }, "name": "com.alibaba.nacos.shaded.io.grpc.okhttp.OkHttpChannelProvider" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.Context$LazyStorage" }, "name": "com.alibaba.nacos.shaded.io.grpc.override.ContextStorageOverride" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.LoadBalancerRegistry" }, "name": "com.alibaba.nacos.shaded.io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.perfmark.PerfMark" }, "name": "com.alibaba.nacos.shaded.io.perfmark.impl.SecretPerfMarkImpl$PerfMarkImpl" }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "getConfigAbility", "parameterTypes": [] }, { "name": "getNamingAbility", "parameterTypes": [] }, { "name": "getRemoteAbility", "parameterTypes": [] } ], "name": "com.alibaba.nacos.api.ability.ClientAbilities", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "isSupportRemoteMetrics", "parameterTypes": [] } ], "name": "com.alibaba.nacos.api.config.ability.ClientConfigAbility", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "getModule", "parameterTypes": [] } ], "name": "com.alibaba.nacos.api.config.remote.request.AbstractConfigRequest", "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.gson.Gson" }, "name": "com.alibaba.nacos.api.config.remote.request.AbstractConfigRequest" }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "getConfigListenContexts", "parameterTypes": [] }, { "name": "isListen", "parameterTypes": [] } ], "name": "com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "getDataId", "parameterTypes": [] }, { "name": "getGroup", "parameterTypes": [] }, { "name": "getMd5", "parameterTypes": [] }, { "name": "getTenant", "parameterTypes": [] } ], "name": "com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest$ConfigListenContext", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.shaded.com.google.gson.internal.bind.CollectionTypeAdapterFactory" }, "methods": [ { "name": "", "parameterTypes": [] } ], "name": "com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest$ConfigListenContext" }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "getDataId", "parameterTypes": [] }, { "name": "getGroup", "parameterTypes": [] }, { "name": "getTag", "parameterTypes": [] }, { "name": "getTenant", "parameterTypes": [] }, { "name": "isNotify", "parameterTypes": [] } ], "name": "com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setChangedConfigs", "parameterTypes": [ "java.util.List" ] } ], "name": "com.alibaba.nacos.api.config.remote.response.ConfigChangeBatchListenResponse", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "name": "com.alibaba.nacos.api.config.remote.response.ConfigChangeBatchListenResponse$ConfigContext", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "", "parameterTypes": [] }, { "name": "setBeta", "parameterTypes": [ "boolean" ] }, { "name": "setContent", "parameterTypes": [ "java.lang.String" ] }, { "name": "setContentType", "parameterTypes": [ "java.lang.String" ] }, { "name": "setEncryptedDataKey", "parameterTypes": [ "java.lang.String" ] }, { "name": "setLastModified", "parameterTypes": [ "long" ] }, { "name": "setMd5", "parameterTypes": [ "java.lang.String" ] } ], "name": "com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "isSupportDeltaPush", "parameterTypes": [] }, { "name": "isSupportRemoteMetric", "parameterTypes": [] } ], "name": "com.alibaba.nacos.api.naming.ability.ClientNamingAbility", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "allDeclaredFields": true, "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ClientWorker$ConfigRpcTransportClient" }, "methods": [ { "name": "isSupportRemoteConnection", "parameterTypes": [] } ], "name": "com.alibaba.nacos.api.remote.ability.ClientRemoteAbility", "queryAllDeclaredConstructors": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate" }, "methods": [ { "name": "", "parameterTypes": [] } ], "name": "com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl" }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate" }, "methods": [ { "name": "", "parameterTypes": [] } ], "name": "com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl" }, { "condition": { "typeReachable": "com.alibaba.nacos.api.config.ConfigFactory" }, "methods": [ { "name": "", "parameterTypes": [ "java.util.Properties" ] } ], "name": "com.alibaba.nacos.client.config.NacosConfigService" }, { "condition": { "typeReachable": "com.alibaba.nacos.client.naming.NacosNamingService" }, "name": "com.alibaba.nacos.api.naming.pojo.Instance", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "setInstanceId", "parameterTypes": [ "java.lang.String" ] } ] } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/resources/META-INF/native-image/resource-config.json ================================================ { "resources": { "includes": [ { "condition": { "typeReachable": "com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.api.config.filter.IConfigFilter\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.common.remote.PayloadRegistry" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.api.remote.Payload\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.client.config.impl.ConfigTransportClient" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.plugin.auth.spi.client.AbstractClientAuthService\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.LoadBalancerRegistry" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.shaded.io.grpc.LoadBalancerProvider\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.ManagedChannelRegistry" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.shaded.io.grpc.ManagedChannelProvider\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.shaded.io.grpc.NameResolverRegistry" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.shaded.io.grpc.NameResolverProvider\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.common.utils.VersionUtils" }, "pattern": "\\Qnacos-version.txt\\E" }, { "condition": { "typeReachable": "com.alibaba.nacos.common.utils.ResourceUtils" }, "pattern": "\\Qnacos_default_setting.properties\\E" } ] }, "bundles": [] } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration com.alibaba.cloud.nacos.discovery.NacosDiscoveryHeartBeatConfiguration com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration com.alibaba.cloud.nacos.loadbalancer.LoadBalancerNacosAutoConfiguration com.alibaba.cloud.nacos.NacosServiceAutoConfiguration com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/resources/META-INF/spring.factories ================================================ org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/NacosDiscoveryClientTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import java.util.Arrays; import java.util.List; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClient; import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery; import com.alibaba.cloud.nacos.discovery.ServiceCache; import com.alibaba.nacos.api.exception.NacosException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.cloud.client.ServiceInstance; import org.springframework.test.util.ReflectionTestUtils; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.when; /** * @author xiaojing * @author echooymxq * @author freeman */ @ExtendWith(MockitoExtension.class) public class NacosDiscoveryClientTests { @Mock private NacosServiceDiscovery serviceDiscovery; @Mock private NacosServiceInstance serviceInstance; @InjectMocks private NacosDiscoveryClient client; @Test public void testGetInstances() throws Exception { when(serviceDiscovery.getInstances("service-1")) .thenReturn(singletonList(serviceInstance)); List serviceInstances = client.getInstances("service-1"); assertThat(serviceInstances).isNotEmpty(); } @Test public void testGetServices() throws Exception { when(serviceDiscovery.getServices()).thenReturn(singletonList("service-1")); List services = client.getServices(); assertThat(services).contains("service-1").size().isEqualTo(1); } @Test public void testGetInstancesFailureToleranceEnabled() throws NacosException { ServiceCache.setInstances("a", singletonList(serviceInstance)); when(serviceDiscovery.getInstances("a")).thenThrow(new NacosException()); ReflectionTestUtils.setField(client, "failureToleranceEnabled", true); List instances = this.client.getInstances("a"); assertThat(instances).isEqualTo(singletonList(serviceInstance)); } @Test public void testGetInstancesFailureToleranceDisabled() throws NacosException { ServiceCache.setInstances("a", singletonList(serviceInstance)); when(serviceDiscovery.getInstances("a")).thenThrow(new NacosException()); ReflectionTestUtils.setField(client, "failureToleranceEnabled", false); assertThatThrownBy(() -> this.client.getInstances("a")); } @Test public void testFailureToleranceEnabled() throws NacosException { ServiceCache.setServiceIds(Arrays.asList("a", "b")); when(serviceDiscovery.getServices()).thenThrow(new NacosException()); ReflectionTestUtils.setField(client, "failureToleranceEnabled", true); List services = this.client.getServices(); assertThat(services).isEqualTo(Arrays.asList("a", "b")); } @Test public void testFailureToleranceDisabled() throws NacosException { ServiceCache.setServiceIds(Arrays.asList("a", "b")); when(serviceDiscovery.getServices()).thenThrow(new NacosException()); ReflectionTestUtils.setField(client, "failureToleranceEnabled", false); List services = this.client.getServices(); assertThat(services).isEqualTo(emptyList()); } @Test public void testCacheIsOK() throws NacosException { when(serviceDiscovery.getInstances("a")) .thenReturn(singletonList(serviceInstance)); this.client.getInstances("a"); assertThat(ServiceCache.getInstances("a")).isEqualTo(singletonList(serviceInstance)); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/NacosDiscoveryPropertiesServerAddressBothLevelTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration; import com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.context.annotation.Configuration; import static com.alibaba.cloud.nacos.NacosDiscoveryPropertiesServerAddressBothLevelTests.TestConfig; import static org.assertj.core.api.Assertions.assertThat; /** * @author lyuzb */ @SpringBootTest(classes = TestConfig.class, properties = { "spring.application.name=app", "spring.cloud.nacos.discovery.server-addr=321.321.321.321:8848", "spring.cloud.nacos.server-addr=123.123.123.123:8848" }) public class NacosDiscoveryPropertiesServerAddressBothLevelTests { @Autowired private NacosDiscoveryProperties properties; @Test public void testGetServerAddr() { assertThat(properties.getServerAddr()).isEqualTo("321.321.321.321:8848"); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosServiceRegistryAutoConfiguration.class }) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/NacosDiscoveryPropertiesServerAddressTopLevelTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration; import com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.context.annotation.Configuration; import static com.alibaba.cloud.nacos.NacosDiscoveryPropertiesServerAddressTopLevelTests.TestConfig; import static org.assertj.core.api.Assertions.assertThat; /** * @author lyuzb * */ @SpringBootTest(classes = TestConfig.class, properties = { "spring.application.name=app", "spring.cloud.nacos.server-addr=123.123.123.123:8848" }) public class NacosDiscoveryPropertiesServerAddressTopLevelTests { @Autowired private NacosDiscoveryProperties properties; @Test public void testGetServerAddr() { assertThat(properties.getServerAddr()).isEqualTo("123.123.123.123:8848"); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosServiceRegistryAutoConfiguration.class }) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryAutoConfigurationTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration; import com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.commons.util.UtilAutoConfiguration; import static org.assertj.core.api.Assertions.assertThat; /** * @author echooymxq **/ public class NacosDiscoveryAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(UtilAutoConfiguration.class, UtilIPv6AutoConfiguration.class, NacosServiceAutoConfiguration.class, NacosDiscoveryAutoConfiguration.class)); @Test public void testDefaultInitialization() { contextRunner.run(context -> { assertThat(context).hasSingleBean(NacosDiscoveryProperties.class); assertThat(context).hasSingleBean(NacosServiceDiscovery.class); }); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryClientConfigurationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration; import com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration; import com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.cloud.commons.util.UtilAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import static org.assertj.core.api.Assertions.assertThat; /** * @author echooymxq **/ public class NacosDiscoveryClientConfigurationTest { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of( AutoServiceRegistrationConfiguration.class, NacosServiceRegistryAutoConfiguration.class, UtilAutoConfiguration.class, UtilIPv6AutoConfiguration.class, NacosServiceAutoConfiguration.class, NacosDiscoveryAutoConfiguration.class, NacosDiscoveryClientConfiguration.class, this.getClass())); @Bean public TaskScheduler taskScheduler() { return new ThreadPoolTaskScheduler(); } @Test public void testDefaultInitialization() { contextRunner.run(context -> { assertThat(context).hasSingleBean(DiscoveryClient.class); // NacosWatch is no longer enabled by default assertThat(context).doesNotHaveBean(NacosWatch.class); }); } @Test public void testDiscoveryBlockingDisabled() { contextRunner.withPropertyValues("spring.cloud.discovery.blocking.enabled=false") .run(context -> { assertThat(context).doesNotHaveBean(DiscoveryClient.class); assertThat(context).doesNotHaveBean(NacosWatch.class); }); } @Test public void testNacosWatchEnabled() { contextRunner.withPropertyValues("spring.cloud.nacos.discovery.watch.enabled=true") .run(context -> assertThat(context).hasSingleBean(NacosWatch.class)); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryHeartBeatConfigurationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration; import com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration; import com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.cloud.commons.util.UtilAutoConfiguration; import static org.assertj.core.api.Assertions.assertThat; /** * @author zhangbin **/ public class NacosDiscoveryHeartBeatConfigurationTest { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of( AutoServiceRegistrationConfiguration.class, NacosServiceRegistryAutoConfiguration.class, UtilAutoConfiguration.class, UtilIPv6AutoConfiguration.class, NacosServiceAutoConfiguration.class, NacosDiscoveryAutoConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosDiscoveryHeartBeatConfiguration.class, this.getClass())); @Test public void testDefaultNacosDiscoveryHeartBeatPublisher() { contextRunner.run(context -> assertThat(context).doesNotHaveBean(NacosDiscoveryHeartBeatPublisher.class) ); } @Test public void testNacosDiscoveryHeartBeatPublisherEnabledForGateway() { contextRunner .withPropertyValues("spring.cloud.gateway.server.webflux.discovery.locator.enabled=true") .run(context -> assertThat(context).hasSingleBean(NacosDiscoveryHeartBeatPublisher.class) ); } @Test public void testNacosDiscoveryHeartBeatPublisherEnabledForProperties() { contextRunner .withPropertyValues("spring.cloud.nacos.discovery.heart-beat.enabled=true") .run(context -> assertThat(context).hasSingleBean(NacosDiscoveryHeartBeatPublisher.class) ); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/NacosDiscoveryLoadBalancerConfigurationTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration; import com.alibaba.cloud.nacos.loadbalancer.LoadBalancerAlgorithm; import com.alibaba.cloud.nacos.loadbalancer.LoadBalancerNacosAutoConfiguration; import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancerClientConfiguration; import com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration; import com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.cloud.commons.util.UtilAutoConfiguration; import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration; import static org.assertj.core.api.Assertions.assertThat; /** * @author zhangbinhub **/ public class NacosDiscoveryLoadBalancerConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of( AutoServiceRegistrationConfiguration.class, NacosServiceRegistryAutoConfiguration.class, UtilAutoConfiguration.class, UtilIPv6AutoConfiguration.class, NacosServiceAutoConfiguration.class, NacosDiscoveryAutoConfiguration.class, NacosDiscoveryClientConfiguration.class, LoadBalancerAutoConfiguration.class, this.getClass())); @Test public void testNacosLoadBalancerEnabled() { contextRunner.withPropertyValues( "spring.cloud.loadbalancer.nacos.enabled=true", "spring.cloud.loadbalancer.client.name=testService", "spring.cloud.loadbalancer.configurations=none") .withConfiguration(AutoConfigurations.of( LoadBalancerNacosAutoConfiguration.class, NacosLoadBalancerClientConfiguration.class)) .run(context -> { assertThat(context).hasSingleBean(LoadBalancerAlgorithm.class); assertThat(context).hasBean("nacosLoadBalancer"); }); } @Test public void testNacosLoadBalancerDisabled() { contextRunner.withPropertyValues( "spring.cloud.loadbalancer.nacos.enabled=false", "spring.cloud.loadbalancer.client.name=testService", "spring.cloud.loadbalancer.configurations=none") .withConfiguration(AutoConfigurations.of( LoadBalancerNacosAutoConfiguration.class, NacosLoadBalancerClientConfiguration.class)) .run(context -> { assertThat(context).doesNotHaveBean(LoadBalancerAlgorithm.class); assertThat(context).doesNotHaveBean("nacosLoadBalancer"); }); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/NacosServiceDiscoveryTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import org.junit.jupiter.api.Test; import org.springframework.cloud.client.ServiceInstance; import static com.alibaba.cloud.nacos.test.NacosMockTest.serviceInstance; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author xiaojing * @author echooymxq **/ public class NacosServiceDiscoveryTest { private String host = "123.123.123.123"; private int port = 8888; private String serviceName = "test-service"; @Test public void testGetInstances() throws NacosException { ArrayList instances = new ArrayList<>(); HashMap map = new HashMap<>(); map.put("test-key", "test-value"); map.put("secure", "true"); instances.add(serviceInstance(serviceName, true, host, port, map)); NacosDiscoveryProperties nacosDiscoveryProperties = mock( NacosDiscoveryProperties.class); NacosServiceManager nacosServiceManager = mock(NacosServiceManager.class); NamingService namingService = mock(NamingService.class); when(nacosServiceManager.getNamingService()) .thenReturn(namingService); when(nacosDiscoveryProperties.getGroup()).thenReturn("DEFAULT"); when(namingService.selectInstances(eq(serviceName), eq("DEFAULT"), eq(true))) .thenReturn(instances); NacosServiceDiscovery serviceDiscovery = new NacosServiceDiscovery( nacosDiscoveryProperties, nacosServiceManager); List serviceInstances = serviceDiscovery .getInstances(serviceName); assertThat(serviceInstances.size()).isEqualTo(1); ServiceInstance serviceInstance = serviceInstances.get(0); assertThat(serviceInstance.getServiceId()).isEqualTo(serviceName); assertThat(serviceInstance.getHost()).isEqualTo(host); assertThat(serviceInstance.getPort()).isEqualTo(port); assertThat(serviceInstance.isSecure()).isEqualTo(true); assertThat(serviceInstance.getUri().toString()) .isEqualTo(getUri(serviceInstance)); assertThat(serviceInstance.getMetadata().get("test-key")).isEqualTo("test-value"); } @Test public void testGetServices() throws NacosException { ListView nacosServices = new ListView<>(); nacosServices.setData(new LinkedList<>()); nacosServices.getData().add(serviceName + "1"); nacosServices.getData().add(serviceName + "2"); nacosServices.getData().add(serviceName + "3"); NacosDiscoveryProperties nacosDiscoveryProperties = mock( NacosDiscoveryProperties.class); NacosServiceManager nacosServiceManager = mock(NacosServiceManager.class); NamingService namingService = mock(NamingService.class); when(nacosServiceManager .getNamingService()) .thenReturn(namingService); when(nacosDiscoveryProperties.getGroup()).thenReturn("DEFAULT"); when(namingService.getServicesOfServer(eq(1), eq(Integer.MAX_VALUE), eq("DEFAULT"))).thenReturn(nacosServices); NacosServiceDiscovery serviceDiscovery = new NacosServiceDiscovery( nacosDiscoveryProperties, nacosServiceManager); List services = serviceDiscovery.getServices(); assertThat(services.size()).isEqualTo(3); assertThat(services.contains(serviceName + "1")); assertThat(services.contains(serviceName + "2")); assertThat(services.contains(serviceName + "3")); } private String getUri(ServiceInstance instance) { if (instance.isSecure()) { return "https://" + host + ":" + port; } return "http://" + host + ":" + port; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/reactive/NacosReactiveDiscoveryClientConfigurationTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery.reactive; import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration; import com.alibaba.cloud.nacos.util.UtilIPv6AutoConfiguration; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; import org.springframework.cloud.commons.util.UtilAutoConfiguration; import static org.assertj.core.api.Assertions.assertThat; /** * @author echooymxq **/ public class NacosReactiveDiscoveryClientConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(UtilAutoConfiguration.class, UtilIPv6AutoConfiguration.class, NacosDiscoveryAutoConfiguration.class, NacosServiceAutoConfiguration.class, NacosReactiveDiscoveryClientConfiguration.class)); @Test public void testDefaultInitialization() { contextRunner.run(context -> assertThat(context) .hasSingleBean(ReactiveDiscoveryClient.class)); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/discovery/reactive/NacosReactiveDiscoveryClientTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.discovery.reactive; import java.util.Arrays; import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery; import com.alibaba.cloud.nacos.discovery.ServiceCache; import com.alibaba.nacos.api.exception.NacosException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; import org.springframework.cloud.client.ServiceInstance; import org.springframework.test.util.ReflectionTestUtils; import static java.util.Collections.singletonList; import static org.mockito.Mockito.when; /** * @author echooymxq * @author freeman **/ @ExtendWith(MockitoExtension.class) class NacosReactiveDiscoveryClientTests { @Mock private NacosServiceDiscovery serviceDiscovery; @Mock private ServiceInstance serviceInstance; @InjectMocks private NacosReactiveDiscoveryClient client; @Test void testGetInstances() throws NacosException { when(serviceDiscovery.getInstances("reactive-service")) .thenReturn(singletonList(serviceInstance)); Flux instances = this.client.getInstances("reactive-service"); StepVerifier.create(instances).expectNextCount(1).expectComplete().verify(); } @Test void testGetServices() throws NacosException { when(serviceDiscovery.getServices()) .thenReturn(Arrays.asList("reactive-service1", "reactive-service2")); Flux services = this.client.getServices(); StepVerifier.create(services).expectNext("reactive-service1", "reactive-service2") .expectComplete().verify(); } @Test public void testGetInstancesFailureToleranceEnabled() throws NacosException { ServiceCache.setInstances("a", singletonList(serviceInstance)); when(serviceDiscovery.getInstances("a")).thenThrow(new NacosException()); ReflectionTestUtils.setField(client, "failureToleranceEnabled", true); Flux instances = this.client.getInstances("a"); StepVerifier.create(instances).expectNext(serviceInstance) .expectComplete().verify(); } @Test public void testGetInstancesFailureToleranceDisabled() throws NacosException { ServiceCache.setInstances("a", singletonList(serviceInstance)); when(serviceDiscovery.getInstances("a")).thenThrow(new NacosException()); ReflectionTestUtils.setField(client, "failureToleranceEnabled", false); Flux instances = this.client.getInstances("a"); StepVerifier.create(instances).expectComplete().verify(); } @Test public void testFailureToleranceEnabled() throws NacosException { ServiceCache.setServiceIds(Arrays.asList("a", "b")); when(serviceDiscovery.getServices()).thenThrow(new NacosException()); ReflectionTestUtils.setField(client, "failureToleranceEnabled", true); Flux services = this.client.getServices(); StepVerifier.create(services).expectNext("a", "b") .expectComplete().verify(); } @Test public void testFailureToleranceDisabled() throws NacosException { ServiceCache.setServiceIds(Arrays.asList("a", "b")); when(serviceDiscovery.getServices()).thenThrow(new NacosException()); ReflectionTestUtils.setField(client, "failureToleranceEnabled", false); Flux services = this.client.getServices(); StepVerifier.create(services).expectComplete().verify(); } @Test public void testCacheIsOK() throws NacosException, InterruptedException { when(serviceDiscovery.getInstances("a")) .thenReturn(singletonList(serviceInstance)); Flux instances = this.client.getInstances("a"); instances = instances.doOnComplete(() -> { if (!ServiceCache.getInstances("a").equals(singletonList(serviceInstance))) { throw new RuntimeException(); } }); StepVerifier.create(instances) .expectNext(serviceInstance) .expectComplete().verify(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/registry/MockNamingService.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.util.Collections; import java.util.List; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import com.alibaba.nacos.api.naming.selector.NamingSelector; import com.alibaba.nacos.api.selector.AbstractSelector; /** * @author Jim */ public abstract class MockNamingService implements NamingService { @Override public void registerInstance(String serviceName, String ip, int port) throws NacosException { } @Override public void registerInstance(String serviceName, String groupName, String ip, int port) throws NacosException { } @Override public void registerInstance(String serviceName, String ip, int port, String clusterName) throws NacosException { } @Override public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName) throws NacosException { } @Override public void registerInstance(String serviceName, Instance instance) throws NacosException { } @Override public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { } @Override public void batchRegisterInstance(String serviceName, String groupName, List instances) throws NacosException { } @Override public void batchDeregisterInstance(String serviceName, String groupName, List instances) throws NacosException { } @Override public void deregisterInstance(String serviceName, String ip, int port) throws NacosException { } @Override public void deregisterInstance(String serviceName, String groupName, String ip, int port) throws NacosException { } @Override public void deregisterInstance(String serviceName, String ip, int port, String clusterName) throws NacosException { } @Override public void deregisterInstance(String serviceName, String groupName, String ip, int port, String clusterName) throws NacosException { } @Override public void deregisterInstance(String serviceName, Instance instance) throws NacosException { } @Override public void deregisterInstance(String serviceName, String groupName, Instance instance) throws NacosException { } @Override public List getAllInstances(String serviceName) throws NacosException { return Collections.emptyList(); } @Override public List getAllInstances(String serviceName, String groupName) throws NacosException { return Collections.emptyList(); } @Override public List getAllInstances(String serviceName, boolean subscribe) throws NacosException { return Collections.emptyList(); } @Override public List getAllInstances(String serviceName, String groupName, boolean subscribe) throws NacosException { return Collections.emptyList(); } @Override public List getAllInstances(String serviceName, List clusters) throws NacosException { return Collections.emptyList(); } @Override public List getAllInstances(String serviceName, String groupName, List clusters) throws NacosException { return Collections.emptyList(); } @Override public List getAllInstances(String serviceName, List clusters, boolean subscribe) throws NacosException { return Collections.emptyList(); } @Override public List getAllInstances(String serviceName, String groupName, List clusters, boolean subscribe) throws NacosException { return Collections.emptyList(); } @Override public List selectInstances(String serviceName, boolean healthy) throws NacosException { return Collections.emptyList(); } @Override public List selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException { return Collections.emptyList(); } @Override public List selectInstances(String serviceName, boolean healthy, boolean subscribe) throws NacosException { return Collections.emptyList(); } @Override public List selectInstances(String serviceName, String groupName, boolean healthy, boolean subscribe) throws NacosException { return Collections.emptyList(); } @Override public List selectInstances(String serviceName, List clusters, boolean healthy) throws NacosException { return Collections.emptyList(); } @Override public List selectInstances(String serviceName, String groupName, List clusters, boolean healthy) throws NacosException { return Collections.emptyList(); } @Override public List selectInstances(String serviceName, List clusters, boolean healthy, boolean subscribe) throws NacosException { return Collections.emptyList(); } @Override public List selectInstances(String serviceName, String groupName, List clusters, boolean healthy, boolean subscribe) throws NacosException { return Collections.emptyList(); } @Override public Instance selectOneHealthyInstance(String serviceName) throws NacosException { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, String groupName) throws NacosException { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, boolean subscribe) throws NacosException { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, String groupName, boolean subscribe) throws NacosException { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, List clusters) throws NacosException { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, String groupName, List clusters) throws NacosException { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, List clusters, boolean subscribe) throws NacosException { return null; } @Override public Instance selectOneHealthyInstance(String serviceName, String groupName, List clusters, boolean subscribe) throws NacosException { return null; } @Override public void subscribe(String serviceName, EventListener listener) throws NacosException { } @Override public void subscribe(String serviceName, String groupName, EventListener listener) throws NacosException { } @Override public void subscribe(String serviceName, List clusters, EventListener listener) throws NacosException { } @Override public void subscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException { } @Override public void unsubscribe(String serviceName, EventListener listener) throws NacosException { } @Override public void unsubscribe(String serviceName, String groupName, EventListener listener) throws NacosException { } @Override public void unsubscribe(String serviceName, List clusters, EventListener listener) throws NacosException { } @Override public void unsubscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException { } @Override public ListView getServicesOfServer(int pageNo, int pageSize) throws NacosException { return this.emptyListView(); } @Override public ListView getServicesOfServer(int pageNo, int pageSize, String groupName) throws NacosException { return this.emptyListView(); } @Override public ListView getServicesOfServer(int pageNo, int pageSize, AbstractSelector selector) throws NacosException { return this.emptyListView(); } @Override public ListView getServicesOfServer(int pageNo, int pageSize, String groupName, AbstractSelector selector) throws NacosException { return this.emptyListView(); } @Override public List getSubscribeServices() throws NacosException { return Collections.emptyList(); } @Override public String getServerStatus() { return null; } @Override public void shutDown() throws NacosException { } private ListView emptyListView() { ListView emptyListView = new ListView<>(); emptyListView.setCount(0); emptyListView.setData(Collections.emptyList()); return emptyListView; } @Override public void subscribe(String s, NamingSelector namingSelector, EventListener eventListener) throws NacosException { } @Override public void subscribe(String s, String s1, NamingSelector namingSelector, EventListener eventListener) throws NacosException { } @Override public void unsubscribe(String s, NamingSelector namingSelector, EventListener eventListener) throws NacosException { } @Override public void unsubscribe(String s, String s1, NamingSelector namingSelector, EventListener eventListener) throws NacosException { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/registry/NacosAutoServiceRegistrationIpNetworkInterfaceTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; import java.util.Properties; import java.util.concurrent.Future; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.naming.pojo.ListView; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** * @author xiaojing */ @SpringBootTest( classes = NacosAutoServiceRegistrationIpNetworkInterfaceTests.TestConfig.class, properties = { "spring.application.name=myTestService1", "spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848" }, webEnvironment = RANDOM_PORT) public class NacosAutoServiceRegistrationIpNetworkInterfaceTests { @Autowired private NacosRegistration registration; @Autowired private NacosAutoServiceRegistration nacosAutoServiceRegistration; @Autowired private NacosDiscoveryProperties properties; @Autowired private InetUtils inetUtils; private static MockedStatic nacosFactoryMockedStatic; static { nacosFactoryMockedStatic = Mockito.mockStatic(NacosFactory.class); nacosFactoryMockedStatic.when(() -> NacosFactory.createNamingService((Properties) any())) .thenReturn(new MockNamingService() { @Override public void fuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void fuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public Future> fuzzyWatchWithServiceKeys(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public Future> fuzzyWatchWithServiceKeys(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public void cancelFuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void cancelFuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } }); } @AfterAll public static void finished() { if (nacosFactoryMockedStatic != null) { nacosFactoryMockedStatic.close(); } } @Test public void contextLoads() throws Exception { assertThat(registration).isNotNull(); assertThat(properties).isNotNull(); assertThat(nacosAutoServiceRegistration).isNotNull(); checkoutNacosDiscoveryServiceIP(); } private void checkoutNacosDiscoveryServiceIP() { assertThat(registration.getHost()) .isEqualTo(getIPFromNetworkInterface(TestConfig.netWorkInterfaceName)); } private String getIPFromNetworkInterface(String networkInterface) { if (!TestConfig.hasValidNetworkInterface) { return inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); } try { NetworkInterface netInterface = NetworkInterface.getByName(networkInterface); Enumeration inetAddress = netInterface.getInetAddresses(); while (inetAddress.hasMoreElements()) { InetAddress currentAddress = inetAddress.nextElement(); if (currentAddress instanceof Inet4Address || currentAddress instanceof Inet6Address && !currentAddress.isLoopbackAddress()) { return currentAddress.getHostAddress(); } } return networkInterface; } catch (Exception e) { return networkInterface; } } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosServiceRegistryAutoConfiguration.class }) public static class TestConfig { static boolean hasValidNetworkInterface = false; static String netWorkInterfaceName; static { try { Enumeration enumeration = NetworkInterface .getNetworkInterfaces(); while (enumeration.hasMoreElements() && !hasValidNetworkInterface) { NetworkInterface networkInterface = enumeration.nextElement(); Enumeration inetAddress = networkInterface .getInetAddresses(); while (inetAddress.hasMoreElements()) { InetAddress currentAddress = inetAddress.nextElement(); if (currentAddress instanceof Inet4Address || currentAddress instanceof Inet6Address && !currentAddress.isLoopbackAddress()) { hasValidNetworkInterface = true; netWorkInterfaceName = networkInterface.getName(); System.setProperty( "spring.cloud.nacos.discovery.network-interface", networkInterface.getName()); break; } } } } catch (Exception e) { } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/registry/NacosAutoServiceRegistrationIpTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.util.Properties; import java.util.concurrent.Future; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.naming.pojo.ListView; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** * @author xiaojing */ @SpringBootTest(classes = NacosAutoServiceRegistrationIpTests.TestConfig.class, properties = { "spring.application.name=myTestService1", "spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848", "spring.cloud.nacos.discovery.ip=123.123.123.123" }, webEnvironment = RANDOM_PORT) public class NacosAutoServiceRegistrationIpTests { @Autowired private NacosRegistration registration; @Autowired private NacosAutoServiceRegistration nacosAutoServiceRegistration; @Autowired private NacosDiscoveryProperties properties; private static MockedStatic nacosFactoryMockedStatic; static { nacosFactoryMockedStatic = Mockito.mockStatic(NacosFactory.class); nacosFactoryMockedStatic.when(() -> NacosFactory.createNamingService((Properties) any())) .thenReturn(new MockNamingService() { @Override public void fuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void fuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public Future> fuzzyWatchWithServiceKeys(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public Future> fuzzyWatchWithServiceKeys(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public void cancelFuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void cancelFuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } }); } @AfterAll public static void finished() { if (nacosFactoryMockedStatic != null) { nacosFactoryMockedStatic.close(); } } @Test public void contextLoads() throws Exception { assertThat(registration).isNotNull(); assertThat(properties).isNotNull(); assertThat(nacosAutoServiceRegistration).isNotNull(); checkoutNacosDiscoveryServiceIP(); } private void checkoutNacosDiscoveryServiceIP() { assertThat(registration.getHost()).isEqualTo("123.123.123.123"); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosServiceRegistryAutoConfiguration.class }) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/registry/NacosAutoServiceRegistrationManagementPortTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.util.Properties; import java.util.concurrent.Future; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.naming.pojo.ListView; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** * @author xiaojing */ @SpringBootTest( classes = NacosAutoServiceRegistrationManagementPortTests.TestConfig.class, properties = { "spring.application.name=myTestService1", "management.server.port=8888", "management.server.servlet.context-path=/test-context-path", "spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848", "spring.cloud.nacos.discovery.port=8888" }, webEnvironment = RANDOM_PORT) public class NacosAutoServiceRegistrationManagementPortTests { @Autowired private NacosRegistration registration; @Autowired private NacosAutoServiceRegistration nacosAutoServiceRegistration; @Autowired private NacosDiscoveryProperties properties; private static MockedStatic nacosFactoryMockedStatic; static { nacosFactoryMockedStatic = Mockito.mockStatic(NacosFactory.class); nacosFactoryMockedStatic.when(() -> NacosFactory.createNamingService((Properties) any())) .thenReturn(new MockNamingService() { @Override public void fuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void fuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public Future> fuzzyWatchWithServiceKeys(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public Future> fuzzyWatchWithServiceKeys(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public void cancelFuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void cancelFuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } }); } @AfterAll public static void finished() { if (nacosFactoryMockedStatic != null) { nacosFactoryMockedStatic.close(); } } @Test public void contextLoads() throws Exception { assertThat(registration).isNotNull(); assertThat(properties).isNotNull(); assertThat(nacosAutoServiceRegistration).isNotNull(); checkoutNacosDiscoveryManagementData(); } private void checkoutNacosDiscoveryManagementData() { assertThat(properties.getMetadata().get(NacosRegistration.MANAGEMENT_PORT)) .isEqualTo("8888"); assertThat( properties.getMetadata().get(NacosRegistration.MANAGEMENT_CONTEXT_PATH)) .isEqualTo("/test-context-path"); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosServiceRegistryAutoConfiguration.class }) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/registry/NacosAutoServiceRegistrationPortTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.util.Properties; import java.util.concurrent.Future; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.naming.pojo.ListView; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** * @author xiaojing */ @SpringBootTest(classes = NacosAutoServiceRegistrationPortTests.TestConfig.class, properties = { "spring.application.name=myTestService1", "spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848", "spring.cloud.nacos.discovery.port=8888" }, webEnvironment = RANDOM_PORT) public class NacosAutoServiceRegistrationPortTests { @Autowired private NacosRegistration registration; @Autowired private NacosAutoServiceRegistration nacosAutoServiceRegistration; @Autowired private NacosDiscoveryProperties properties; private static MockedStatic nacosFactoryMockedStatic; static { nacosFactoryMockedStatic = Mockito.mockStatic(NacosFactory.class); nacosFactoryMockedStatic.when(() -> NacosFactory.createNamingService((Properties) any())) .thenReturn(new MockNamingService() { @Override public void fuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void fuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public Future> fuzzyWatchWithServiceKeys(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public Future> fuzzyWatchWithServiceKeys(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public void cancelFuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void cancelFuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } }); } @AfterAll public static void finished() { if (nacosFactoryMockedStatic != null) { nacosFactoryMockedStatic.close(); } } @Test public void contextLoads() throws Exception { assertThat(registration).isNotNull(); assertThat(properties).isNotNull(); assertThat(nacosAutoServiceRegistration).isNotNull(); checkoutNacosDiscoveryServicePort(); } private void checkoutNacosDiscoveryServicePort() { assertThat(registration.getPort()).isEqualTo(8888); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosServiceRegistryAutoConfiguration.class }) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/registry/NacosAutoServiceRegistrationTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.util.Map; import java.util.Properties; import java.util.concurrent.Future; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration; import com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpoint; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.naming.pojo.ListView; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.cloud.commons.util.InetUtils; import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** * @author xiaojing */ @SpringBootTest(classes = NacosAutoServiceRegistrationTests.TestConfig.class, properties = { "spring.application.name=myTestService1", "spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848", "spring.cloud.nacos.discovery.endpoint=test-endpoint", "spring.cloud.nacos.discovery.namespace=test-namespace", "spring.cloud.nacos.discovery.log-name=test-logName", "spring.cloud.nacos.discovery.weight=2", "spring.cloud.nacos.discovery.clusterName=test-cluster", "spring.cloud.nacos.discovery.namingLoadCacheAtStart=true", "spring.cloud.nacos.discovery.secure=true", "spring.cloud.nacos.discovery.accessKey=test-accessKey", "spring.cloud.nacos.discovery.ip=8.8.8.8", "spring.cloud.nacos.discovery.secretKey=test-secretKey", "spring.cloud.nacos.discovery.heart-beat-interval=3000", "spring.cloud.nacos.discovery.heart-beat-timeout=6000", "spring.cloud.nacos.discovery.ip-delete-timeout=9000" }, webEnvironment = RANDOM_PORT) public class NacosAutoServiceRegistrationTests { @Autowired private NacosRegistration registration; @Autowired private NacosAutoServiceRegistration nacosAutoServiceRegistration; @LocalServerPort private int port; @Autowired private NacosDiscoveryProperties properties; @Autowired private NacosServiceManager nacosServiceManager; @Autowired private InetUtils inetUtils; private static MockedStatic nacosFactoryMockedStatic; static { nacosFactoryMockedStatic = Mockito.mockStatic(NacosFactory.class); nacosFactoryMockedStatic.when(() -> NacosFactory.createNamingService((Properties) any())) .thenReturn(new MockNamingService() { @Override public void fuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void fuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public Future> fuzzyWatchWithServiceKeys(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public Future> fuzzyWatchWithServiceKeys(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public void cancelFuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void cancelFuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } }); } @AfterAll public static void finished() { if (nacosFactoryMockedStatic != null) { nacosFactoryMockedStatic.close(); } } @Test public void contextLoads() throws Exception { assertThat(registration).isNotNull(); assertThat(properties).isNotNull(); assertThat(nacosAutoServiceRegistration).isNotNull(); checkoutNacosDiscoveryServerAddr(); checkoutNacosDiscoveryEndpoint(); checkoutNacosDiscoveryNamespace(); checkoutNacosDiscoveryLogName(); checkoutNacosDiscoveryWeight(); checkoutNacosDiscoveryClusterName(); checkoutNacosDiscoveryCache(); checkoutNacosDiscoverySecure(); checkoutNacosDiscoveryAccessKey(); checkoutNacosDiscoverySecrectKey(); checkoutNacosDiscoveryHeartBeatInterval(); checkoutNacosDiscoveryHeartBeatTimeout(); checkoutNacosDiscoveryIpDeleteTimeout(); checkoutNacosDiscoveryServiceName(); checkoutNacosDiscoveryServiceIP(); checkoutNacosDiscoveryServicePort(); checkAutoRegister(); checkoutEndpoint(); } private void checkAutoRegister() { assertThat(nacosAutoServiceRegistration.isRunning()).isEqualTo(Boolean.TRUE); } private void checkoutNacosDiscoveryServerAddr() { assertThat(properties.getServerAddr()).isEqualTo("127.0.0.1:8848"); } private void checkoutNacosDiscoveryEndpoint() { assertThat(properties.getEndpoint()).isEqualTo("test-endpoint"); } private void checkoutNacosDiscoveryNamespace() { assertThat(properties.getNamespace()).isEqualTo("test-namespace"); } private void checkoutNacosDiscoveryLogName() { assertThat(properties.getLogName()).isEqualTo("test-logName"); } private void checkoutNacosDiscoveryWeight() { assertThat(properties.getWeight()).isEqualTo(2); } private void checkoutNacosDiscoveryClusterName() { assertThat(properties.getClusterName()).isEqualTo("test-cluster"); } private void checkoutNacosDiscoveryCache() { assertThat(properties.getNamingLoadCacheAtStart()).isEqualTo("true"); } private void checkoutNacosDiscoverySecure() { assertThat(properties.isSecure()).isEqualTo(true); assertThat(properties.getMetadata().get("secure")).isEqualTo("true"); } private void checkoutNacosDiscoveryAccessKey() { assertThat(properties.getAccessKey()).isEqualTo("test-accessKey"); } private void checkoutNacosDiscoverySecrectKey() { assertThat(properties.getSecretKey()).isEqualTo("test-secretKey"); } private void checkoutNacosDiscoveryHeartBeatInterval() { assertThat(properties.getHeartBeatInterval()).isEqualTo(Integer.valueOf(3000)); } private void checkoutNacosDiscoveryHeartBeatTimeout() { assertThat(properties.getHeartBeatTimeout()).isEqualTo(Integer.valueOf(6000)); } private void checkoutNacosDiscoveryIpDeleteTimeout() { assertThat(properties.getIpDeleteTimeout()).isEqualTo(Integer.valueOf(9000)); } private void checkoutNacosDiscoveryServiceName() { assertThat(properties.getService()).isEqualTo("myTestService1"); } private void checkoutNacosDiscoveryServiceIP() { assertThat(registration.getHost()).isEqualTo("8.8.8.8"); } private void checkoutNacosDiscoveryServicePort() { assertThat(registration.getPort()).isEqualTo(port); } private void checkoutEndpoint() throws Exception { NacosDiscoveryEndpoint nacosDiscoveryEndpoint = new NacosDiscoveryEndpoint( nacosServiceManager, properties); Map map = nacosDiscoveryEndpoint.nacosDiscovery(); assertThat(properties).isEqualTo(map.get("NacosDiscoveryProperties")); // assertThat(properties.namingServiceInstance().getSubscribeServices().toString()) // .isEqualTo(map.get("subscribe").toString()); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosServiceRegistryAutoConfiguration.class }) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/registry/NacosGracefulShutdownDelegateTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.context.ApplicationContext; import org.springframework.context.event.ContextClosedEvent; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * @author uuuyuqi */ @ExtendWith(MockitoExtension.class) public class NacosGracefulShutdownDelegateTests { @Mock private NacosAutoServiceRegistration autoServiceRegistration; @Mock private NacosDiscoveryProperties nacosDiscoveryProperties; @Mock private ApplicationContext applicationContext; @InjectMocks private NacosGracefulShutdownDelegate delegate; @BeforeEach void setUp() { delegate.setApplicationContext(applicationContext); } @Test public void sameContextShouldTriggerStop() { when(nacosDiscoveryProperties.getGracefulShutdownWaitTime()).thenReturn(0); delegate.onApplicationEvent(new ContextClosedEvent(applicationContext)); verify(autoServiceRegistration).stop(); } @Test public void differentContextShouldNotTriggerStop() { delegate.onApplicationEvent(new ContextClosedEvent(Mockito.mock(ApplicationContext.class))); verify(autoServiceRegistration, never()).stop(); } @Test public void stopExceptionShouldBeSwallowed() { doThrow(new RuntimeException("boom")).when(autoServiceRegistration).stop(); delegate.onApplicationEvent(new ContextClosedEvent(applicationContext)); verify(autoServiceRegistration).stop(); } @Test public void supportsAsyncExecutionShouldBeFalse() { assertThat(delegate.supportsAsyncExecution()).isFalse(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/registry/NacosRegistrationCustomizerTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.registry; import java.util.Map; import java.util.Properties; import java.util.concurrent.Future; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.listener.FuzzyWatchEventWatcher; import com.alibaba.nacos.api.naming.pojo.ListView; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; // 导入 AssertJ 的断言 import static org.mockito.ArgumentMatchers.any; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** * @author L.cm */ @SpringBootTest(classes = NacosRegistrationCustomizerTest.TestConfig.class, properties = { "spring.application.name=myTestService1", "spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848" }, webEnvironment = RANDOM_PORT) public class NacosRegistrationCustomizerTest { @Autowired private NacosAutoServiceRegistration nacosAutoServiceRegistration; private static MockedStatic nacosFactoryMockedStatic; static { nacosFactoryMockedStatic = Mockito.mockStatic(NacosFactory.class); nacosFactoryMockedStatic.when(() -> NacosFactory.createNamingService((Properties) any())) .thenReturn(new MockNamingService() { @Override public void fuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void fuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public Future> fuzzyWatchWithServiceKeys(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public Future> fuzzyWatchWithServiceKeys(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { return null; } @Override public void cancelFuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } @Override public void cancelFuzzyWatch(String serviceNamePattern, String groupNamePattern, FuzzyWatchEventWatcher listener) throws NacosException { } }); } @AfterAll public static void finished() { if (nacosFactoryMockedStatic != null) { nacosFactoryMockedStatic.close(); } } @Test public void contextLoads() throws Exception { NacosRegistration registration = nacosAutoServiceRegistration.getRegistration(); Map metadata = registration.getMetadata(); assertThat(metadata.get("test1")).isEqualTo("test1"); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class, NacosDiscoveryClientConfiguration.class, NacosServiceRegistryAutoConfiguration.class }) public static class TestConfig { @Bean public NacosRegistrationCustomizer nacosRegistrationCustomizer() { return registration -> { Map metadata = registration.getMetadata(); metadata.put("test1", "test1"); }; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/test/CommonTestConfig.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.test; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * @author xiaojing */ @Configuration public class CommonTestConfig { @Bean @LoadBalanced RestTemplate loadBalancedRestTemplate() { return new RestTemplate(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/test/java/com/alibaba/cloud/nacos/test/NacosMockTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.nacos.test; import java.util.Map; import java.util.UUID; import com.alibaba.nacos.api.naming.pojo.Instance; /** * @author xiaojing */ public final class NacosMockTest { private NacosMockTest() { } public static Instance serviceInstance(String serviceName, boolean isHealthy, Map metadata) { Instance instance = new Instance(); instance.setInstanceId(UUID.randomUUID().toString()); instance.setServiceName(serviceName); instance.setHealthy(isHealthy); instance.setMetadata(metadata); return instance; } public static Instance serviceInstance(String serviceName, boolean isHealthy, String host, int port, Map metadata) { Instance instance = new Instance(); instance.setIp(host); instance.setPort(port); instance.setServiceName(serviceName); instance.setHealthy(isHealthy); instance.setMetadata(metadata); return instance; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/pom.xml ================================================ 4.0.0 com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml spring-cloud-starter-alibaba-schedulerx Spring Cloud Starter Alibaba Scheduling org.springframework.boot spring-boot-jdbc provided org.springframework.boot spring-boot-autoconfigure org.springframework.boot spring-boot-starter-test test org.slf4j slf4j-api junit junit test jakarta.annotation jakarta.annotation-api com.aliyun.schedulerx schedulerx2-worker com.aliyun aliyun-java-sdk-schedulerx2 1.1.0 com.aliyun aliyun-java-sdk-core 4.4.6 net.javacrumbs.shedlock shedlock-spring net.javacrumbs.shedlock shedlock-provider-jdbc-template ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/SchedulingConstants.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling; /** * @author yaohui **/ public final class SchedulingConstants { /** * Scheduling config prefix. */ public static final String SCHEDULING_CONFIG_PREFIX = "spring.cloud.scheduling"; /** * Scheduling distributed mode. */ public static final String SCHEDULING_CONFIG_DISTRIBUTED_MODE_KEY = SCHEDULING_CONFIG_PREFIX + ".distributed-mode"; private SchedulingConstants() { throw new AssertionError("Must not instantiate constant utility class"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/JobProperty.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.schedulerx; import com.alibaba.schedulerx.common.domain.ExecuteMode; import com.alibaba.schedulerx.common.domain.JobType; import org.springframework.boot.context.properties.ConfigurationProperties; /** * Job property. * * @author xiaomeng.hxm */ @ConfigurationProperties(prefix = SchedulerxProperties.CONFIG_PREFIX) public final class JobProperty { private String jobName; private String jobType = JobType.JAVA.getKey(); private String jobModel = ExecuteMode.STANDALONE.getKey(); private String className; private String content; private Integer timeType; private String timeExpression; private String cron; private String oneTime; private String jobParameter; private String description; private boolean overwrite = false; public String getJobType() { return jobType; } public void setJobType(String jobType) { this.jobType = jobType; } public String getJobModel() { return jobModel; } public void setJobModel(String jobModel) { this.jobModel = jobModel; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getCron() { return cron; } public void setCron(String cron) { this.cron = cron; } public String getOneTime() { return oneTime; } public void setOneTime(String oneTime) { this.oneTime = oneTime; } public String getJobParameter() { return jobParameter; } public void setJobParameter(String jobParameter) { this.jobParameter = jobParameter; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isOverwrite() { return overwrite; } public void setOverwrite(boolean overwrite) { this.overwrite = overwrite; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getJobName() { return jobName; } public void setJobName(String jobName) { this.jobName = jobName; } public Integer getTimeType() { return timeType; } public void setTimeType(Integer timeType) { this.timeType = timeType; } public String getTimeExpression() { return timeExpression; } public void setTimeExpression(String timeExpression) { this.timeExpression = timeExpression; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxAutoConfigure.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.schedulerx; import com.alibaba.cloud.scheduling.SchedulingConstants; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Import; /** * schedulerx2 starter. * * @author yaohui **/ @EnableConfigurationProperties(SchedulerxProperties.class) @ConditionalOnProperty(name = SchedulingConstants.SCHEDULING_CONFIG_DISTRIBUTED_MODE_KEY, havingValue = "schedulerx") @Import({SchedulerxConfigurations.SchedulerxWorkerConfiguration.class, SchedulerxConfigurations.SpringScheduleAdaptConfiguration.class}) public class SchedulerxAutoConfigure { } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxConfigurations.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.schedulerx; import com.alibaba.cloud.scheduling.schedulerx.service.JobSyncService; import com.alibaba.cloud.scheduling.schedulerx.service.ScheduledJobSyncConfigurer; import com.alibaba.schedulerx.common.util.ConfigUtil; import com.alibaba.schedulerx.common.util.StringUtils; import com.alibaba.schedulerx.worker.SchedulerxWorker; import com.alibaba.schedulerx.worker.domain.WorkerConstants; import com.alibaba.schedulerx.worker.log.LogFactory; import com.alibaba.schedulerx.worker.log.Logger; import com.alibaba.schedulerx.worker.processor.springscheduling.NoOpScheduler; import com.alibaba.schedulerx.worker.processor.springscheduling.SchedulerxAnnotationBeanPostProcessor; import com.alibaba.schedulerx.worker.processor.springscheduling.SchedulerxSchedulingConfigurer; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor; /** * @author yaohui **/ public class SchedulerxConfigurations { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(SchedulerxWorker.class) @ConditionalOnProperty(prefix = SchedulerxProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) static class SchedulerxWorkerConfiguration { private static final Logger LOGGER = LogFactory.getLogger(SchedulerxAutoConfigure.class); private static final String WORKER_STARTER_SPRING_CLOUD = "springcloud"; @Autowired private SchedulerxProperties properties; @Bean public JobSyncService jobSyncService() { return new JobSyncService(); } @PostConstruct public void syncJobs() throws Exception { if (!properties.getJobs().isEmpty()) { LOGGER.info("{}.jobs is not empty, start to sync jobs...", SchedulerxProperties.CONFIG_PREFIX); jobSyncService().syncJobs(); LOGGER.info("sync jobs finished."); } } @Bean public SchedulerxWorker schedulerxWorker() { SchedulerxWorker schedulerxWorker = new SchedulerxWorker(); schedulerxWorker.setDomainName(properties.getDomainName()); schedulerxWorker.setGroupId(properties.getGroupId()); schedulerxWorker.setEnableBatchWork(properties.isEnableBatchWork()); schedulerxWorker.setDisableSites(properties.getDisableSites()); schedulerxWorker.setEnableSites(properties.getEnableSites()); schedulerxWorker.setDisableUnits(properties.getDisableUnits()); schedulerxWorker.setEnableUnits(properties.getEnableUnits()); schedulerxWorker.setAppKey(properties.getAppKey()); schedulerxWorker.setAliyunAccessKey(properties.getAliyunAccessKey()); schedulerxWorker.setAliyunSecretKey(properties.getAliyunSecretKey()); schedulerxWorker.setNamespace(properties.getNamespace()); schedulerxWorker.setHost(properties.getHost()); schedulerxWorker.setPort(properties.getPort()); schedulerxWorker.setEndpoint(properties.getEndpoint()); schedulerxWorker.setNamespaceSource(properties.getNamespaceSource()); schedulerxWorker.setMaxTaskBodySize(properties.getMaxTaskBodySize()); schedulerxWorker.setBlockAppStart(properties.isBlockAppStart()); schedulerxWorker.setSTSAccessKey(properties.getStsAccessKey()); schedulerxWorker.setSTSSecretKey(properties.getStsSecretKey()); schedulerxWorker.setSTSSecretToken(properties.getStsToken()); schedulerxWorker.setSlsCollectorEnable(properties.isSlsCollectorEnable()); schedulerxWorker.setShareContainerPool(properties.isShareContainerPool()); schedulerxWorker.setThreadPoolMode(properties.getThreadPoolMode()); schedulerxWorker.setLabel(properties.getLabel()); schedulerxWorker.setLabelPath(properties.getLabelPath()); if (properties.isShareContainerPool() || WorkerConstants.THREAD_POOL_MODE_ALL.equals(properties.getThreadPoolMode())) { schedulerxWorker.setSharePoolSize(properties.getSharePoolSize()); schedulerxWorker.setSharePoolQueueSize(properties.getSharePoolQueueSize()); } if (StringUtils.isNotEmpty(properties.getEndpointPort())) { schedulerxWorker.setEndpointPort(Integer.parseInt(properties.getEndpointPort())); } schedulerxWorker.setEnableCgroupMetrics(properties.isEnableCgroupMetrics()); if (properties.isEnableCgroupMetrics()) { schedulerxWorker.setCgroupPathPrefix(properties.getCgroupPathPrefix()); } if (StringUtils.isNotEmpty(properties.getNamespaceSource())) { schedulerxWorker.setNamespaceSource(properties.getNamespaceSource()); } schedulerxWorker.setAkkaRemotingAutoRecover(properties.isAkkaRemotingAutoRecover()); schedulerxWorker.setEnableHeartbeatLog(properties.isEnableHeartbeatLog()); schedulerxWorker.setMapMasterStatusCheckInterval(properties.getMapMasterStatusCheckInterval()); schedulerxWorker.setEnableSecondDelayCycleIntervalMs(properties.isEnableSecondDelayCycleIntervalMs()); schedulerxWorker.setEnableMapMasterFailover(properties.isEnableMapMasterFailover()); schedulerxWorker.setEnableSecondDelayStandaloneDispatch(properties.isEnableSecondDelayStandaloneDispatch()); schedulerxWorker.setPageSize(properties.getPageSize()); schedulerxWorker.setGraceShutdownMode(properties.getGraceShutdownMode()); if (properties.getGraceShutdownTimeout() > 0) { schedulerxWorker.setGraceShutdownTimeout(properties.getGraceShutdownTimeout()); } schedulerxWorker.setBroadcastDispatchThreadNum(properties.getBroadcastDispatchThreadNum()); schedulerxWorker.setBroadcastDispatchThreadEnable(properties.isBroadcastDispatchThreadEnable()); schedulerxWorker.setBroadcastMasterExecEnable(properties.isBroadcastMasterExecEnable()); schedulerxWorker.setBroadcastDispatchRetryTimes(properties.getBroadcastDispatchRetryTimes()); schedulerxWorker.setProcessorPoolSize(properties.getProcessorPoolSize()); schedulerxWorker.setMapMasterDispatchRandom(properties.isMapMasterDispatchRandom()); schedulerxWorker.setMapMasterRouterStrategy(properties.getMapMasterRouterStrategy()); if (StringUtils.isNotEmpty(properties.getH2DatabaseUser())) { schedulerxWorker.setH2DatabaseUser(properties.getH2DatabaseUser()); } if (StringUtils.isNotEmpty(properties.getH2DatabasePassword())) { schedulerxWorker.setH2DatabasePassword(properties.getH2DatabasePassword()); } schedulerxWorker.setHttpServerEnable(properties.getHttpServerEnable()); schedulerxWorker.setHttpServerPort(properties.getHttpServerPort()); if (properties.getMaxMapDiskPercent() != null) { schedulerxWorker.setMaxMapDiskPercent(properties.getMaxMapDiskPercent()); } ConfigUtil.getWorkerConfig().setProperty(WorkerConstants.WORKER_STARTER_MODE, WORKER_STARTER_SPRING_CLOUD); return schedulerxWorker; } } @Configuration(proxyBeanMethods = false) @AutoConfigureAfter(SchedulerxWorkerConfiguration.class) @ConditionalOnBean(SchedulerxWorker.class) static class SpringScheduleAdaptConfiguration { @Bean(ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME) public NoOpScheduler noOpScheduler() { return new NoOpScheduler(); } @Bean public SchedulerxSchedulingConfigurer schedulerxSchedulingConfigurer() { return new SchedulerxSchedulingConfigurer(noOpScheduler(), true); } @Bean public static SchedulerxAnnotationBeanPostProcessor schedulerxAnnotationBeanPostProcessor() { return new SchedulerxAnnotationBeanPostProcessor(); } @Bean @ConditionalOnProperty(prefix = SchedulerxProperties.CONFIG_PREFIX, name = "task-sync", havingValue = "true") public ScheduledJobSyncConfigurer scheduledJobSyncConfigurer() { return new ScheduledJobSyncConfigurer(); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/SchedulerxProperties.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.schedulerx; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import com.alibaba.cloud.scheduling.SchedulingConstants; import com.alibaba.schedulerx.common.domain.ContactInfo; import com.alibaba.schedulerx.common.util.JsonUtil; import com.alibaba.schedulerx.worker.domain.WorkerConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.context.properties.ConfigurationProperties; /** * schedulerx worker properties. * * @author yaohui **/ @ConfigurationProperties(prefix = SchedulerxProperties.CONFIG_PREFIX) public class SchedulerxProperties implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(SchedulerxProperties.class); /** * schedulerx config prefix. */ public static final String CONFIG_PREFIX = SchedulingConstants.SCHEDULING_CONFIG_PREFIX + ".schedulerx"; /** * domainName. */ private String domainName; /** * groupId. */ private String groupId; /** * host. */ private String host; /** * client port. */ private int port = 0; private String enableUnits; private String disableUnits; private String enableSites; private String disableSites; private boolean enableBatchWork; /** * enabled: true; false. */ private boolean enabled = true; /** * appName. */ private String appName; /** * appKey. */ private String appKey; /** * aliyunRamRole. */ private String aliyunRamRole; /** * aliyunAccessKey. */ private String aliyunAccessKey; /** * aliyunSecretKey. */ private String aliyunSecretKey; /** * STS ak. */ private String stsAccessKey; /** * STS sk. */ private String stsSecretKey; /** * STS secret token. */ private String stsToken; /** * Namespace UID. */ private String namespace; /** * endpoint. */ private String endpoint; /** * endpointPort. */ private String endpointPort; /** * namespaceName. */ private String namespaceName; /** * namespaceSource. */ private String namespaceSource; /** * maxTaskBodySize (byte). */ private int maxTaskBodySize = WorkerConstants.TASK_BODY_SIZE_MAX_DEFAULT; private boolean blockAppStart = true; /** * slsCollectorEnable. */ private boolean slsCollectorEnable = true; /** * shareContainerPool. */ private boolean shareContainerPool = false; /** * threadPoolMode. */ private String threadPoolMode; /** * sharePoolSize. */ private int sharePoolSize = WorkerConstants.SHARE_POOL_SIZE_DEFAULT; /** * sharePoolQueueSize. */ private int sharePoolQueueSize = Integer.MAX_VALUE; /** * Wlabel. */ private String label; private String labelPath = "/etc/podinfo/labels"; /** * enableCgroupMetrics. */ private boolean enableCgroupMetrics = false; /** * cgroupPathPrefix. */ private String cgroupPathPrefix = "/sys/fs/cgroup/cpu/"; /** * akkaRemotingAutoRecover. */ private boolean akkaRemotingAutoRecover = true; /** * enableHeartbeatLog. */ private boolean enableHeartbeatLog = true; /** * mapMasterStatusCheckInterval(ms). */ private int mapMasterStatusCheckInterval = WorkerConstants.Map_MASTER_STATUS_CHECK_INTERVAL_DEFAULT; /** * enableSecondDelayCycleIntervalMs. */ private boolean enableSecondDelayCycleIntervalMs = false; /** * enableMapMasterFailover. */ private boolean enableMapMasterFailover = true; /** * enableSecondDelayStandaloneDispatch. */ private boolean enableSecondDelayStandaloneDispatch = false; /** * pageSize. */ private int pageSize = 1000; /** * GraceShutdownMode(WAIT_ALL; WAIT_RUNNING;). */ private String graceShutdownMode; /** * graceShutdownTimeout. */ private long graceShutdownTimeout = WorkerConstants.GRACE_SHUTDOWN_TIMEOUT_DEFAULT; /** * broadcastDispatchThreadNum. */ private int broadcastDispatchThreadNum = 4; /** * broadcastDispatchRetryTimes. */ private int broadcastDispatchRetryTimes = 1; /** * broadcastDispatchThreadEnable. */ private boolean broadcastDispatchThreadEnable = false; /** * broadcastMasterExecEnable. */ private boolean broadcastMasterExecEnable = true; /** * mapMasterDispatchRandom. */ private boolean mapMasterDispatchRandom = false; private Integer mapMasterRouterStrategy; private String regionId; /** * h2DatabaseUser. */ private String h2DatabaseUser; /** * h2DatabasePassword. */ private String h2DatabasePassword; /** * httpServerEnable. */ private Boolean httpServerEnable; /** * httpServerPort. */ private Integer httpServerPort; /** * maxMapDiskPercent. */ private Float maxMapDiskPercent; private Map jobs = new LinkedHashMap<>(); private String alarmChannel; private Map alarmUsers = new LinkedHashMap<>(); private Map processorPoolSize = new HashMap<>(); public String getDomainName() { return domainName; } public void setDomainName(String domainName) { this.domainName = domainName; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getEnableUnits() { return enableUnits; } public void setEnableUnits(String enableUnits) { this.enableUnits = enableUnits; } public String getDisableUnits() { return disableUnits; } public void setDisableUnits(String disableUnits) { this.disableUnits = disableUnits; } public String getEnableSites() { return enableSites; } public void setEnableSites(String enableSites) { this.enableSites = enableSites; } public String getDisableSites() { return disableSites; } public void setDisableSites(String disableSites) { this.disableSites = disableSites; } public boolean isEnableBatchWork() { return enableBatchWork; } public void setEnableBatchWork(boolean enableBatchWork) { this.enableBatchWork = enableBatchWork; } public String getAliyunAccessKey() { return aliyunAccessKey; } public void setAliyunAccessKey(String aliyunAccessKey) { this.aliyunAccessKey = aliyunAccessKey; } public String getAliyunSecretKey() { return aliyunSecretKey; } public void setAliyunSecretKey(String aliyunSecretKey) { this.aliyunSecretKey = aliyunSecretKey; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getEndpoint() { return endpoint; } public void setEndpoint(String endpoint) { this.endpoint = endpoint; } public String getEndpointPort() { return endpointPort; } public void setEndpointPort(String endpointPort) { this.endpointPort = endpointPort; } public String getNamespaceName() { return namespaceName; } public void setNamespaceName(String namespaceName) { this.namespaceName = namespaceName; } public String getNamespaceSource() { return namespaceSource; } public void setNamespaceSource(String namespaceSource) { this.namespaceSource = namespaceSource; } public int getMaxTaskBodySize() { return maxTaskBodySize; } public void setMaxTaskBodySize(int maxTaskBodySize) { this.maxTaskBodySize = maxTaskBodySize; } public boolean isBlockAppStart() { return blockAppStart; } public void setBlockAppStart(boolean blockAppStart) { this.blockAppStart = blockAppStart; } public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public String getAppKey() { return appKey; } public void setAppKey(String appKey) { this.appKey = appKey; } public String getStsAccessKey() { return stsAccessKey; } public void setStsAccessKey(String stsAccessKey) { this.stsAccessKey = stsAccessKey; } public String getStsSecretKey() { return stsSecretKey; } public void setStsSecretKey(String stsSecretKey) { this.stsSecretKey = stsSecretKey; } public String getStsToken() { return stsToken; } public String getAliyunRamRole() { return aliyunRamRole; } public void setAliyunRamRole(String aliyunRamRole) { this.aliyunRamRole = aliyunRamRole; } public void setStsToken(String stsToken) { this.stsToken = stsToken; } public boolean isSlsCollectorEnable() { return slsCollectorEnable; } public void setSlsCollectorEnable(boolean slsCollectorEnable) { this.slsCollectorEnable = slsCollectorEnable; } public boolean isShareContainerPool() { return shareContainerPool; } public void setShareContainerPool(boolean shareContainerPool) { this.shareContainerPool = shareContainerPool; } public int getSharePoolSize() { return sharePoolSize; } public void setSharePoolSize(int sharePoolSize) { this.sharePoolSize = sharePoolSize; } public String getLabel() { return label; } public void setLabel(String label) { if (label != null) { if (label.startsWith("#") && label.endsWith("#")) { String labelKey = label.substring(1, label.length() - 1); this.label = System.getenv(labelKey); return; } } this.label = label; } public String getLabelPath() { return labelPath; } public void setLabelPath(String labelPath) { this.labelPath = labelPath; } public boolean isEnableCgroupMetrics() { return enableCgroupMetrics; } public void setEnableCgroupMetrics(boolean enableCgroupMetrics) { this.enableCgroupMetrics = enableCgroupMetrics; } public String getCgroupPathPrefix() { return cgroupPathPrefix; } public void setCgroupPathPrefix(String cgroupPathPrefix) { this.cgroupPathPrefix = cgroupPathPrefix; } public boolean isAkkaRemotingAutoRecover() { return akkaRemotingAutoRecover; } public void setAkkaRemotingAutoRecover(boolean akkaRemotingAutoRecover) { this.akkaRemotingAutoRecover = akkaRemotingAutoRecover; } public boolean isEnableHeartbeatLog() { return enableHeartbeatLog; } public void setEnableHeartbeatLog(boolean enableHeartbeatLog) { this.enableHeartbeatLog = enableHeartbeatLog; } public int getMapMasterStatusCheckInterval() { return mapMasterStatusCheckInterval; } public void setMapMasterStatusCheckInterval(int mapMasterStatusCheckInterval) { this.mapMasterStatusCheckInterval = mapMasterStatusCheckInterval; } public boolean isEnableSecondDelayCycleIntervalMs() { return enableSecondDelayCycleIntervalMs; } public void setEnableSecondDelayCycleIntervalMs(boolean enableSecondDelayCycleIntervalMs) { this.enableSecondDelayCycleIntervalMs = enableSecondDelayCycleIntervalMs; } public boolean isEnableMapMasterFailover() { return enableMapMasterFailover; } public void setEnableMapMasterFailover(boolean enableMapMasterFailover) { this.enableMapMasterFailover = enableMapMasterFailover; } public boolean isEnableSecondDelayStandaloneDispatch() { return enableSecondDelayStandaloneDispatch; } public void setEnableSecondDelayStandaloneDispatch(boolean enableSecondDelayStandaloneDispatch) { this.enableSecondDelayStandaloneDispatch = enableSecondDelayStandaloneDispatch; } public int getPageSize() { return pageSize; } public String getGraceShutdownMode() { return graceShutdownMode; } public void setGraceShutdownMode(String graceShutdownMode) { this.graceShutdownMode = graceShutdownMode; } public long getGraceShutdownTimeout() { return graceShutdownTimeout; } public void setGraceShutdownTimeout(long graceShutdownTimeout) { this.graceShutdownTimeout = graceShutdownTimeout; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public String getRegionId() { return regionId; } public void setRegionId(String regionId) { this.regionId = regionId; } public Map getJobs() { return jobs; } public void setJobs(Map jobs) { this.jobs = jobs; } public String getAlarmChannel() { return alarmChannel; } public void setAlarmChannel(String alarmChannel) { this.alarmChannel = alarmChannel; } public Map getAlarmUsers() { return alarmUsers; } public void setAlarmUsers(Map alarmUsers) { this.alarmUsers = alarmUsers; } public int getBroadcastDispatchThreadNum() { return broadcastDispatchThreadNum; } public void setBroadcastDispatchThreadNum(int broadcastDispatchThreadNum) { this.broadcastDispatchThreadNum = broadcastDispatchThreadNum; } public boolean isBroadcastDispatchThreadEnable() { return broadcastDispatchThreadEnable; } public void setBroadcastDispatchThreadEnable(boolean broadcastDispatchThreadEnable) { this.broadcastDispatchThreadEnable = broadcastDispatchThreadEnable; } public String getThreadPoolMode() { return threadPoolMode; } public void setThreadPoolMode(String threadPoolMode) { this.threadPoolMode = threadPoolMode; } public Map getProcessorPoolSize() { return processorPoolSize; } public void setProcessorPoolSize(Map processorPoolSize) { this.processorPoolSize = processorPoolSize; } public int getSharePoolQueueSize() { return sharePoolQueueSize; } public void setSharePoolQueueSize(int sharePoolQueueSize) { this.sharePoolQueueSize = sharePoolQueueSize; } public boolean isMapMasterDispatchRandom() { return mapMasterDispatchRandom; } public void setMapMasterDispatchRandom(boolean mapMasterDispatchRandom) { this.mapMasterDispatchRandom = mapMasterDispatchRandom; } public boolean isBroadcastMasterExecEnable() { return broadcastMasterExecEnable; } public void setBroadcastMasterExecEnable(boolean broadcastMasterExecEnable) { this.broadcastMasterExecEnable = broadcastMasterExecEnable; } public int getBroadcastDispatchRetryTimes() { return broadcastDispatchRetryTimes; } public void setBroadcastDispatchRetryTimes(int broadcastDispatchRetryTimes) { this.broadcastDispatchRetryTimes = broadcastDispatchRetryTimes; } public Integer getMapMasterRouterStrategy() { return mapMasterRouterStrategy; } public void setMapMasterRouterStrategy(Integer mapMasterRouterStrategy) { this.mapMasterRouterStrategy = mapMasterRouterStrategy; } public String getH2DatabaseUser() { return h2DatabaseUser; } public void setH2DatabaseUser(String h2DatabaseUser) { this.h2DatabaseUser = h2DatabaseUser; } public String getH2DatabasePassword() { return h2DatabasePassword; } public void setH2DatabasePassword(String h2DatabasePassword) { this.h2DatabasePassword = h2DatabasePassword; } public Boolean getHttpServerEnable() { return httpServerEnable; } public void setHttpServerEnable(Boolean httpServerEnable) { this.httpServerEnable = httpServerEnable; } public Integer getHttpServerPort() { return httpServerPort; } public void setHttpServerPort(Integer httpServerPort) { this.httpServerPort = httpServerPort; } public Float getMaxMapDiskPercent() { return maxMapDiskPercent; } public void setMaxMapDiskPercent(float maxMapDiskPercent) { this.maxMapDiskPercent = maxMapDiskPercent; } @Override public void afterPropertiesSet() { logger.info("SchedulerxProperties->" + JsonUtil.toJson(this)); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/constants/SchedulerxConstants.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.schedulerx.constants; /** * Schedulerx constants. * * @author yaohui */ public final class SchedulerxConstants { /** * Schedulerx default namespace source. */ public static final String NAMESPACE_SOURCE_SPRINGBOOT = "springboot"; /** * Aliyun pop product. */ public static final String ALIYUN_POP_PRODUCT = "schedulerx2"; /** * Aliyun pop endpoint. */ public static final String ALIYUN_POP_SCHEDULERX_ENDPOINT = "schedulerx.aliyuncs.com"; /** * Second delay max value. */ public static final int SECOND_DELAY_MAX_VALUE = 60; /** * Second delay min value. */ public static final int SECOND_DELAY_MIN_VALUE = 1; /** * Job timeout default value. */ public static final long JOB_TIMEOUT_DEFAULT = 3600L; /** * Job retry count default value. */ public static final int JOB_RETRY_COUNT_DEFAULT = 3; /** * Job retry interval default value. */ public static final int JOB_RETRY_INTERVAL_DEFAULT = 30; /** * Job alarm channel default value. */ public static final String JOB_ALARM_CHANNEL_DEFAULT = "default"; /** * Job model mapreduce alias. */ public static final String JOB_MODEL_MAPREDUCE_ALIAS = "mapreduce"; @Override public String toString() { return super.toString(); } private SchedulerxConstants() { throw new AssertionError("Must not instantiate constant utility class"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/service/JobSyncService.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.schedulerx.service; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import com.alibaba.cloud.scheduling.schedulerx.JobProperty; import com.alibaba.cloud.scheduling.schedulerx.SchedulerxProperties; import com.alibaba.cloud.scheduling.schedulerx.constants.SchedulerxConstants; import com.alibaba.cloud.scheduling.schedulerx.util.CronExpression; import com.alibaba.schedulerx.common.domain.ContactInfo; import com.alibaba.schedulerx.common.domain.ExecuteMode; import com.alibaba.schedulerx.common.domain.JobType; import com.alibaba.schedulerx.common.domain.TimeType; import com.alibaba.schedulerx.common.sdk.common.MonitorConfig; import com.alibaba.schedulerx.common.util.JsonUtil; import com.alibaba.schedulerx.common.util.StringUtils; import com.alibaba.schedulerx.worker.log.LogFactory; import com.alibaba.schedulerx.worker.log.Logger; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.auth.InstanceProfileCredentialsProvider; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.schedulerx2.model.v20190430.CreateAppGroupRequest; import com.aliyuncs.schedulerx2.model.v20190430.CreateAppGroupResponse; import com.aliyuncs.schedulerx2.model.v20190430.CreateJobRequest; import com.aliyuncs.schedulerx2.model.v20190430.CreateJobResponse; import com.aliyuncs.schedulerx2.model.v20190430.CreateNamespaceRequest; import com.aliyuncs.schedulerx2.model.v20190430.CreateNamespaceResponse; import com.aliyuncs.schedulerx2.model.v20190430.GetJobInfoRequest; import com.aliyuncs.schedulerx2.model.v20190430.GetJobInfoResponse; import com.aliyuncs.schedulerx2.model.v20190430.GetJobInfoResponse.Data.JobConfigInfo; import com.aliyuncs.schedulerx2.model.v20190430.UpdateJobRequest; import com.aliyuncs.schedulerx2.model.v20190430.UpdateJobResponse; import org.springframework.beans.factory.annotation.Autowired; /** * JobSyncService. * * @author xiaomeng.hxm */ public class JobSyncService { private static final Logger logger = LogFactory.getLogger(JobSyncService.class); @Autowired private SchedulerxProperties properties; private DefaultAcsClient client; private synchronized DefaultAcsClient getClient() { // build aliyun pop client if (client == null) { DefaultProfile.addEndpoint(properties.getRegionId(), SchedulerxConstants.ALIYUN_POP_PRODUCT, SchedulerxConstants.ALIYUN_POP_SCHEDULERX_ENDPOINT); if (StringUtils.isNotEmpty(properties.getAliyunRamRole())) { DefaultProfile profile = DefaultProfile.getProfile(properties.getRegionId()); InstanceProfileCredentialsProvider provider = new InstanceProfileCredentialsProvider( properties.getAliyunRamRole()); client = new DefaultAcsClient(profile, provider); } else { DefaultProfile defaultProfile = DefaultProfile.getProfile(properties.getRegionId(), properties.getAliyunAccessKey(), properties.getAliyunSecretKey()); client = new DefaultAcsClient(defaultProfile); } } return client; } /** * Sync job config. * * @param jobs job configs * @param namespaceSource namespace source * @throws Exception sync job config exception */ public synchronized void syncJobs(Map jobs, String namespaceSource) throws Exception { DefaultAcsClient client = getClient(); for (Entry entry : jobs.entrySet()) { String jobName = entry.getKey(); JobProperty jobProperty = entry.getValue(); JobConfigInfo jobConfigInfo = getJob(client, jobName, namespaceSource); if (jobConfigInfo == null) { createJob(client, jobName, jobProperty, namespaceSource); } else if (jobProperty.isOverwrite()) { updateJob(client, jobConfigInfo, jobProperty, namespaceSource); } } } /** * sync jobs. * * @throws Exception sync jobs exception */ public void syncJobs() throws Exception { // 1. create namespace if (syncNamespace(getClient())) { // 2. create app group if (syncAppGroup(getClient())) { syncJobs(properties.getJobs(), getNamespaceSource()); properties.setNamespaceSource(getNamespaceSource()); } } } /** * sync namespace. * * @param client pop client * @return true if success * @throws Exception sync namespace exception */ public boolean syncNamespace(DefaultAcsClient client) throws Exception { if (StringUtils.isEmpty(properties.getNamespace())) { logger.error("please set {}.namespace", SchedulerxProperties.CONFIG_PREFIX); throw new IOException(String.format("please set %s.namespace", SchedulerxProperties.CONFIG_PREFIX)); } if (StringUtils.isEmpty(properties.getNamespaceName())) { logger.error("please set {}.namespaceName", SchedulerxProperties.CONFIG_PREFIX); throw new IOException(String.format("please set %s.namespaceName", SchedulerxProperties.CONFIG_PREFIX)); } CreateNamespaceRequest request = new CreateNamespaceRequest(); request.setUid(properties.getNamespace()); request.setName(properties.getNamespaceName()); request.setSource(getNamespaceSource()); CreateNamespaceResponse response = client.getAcsResponse(request); if (response.getSuccess()) { logger.info(JsonUtil.toJson(response)); return true; } else { throw new IOException(response.getMessage()); } } /** * sync app group. * * @param client pop client * @return sync app group result * @throws IOException sync app group exception. * @throws ClientException sync app group pop client exception. */ public boolean syncAppGroup(DefaultAcsClient client) throws IOException, ClientException { if (StringUtils.isEmpty(properties.getAppName())) { logger.error("please set {}.appName", SchedulerxProperties.CONFIG_PREFIX); throw new IOException(String.format("please set %s.appName", SchedulerxProperties.CONFIG_PREFIX)); } if (StringUtils.isEmpty(properties.getAppKey())) { logger.error("please set {}.appKey", SchedulerxProperties.CONFIG_PREFIX); throw new IOException(String.format("please set %s.appKey", SchedulerxProperties.CONFIG_PREFIX)); } if (StringUtils.isEmpty(properties.getGroupId())) { logger.error("please set {}.groupId", SchedulerxProperties.CONFIG_PREFIX); throw new IOException(String.format("please set %s.groupId", SchedulerxProperties.CONFIG_PREFIX)); } CreateAppGroupRequest request = new CreateAppGroupRequest(); request.setNamespace(properties.getNamespace()); request.setNamespaceSource(getNamespaceSource()); request.setAppName(properties.getAppName()); request.setGroupId(properties.getGroupId()); request.setAppKey(properties.getAppKey()); if (StringUtils.isNotEmpty(properties.getAlarmChannel())) { MonitorConfig monitorConfig = new MonitorConfig(); monitorConfig.setSendChannel(properties.getAlarmChannel()); request.setMonitorConfigJson(JsonUtil.toJson(monitorConfig)); } if (!properties.getAlarmUsers().isEmpty()) { List contactInfos = new ArrayList(properties.getAlarmUsers().values()); request.setMonitorContactsJson(JsonUtil.toJson(contactInfos)); } CreateAppGroupResponse response = client.getAcsResponse(request); if (response.getSuccess()) { logger.info(JsonUtil.toJson(response)); return true; } else { throw new IOException(response.getMessage()); } } /** * Get job config info. * * @param client pop client * @param jobName job name * @param namespaceSource namespace source * @return job config info. * @throws Exception get job config info exception. */ private JobConfigInfo getJob(DefaultAcsClient client, String jobName, String namespaceSource) throws Exception { GetJobInfoRequest request = new GetJobInfoRequest(); request.setNamespace(properties.getNamespace()); request.setNamespaceSource(namespaceSource); request.setGroupId(properties.getGroupId()); request.setJobId(0L); request.setJobName(jobName); GetJobInfoResponse response = client.getAcsResponse(request); if (response.getSuccess()) { return response.getData().getJobConfigInfo(); } return null; } /** * create job. * * @param client pop client * @param jobName job name * @param jobProperty job property * @param namespaceSource namespace source * @throws Exception create job exception */ private void createJob(DefaultAcsClient client, String jobName, JobProperty jobProperty, String namespaceSource) throws Exception { CreateJobRequest request = new CreateJobRequest(); request.setNamespace(properties.getNamespace()); request.setNamespaceSource(namespaceSource); request.setGroupId(properties.getGroupId()); request.setName(jobName); request.setParameters(jobProperty.getJobParameter()); if (jobProperty.getJobType().equals(JobType.JAVA.getKey())) { request.setJobType(JobType.JAVA.getKey()); request.setClassName(jobProperty.getClassName()); } else { request.setJobType(jobProperty.getJobType()); } if (SchedulerxConstants.JOB_MODEL_MAPREDUCE_ALIAS.equals(jobProperty.getJobModel())) { request.setExecuteMode(ExecuteMode.BATCH.getKey()); } else { request.setExecuteMode(jobProperty.getJobModel()); } if (StringUtils.isNotEmpty(jobProperty.getDescription())) { request.setDescription(jobProperty.getDescription()); } if (StringUtils.isNotEmpty(jobProperty.getContent())) { request.setContent(jobProperty.getContent()); } if (StringUtils.isNotEmpty(jobProperty.getCron()) && StringUtils.isNotEmpty(jobProperty.getOneTime())) { throw new IOException("cron and oneTime shouldn't set together"); } if (StringUtils.isNotEmpty(jobProperty.getCron())) { CronExpression cronExpression = new CronExpression(jobProperty.getCron()); Date now = new Date(); Date nextData = cronExpression.getTimeAfter(now); Date next2Data = cronExpression.getTimeAfter(nextData); if (nextData != null && next2Data != null) { long interval = TimeUnit.MILLISECONDS.toSeconds((next2Data.getTime() - nextData.getTime())); if (interval < SchedulerxConstants.SECOND_DELAY_MAX_VALUE) { request.setTimeType(TimeType.SECOND_DELAY.getValue()); request.setTimeExpression(String.valueOf(interval < SchedulerxConstants.SECOND_DELAY_MIN_VALUE ? SchedulerxConstants.SECOND_DELAY_MIN_VALUE : interval)); } else { request.setTimeType(TimeType.CRON.getValue()); request.setTimeExpression(jobProperty.getCron()); } } else { request.setTimeType(TimeType.CRON.getValue()); request.setTimeExpression(jobProperty.getCron()); } } else if (StringUtils.isNotEmpty(jobProperty.getOneTime())) { request.setTimeType(TimeType.ONE_TIME.getValue()); request.setTimeExpression(jobProperty.getOneTime()); } else { request.setTimeType(TimeType.API.getValue()); } if (jobProperty.getTimeType() != null) { request.setTimeType(jobProperty.getTimeType()); if (StringUtils.isNotEmpty(jobProperty.getTimeExpression())) { request.setTimeExpression(jobProperty.getTimeExpression()); } } request.setTimeoutEnable(true); request.setTimeoutKillEnable(true); request.setSendChannel(SchedulerxConstants.JOB_ALARM_CHANNEL_DEFAULT); request.setFailEnable(true); request.setTimeout(SchedulerxConstants.JOB_TIMEOUT_DEFAULT); request.setMaxAttempt(SchedulerxConstants.JOB_RETRY_COUNT_DEFAULT); request.setAttemptInterval(SchedulerxConstants.JOB_RETRY_INTERVAL_DEFAULT); CreateJobResponse response = client.getAcsResponse(request); if (response.getSuccess()) { logger.info("create schedulerx job successfully, jobId={}, jobName={}", response.getData().getJobId(), jobName); } else { throw new IOException("create schedulerx job failed, jobName=" + jobName + ", message=" + response.getMessage()); } } /** * update job. * * @param client pop client * @param jobConfigInfo job config info * @param jobProperty job property * @param namespaceSource namespace source * @throws Exception update job exception */ private void updateJob(DefaultAcsClient client, JobConfigInfo jobConfigInfo, JobProperty jobProperty, String namespaceSource) throws Exception { String executeMode = jobProperty.getJobModel(); if (SchedulerxConstants.JOB_MODEL_MAPREDUCE_ALIAS.equals(jobProperty.getJobModel())) { executeMode = ExecuteMode.BATCH.getKey(); } int timeType; String timeExpression = null; if (StringUtils.isNotEmpty(jobProperty.getCron()) && StringUtils.isNotEmpty(jobProperty.getOneTime())) { throw new IOException("cron and oneTime shouldn't set together"); } if (StringUtils.isNotEmpty(jobProperty.getCron())) { CronExpression cronExpression = new CronExpression(jobProperty.getCron()); Date now = new Date(); Date nextData = cronExpression.getTimeAfter(now); Date next2Data = cronExpression.getTimeAfter(nextData); if (nextData != null && next2Data != null) { long interval = TimeUnit.MILLISECONDS.toSeconds((next2Data.getTime() - nextData.getTime())); if (interval < SchedulerxConstants.SECOND_DELAY_MAX_VALUE) { timeType = TimeType.SECOND_DELAY.getValue(); timeExpression = String.valueOf(interval < SchedulerxConstants.SECOND_DELAY_MIN_VALUE ? SchedulerxConstants.SECOND_DELAY_MIN_VALUE : interval); } else { timeType = TimeType.CRON.getValue(); timeExpression = jobProperty.getCron(); } } else { timeType = TimeType.CRON.getValue(); timeExpression = jobProperty.getCron(); } } else if (StringUtils.isNotEmpty(jobProperty.getOneTime())) { timeType = TimeType.ONE_TIME.getValue(); timeExpression = jobProperty.getOneTime(); } else { timeType = TimeType.API.getValue(); } if (!jobConfigInfo.getDescription().equals(jobProperty.getDescription()) || !jobConfigInfo.getClassName().equals(jobProperty.getClassName()) || !jobConfigInfo.getParameters().equals(jobProperty.getJobParameter()) || !jobConfigInfo.getExecuteMode().equals(executeMode) || jobConfigInfo.getTimeConfig().getTimeType() != timeType || !jobConfigInfo.getTimeConfig().getTimeExpression().equals(timeExpression)) { UpdateJobRequest request = new UpdateJobRequest(); request.setNamespace(properties.getNamespace()); request.setNamespaceSource(namespaceSource); request.setGroupId(properties.getGroupId()); request.setJobId(jobConfigInfo.getJobId()); request.setName(jobConfigInfo.getName()); request.setParameters(jobProperty.getJobParameter()); //java任务 if (jobProperty.getJobType().equals(JobType.JAVA.getKey())) { request.setClassName(jobProperty.getClassName()); } request.setExecuteMode(executeMode); if (StringUtils.isNotEmpty(jobProperty.getDescription())) { request.setDescription(jobProperty.getDescription()); } request.setTimeType(timeType); request.setTimeExpression(timeExpression); request.setTimeoutEnable(true); request.setTimeoutKillEnable(true); request.setSendChannel(SchedulerxConstants.JOB_ALARM_CHANNEL_DEFAULT); request.setFailEnable(true); request.setTimeout(SchedulerxConstants.JOB_TIMEOUT_DEFAULT); request.setMaxAttempt(SchedulerxConstants.JOB_RETRY_COUNT_DEFAULT); request.setAttemptInterval(SchedulerxConstants.JOB_RETRY_INTERVAL_DEFAULT); UpdateJobResponse response = client.getAcsResponse(request); if (response.getSuccess()) { logger.info("update schedulerx job successfully, jobId={}, jobName={}", jobConfigInfo.getJobId(), jobConfigInfo.getName()); } else { throw new IOException("update schedulerx job failed, jobName=" + jobConfigInfo.getName() + ", message=" + response.getMessage()); } } } /** * Get namespace source. * * @return namespace source */ private String getNamespaceSource() { if (StringUtils.isEmpty(properties.getNamespaceSource())) { return SchedulerxConstants.NAMESPACE_SOURCE_SPRINGBOOT; } return properties.getNamespaceSource(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/service/ScheduledJobSyncConfigurer.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.schedulerx.service; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.alibaba.cloud.scheduling.schedulerx.JobProperty; import com.alibaba.cloud.scheduling.schedulerx.SchedulerxProperties; import com.alibaba.schedulerx.common.domain.ExecuteMode; import com.alibaba.schedulerx.common.domain.JobType; import com.alibaba.schedulerx.common.domain.Pair; import com.alibaba.schedulerx.common.domain.TimeType; import com.alibaba.schedulerx.common.util.JsonUtil; import com.alibaba.schedulerx.common.util.StringUtils; import com.alibaba.schedulerx.scheduling.annotation.SchedulerX; import com.alibaba.schedulerx.worker.domain.SpringScheduleProfile; import com.alibaba.schedulerx.worker.log.LogFactory; import com.alibaba.schedulerx.worker.log.Logger; import com.alibaba.schedulerx.worker.processor.springscheduling.SchedulerxJobRegister; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.CronTask; import org.springframework.scheduling.config.IntervalTask; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.Task; import org.springframework.scheduling.support.ScheduledMethodRunnable; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; /** * Spring scheduled job sync. * * @author yaohui * @create 2022/8/17 下午2:21 **/ public class ScheduledJobSyncConfigurer implements SchedulingConfigurer { private static final Logger logger = LogFactory.getLogger(ScheduledJobSyncConfigurer.class); @Autowired private JobSyncService jobSyncService; @Autowired private SchedulerxProperties properties; @Value("${" + SchedulerxProperties.CONFIG_PREFIX + ".task-overwrite:false}") private Boolean overwrite = false; @Value("${" + SchedulerxProperties.CONFIG_PREFIX + ".task-model-default:broadcast}") private String defaultModel = ExecuteMode.BROADCAST.getKey(); private boolean isValidModel(String mode) { if (mode == null) { return false; } return (ExecuteMode.BROADCAST.getKey().equals(mode) || ExecuteMode.STANDALONE.getKey().equals(mode)); } private JobProperty convertToJobProperty(Task task, Object target, Method method) { JobProperty jobProperty = new JobProperty(); Class targetClass = AopProxyUtils.ultimateTargetClass(target); if (ClassUtils.isCglibProxyClass(targetClass)) { targetClass = ClassUtils.getUserClass(target); } String jobName = targetClass.getSimpleName() + "_" + method.getName(); String model = this.defaultModel; if (task != null && task instanceof CronTask) { String expression = ((CronTask) task).getExpression(); jobProperty.setCron(expression); } if (task != null && task instanceof IntervalTask) { long interval = ((IntervalTask) task).getInterval() / 1000; interval = interval < 1 ? 1 : interval; if (interval < 60) { jobProperty.setTimeType(TimeType.SECOND_DELAY.getValue()); } else { jobProperty.setTimeType(TimeType.FIXED_RATE.getValue()); } jobProperty.setTimeExpression(String.valueOf(interval)); } SchedulerX schedulerXMethod = AnnotatedElementUtils.getMergedAnnotation(method, SchedulerX.class); if (schedulerXMethod != null) { if (StringUtils.isNotEmpty(schedulerXMethod.name())) { jobName = schedulerXMethod.name(); } if (isValidModel(schedulerXMethod.model())) { model = schedulerXMethod.model(); } if (StringUtils.isNotEmpty(schedulerXMethod.cron())) { jobProperty.setCron(schedulerXMethod.cron()); } if (schedulerXMethod.fixedRate() > 0) { long interval = schedulerXMethod.timeUnit().toSeconds(schedulerXMethod.fixedRate()); interval = interval < 1 ? 1 : interval; if (interval < 60) { jobProperty.setTimeType(TimeType.SECOND_DELAY.getValue()); } else { jobProperty.setTimeType(TimeType.FIXED_RATE.getValue()); } jobProperty.setTimeExpression(String.valueOf(interval)); } } jobProperty.setJobName(jobName); jobProperty.setJobType(JobType.SPRINGSCHEDULE.getKey()); jobProperty.setJobModel(model); SpringScheduleProfile profile = new SpringScheduleProfile(); profile.setClassName(targetClass.getName()); profile.setMethod(method.getName()); jobProperty.setContent(JsonUtil.toJson(profile)); jobProperty.setOverwrite(overwrite); return jobProperty; } @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { logger.info("spring scheduled job is not empty, start to sync jobs..."); try { Map jobs = new HashMap<>(); if (!CollectionUtils.isEmpty(taskRegistrar.getCronTaskList())) { for (CronTask cronTask : taskRegistrar.getCronTaskList()) { if (cronTask.getRunnable() instanceof ScheduledMethodRunnable) { ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) cronTask.getRunnable(); JobProperty jobProperty = convertToJobProperty(cronTask, runnable.getTarget(), runnable.getMethod()); jobs.put(jobProperty.getJobName(), jobProperty); } } } if (!CollectionUtils.isEmpty(taskRegistrar.getFixedDelayTaskList())) { for (IntervalTask intervalTask : taskRegistrar.getFixedDelayTaskList()) { if (intervalTask.getRunnable() instanceof ScheduledMethodRunnable) { ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) intervalTask.getRunnable(); JobProperty jobProperty = convertToJobProperty(intervalTask, runnable.getTarget(), runnable.getMethod()); jobs.put(jobProperty.getJobName(), jobProperty); } } } if (!CollectionUtils.isEmpty(taskRegistrar.getFixedRateTaskList())) { for (IntervalTask intervalTask : taskRegistrar.getFixedRateTaskList()) { if (intervalTask.getRunnable() instanceof ScheduledMethodRunnable) { ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) intervalTask.getRunnable(); JobProperty jobProperty = convertToJobProperty(intervalTask, runnable.getTarget(), runnable.getMethod()); jobs.put(jobProperty.getJobName(), jobProperty); } } } // 获取仅SchedulerX注解任务 Collection> schedulerXTasks = SchedulerxJobRegister.getInstance().getSchedulerXTaskTargets(); if (schedulerXTasks != null && schedulerXTasks.size() > 0) { for (Pair task : schedulerXTasks) { JobProperty jobProperty = convertToJobProperty(null, task.getFirst(), task.getSecond()); jobs.put(jobProperty.getJobName(), jobProperty); } } jobSyncService.syncJobs(jobs, properties.getNamespaceSource()); logger.info("spring scheduled job is not empty, sync jobs finished."); } catch (Exception e) { logger.info("spring scheduled job is not empty, sync jobs failed.", e); throw new RuntimeException(e); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpression.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.schedulerx.util; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TimeZone; import java.util.TreeSet; import org.springframework.util.Assert; /** * @author yaohui */ public final class CronExpression { protected static final int SECOND = 0; protected static final int MINUTE = 1; protected static final int HOUR = 2; protected static final int DAY_OF_MONTH = 3; protected static final int MONTH = 4; protected static final int DAY_OF_WEEK = 5; protected static final int YEAR = 6; protected static final int ALL_SPEC_INT = 99; // '*' protected static final int NO_SPEC_INT = 98; // '?' protected static final Integer ALL_SPEC = ALL_SPEC_INT; protected static final Integer NO_SPEC = NO_SPEC_INT; protected static final Map monthMap = new HashMap<>(20); protected static final Map dayMap = new HashMap<>(60); static { monthMap.put("JAN", 0); monthMap.put("FEB", 1); monthMap.put("MAR", 2); monthMap.put("APR", 3); monthMap.put("MAY", 4); monthMap.put("JUN", 5); monthMap.put("JUL", 6); monthMap.put("AUG", 7); monthMap.put("SEP", 8); monthMap.put("OCT", 9); monthMap.put("NOV", 10); monthMap.put("DEC", 11); dayMap.put("SUN", 1); dayMap.put("MON", 2); dayMap.put("TUE", 3); dayMap.put("WED", 4); dayMap.put("THU", 5); dayMap.put("FRI", 6); dayMap.put("SAT", 7); } private final String cronExpression; private TimeZone timeZone = null; protected transient TreeSet seconds; protected transient TreeSet minutes; protected transient TreeSet hours; protected transient TreeSet daysOfMonth; protected transient TreeSet months; protected transient TreeSet daysOfWeek; protected transient TreeSet years; protected transient boolean lastdayOfWeek = false; protected transient int nthdayOfWeek = 0; protected transient boolean lastdayOfMonth = false; protected transient boolean nearestWeekday = false; protected transient int lastdayOffset = 0; protected transient boolean expressionParsed = false; /** * MAX_YEAR. */ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100; /** * MIN_CAL. */ public static final Calendar MIN_CAL = Calendar.getInstance(); static { MIN_CAL.set(1970, 0, 1); } /** * MIN_DATE. */ public static final Date MIN_DATE = MIN_CAL.getTime(); /** * Constructs a new CronExpression based on the specified * parameter. * * @param cronExpression String representation of the cron expression the * new object should represent * @throws ParseException if the string expression cannot be parsed into a valid * CronExpression */ public CronExpression(final String cronExpression) throws ParseException { if (cronExpression == null) { throw new IllegalArgumentException("cronExpression cannot be null"); } this.cronExpression = cronExpression.toUpperCase(Locale.US); buildExpression(this.cronExpression); } /** * Indicates whether the given date satisfies the cron expression. Note that * milliseconds are ignored, so two Dates falling on different milliseconds * of the same second will always have the same result here. * * @param date the date to evaluate * @return a boolean indicating whether the given date satisfies the cron * expression. */ public boolean isSatisfiedBy(final Date date) { final Calendar testDateCal = Calendar.getInstance(getTimeZone()); testDateCal.setTime(date); testDateCal.set(Calendar.MILLISECOND, 0); final Date originalDate = testDateCal.getTime(); testDateCal.add(Calendar.SECOND, -1); final Date timeAfter = getTimeAfter(testDateCal.getTime()); return ((timeAfter != null) && (timeAfter.equals(originalDate))); } /** * Returns the next date/time after the given date/time which * satisfies the cron expression. * * @param date the date/time at which to begin the search for the next valid * date/time * @return the next valid date/time. */ public Date getNextValidTimeAfter(final Date date) { return getTimeAfter(date); } /** * Returns the next date/time after the given date/time which does * not satisfy the expression. * * @param date the date/time at which to begin the search for the next * invalid date/time * @return the next valid date/time. */ public Date getNextInvalidTimeAfter(final Date date) { long difference = 1000; //move back to the nearest second so differences will be accurate final Calendar adjustCal = Calendar.getInstance(getTimeZone()); adjustCal.setTime(date); adjustCal.set(Calendar.MILLISECOND, 0); Date lastDate = adjustCal.getTime(); Date newDate; //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution. //keep getting the next included time until it's farther than one second // apart. At that point, lastDate is the last valid fire time. We return // the second immediately following it. while (difference == 1000) { newDate = getTimeAfter(lastDate); if (newDate == null) { break; } difference = newDate.getTime() - lastDate.getTime(); if (difference == 1000) { lastDate = newDate; } } return new Date(lastDate.getTime() + 1000); } /** * Returns the time zone for which this CronExpression * will be resolved. * * @return the time zone. */ public TimeZone getTimeZone() { if (timeZone == null) { timeZone = TimeZone.getDefault(); } return timeZone; } public void setTimeZone(final TimeZone timeZone) { this.timeZone = timeZone; } @Override public String toString() { return cronExpression; } /** * check cron expression is valid or not. * * @param cronExpression cron expression * @return return{@code true}if Cron expression is valid,otherwise return{@code false}. */ public static boolean isValidExpression(final String cronExpression) { try { new CronExpression(cronExpression); } catch (final ParseException pe) { return false; } return true; } public static void validateExpression(final String cronExpression) throws ParseException { new CronExpression(cronExpression); } protected void buildExpression(final String expression) throws ParseException { expressionParsed = true; try { if (seconds == null) { seconds = new TreeSet<>(); } if (minutes == null) { minutes = new TreeSet<>(); } if (hours == null) { hours = new TreeSet<>(); } if (daysOfMonth == null) { daysOfMonth = new TreeSet<>(); } if (months == null) { months = new TreeSet<>(); } if (daysOfWeek == null) { daysOfWeek = new TreeSet<>(); } if (years == null) { years = new TreeSet<>(); } int exprOn = SECOND; final StringTokenizer exprsTok = new StringTokenizer(expression, " \t", false); while (exprsTok.hasMoreTokens() && exprOn <= YEAR) { final String expr = exprsTok.nextToken().trim(); // throw an exception if L is used with other days of the month if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1); } // throw an exception if L is used with other days of the week if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) { throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1); } if (exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') + 1) != -1) { throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1); } final StringTokenizer vTok = new StringTokenizer(expr, ","); while (vTok.hasMoreTokens()) { final String v = vTok.nextToken(); storeExpressionVals(0, v, exprOn); } exprOn++; } if (exprOn <= DAY_OF_WEEK) { throw new ParseException("Unexpected end of expression.", expression.length()); } if (exprOn <= YEAR) { storeExpressionVals(0, "*", YEAR); } final TreeSet dow = getSet(DAY_OF_WEEK); final TreeSet dom = getSet(DAY_OF_MONTH); // Copying the logic from the UnsupportedOperationException below final boolean dayOfMSpec = !dom.contains(NO_SPEC); final boolean dayOfWSpec = !dow.contains(NO_SPEC); if (!dayOfMSpec || dayOfWSpec) { if (!dayOfWSpec || dayOfMSpec) { throw new ParseException( "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0); } } } catch (final ParseException pe) { throw pe; } catch (final Exception e) { throw new ParseException("Illegal cron expression format (" + e.toString() + ")", 0); } } protected int storeExpressionVals(final int pos, final String s, final int type) throws ParseException { int incr = 0; int i = skipWhiteSpace(pos, s); if (i >= s.length()) { return i; } char c = s.charAt(i); if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) { String sub = s.substring(i, i + 3); int sval = -1; int eval = -1; if (type == MONTH) { sval = getMonthNumber(sub) + 1; if (sval <= 0) { throw new ParseException("Invalid Month value: '" + sub + "'", i); } if (s.length() > i + 3 && (s.charAt(i + 3) == '-')) { i += 4; sub = s.substring(i, i + 3); eval = getMonthNumber(sub) + 1; Assert.isTrue(eval > 0, "Invalid Month value: '" + sub + "'"); } } else if (type == DAY_OF_WEEK) { sval = getDayOfWeekNumber(sub); if (sval < 0) { throw new ParseException("Invalid Day-of-Week value: '" + sub + "'", i); } if (s.length() > i + 3) { c = s.charAt(i + 3); if (c == '-') { i += 4; sub = s.substring(i, i + 3); eval = getDayOfWeekNumber(sub); Assert.isTrue(eval >= 0, "Invalid Day-of-Week value: '" + sub + "'"); } else if (c == '#') { i += 4; nthdayOfWeek = Integer.parseInt(s.substring(i)); Assert.isTrue(nthdayOfWeek > 0 && nthdayOfWeek < 6, "A numeric value between 1 and 5 must follow the '#' option"); } else if (c == 'L') { lastdayOfWeek = true; i++; } } } else { throw new ParseException( "Illegal characters for this position: '" + sub + "'", i); } if (eval != -1) { incr = 1; } addToSet(sval, eval, incr, type); return (i + 3); } if (c == '?') { i++; if ((i + 1) < s.length() && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) { throw new ParseException("Illegal character after '?': " + s.charAt(i), i); } if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) { throw new ParseException( "'?' can only be specfied for Day-of-Month or Day-of-Week.", i); } if (type == DAY_OF_WEEK && !lastdayOfMonth) { final int val = daysOfMonth.last(); if (val == NO_SPEC_INT) { throw new ParseException( "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.", i); } } addToSet(NO_SPEC_INT, -1, 0, type); return i; } if (c == '*' || c == '/') { if (c == '*' && (i + 1) >= s.length()) { addToSet(ALL_SPEC_INT, -1, incr, type); return i + 1; } else if (c == '/' && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s .charAt(i + 1) == '\t')) { throw new ParseException("'/' must be followed by an integer.", i); } else if (c == '*') { i++; } c = s.charAt(i); if (c == '/') { // is an increment specified? i++; if (i >= s.length()) { throw new ParseException("Unexpected end of string.", i); } incr = getNumericValue(s, i); i++; if (incr > 10) { i++; } if (incr > 59 && (type == SECOND || type == MINUTE)) { throw new ParseException("Increment > 60 : " + incr, i); } else if (incr > 23 && (type == HOUR)) { throw new ParseException("Increment > 24 : " + incr, i); } else if (incr > 31 && (type == DAY_OF_MONTH)) { throw new ParseException("Increment > 31 : " + incr, i); } else if (incr > 7 && (type == DAY_OF_WEEK)) { throw new ParseException("Increment > 7 : " + incr, i); } else if (incr > 12 && (type == MONTH)) { throw new ParseException("Increment > 12 : " + incr, i); } } else { incr = 1; } addToSet(ALL_SPEC_INT, -1, incr, type); return i; } else if (c == 'L') { i++; if (type == DAY_OF_MONTH) { lastdayOfMonth = true; } if (type == DAY_OF_WEEK) { addToSet(7, 7, 0, type); } if (type == DAY_OF_MONTH && s.length() > i) { c = s.charAt(i); if (c == '-') { final ValueSet vs = getValue(0, s, i + 1); lastdayOffset = vs.value; if (lastdayOffset > 30) { throw new ParseException("Offset from last day must be <= 30", i + 1); } i = vs.pos; } if (s.length() > i) { c = s.charAt(i); if (c == 'W') { nearestWeekday = true; i++; } } } return i; } else if (c >= '0' && c <= '9') { int val = Integer.parseInt(String.valueOf(c)); i++; if (i >= s.length()) { addToSet(val, -1, -1, type); } else { c = s.charAt(i); if (c >= '0' && c <= '9') { final ValueSet vs = getValue(val, s, i); val = vs.value; i = vs.pos; } i = checkNext(i, s, val, type); return i; } } else { throw new ParseException("Unexpected character: " + c, i); } return i; } protected int checkNext(final int pos, final String s, final int val, final int type) throws ParseException { int end = -1; int i = pos; if (i >= s.length()) { addToSet(val, end, -1, type); return i; } char c = s.charAt(pos); if (c == 'L') { if (type == DAY_OF_WEEK) { if (val < 1 || val > 7) { throw new ParseException("Day-of-Week values must be between 1 and 7", -1); } lastdayOfWeek = true; } else { throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i); } final TreeSet set = getSet(type); set.add(val); i++; return i; } if (c == 'W') { if (type == DAY_OF_MONTH) { nearestWeekday = true; } else { throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i); } if (val > 31) { throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i); } final TreeSet set = getSet(type); set.add(val); i++; return i; } if (c == '#') { if (type != DAY_OF_WEEK) { throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i); } i++; try { nthdayOfWeek = Integer.parseInt(s.substring(i)); if (nthdayOfWeek < 1 || nthdayOfWeek > 5) { throw new Exception(); } } catch (final Exception e) { throw new ParseException( "A numeric value between 1 and 5 must follow the '#' option", i); } final TreeSet set = getSet(type); set.add(val); i++; return i; } if (c == '-') { i++; c = s.charAt(i); final int v = Integer.parseInt(String.valueOf(c)); end = v; i++; if (i >= s.length()) { addToSet(val, end, 1, type); return i; } c = s.charAt(i); if (c >= '0' && c <= '9') { final ValueSet vs = getValue(v, s, i); end = vs.value; i = vs.pos; } if (i < s.length() && (s.charAt(i) == '/')) { i++; c = s.charAt(i); final int v2 = Integer.parseInt(String.valueOf(c)); i++; if (i >= s.length()) { addToSet(val, end, v2, type); return i; } c = s.charAt(i); if (c >= '0' && c <= '9') { final ValueSet vs = getValue(v2, s, i); final int v3 = vs.value; addToSet(val, end, v3, type); i = vs.pos; return i; } else { addToSet(val, end, v2, type); return i; } } else { addToSet(val, end, 1, type); return i; } } if (c == '/') { i++; c = s.charAt(i); final int v2 = Integer.parseInt(String.valueOf(c)); i++; if (i >= s.length()) { addToSet(val, end, v2, type); return i; } c = s.charAt(i); if (c >= '0' && c <= '9') { final ValueSet vs = getValue(v2, s, i); final int v3 = vs.value; addToSet(val, end, v3, type); i = vs.pos; return i; } else { throw new ParseException("Unexpected character '" + c + "' after '/'", i); } } addToSet(val, end, 0, type); i++; return i; } public String getCronExpression() { return cronExpression; } public String getExpressionSummary() { final StringBuilder buf = new StringBuilder(); buf.append("seconds: "); buf.append(getExpressionSetSummary(seconds)); buf.append("\n"); buf.append("minutes: "); buf.append(getExpressionSetSummary(minutes)); buf.append("\n"); buf.append("hours: "); buf.append(getExpressionSetSummary(hours)); buf.append("\n"); buf.append("daysOfMonth: "); buf.append(getExpressionSetSummary(daysOfMonth)); buf.append("\n"); buf.append("months: "); buf.append(getExpressionSetSummary(months)); buf.append("\n"); buf.append("daysOfWeek: "); buf.append(getExpressionSetSummary(daysOfWeek)); buf.append("\n"); buf.append("lastdayOfWeek: "); buf.append(lastdayOfWeek); buf.append("\n"); buf.append("nearestWeekday: "); buf.append(nearestWeekday); buf.append("\n"); buf.append("NthDayOfWeek: "); buf.append(nthdayOfWeek); buf.append("\n"); buf.append("lastdayOfMonth: "); buf.append(lastdayOfMonth); buf.append("\n"); buf.append("years: "); buf.append(getExpressionSetSummary(years)); buf.append("\n"); return buf.toString(); } protected String getExpressionSetSummary(final java.util.Set set) { if (set.contains(NO_SPEC)) { return "?"; } if (set.contains(ALL_SPEC)) { return "*"; } final StringBuilder buf = new StringBuilder(); final Iterator itr = set.iterator(); boolean first = true; while (itr.hasNext()) { final Integer iVal = itr.next(); final String val = iVal.toString(); if (!first) { buf.append(","); } buf.append(val); first = false; } return buf.toString(); } protected String getExpressionSetSummary(final java.util.ArrayList list) { if (list.contains(NO_SPEC)) { return "?"; } if (list.contains(ALL_SPEC)) { return "*"; } final StringBuilder buf = new StringBuilder(); final Iterator itr = list.iterator(); boolean first = true; while (itr.hasNext()) { final Integer iVal = itr.next(); final String val = iVal.toString(); if (!first) { buf.append(","); } buf.append(val); first = false; } return buf.toString(); } protected int skipWhiteSpace(int i, final String s) { for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) { // empty } return i; } protected int findNextWhiteSpace(int i, final String s) { for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) { // empty } return i; } protected void addToSet(final int val, final int end, int incr, final int type) throws ParseException { final TreeSet set = getSet(type); if (type == SECOND || type == MINUTE) { if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) { throw new ParseException( "Minute and Second values must be between 0 and 59", -1); } } else if (type == HOUR) { if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) { throw new ParseException( "Hour values must be between 0 and 23", -1); } } else if (type == DAY_OF_MONTH) { if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) { throw new ParseException( "Day of month values must be between 1 and 31", -1); } } else if (type == MONTH) { if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) { throw new ParseException( "Month values must be between 1 and 12", -1); } } else if (type == DAY_OF_WEEK) { if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) { throw new ParseException( "Day-of-Week values must be between 1 and 7", -1); } } if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) { if (val != -1) { set.add(val); } else { set.add(NO_SPEC); } return; } int startAt = val; int stopAt = end; if (val == ALL_SPEC_INT && incr <= 0) { incr = 1; set.add(ALL_SPEC); // put in a marker, but also fill values } if (type == SECOND || type == MINUTE) { if (stopAt == -1) { stopAt = 59; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 0; } } else if (type == HOUR) { if (stopAt == -1) { stopAt = 23; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 0; } } else if (type == DAY_OF_MONTH) { if (stopAt == -1) { stopAt = 31; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 1; } } else if (type == MONTH) { if (stopAt == -1) { stopAt = 12; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 1; } } else if (type == DAY_OF_WEEK) { if (stopAt == -1) { stopAt = 7; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 1; } } else if (type == YEAR) { if (stopAt == -1) { stopAt = MAX_YEAR; } if (startAt == -1 || startAt == ALL_SPEC_INT) { startAt = 1970; } } // if the end of the range is before the start, then we need to overflow into // the next day, month etc. This is done by adding the maximum amount for that // type, and using modulus max to determine the value being added. int max = -1; if (stopAt < startAt) { switch (type) { case SECOND: max = 60; break; case MINUTE: max = 60; break; case HOUR: max = 24; break; case MONTH: max = 12; break; case DAY_OF_WEEK: max = 7; break; case DAY_OF_MONTH: max = 31; break; case YEAR: throw new IllegalArgumentException("Start year must be less than stop year"); default: throw new IllegalArgumentException("Unexpected type encountered"); } stopAt += max; } for (int i = startAt; i <= stopAt; i += incr) { if (max == -1) { // ie: there's no max to overflow over set.add(i); } else { // take the modulus to get the real value int i2 = i % max; // 1-indexed ranges should not include 0, and should include their max if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) { i2 = max; } set.add(i2); } } } TreeSet getSet(final int type) throws ParseException { switch (type) { case SECOND: return seconds; case MINUTE: return minutes; case HOUR: return hours; case DAY_OF_MONTH: return daysOfMonth; case MONTH: return months; case DAY_OF_WEEK: return daysOfWeek; case YEAR: return years; default: throw new ParseException("Invalid type value: '" + type + "'", -1); } } protected ValueSet getValue(final int v, final String s, int i) { char c = s.charAt(i); final StringBuilder s1 = new StringBuilder(String.valueOf(v)); while (c >= '0' && c <= '9') { s1.append(c); i++; if (i >= s.length()) { break; } c = s.charAt(i); } final ValueSet val = new ValueSet(); val.pos = (i < s.length()) ? i : i + 1; val.value = Integer.parseInt(s1.toString()); return val; } protected int getNumericValue(final String s, final int i) { final int endOfVal = findNextWhiteSpace(i, s); final String val = s.substring(i, endOfVal); return Integer.parseInt(val); } protected int getMonthNumber(final String s) { final Integer integer = monthMap.get(s); if (integer == null) { return -1; } return integer; } protected int getDayOfWeekNumber(final String s) { final Integer integer = dayMap.get(s); if (integer == null) { return -1; } return integer; } public Date getTimeAfter(Date afterTime) { // Computation is based on Gregorian year only. final Calendar cl = new java.util.GregorianCalendar(getTimeZone()); // move ahead one second, since we're computing the time *after* the // given time if (afterTime == null) { return null; } afterTime = new Date(afterTime.getTime() + 1000); // CronTrigger does not deal with milliseconds cl.setTime(afterTime); cl.set(Calendar.MILLISECOND, 0); boolean gotOne = false; // loop until we've computed the next time, or we've past the endTime while (!gotOne) { //if (endTime != null && cl.getTime().after(endTime)) return null; if (cl.get(Calendar.YEAR) > 2999) { // prevent endless loop... return null; } SortedSet st = null; int t = 0; int sec = cl.get(Calendar.SECOND); int min = cl.get(Calendar.MINUTE); // get second................................................. st = seconds.tailSet(sec); if (st != null && st.size() != 0) { sec = st.first(); } else { sec = seconds.first(); min++; cl.set(Calendar.MINUTE, min); } cl.set(Calendar.SECOND, sec); min = cl.get(Calendar.MINUTE); int hr = cl.get(Calendar.HOUR_OF_DAY); t = -1; // get minute................................................. st = minutes.tailSet(min); if (st != null && st.size() != 0) { t = min; min = st.first(); } else { min = minutes.first(); hr++; } if (min != t) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, min); setCalendarHour(cl, hr); continue; } cl.set(Calendar.MINUTE, min); hr = cl.get(Calendar.HOUR_OF_DAY); int day = cl.get(Calendar.DAY_OF_MONTH); t = -1; // get hour................................................... st = hours.tailSet(hr); if (st != null && st.size() != 0) { t = hr; hr = st.first(); } else { hr = hours.first(); day++; } if (hr != t) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.DAY_OF_MONTH, day); setCalendarHour(cl, hr); continue; } cl.set(Calendar.HOUR_OF_DAY, hr); day = cl.get(Calendar.DAY_OF_MONTH); int mon = cl.get(Calendar.MONTH) + 1; // '+ 1' because calendar is 0-based for this field, and we are // 1-based t = -1; int tmon = mon; // get day................................................... final boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC); final boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC); if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule st = daysOfMonth.tailSet(day); if (lastdayOfMonth) { if (!nearestWeekday) { t = day; day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); day -= lastdayOffset; day = t > day ? 1 : day; if (t > day && ++mon > 12) { mon = 1; tmon = 3333; // ensure test of mon != tmon further below fails cl.add(Calendar.YEAR, 1); } } else { t = day; day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); day -= lastdayOffset; final Calendar tcal = Calendar.getInstance(getTimeZone()); tcal.set(Calendar.SECOND, 0); tcal.set(Calendar.MINUTE, 0); tcal.set(Calendar.HOUR_OF_DAY, 0); tcal.set(Calendar.DAY_OF_MONTH, day); tcal.set(Calendar.MONTH, mon - 1); tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); final int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); final int dow = tcal.get(Calendar.DAY_OF_WEEK); if (dow == Calendar.SATURDAY && day == 1) { day += 2; } else if (dow == Calendar.SATURDAY) { day -= 1; } else if (dow == Calendar.SUNDAY && day == ldom) { day -= 2; } else if (dow == Calendar.SUNDAY) { day += 1; } tcal.set(Calendar.SECOND, sec); tcal.set(Calendar.MINUTE, min); tcal.set(Calendar.HOUR_OF_DAY, hr); tcal.set(Calendar.DAY_OF_MONTH, day); tcal.set(Calendar.MONTH, mon - 1); final Date nTime = tcal.getTime(); if (nTime.before(afterTime)) { day = 1; mon++; } } } else if (nearestWeekday) { t = day; day = daysOfMonth.first(); final Calendar tcal = Calendar.getInstance(getTimeZone()); tcal.set(Calendar.SECOND, 0); tcal.set(Calendar.MINUTE, 0); tcal.set(Calendar.HOUR_OF_DAY, 0); tcal.set(Calendar.DAY_OF_MONTH, day); tcal.set(Calendar.MONTH, mon - 1); tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR)); final int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); final int dow = tcal.get(Calendar.DAY_OF_WEEK); if (dow == Calendar.SATURDAY && day == 1) { day += 2; } else if (dow == Calendar.SATURDAY) { day -= 1; } else if (dow == Calendar.SUNDAY && day == ldom) { day -= 2; } else if (dow == Calendar.SUNDAY) { day += 1; } tcal.set(Calendar.SECOND, sec); tcal.set(Calendar.MINUTE, min); tcal.set(Calendar.HOUR_OF_DAY, hr); tcal.set(Calendar.DAY_OF_MONTH, day); tcal.set(Calendar.MONTH, mon - 1); final Date nTime = tcal.getTime(); if (nTime.before(afterTime)) { day = daysOfMonth.first(); mon++; } } else if (st != null && st.size() != 0) { t = day; day = st.first(); // make sure we don't over-run a short month, such as february final int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); if (day > lastDay) { day = daysOfMonth.first(); mon++; } } else { day = daysOfMonth.first(); mon++; } if (day != t || mon != tmon) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, day); cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is 0-based for this field, and we // are 1-based continue; } } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule if (lastdayOfWeek) { // are we looking for the last XXX day of // the month? final int dow = daysOfWeek.first(); // desired // d-o-w final int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w int daysToAdd = 0; if (cDow < dow) { daysToAdd = dow - cDow; } if (cDow > dow) { daysToAdd = dow + (7 - cDow); } final int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); if (day + daysToAdd > lDay) { // did we already miss the // last one? cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, mon); // no '- 1' here because we are promoting the month continue; } // find date of last occurrence of this day in this month... while ((day + daysToAdd + 7) <= lDay) { daysToAdd += 7; } day += daysToAdd; if (daysToAdd > 0) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, day); cl.set(Calendar.MONTH, mon - 1); // '- 1' here because we are not promoting the month continue; } } else if (nthdayOfWeek != 0) { // are we looking for the Nth XXX day in the month? final int dow = daysOfWeek.first(); // desired // d-o-w final int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w int daysToAdd = 0; if (cDow < dow) { daysToAdd = dow - cDow; } else if (cDow > dow) { daysToAdd = dow + (7 - cDow); } boolean dayShifted = false; if (daysToAdd > 0) { dayShifted = true; } day += daysToAdd; int weekOfMonth = day / 7; if (day % 7 > 0) { weekOfMonth++; } daysToAdd = (nthdayOfWeek - weekOfMonth) * 7; day += daysToAdd; if (daysToAdd < 0 || day > getLastDayOfMonth(mon, cl .get(Calendar.YEAR))) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, mon); // no '- 1' here because we are promoting the month continue; } else if (daysToAdd > 0 || dayShifted) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, day); cl.set(Calendar.MONTH, mon - 1); // '- 1' here because we are NOT promoting the month continue; } } else { final int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w int dow = daysOfWeek.first(); // desired // d-o-w st = daysOfWeek.tailSet(cDow); if (st != null && st.size() > 0) { dow = st.first(); } int daysToAdd = 0; if (cDow < dow) { daysToAdd = dow - cDow; } if (cDow > dow) { daysToAdd = dow + (7 - cDow); } final int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR)); if (day + daysToAdd > lDay) { // will we pass the end of // the month? cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, mon); // no '- 1' here because we are promoting the month continue; } else if (daysToAdd > 0) { // are we switching days? cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd); cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is 0-based for this field, // and we are 1-based continue; } } } else { // dayOfWSpec && !dayOfMSpec throw new UnsupportedOperationException( "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented."); } cl.set(Calendar.DAY_OF_MONTH, day); mon = cl.get(Calendar.MONTH) + 1; // '+ 1' because calendar is 0-based for this field, and we are // 1-based int year = cl.get(Calendar.YEAR); t = -1; // test for expressions that never generate a valid fire date, // but keep looping... if (year > MAX_YEAR) { return null; } // get month................................................... st = months.tailSet(mon); if (st != null && st.size() != 0) { t = mon; mon = st.first(); } else { mon = months.first(); year++; } if (mon != t) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is 0-based for this field, and we are // 1-based cl.set(Calendar.YEAR, year); continue; } cl.set(Calendar.MONTH, mon - 1); // '- 1' because calendar is 0-based for this field, and we are // 1-based year = cl.get(Calendar.YEAR); t = -1; // get year................................................... st = years.tailSet(year); if (st != null && st.size() != 0) { t = year; year = st.first(); } else { return null; // ran out of years... } if (year != t) { cl.set(Calendar.SECOND, 0); cl.set(Calendar.MINUTE, 0); cl.set(Calendar.HOUR_OF_DAY, 0); cl.set(Calendar.DAY_OF_MONTH, 1); cl.set(Calendar.MONTH, 0); // '- 1' because calendar is 0-based for this field, and we are // 1-based cl.set(Calendar.YEAR, year); continue; } cl.set(Calendar.YEAR, year); gotOne = true; } // while( !done ) return cl.getTime(); } /** * Advance the calendar to the particular hour paying particular attention * to daylight saving problems. * * @param cal the calendar to operate on * @param hour the hour to set */ protected void setCalendarHour(final Calendar cal, final int hour) { cal.set(Calendar.HOUR_OF_DAY, hour); if (cal.get(Calendar.HOUR_OF_DAY) != hour && hour != 24) { cal.set(Calendar.HOUR_OF_DAY, hour + 1); } } protected Date getTimeBefore(final Date targetDate) { final Calendar cl = Calendar.getInstance(getTimeZone()); // CronTrigger does not deal with milliseconds, so truncate target cl.setTime(targetDate); cl.set(Calendar.MILLISECOND, 0); final Date targetDateNoMs = cl.getTime(); // to match this Date start = targetDateNoMs; final long minIncrement = findMinIncrement(); Date prevFireTime; do { final Date prevCheckDate = new Date(start.getTime() - minIncrement); prevFireTime = getTimeAfter(prevCheckDate); if (prevFireTime == null || prevFireTime.before(MIN_DATE)) { return null; } start = prevCheckDate; } while (prevFireTime.compareTo(targetDateNoMs) >= 0); return prevFireTime; } public Date getPrevFireTime(final Date targetDate) { return getTimeBefore(targetDate); } private long findMinIncrement() { if (seconds.size() != 1) { return minInSet(seconds) * 1000; } else if (seconds.first() == ALL_SPEC_INT) { return 1000; } if (minutes.size() != 1) { return minInSet(minutes) * 60000; } else if (minutes.first() == ALL_SPEC_INT) { return 60000; } if (hours.size() != 1) { return minInSet(hours) * 3600000; } else if (hours.first() == ALL_SPEC_INT) { return 3600000; } return 86400000; } private int minInSet(final TreeSet set) { int previous = 0; int min = Integer.MAX_VALUE; boolean first = true; for (final int value : set) { if (first) { previous = value; first = false; continue; } else { final int diff = value - previous; if (diff < min) { min = diff; } } } return min; } protected boolean isLeapYear(final int year) { return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); } protected int getLastDayOfMonth(final int monthNum, final int year) { switch (monthNum) { case 1: return 31; case 2: return (isLeapYear(year)) ? 29 : 28; case 3: return 31; case 4: return 30; case 5: return 31; case 6: return 30; case 7: return 31; case 8: return 31; case 9: return 30; case 10: return 31; case 11: return 30; case 12: return 31; default: throw new IllegalArgumentException("Illegal month number: " + monthNum); } } private static final class ValueSet { /** * value. */ public int value; /** * position in the expression. */ public int pos; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/shedlock/ShedLockAutoConfigure.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.shedlock; import javax.sql.DataSource; import com.alibaba.cloud.scheduling.SchedulingConstants; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.init.DataSourceInitializer; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; /** * @author yaohui **/ @Configuration(proxyBeanMethods = false) @AutoConfigureAfter(DataSourceAutoConfiguration.class) @EnableSchedulerLock(defaultLockAtMostFor = "1m") @ConditionalOnBean(DataSource.class) @ConditionalOnProperty(name = SchedulingConstants.SCHEDULING_CONFIG_DISTRIBUTED_MODE_KEY, havingValue = "shedlock") public class ShedLockAutoConfigure { private static final String SCHEDULED_LOCK_SCHEMA_PATH = "shedlock/schema/schema-mysql.sql"; @Bean @ConditionalOnMissingBean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(new JdbcTemplate(dataSource)) .usingDbTime() .build()); } @Bean @ConditionalOnMissingBean public DataSourceInitializer shedLockDataSourceInitializer(DataSource dataSource) { ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator(); resourceDatabasePopulator.addScript(new ClassPathResource(SCHEDULED_LOCK_SCHEMA_PATH)); DataSourceInitializer dataSourceInitializer = new DataSourceInitializer(); dataSourceInitializer.setDataSource(dataSource); dataSourceInitializer.setDatabasePopulator(resourceDatabasePopulator); return dataSourceInitializer; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.scheduling.schedulerx.SchedulerxAutoConfigure com.alibaba.cloud.scheduling.shedlock.ShedLockAutoConfigure ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.alibaba.cloud.scheduling.schedulerx.SchedulerxAutoConfigure,\ com.alibaba.cloud.scheduling.shedlock.ShedLockAutoConfigure ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/resources/shedlock/schema/schema-mysql.sql ================================================ CREATE TABLE IF NOT EXISTS shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL, locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name)); ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/test/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpressionTest.java ================================================ /* * Copyright 2024-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.scheduling.schedulerx.util; import java.text.ParseException; import java.util.Date; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author yaohui */ class CronExpressionTest { @Test void isValidExpression() { assertThat(CronExpression.isValidExpression("0 0 0 * * ?")).isEqualTo(true); assertThat(CronExpression.isValidExpression("0 */5 * * * ?")).isEqualTo(true); assertThat(CronExpression.isValidExpression("0 0 8 1 JAN ?")).isEqualTo(true); assertThat(CronExpression.isValidExpression("0 0 8 ? 10 THU")).isEqualTo(true); assertThat(CronExpression.isValidExpression("0 0 8 ? 10 THU-SAT")).isEqualTo(true); assertThat(CronExpression.isValidExpression("0 0 8 ? 10 FRI#1")).isEqualTo(true); assertThat(CronExpression.isValidExpression("0 0 8 ? 10 FRI#5")).isEqualTo(true); // false assertThat(CronExpression.isValidExpression("0 0 8 1 JAW ?")).isEqualTo(false); assertThat(CronExpression.isValidExpression("0 0 8 ? 10 THP-SAT")).isEqualTo(false); assertThat(CronExpression.isValidExpression("0 0 8 ? 10 FRI#0")).isEqualTo(false); assertThat(CronExpression.isValidExpression("0 0 8 ? 10 FRI#6")).isEqualTo(false); } @Test void getTimeAfter() throws ParseException { CronExpression cronExpression = new CronExpression("0 0 8 1 JAN ?"); Date nextDate = cronExpression.getTimeAfter(new Date()); System.out.println(nextDate); assertThat(nextDate).isNotNull(); cronExpression = new CronExpression("0 */5 * * * ?"); nextDate = cronExpression.getTimeAfter(new Date()); System.out.println(nextDate); assertThat(nextDate).isNotNull(); } @Test void getTimeBefore() throws ParseException { CronExpression cronExpression = new CronExpression("0 0 8 1 JAN ?"); Date beforeDate = cronExpression.getTimeBefore(new Date()); System.out.println(beforeDate); assertThat(beforeDate).isNotNull(); cronExpression = new CronExpression("0 */5 * * * ?"); beforeDate = cronExpression.getTimeBefore(new Date()); System.out.println(beforeDate); assertThat(beforeDate).isNotNull(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/pom.xml ================================================ 4.0.0 com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml spring-cloud-starter-alibaba-seata Spring Cloud Starter Alibaba Seata org.springframework.boot spring-boot-webclient org.springframework.boot spring-boot-starter-aspectj org.springframework.boot spring-boot-starter-actuator true org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot-starter-web true org.springframework.boot spring-boot-starter-webflux true org.apache.seata seata-spring-boot-starter org.springframework.cloud spring-cloud-starter-openfeign true com.alibaba.cloud spring-cloud-starter-alibaba-sentinel true org.springframework.cloud spring-cloud-commons true org.springframework.cloud spring-cloud-starter-loadbalancer true org.springframework.boot spring-boot-starter-test test ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/feign/SeataFeignBuilderBeanPostProcessor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.feign; import feign.Feign; import feign.Retryer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; /** * @author wang.liang * @since 2.2.5 */ public class SeataFeignBuilderBeanPostProcessor implements BeanPostProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(SeataFeignBuilderBeanPostProcessor.class); @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Feign.Builder) { ((Feign.Builder) bean).retryer(Retryer.NEVER_RETRY); LOGGER.info("change the retryer of the bean '{}' to 'Retryer.NEVER_RETRY'", beanName); } return bean; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/feign/SeataFeignClientAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.feign; import feign.Client; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author xiaojing * @author wang.liang */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Client.class) public class SeataFeignClientAutoConfiguration { @Bean public static SeataFeignBuilderBeanPostProcessor seataFeignBuilderBeanPostProcessor() { return new SeataFeignBuilderBeanPostProcessor(); } @Bean public SeataFeignRequestInterceptor seataFeignRequestInterceptor() { return new SeataFeignRequestInterceptor(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/feign/SeataFeignRequestInterceptor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.feign; import feign.RequestInterceptor; import feign.RequestTemplate; import org.apache.seata.core.context.RootContext; import org.springframework.util.StringUtils; /** * @author wang.liang */ public class SeataFeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { String xid = RootContext.getXID(); if (!StringUtils.hasLength(xid)) { return; } template.header(RootContext.KEY_XID, xid); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/rest/SeataRestTemplateAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.rest; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; /** * @author xiaojing * @author ChangJin Wei (魏昌进) */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({RestClient.class, RestTemplate.class}) public class SeataRestTemplateAutoConfiguration { @Bean public SeataRestTemplateInterceptor seataRestTemplateInterceptor() { return new SeataRestTemplateInterceptor(); } @Bean public SeataRestTemplateInterceptorAfterPropertiesSet seataRestTemplateInterceptorConfiguration() { return new SeataRestTemplateInterceptorAfterPropertiesSet(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/rest/SeataRestTemplateInterceptor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.rest; import java.io.IOException; import org.apache.seata.core.context.RootContext; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.support.HttpRequestWrapper; import org.springframework.util.StringUtils; /** * @author xiaojing */ public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest); String xid = RootContext.getXID(); if (StringUtils.hasLength(xid)) { requestWrapper.getHeaders().add(RootContext.KEY_XID, xid); } return clientHttpRequestExecution.execute(requestWrapper, bytes); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/rest/SeataRestTemplateInterceptorAfterPropertiesSet.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.rest; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; /** * @author ZhangZhi */ public class SeataRestTemplateInterceptorAfterPropertiesSet implements InitializingBean { @Autowired(required = false) private Collection restTemplates; @Autowired private SeataRestTemplateInterceptor seataRestTemplateInterceptor; @Override public void afterPropertiesSet() { if (this.restTemplates != null) { for (RestTemplate restTemplate : restTemplates) { List interceptors = new ArrayList<>( restTemplate.getInterceptors()); interceptors.add(this.seataRestTemplateInterceptor); restTemplate.setInterceptors(interceptors); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/web/SeataHandlerInterceptor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.web; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.seata.common.util.StringUtils; import org.apache.seata.core.context.RootContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.HandlerInterceptor; /** * @author xiaojing * * Seata HandlerInterceptor, Convert Seata information into * @see org.apache.seata.core.context.RootContext from http request's header in * {@link org.springframework.web.servlet.HandlerInterceptor#preHandle(HttpServletRequest, HttpServletResponse, Object)}, * And clean up Seata information after servlet method invocation in * {@link org.springframework.web.servlet.HandlerInterceptor#afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception)} */ public class SeataHandlerInterceptor implements HandlerInterceptor { private static final Logger log = LoggerFactory .getLogger(SeataHandlerInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String xid = RootContext.getXID(); String rpcXid = request.getHeader(RootContext.KEY_XID); if (log.isDebugEnabled()) { log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid); } if (StringUtils.isBlank(xid) && rpcXid != null) { RootContext.bind(rpcXid); if (log.isDebugEnabled()) { log.debug("bind {} to RootContext", rpcXid); } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) { if (StringUtils.isNotBlank(RootContext.getXID())) { String rpcXid = request.getHeader(RootContext.KEY_XID); if (StringUtils.isEmpty(rpcXid)) { return; } String unbindXid = RootContext.unbind(); if (log.isDebugEnabled()) { log.debug("unbind {} from RootContext", unbindXid); } if (!rpcXid.equalsIgnoreCase(unbindXid)) { log.warn("xid in change during RPC from {} to {}", rpcXid, unbindXid); if (unbindXid != null) { RootContext.bind(unbindXid); log.warn("bind {} back to RootContext", unbindXid); } } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/web/SeataHandlerInterceptorConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.web; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author xiaojing * @author ChangJin Wei (魏昌进) */ @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) public class SeataHandlerInterceptorConfiguration implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/webclient/SeataWebClientAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.webclient; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.webclient.WebClientCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; /** * @author ChangJin Wei (魏昌进) */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(WebClient.class) public class SeataWebClientAutoConfiguration { @Bean public SeataWebClientFilter seataWebClientFilter() { return new SeataWebClientFilter(); } @Bean public WebClientCustomizer seataWebClientCustomizer(SeataWebClientFilter filter) { return new SeataWebClientBuilderCustomizer(filter); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/webclient/SeataWebClientBuilderCustomizer.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.webclient; import org.springframework.boot.webclient.WebClientCustomizer; import org.springframework.web.reactive.function.client.WebClient; /** * @author ChangJin Wei (魏昌进) */ public class SeataWebClientBuilderCustomizer implements WebClientCustomizer { private final SeataWebClientFilter filter; public SeataWebClientBuilderCustomizer(SeataWebClientFilter filter) { this.filter = filter; } @Override public void customize(WebClient.Builder builder) { builder.filter(filter); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/webclient/SeataWebClientFilter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.webclient; import org.apache.seata.core.context.RootContext; import reactor.core.publisher.Mono; import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeFunction; /** * @author ChangJin Wei (魏昌进) */ public class SeataWebClientFilter implements ExchangeFilterFunction { @Override public Mono filter(ClientRequest request, ExchangeFunction next) { String xid = RootContext.getXID(); if (!StringUtils.hasLength(xid)) { return next.exchange(request); } ClientRequest newReq = ClientRequest.from(request) .headers(h -> h.add(RootContext.KEY_XID, xid)) .build(); return next.exchange(newReq); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/webflux/SeataWebFilter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.webflux; import org.apache.seata.common.util.StringUtils; import org.apache.seata.core.context.RootContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; /** * @author ChangJin Wei (魏昌进) */ public class SeataWebFilter implements WebFilter { private static final Logger log = LoggerFactory.getLogger(SeataWebFilter.class); @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { String xid = RootContext.getXID(); String rpcXid = exchange.getRequest().getHeaders().getFirst(RootContext.KEY_XID); if (log.isDebugEnabled()) { log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid); } if (StringUtils.isBlank(xid) && rpcXid != null) { RootContext.bind(rpcXid); if (log.isDebugEnabled()) { log.debug("bind {} to RootContext", rpcXid); } } return chain.filter(exchange) .doFinally(sig -> { if (StringUtils.isNotBlank(RootContext.getXID())) { String headerXid = exchange.getRequest().getHeaders().getFirst(RootContext.KEY_XID); if (StringUtils.isEmpty(headerXid)) { return; } String unbindXid = RootContext.unbind(); if (log.isDebugEnabled()) { log.debug("unbind {} from RootContext", unbindXid); } if (!headerXid.equalsIgnoreCase(unbindXid)) { log.warn("xid in change during RPC from {} to {}", headerXid, unbindXid); if (unbindXid != null) { RootContext.bind(unbindXid); log.warn("bind {} back to RootContext", unbindXid); } } } }); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/java/com/alibaba/cloud/seata/webflux/SeataWebfluxAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.seata.webflux; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.server.WebFilter; /** * @author ChangJin Wei (魏昌进) */ @ConditionalOnClass({WebFilter.class, WebFluxConfigurer.class}) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) public class SeataWebfluxAutoConfiguration { @Bean public WebFilter seataWebFilter() { return new SeataWebFilter(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/resources/META-INF/native-image/com.alibaba.cloud/spring-cloud-starter-alibaba-seata/native-image.properties ================================================ Args = --initialize-at-build-time=ch.qos.logback.classic.Logger \ --initialize-at-build-time=ch.qos.logback.core.status.InfoStatus \ --initialize-at-build-time=ch.qos.logback.core.status.StatusBase \ --initialize-at-build-time=org.slf4j.LoggerFactory \ --initialize-at-build-time=ch.qos.logback.core.CoreConstants \ --initialize-at-build-time=org.slf4j.MDC \ --initialize-at-build-time=ch.qos.logback.classic.Level \ --initialize-at-build-time=ch.qos.logback.core.util.Loader \ --initialize-at-build-time=ch.qos.logback.core.util.StatusPrinter \ --initialize-at-run-time=io.netty.channel.epoll.Epoll \ --initialize-at-run-time=io.netty.channel.epoll.Native \ --initialize-at-run-time=io.netty.channel.epoll.EpollEventLoop \ --initialize-at-run-time=io.netty.channel.epoll.EpollEventArray \ --initialize-at-run-time=io.netty.channel.DefaultFileRegion \ --initialize-at-run-time=io.netty.channel.kqueue.KQueueEventArray \ --initialize-at-run-time=io.netty.channel.kqueue.KQueueEventLoop \ --initialize-at-run-time=io.netty.channel.kqueue.Native \ --initialize-at-run-time=io.netty.channel.unix.Errors \ --initialize-at-run-time=io.netty.channel.unix.IovArray \ --initialize-at-run-time=io.netty.channel.unix.Limits \ --initialize-at-run-time=io.netty.util.internal.logging.Log4JLogger \ --initialize-at-run-time=io.netty.channel.unix.Socket \ --initialize-at-run-time=io.netty.channel.ChannelHandlerMask ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/resources/META-INF/native-image/proxy-config.json ================================================ [ { "condition": { "typeReachable": "org.apache.config.ConfigurationCache" }, "interfaces": [ "org.apache.config.Configuration" ] }, { "condition": { "typeReachable": "org.apache.spring.boot.autoconfigure.provider.SpringBootConfigurationProvider" }, "interfaces": [ "org.apache.config.Configuration" ] } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/resources/META-INF/native-image/reflect-config.json ================================================ [ { "condition": { "typeReachable": "org.apache.config.ConfigurationCache" }, "name": "org.apache.config.Configuration" }, { "condition": { "typeReachable": "org.apache.config.ConfigurationCache$$Lambda$494/0x00000008010e8000" }, "name": "org.apache.config.Configuration" }, { "condition": { "typeReachable": "org.apache.core.event.GuavaEventBus" }, "name": "org.apache.config.ConfigurationChangeListener", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "name": "org.apache.config.apollo.ApolloConfigurationProvider" }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "name": "org.apache.config.consul.ConsulConfigurationProvider" }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "name": "org.apache.config.custom.CustomConfigurationProvider" }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "name": "org.apache.config.etcd3.EtcdConfigurationProvider" }, { "condition": { "typeReachable": "org.apache.common.loader.EnhancedServiceLoader$InnerEnhancedServiceLoader" }, "name": "org.apache.config.file.SimpleFileConfig", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.common.loader.EnhancedServiceLoader$InnerEnhancedServiceLoader" }, "name": "org.apache.config.file.YamlFileConfig" }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "name": "org.apache.config.nacos.NacosConfigurationProvider", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.config.processor.ConfigProcessor" }, "name": "org.apache.config.processor.ProcessorProperties", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.config.processor.ConfigProcessor" }, "name": "org.apache.config.processor.ProcessorYaml" }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "name": "org.apache.config.springcloud.SpringCloudConfigurationProvider" }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "name": "org.apache.config.zk.ZookeeperConfigurationProvider" }, { "condition": { "typeReachable": "org.apache.core.rpc.netty.TmNettyRemotingClient" }, "name": "org.apache.core.auth.DefaultAuthSigner", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.core.context.ContextCoreLoader$ContextCoreHolder" }, "name": "org.apache.core.context.FastThreadLocalContextCore", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.core.context.ContextCoreLoader$ContextCoreHolder" }, "name": "org.apache.core.context.ThreadLocalContextCore" }, { "condition": { "typeReachable": "org.apache.core.rpc.netty.AbstractNettyRemoting" }, "name": "org.apache.core.rpc.hook.StatusRpcHook", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.core.rpc.netty.NettyClientBootstrap" }, "name": "org.apache.core.rpc.netty.AbstractNettyRemotingClient$ClientHandler", "methods": [ { "name": "channelInactive", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext" ] }, { "name": "channelRead", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] }, { "name": "channelWritabilityChanged", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext" ] }, { "name": "close", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext", "io.netty.channel.ChannelPromise" ] }, { "name": "exceptionCaught", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext", "java.lang.Throwable" ] }, { "name": "userEventTriggered", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] } ] }, { "condition": { "typeReachable": "org.apache.core.rpc.netty.NettyClientBootstrap" }, "name": "org.apache.core.rpc.netty.NettyClientBootstrap$1" }, { "condition": { "typeReachable": "org.apache.core.rpc.netty.NettyClientBootstrap$1" }, "name": "org.apache.core.rpc.netty.v1.ProtocolV1Decoder" }, { "condition": { "typeReachable": "org.apache.core.rpc.netty.NettyClientBootstrap$1" }, "name": "org.apache.core.rpc.netty.v1.ProtocolV1Encoder" }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "name": "org.apache.discovery.registry.FileRegistryProvider" }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "name": "org.apache.discovery.registry.consul.ConsulRegistryProvider" }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "name": "org.apache.discovery.registry.custom.CustomRegistryProvider" }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "name": "org.apache.discovery.registry.etcd3.EtcdRegistryProvider" }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "name": "org.apache.discovery.registry.eureka.EurekaRegistryProvider" }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "name": "org.apache.discovery.registry.nacos.NacosRegistryProvider", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "name": "org.apache.discovery.registry.redis.RedisRegistryProvider" }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "name": "org.apache.discovery.registry.sofa.SofaRegistryProvider" }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "name": "org.apache.discovery.registry.zk.ZookeeperRegistryProvider" }, { "condition": { "typeReachable": "org.apache.spring.annotation.GlobalTransactionScanner" }, "name": "org.apache.integration.http.JakartaSeataWebMvcConfigurer", "queryAllPublicMethods": true }, { "condition": { "typeReachable": "org.apache.rm.DefaultRMHandler" }, "name": "org.apache.rm.RMHandlerAT", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.DefaultRMHandler" }, "name": "org.apache.rm.RMHandlerXA", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.DefaultResourceManager" }, "name": "org.apache.rm.datasource.DataSourceManager", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.DefaultResourceManager" }, "name": "org.apache.rm.datasource.xa.ResourceManagerXA", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.DefaultRMHandler" }, "name": "org.apache.rm.tcc.RMHandlerTCC", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.DefaultResourceManager" }, "name": "org.apache.rm.tcc.TCCResourceManager", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.tcc.remoting.parser.DefaultRemotingParser" }, "name": "org.apache.rm.tcc.remoting.parser.DubboRemotingParser", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.tcc.remoting.parser.DefaultRemotingParser" }, "name": "org.apache.rm.tcc.remoting.parser.HSFRemotingParser", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.tcc.remoting.parser.DefaultRemotingParser" }, "name": "org.apache.rm.tcc.remoting.parser.LocalTCCRemotingParser", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.tcc.remoting.parser.DefaultRemotingParser" }, "name": "org.apache.rm.tcc.remoting.parser.SofaRpcRemotingParser", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.DefaultRMHandler" }, "name": "org.apache.saga.rm.RMHandlerSaga", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.rm.DefaultResourceManager" }, "name": "org.apache.saga.rm.SagaResourceManager", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.core.serializer.SerializerServiceLoader" }, "name": "org.apache.serializer.fst.FstSerializer" }, { "condition": { "typeReachable": "org.apache.core.serializer.SerializerServiceLoader" }, "name": "org.apache.serializer.hessian.HessianSerializer" }, { "condition": { "typeReachable": "org.apache.core.serializer.SerializerServiceLoader" }, "name": "org.apache.serializer.kryo.KryoSerializer" }, { "condition": { "typeReachable": "org.apache.core.serializer.SerializerServiceLoader" }, "name": "org.apache.serializer.seata.SeataSerializer", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.core.event.GuavaEventBus" }, "name": "org.apache.spring.annotation.GlobalTransactionalInterceptor", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.apache.core.event.GuavaEventBus" }, "name": "org.apache.spring.annotation.SeataInterceptor", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.apache.spring.boot.autoconfigure.SeataAutoConfiguration" }, "name": "org.apache.spring.annotation.scannercheckers.ConfigBeansScannerChecker", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.spring.boot.autoconfigure.SeataAutoConfiguration" }, "name": "org.apache.spring.annotation.scannercheckers.PackageScannerChecker", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.spring.boot.autoconfigure.SeataAutoConfiguration" }, "name": "org.apache.spring.annotation.scannercheckers.ScopeBeansScannerChecker", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "name": "org.apache.spring.boot.autoconfigure.provider.SpringBootConfigurationProvider", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.tm.TransactionManagerHolder$SingletonHolder" }, "name": "org.apache.tm.DefaultTransactionManager", "methods": [ { "name": "", "parameterTypes": [] } ] } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/resources/META-INF/native-image/resource-config.json ================================================ { "resources": { "includes": [ { "condition": { "typeReachable": "org.apache.config.nacos.NacosConfiguration" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.api.config.filter.IConfigFilter\\E" }, { "condition": { "typeReachable": "org.apache.config.nacos.NacosConfiguration" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.api.remote.Payload\\E" }, { "condition": { "typeReachable": "org.apache.config.nacos.NacosConfiguration" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.plugin.auth.spi.client.AbstractClientAuthService\\E" }, { "condition": { "typeReachable": "org.apache.config.nacos.NacosConfiguration" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.shaded.io.grpc.LoadBalancerProvider\\E" }, { "condition": { "typeReachable": "org.apache.config.nacos.NacosConfiguration" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.shaded.io.grpc.ManagedChannelProvider\\E" }, { "condition": { "typeReachable": "org.apache.config.nacos.NacosConfiguration" }, "pattern": "\\QMETA-INF/services/com.alibaba.nacos.shaded.io.grpc.NameResolverProvider\\E" }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "pattern": "\\QMETA-INF/services/org.apache.config.ConfigurationProvider\\E" }, { "condition": { "typeReachable": "org.apache.config.ConfigurationFactory" }, "pattern": "\\QMETA-INF/services/org.apache.config.ExtConfigurationProvider\\E" }, { "condition": { "typeReachable": "org.apache.common.loader.EnhancedServiceLoader$InnerEnhancedServiceLoader" }, "pattern": "\\QMETA-INF/services/org.apache.config.file.FileConfig\\E" }, { "condition": { "typeReachable": "org.apache.config.processor.ConfigProcessor" }, "pattern": "\\QMETA-INF/services/org.apache.config.processor.Processor\\E" }, { "condition": { "typeReachable": "org.apache.core.rpc.netty.TmNettyRemotingClient" }, "pattern": "\\QMETA-INF/services/org.apache.core.auth.AuthSigner\\E" }, { "condition": { "typeReachable": "org.apache.core.context.ContextCoreLoader$ContextCoreHolder" }, "pattern": "\\QMETA-INF/services/org.apache.core.context.ContextCore\\E" }, { "condition": { "typeReachable": "org.apache.rm.DefaultResourceManager" }, "pattern": "\\QMETA-INF/services/org.apache.core.model.ResourceManager\\E" }, { "condition": { "typeReachable": "org.apache.tm.TransactionManagerHolder$SingletonHolder" }, "pattern": "\\QMETA-INF/services/org.apache.core.model.TransactionManager\\E" }, { "condition": { "typeReachable": "org.apache.core.rpc.netty.AbstractNettyRemoting" }, "pattern": "\\QMETA-INF/services/org.apache.core.rpc.hook.RpcHook\\E" }, { "condition": { "typeReachable": "org.apache.core.serializer.SerializerServiceLoader" }, "pattern": "\\QMETA-INF/services/org.apache.core.serializer.Serializer\\E" }, { "condition": { "typeReachable": "org.apache.discovery.registry.RegistryFactory" }, "pattern": "\\QMETA-INF/services/org.apache.discovery.registry.RegistryProvider\\E" }, { "condition": { "typeReachable": "org.apache.rm.DefaultRMHandler" }, "pattern": "\\QMETA-INF/services/org.apache.rm.AbstractRMHandler\\E" }, { "condition": { "typeReachable": "org.apache.rm.tcc.remoting.parser.DefaultRemotingParser" }, "pattern": "\\QMETA-INF/services/org.apache.rm.tcc.remoting.RemotingParser\\E" }, { "condition": { "typeReachable": "org.apache.spring.boot.autoconfigure.SeataAutoConfiguration" }, "pattern": "\\QMETA-INF/services/org.apache.spring.annotation.ScannerChecker\\E" }, { "condition": { "typeReachable": "org.apache.config.FileConfiguration" }, "pattern": "\\Q\\E" }, { "condition": { "typeReachable": "org.apache.config.nacos.NacosConfiguration" }, "pattern": "\\Qnacos-version.txt\\E" }, { "condition": { "typeReachable": "org.apache.spring.annotation.GlobalTransactionalInterceptor$2" }, "pattern": "\\Qorg/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration$BlockingRetryConfiguration.class\\E" }, { "condition": { "typeReachable": "org.apache.spring.annotation.GlobalTransactionalInterceptor$2" }, "pattern": "\\Qorg/springframework/cloud/loadbalancer/annotation/LoadBalancerClientConfiguration$BlockingSupportConfiguration.class\\E" } ] }, "bundles": [] } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-seata/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.seata.rest.SeataRestTemplateAutoConfiguration com.alibaba.cloud.seata.web.SeataHandlerInterceptorConfiguration com.alibaba.cloud.seata.feign.SeataFeignClientAutoConfiguration com.alibaba.cloud.seata.webclient.SeataWebClientAutoConfiguration com.alibaba.cloud.seata.webflux.SeataWebfluxAutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/pom.xml ================================================ 4.0.0 com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml spring-cloud-starter-alibaba-sentinel Spring Cloud Starter Alibaba Sentinel org.springframework.boot spring-boot-health provided org.springframework.boot spring-boot-starter-aspectj true org.springframework.boot spring-boot-actuator true org.springframework.boot spring-boot-actuator-autoconfigure true org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot true org.springframework.boot spring-boot-autoconfigure true org.springframework.boot spring-boot-starter true org.springframework.boot spring-boot-starter-web true org.springframework.boot spring-boot-starter-webflux true org.springframework.cloud spring-cloud-starter-openfeign true org.springframework.cloud spring-cloud-commons true org.springframework.cloud spring-cloud-starter-loadbalancer true com.alibaba.csp sentinel-transport-simple-http com.alibaba.csp sentinel-annotation-aspectj com.alibaba.cloud spring-cloud-circuitbreaker-sentinel com.alibaba.csp sentinel-spring-webflux-adapter com.alibaba.csp sentinel-spring-webmvc-v6x-adapter com.alibaba.csp sentinel-parameter-flow-control com.alibaba.csp sentinel-api-gateway-adapter-common true com.alibaba.csp sentinel-cluster-server-default com.alibaba.csp sentinel-cluster-client-default com.alibaba.cloud spring-cloud-alibaba-sentinel-datasource tools.jackson.dataformat jackson-dataformat-xml true org.springframework.boot spring-boot-starter-test test junit junit test ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/SentinelConstants.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; /** * @author fangjian */ public final class SentinelConstants { /** * Prefix of {@link SentinelProperties}. */ public static final String PROPERTY_PREFIX = "spring.cloud.sentinel"; /** * Block page key. */ public static final String BLOCK_PAGE_URL_CONF_KEY = "csp.sentinel.web.servlet.block.page"; /** * Block type. */ public static final String BLOCK_TYPE = "block"; /** * Fallback type. */ public static final String FALLBACK_TYPE = "fallback"; /** * UrlCleaner type. */ public static final String URLCLEANER_TYPE = "urlCleaner"; /** * The cold factor. */ public static final String COLD_FACTOR = "3"; /** * The charset. */ public static final String CHARSET = "UTF-8"; /** * The Sentinel api port. */ public static final String API_PORT = "8719"; private SentinelConstants() { throw new AssertionError("Must not instantiate constant utility class"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/SentinelProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TreeMap; import com.alibaba.cloud.sentinel.datasource.config.DataSourcePropertiesConfiguration; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.core.Ordered; import org.springframework.util.StringUtils; /** * {@link ConfigurationProperties} for Sentinel. * * @author xiaojing * @author hengyunabc * @author jiashuai.xie * @author Jim */ @ConfigurationProperties(prefix = SentinelConstants.PROPERTY_PREFIX) public class SentinelProperties { /** * Earlier initialize heart-beat when the spring container starts when the transport * dependency is on classpath, the configuration is effective. */ private boolean eager = false; /** * Enable sentinel auto configure, the default value is true. */ private boolean enabled = true; /** * The process page when the flow control is triggered. */ private String blockPage; /** * Configurations about datasource, like 'nacos', 'apollo', 'file', 'zookeeper'. */ private Map datasource = new TreeMap<>( String.CASE_INSENSITIVE_ORDER); /** * Transport configuration about dashboard and client. */ private Transport transport = new Transport(); /** * Metric configuration about resource. */ private Metric metric = new Metric(); /** * Web servlet configuration when the application is web, the configuration is * effective. */ private Servlet servlet = new Servlet(); /** * Sentinel interceptor when the application is web, the configuration is effective. */ private Filter filter = new Filter(); /** * Sentinel Flow configuration. */ private Flow flow = new Flow(); /** * Sentinel log configuration {@link LogBase}. */ private Log log = new Log(); /** * Add HTTP method prefix for Sentinel Resource. */ private Boolean httpMethodSpecify = false; /** * Specify whether unify web context(i.e. use the default context name), and is true * by default. */ private Boolean webContextUnify = true; public Boolean getWebContextUnify() { return webContextUnify; } public void setWebContextUnify(Boolean webContextUnify) { this.webContextUnify = webContextUnify; } public boolean isEager() { return eager; } public void setEager(boolean eager) { this.eager = eager; } public Flow getFlow() { return flow; } public void setFlow(Flow flow) { this.flow = flow; } public Transport getTransport() { return transport; } public void setTransport(Transport transport) { this.transport = transport; } public Metric getMetric() { return metric; } public void setMetric(Metric metric) { this.metric = metric; } public Servlet getServlet() { return servlet; } public void setServlet(Servlet servlet) { this.servlet = servlet; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public Filter getFilter() { return filter; } public void setFilter(Filter filter) { this.filter = filter; } public Map getDatasource() { return datasource; } public void setDatasource(Map datasource) { this.datasource = datasource; } public Log getLog() { return log; } public void setLog(Log log) { this.log = log; } public Boolean getHttpMethodSpecify() { return httpMethodSpecify; } public void setHttpMethodSpecify(Boolean httpMethodSpecify) { this.httpMethodSpecify = httpMethodSpecify; } public String getBlockPage() { if (StringUtils.hasText(this.blockPage)) { return this.blockPage; } return this.servlet.getBlockPage(); } public void setBlockPage(String blockPage) { this.blockPage = blockPage; } public static class Flow { /** * The cold factor {@link SentinelConfig#COLD_FACTOR}. */ private String coldFactor = SentinelConstants.COLD_FACTOR; public String getColdFactor() { return coldFactor; } public void setColdFactor(String coldFactor) { this.coldFactor = coldFactor; } } public static class Servlet { /** * The process page when the flow control is triggered. */ private String blockPage; @Deprecated @DeprecatedConfigurationProperty( reason = "replaced to SentinelProperties#blockPage.", replacement = SentinelConstants.PROPERTY_PREFIX + ".block-page") public String getBlockPage() { return blockPage; } @Deprecated public void setBlockPage(String blockPage) { this.blockPage = blockPage; } } public static class Metric { /** * The metric file size {@link SentinelConfig#SINGLE_METRIC_FILE_SIZE}. */ private String fileSingleSize; /** * The total metric file count {@link SentinelConfig#TOTAL_METRIC_FILE_COUNT}. */ private String fileTotalCount; /** * Charset when sentinel write or search metric file. * {@link SentinelConfig#CHARSET} */ private String charset = SentinelConstants.CHARSET; public String getFileSingleSize() { return fileSingleSize; } public void setFileSingleSize(String fileSingleSize) { this.fileSingleSize = fileSingleSize; } public String getFileTotalCount() { return fileTotalCount; } public void setFileTotalCount(String fileTotalCount) { this.fileTotalCount = fileTotalCount; } public String getCharset() { return charset; } public void setCharset(String charset) { this.charset = charset; } } public static class Transport { /** * Sentinel api port, default value is 8719 {@link TransportConfig#SERVER_PORT}. */ private String port = SentinelConstants.API_PORT; /** * Sentinel dashboard address, won't try to connect dashboard when address is * empty {@link TransportConfig#CONSOLE_SERVER}. */ private String dashboard = ""; /** * Send heartbeat interval millisecond * {@link TransportConfig#HEARTBEAT_INTERVAL_MS}. */ private String heartbeatIntervalMs; /** * Get heartbeat client local ip. If the client ip not configured, it will be the * address of local host. */ private String clientIp; public String getHeartbeatIntervalMs() { return heartbeatIntervalMs; } public void setHeartbeatIntervalMs(String heartbeatIntervalMs) { this.heartbeatIntervalMs = heartbeatIntervalMs; } public String getPort() { return port; } public void setPort(String port) { this.port = port; } public String getDashboard() { return dashboard; } public void setDashboard(String dashboard) { this.dashboard = dashboard; } public String getClientIp() { return clientIp; } public void setClientIp(String clientIp) { this.clientIp = clientIp; } } public static class Filter { /** * SentinelWebInterceptor order, will be register to InterceptorRegistry. */ private int order = Ordered.HIGHEST_PRECEDENCE; /** * URL pattern for SentinelWebInterceptor, default is /**. */ private List urlPatterns = Arrays.asList("/**"); /** * Enable to instance * {@link com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor}. */ private boolean enabled = true; public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } public List getUrlPatterns() { return urlPatterns; } public void setUrlPatterns(List urlPatterns) { this.urlPatterns = urlPatterns; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } } public static class Log { /** * Sentinel log base dir. */ private String dir; /** * Distinguish the log file by pid number. */ private boolean switchPid = false; public String getDir() { return dir; } public void setDir(String dir) { this.dir = dir; } public boolean isSwitchPid() { return switchPid; } public void setSwitchPid(boolean switchPid) { this.switchPid = switchPid; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/SentinelWebAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import java.util.Optional; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelWebInterceptor; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.DefaultBlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.SentinelWebMvcConfig; import com.alibaba.csp.sentinel.adapter.web.common.UrlCleaner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author xiaojing * @author yuhuangbin */ @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) @ConditionalOnClass(SentinelWebInterceptor.class) @EnableConfigurationProperties(SentinelProperties.class) public class SentinelWebAutoConfiguration implements WebMvcConfigurer { private static final Logger log = LoggerFactory .getLogger(SentinelWebAutoConfiguration.class); @Autowired private SentinelProperties properties; @Autowired private Optional urlCleanerOptional; @Autowired private Optional blockExceptionHandlerOptional; @Autowired private Optional requestOriginParserOptional; @Bean @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) public SentinelWebInterceptor sentinelWebInterceptor( SentinelWebMvcConfig sentinelWebMvcConfig) { return new SentinelWebInterceptor(sentinelWebMvcConfig); } @Bean @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) public SentinelWebMvcConfig sentinelWebMvcConfig() { SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig(); sentinelWebMvcConfig.setHttpMethodSpecify(properties.getHttpMethodSpecify()); sentinelWebMvcConfig.setWebContextUnify(properties.getWebContextUnify()); if (blockExceptionHandlerOptional.isPresent()) { blockExceptionHandlerOptional .ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler); } else { if (StringUtils.hasText(properties.getBlockPage())) { sentinelWebMvcConfig.setBlockExceptionHandler( (request, response, resourceName, e) -> response.sendRedirect(properties.getBlockPage()) ); } else { sentinelWebMvcConfig .setBlockExceptionHandler(new DefaultBlockExceptionHandler()); } } urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner); requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser); return sentinelWebMvcConfig; } @Bean @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) public SentinelWebMvcConfigurer sentinelWebMvcConfigurer() { return new SentinelWebMvcConfigurer(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/SentinelWebFluxAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import java.util.Collections; import java.util.List; import java.util.Optional; import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; import com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter; import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.BlockRequestHandler; import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.WebFluxCallbackManager; import com.alibaba.csp.sentinel.adapter.spring.webflux.exception.SentinelBlockExceptionHandler; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; /** * @author Jim */ @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.REACTIVE) @ConditionalOnClass(SentinelReactorTransformer.class) @ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) @EnableConfigurationProperties(SentinelProperties.class) public class SentinelWebFluxAutoConfiguration { private static final Logger log = LoggerFactory .getLogger(SentinelWebFluxAutoConfiguration.class); private final List viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; @Autowired private Optional blockRequestHandler; public SentinelWebFluxAutoConfiguration( ObjectProvider> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolvers.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @PostConstruct public void init() { blockRequestHandler.ifPresent(WebFluxCallbackManager::setBlockHandler); } @Bean @Order(-2) @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) public SentinelBlockExceptionHandler sentinelBlockExceptionHandler() { return new SentinelBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } @Bean @Order(-1) @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) public SentinelWebFluxFilter sentinelWebFluxFilter() { log.info("[Sentinel Starter] register Sentinel SentinelWebFluxFilter"); return new SentinelWebFluxFilter(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/SentinelWebMvcConfigurer.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import java.util.Optional; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.SentinelWebInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author chao.wu */ public class SentinelWebMvcConfigurer implements WebMvcConfigurer { private static final Logger log = LoggerFactory .getLogger(SentinelWebMvcConfigurer.class); @Autowired private SentinelProperties sentinelProperties; @Autowired private Optional sentinelWebInterceptorOptional; @Override public void addInterceptors(InterceptorRegistry registry) { if (!sentinelWebInterceptorOptional.isPresent()) { return; } SentinelProperties.Filter filterConfig = sentinelProperties.getFilter(); registry.addInterceptor(sentinelWebInterceptorOptional.get()) .order(filterConfig.getOrder()) .addPathPatterns(filterConfig.getUrlPatterns()); log.info( "[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.", filterConfig.getUrlPatterns()); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/annotation/SentinelRestTemplate.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author fangjian */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SentinelRestTemplate { String blockHandler() default ""; Class blockHandlerClass() default void.class; String fallback() default ""; Class fallbackClass() default void.class; String urlCleaner() default ""; Class urlCleanerClass() default void.class; } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/aot/hint/SentinelProtectInterceptorHints.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.aot.hint; import java.lang.reflect.Constructor; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import com.alibaba.cloud.sentinel.custom.SentinelProtectInterceptor; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.web.client.RestTemplate; /** * @author ruansheneg */ public class SentinelProtectInterceptorHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { Constructor constructor; try { constructor = SentinelProtectInterceptor.class.getConstructor(SentinelRestTemplate.class, RestTemplate.class); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } hints.reflection().registerConstructor(constructor, ExecutableMode.INVOKE); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/BlockClassRegistry.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.custom; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.util.StringUtil; /** * @author fangjian */ final class BlockClassRegistry { private BlockClassRegistry() { } private static final Map FALLBACK_MAP = new ConcurrentHashMap<>(); private static final Map BLOCK_HANDLER_MAP = new ConcurrentHashMap<>(); private static final Map URL_CLEANER_MAP = new ConcurrentHashMap<>(); static Method lookupFallback(Class clazz, String name) { return FALLBACK_MAP.get(getKey(clazz, name)); } static Method lookupBlockHandler(Class clazz, String name) { return BLOCK_HANDLER_MAP.get(getKey(clazz, name)); } static Method lookupUrlCleaner(Class clazz, String name) { return URL_CLEANER_MAP.get(getKey(clazz, name)); } static void updateFallbackFor(Class clazz, String name, Method method) { if (clazz == null || StringUtil.isBlank(name)) { throw new IllegalArgumentException("Bad argument"); } FALLBACK_MAP.put(getKey(clazz, name), method); } static void updateBlockHandlerFor(Class clazz, String name, Method method) { if (clazz == null || StringUtil.isBlank(name)) { throw new IllegalArgumentException("Bad argument"); } BLOCK_HANDLER_MAP.put(getKey(clazz, name), method); } static void updateUrlCleanerFor(Class clazz, String name, Method method) { if (clazz == null || StringUtil.isBlank(name)) { throw new IllegalArgumentException("Bad argument"); } URL_CLEANER_MAP.put(getKey(clazz, name), method); } private static String getKey(Class clazz, String name) { return String.format("%s:%s", clazz.getCanonicalName(), name); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.custom; import com.alibaba.cloud.sentinel.SentinelProperties; import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter; import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; import tools.jackson.dataformat.xml.XmlMapper; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; /** * @author xiaojing * @author jiashuai.xie * @author Jim * @author freeman */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) @EnableConfigurationProperties(SentinelProperties.class) public class SentinelAutoConfiguration { @Bean @ConditionalOnMissingBean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } @Bean @ConditionalOnMissingBean @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") @ConditionalOnProperty(name = "resttemplate.sentinel.enabled", havingValue = "true", matchIfMissing = true) public static SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @ConditionalOnMissingBean public SentinelDataSourceHandler sentinelDataSourceHandler( DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties, Environment env) { return new SentinelDataSourceHandler(beanFactory, sentinelProperties, env); } @ConditionalOnClass(ObjectMapper.class) @Configuration(proxyBeanMethods = false) protected static class SentinelConverterConfiguration { @Configuration(proxyBeanMethods = false) protected static class SentinelJsonConfiguration { private final ObjectMapper objectMapper; public SentinelJsonConfiguration() { this.objectMapper = JsonMapper.builder() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); } @Bean("sentinel-json-flow-converter") public JsonConverter jsonFlowConverter() { return new JsonConverter(objectMapper, FlowRule.class); } @Bean("sentinel-json-degrade-converter") public JsonConverter jsonDegradeConverter() { return new JsonConverter(objectMapper, DegradeRule.class); } @Bean("sentinel-json-system-converter") public JsonConverter jsonSystemConverter() { return new JsonConverter(objectMapper, SystemRule.class); } @Bean("sentinel-json-authority-converter") public JsonConverter jsonAuthorityConverter() { return new JsonConverter(objectMapper, AuthorityRule.class); } @Bean("sentinel-json-param-flow-converter") public JsonConverter jsonParamFlowConverter() { return new JsonConverter(objectMapper, ParamFlowRule.class); } } @ConditionalOnClass(XmlMapper.class) @Configuration(proxyBeanMethods = false) protected static class SentinelXmlConfiguration { private final XmlMapper xmlMapper; public SentinelXmlConfiguration() { this.xmlMapper = XmlMapper.builder() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .build(); } @Bean("sentinel-xml-flow-converter") public XmlConverter xmlFlowConverter() { return new XmlConverter(xmlMapper, FlowRule.class); } @Bean("sentinel-xml-degrade-converter") public XmlConverter xmlDegradeConverter() { return new XmlConverter(xmlMapper, DegradeRule.class); } @Bean("sentinel-xml-system-converter") public XmlConverter xmlSystemConverter() { return new XmlConverter(xmlMapper, SystemRule.class); } @Bean("sentinel-xml-authority-converter") public XmlConverter xmlAuthorityConverter() { return new XmlConverter(xmlMapper, AuthorityRule.class); } @Bean("sentinel-xml-param-flow-converter") public XmlConverter xmlParamFlowConverter() { return new XmlConverter(xmlMapper, ParamFlowRule.class); } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelBeanPostProcessor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.custom; import java.lang.reflect.Method; import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.cloud.sentinel.SentinelConstants; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.core.type.StandardMethodMetadata; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; /** * PostProcessor handle @SentinelRestTemplate Annotation, add interceptor for * RestTemplate. * * @author Jim * @see SentinelRestTemplate * @see SentinelProtectInterceptor */ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProcessor { private static final Logger log = LoggerFactory .getLogger(SentinelBeanPostProcessor.class); private final ApplicationContext applicationContext; public SentinelBeanPostProcessor(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } private ConcurrentHashMap cache = new ConcurrentHashMap<>(); @Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { // Fixes #3329: Support custom RestTemplate if (beanName == null || !RestTemplate.class.isAssignableFrom(beanType)) { return; } SentinelRestTemplate sentinelRestTemplate = this.getSentinelRestTemplateFromBeanDefinition(beanDefinition); if (sentinelRestTemplate != null) { // check class and method validation checkSentinelRestTemplate(sentinelRestTemplate, beanName); cache.put(beanName, sentinelRestTemplate); } } private SentinelRestTemplate getSentinelRestTemplateFromBeanDefinition(RootBeanDefinition beanDefinition) { SentinelRestTemplate sentinelRestTemplate = null; if (beanDefinition.getSource() instanceof StandardMethodMetadata sentinelSource) { sentinelRestTemplate = sentinelSource.getIntrospectedMethod().getAnnotation(SentinelRestTemplate.class); } if (sentinelRestTemplate == null && beanDefinition.getResolvedFactoryMethod() != null) { sentinelRestTemplate = beanDefinition.getResolvedFactoryMethod().getAnnotation(SentinelRestTemplate.class); } return sentinelRestTemplate; } private void checkSentinelRestTemplate(SentinelRestTemplate sentinelRestTemplate, String beanName) { checkBlock4RestTemplate(sentinelRestTemplate.blockHandlerClass(), sentinelRestTemplate.blockHandler(), beanName, SentinelConstants.BLOCK_TYPE); checkBlock4RestTemplate(sentinelRestTemplate.fallbackClass(), sentinelRestTemplate.fallback(), beanName, SentinelConstants.FALLBACK_TYPE); checkBlock4RestTemplate(sentinelRestTemplate.urlCleanerClass(), sentinelRestTemplate.urlCleaner(), beanName, SentinelConstants.URLCLEANER_TYPE); } private void checkBlock4RestTemplate(Class blockClass, String blockMethod, String beanName, String type) { if (blockClass == void.class && !StringUtils.hasLength(blockMethod)) { return; } if (blockClass != void.class && !StringUtils.hasLength(blockMethod)) { log.error( "{} class attribute exists but {} method attribute is not exists in bean[{}]", type, type, beanName); throw new IllegalArgumentException(type + " class attribute exists but " + type + " method attribute is not exists in bean[" + beanName + "]"); } else if (blockClass == void.class && StringUtils.hasLength(blockMethod)) { log.error( "{} method attribute exists but {} class attribute is not exists in bean[{}]", type, type, beanName); throw new IllegalArgumentException(type + " method attribute exists but " + type + " class attribute is not exists in bean[" + beanName + "]"); } Class[] args; if (type.equals(SentinelConstants.URLCLEANER_TYPE)) { args = new Class[] {String.class}; } else { args = new Class[] {HttpRequest.class, byte[].class, ClientHttpRequestExecution.class, BlockException.class}; } String argsStr = Arrays.toString( Arrays.stream(args).map(clazz -> clazz.getSimpleName()).toArray()); Method foundMethod = ClassUtils.getStaticMethod(blockClass, blockMethod, args); if (foundMethod == null) { log.error( "{} static method can not be found in bean[{}]. The right method signature is {}#{}{}, please check your class name, method name and arguments", type, beanName, blockClass.getName(), blockMethod, argsStr); throw new IllegalArgumentException(type + " static method can not be found in bean[" + beanName + "]. The right method signature is " + blockClass.getName() + "#" + blockMethod + argsStr + ", please check your class name, method name and arguments"); } Class standardReturnType; if (type.equals(SentinelConstants.URLCLEANER_TYPE)) { standardReturnType = String.class; } else { standardReturnType = ClientHttpResponse.class; } if (!standardReturnType.isAssignableFrom(foundMethod.getReturnType())) { log.error("{} method return value in bean[{}] is not {}: {}#{}{}", type, beanName, standardReturnType.getName(), blockClass.getName(), blockMethod, argsStr); throw new IllegalArgumentException(type + " method return value in bean[" + beanName + "] is not " + standardReturnType.getName() + ": " + blockClass.getName() + "#" + blockMethod + argsStr); } if (type.equals(SentinelConstants.BLOCK_TYPE)) { BlockClassRegistry.updateBlockHandlerFor(blockClass, blockMethod, foundMethod); } else if (type.equals(SentinelConstants.FALLBACK_TYPE)) { BlockClassRegistry.updateFallbackFor(blockClass, blockMethod, foundMethod); } else { BlockClassRegistry.updateUrlCleanerFor(blockClass, blockMethod, foundMethod); } } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName != null && cache.containsKey(beanName)) { // add interceptor for each RestTemplate with @SentinelRestTemplate annotation StringBuilder interceptorBeanNamePrefix = new StringBuilder(); SentinelRestTemplate sentinelRestTemplate = cache.get(beanName); interceptorBeanNamePrefix .append(StringUtils.uncapitalize( SentinelProtectInterceptor.class.getSimpleName())) .append("_") .append(sentinelRestTemplate.blockHandlerClass().getSimpleName()) .append(sentinelRestTemplate.blockHandler()).append("_") .append(sentinelRestTemplate.fallbackClass().getSimpleName()) .append(sentinelRestTemplate.fallback()).append("_") .append(sentinelRestTemplate.urlCleanerClass().getSimpleName()) .append(sentinelRestTemplate.urlCleaner()); RestTemplate restTemplate = (RestTemplate) bean; String interceptorBeanName = interceptorBeanNamePrefix + "@" + bean.toString(); registerBean(interceptorBeanName, sentinelRestTemplate, (RestTemplate) bean); SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext .getBean(interceptorBeanName, SentinelProtectInterceptor.class); restTemplate.getInterceptors().add(0, sentinelProtectInterceptor); } return bean; } private void registerBean(String interceptorBeanName, SentinelRestTemplate sentinelRestTemplate, RestTemplate restTemplate) { // register SentinelProtectInterceptor bean DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext .getAutowireCapableBeanFactory(); BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(SentinelProtectInterceptor.class); beanDefinitionBuilder.addConstructorArgValue(sentinelRestTemplate); beanDefinitionBuilder.addConstructorArgValue(restTemplate); BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder .getRawBeanDefinition(); beanFactory.registerBeanDefinition(interceptorBeanName, interceptorBeanDefinition); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelDataSourceHandler.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.custom; import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import com.alibaba.cloud.sentinel.SentinelProperties; import com.alibaba.cloud.sentinel.datasource.config.AbstractDataSourceProperties; import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter; import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.datasource.ReadableDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.env.Environment; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** * Sentinel {@link ReadableDataSource} Handler Handle the configurations of * 'spring.cloud.sentinel.datasource'. * * @author Jim * @see SentinelProperties#datasource * @see JsonConverter * @see XmlConverter */ public class SentinelDataSourceHandler implements SmartInitializingSingleton { private static final Logger log = LoggerFactory .getLogger(SentinelDataSourceHandler.class); private final List dataTypeList = Arrays.asList("json", "xml"); private final String DATA_TYPE_FIELD = "dataType"; private final String CUSTOM_DATA_TYPE = "custom"; private final String CONVERTER_CLASS_FIELD = "converterClass"; private final DefaultListableBeanFactory beanFactory; private final SentinelProperties sentinelProperties; private final Environment env; public SentinelDataSourceHandler(DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties, Environment env) { this.beanFactory = beanFactory; this.sentinelProperties = sentinelProperties; this.env = env; } @Override public void afterSingletonsInstantiated() { sentinelProperties.getDatasource() .forEach((dataSourceName, dataSourceProperties) -> { try { List validFields = dataSourceProperties.getValidField(); if (validFields.size() != 1) { log.error("[Sentinel Starter] DataSource " + dataSourceName + " multi datasource active and won't loaded: " + dataSourceProperties.getValidField()); return; } AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties .getValidDataSourceProperties(); abstractDataSourceProperties.setEnv(env); abstractDataSourceProperties.preCheck(dataSourceName); registerBean(abstractDataSourceProperties, dataSourceName + "-sentinel-" + validFields.get(0) + "-datasource"); } catch (Exception e) { log.error("[Sentinel Starter] DataSource " + dataSourceName + " build error: " + e.getMessage(), e); } }); } protected BeanDefinitionBuilder parseBeanDefinition(final AbstractDataSourceProperties dataSourceProperties, String dataSourceName) { Map propertyMap = Arrays .stream(dataSourceProperties.getClass().getDeclaredFields()) .filter(field -> !field.isSynthetic()) .collect(HashMap::new, (m, v) -> { try { v.setAccessible(true); m.put(v.getName(), v.get(dataSourceProperties)); } catch (IllegalAccessException e) { log.error("[Sentinel Starter] DataSource " + dataSourceName + " field: " + v.getName() + " invoke error"); throw new RuntimeException( "[Sentinel Starter] DataSource " + dataSourceName + " field: " + v.getName() + " invoke error", e); } }, HashMap::putAll); propertyMap.put(CONVERTER_CLASS_FIELD, dataSourceProperties.getConverterClass()); propertyMap.put(DATA_TYPE_FIELD, dataSourceProperties.getDataType()); BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(dataSourceProperties.getFactoryBeanName()); propertyMap.forEach((propertyName, propertyValue) -> { Field field = ReflectionUtils.findField(dataSourceProperties.getClass(), propertyName); if (null == field) { return; } if (DATA_TYPE_FIELD.equals(propertyName)) { String dataType = StringUtils.trimAllWhitespace(propertyValue.toString()); if (CUSTOM_DATA_TYPE.equals(dataType)) { try { if (!StringUtils .hasLength(dataSourceProperties.getConverterClass())) { throw new RuntimeException("[Sentinel Starter] DataSource " + dataSourceName + "dataType is custom, please set converter-class " + "property"); } // construct custom Converter with 'converterClass' // configuration and register String customConvertBeanName = "sentinel-" + dataSourceProperties.getConverterClass(); if (!this.beanFactory.containsBean(customConvertBeanName)) { this.beanFactory.registerBeanDefinition(customConvertBeanName, BeanDefinitionBuilder .genericBeanDefinition( Class.forName(dataSourceProperties .getConverterClass())) .getBeanDefinition()); } builder.addPropertyReference("converter", customConvertBeanName); } catch (ClassNotFoundException e) { log.error("[Sentinel Starter] DataSource " + dataSourceName + " handle " + dataSourceProperties.getClass().getSimpleName() + " error, class name: " + dataSourceProperties.getConverterClass()); throw new RuntimeException("[Sentinel Starter] DataSource " + dataSourceName + " handle " + dataSourceProperties.getClass().getSimpleName() + " error, class name: " + dataSourceProperties.getConverterClass(), e); } } else { if (!dataTypeList.contains( StringUtils.trimAllWhitespace(propertyValue.toString()))) { throw new RuntimeException("[Sentinel Starter] DataSource " + dataSourceName + " dataType: " + propertyValue + " is not support now. please using these types: " + dataTypeList.toString()); } // converter type now support xml or json. // The bean name of these converters wrapped by // 'sentinel-{converterType}-{ruleType}-converter' builder.addPropertyReference("converter", "sentinel-" + propertyValue.toString() + "-" + dataSourceProperties.getRuleType().getName() + "-converter"); } } else { if (!CONVERTER_CLASS_FIELD.equals(propertyName)) { // wired properties Optional.ofNullable(propertyValue) .ifPresent(v -> builder.addPropertyValue(propertyName, v)); } } }); return builder; } private void registerBean(final AbstractDataSourceProperties dataSourceProperties, String dataSourceName) { BeanDefinitionBuilder builder = parseBeanDefinition(dataSourceProperties, dataSourceName); this.beanFactory.registerBeanDefinition(dataSourceName, builder.getBeanDefinition()); // init in Spring AbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory .getBean(dataSourceName); // register property in RuleManager dataSourceProperties.postRegister(newDataSource); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelProtectInterceptor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.custom; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.RestTemplate; /** * Interceptor using by SentinelRestTemplate. * * @author Jim */ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor { private final SentinelRestTemplate sentinelRestTemplate; private final RestTemplate restTemplate; public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate, RestTemplate restTemplate) { this.sentinelRestTemplate = sentinelRestTemplate; this.restTemplate = restTemplate; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { URI uri = request.getURI(); String hostResource = request.getMethod().toString() + ":" + uri.getScheme() + "://" + uri.getHost() + (uri.getPort() == -1 ? "" : ":" + uri.getPort()); String hostWithPathResource = hostResource + uri.getPath(); boolean entryWithPath = true; if (hostResource.equals(hostWithPathResource)) { entryWithPath = false; } Method urlCleanerMethod = BlockClassRegistry.lookupUrlCleaner( sentinelRestTemplate.urlCleanerClass(), sentinelRestTemplate.urlCleaner()); if (urlCleanerMethod != null) { hostWithPathResource = (String) methodInvoke(urlCleanerMethod, hostWithPathResource); } Entry hostEntry = null; Entry hostWithPathEntry = null; ClientHttpResponse response = null; try { hostEntry = SphU.entry(hostResource, EntryType.OUT); if (entryWithPath) { hostWithPathEntry = SphU.entry(hostWithPathResource, EntryType.OUT); } response = execution.execute(request, body); if (this.restTemplate.getErrorHandler().hasError(response)) { Tracer.trace( new IllegalStateException("RestTemplate ErrorHandler has error")); } return response; } catch (Throwable e) { if (BlockException.isBlockException(e)) { return handleBlockException(request, body, execution, (BlockException) e); } else { Tracer.traceEntry(e, hostEntry); if (e instanceof IOException ioException) { throw ioException; } else if (e instanceof RuntimeException runtimeException) { throw runtimeException; } else { throw new IOException(e); } } } finally { if (hostWithPathEntry != null) { hostWithPathEntry.exit(); } if (hostEntry != null) { hostEntry.exit(); } } } private ClientHttpResponse handleBlockException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { Object[] args = new Object[] { request, body, execution, ex }; // handle degrade if (isDegradeFailure(ex)) { Method fallbackMethod = extractFallbackMethod(sentinelRestTemplate.fallback(), sentinelRestTemplate.fallbackClass()); if (fallbackMethod != null) { return (ClientHttpResponse) methodInvoke(fallbackMethod, args); } else { return new SentinelClientHttpResponse(); } } // handle flow Method blockHandler = extractBlockHandlerMethod( sentinelRestTemplate.blockHandler(), sentinelRestTemplate.blockHandlerClass()); if (blockHandler != null) { return (ClientHttpResponse) methodInvoke(blockHandler, args); } else { return new SentinelClientHttpResponse(); } } private Object methodInvoke(Method method, Object... args) { try { return method.invoke(null, args); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } private Method extractFallbackMethod(String fallback, Class fallbackClass) { return BlockClassRegistry.lookupFallback(fallbackClass, fallback); } private Method extractBlockHandlerMethod(String block, Class blockClass) { return BlockClassRegistry.lookupBlockHandler(blockClass, block); } private boolean isDegradeFailure(BlockException ex) { return ex instanceof DegradeException; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/context/SentinelApplicationContextInitializer.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.custom.context; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.cloud.sentinel.SentinelConstants; import com.alibaba.cloud.sentinel.SentinelProperties; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.init.InitExecutor; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import static com.alibaba.cloud.sentinel.SentinelConstants.BLOCK_PAGE_URL_CONF_KEY; import static com.alibaba.csp.sentinel.config.SentinelConfig.setConfig; /** * @author yuluo * @author yuluo */ public class SentinelApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { ConfigurableEnvironment environment = applicationContext.getEnvironment(); String applicationName = environment.getProperty("spring.application.name"); SentinelProperties sentinelProperties = Binder.get(environment) .bindOrCreate(SentinelConstants.PROPERTY_PREFIX, SentinelProperties.class); initSentinelConfig(sentinelProperties, applicationName); } private void initSentinelConfig(SentinelProperties properties, String applicationName) { if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR)) && StringUtils.isNotBlank(properties.getLog().getDir())) { System.setProperty(LogBase.LOG_DIR, properties.getLog().getDir()); } if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_NAME_USE_PID)) && properties.getLog().isSwitchPid()) { System.setProperty(LogBase.LOG_NAME_USE_PID, String.valueOf(properties.getLog().isSwitchPid())); } if (StringUtils.isEmpty(System.getProperty(SentinelConfig.APP_NAME_PROP_KEY)) && StringUtils.isNotBlank(applicationName)) { System.setProperty(SentinelConfig.APP_NAME_PROP_KEY, applicationName); } if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT)) && StringUtils.isNotBlank(properties.getTransport().getPort())) { System.setProperty(TransportConfig.SERVER_PORT, properties.getTransport().getPort()); } if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER)) && StringUtils.isNotBlank(properties.getTransport().getDashboard())) { System.setProperty(TransportConfig.CONSOLE_SERVER, properties.getTransport().getDashboard()); } if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_INTERVAL_MS)) && StringUtils .isNotBlank(properties.getTransport().getHeartbeatIntervalMs())) { System.setProperty(TransportConfig.HEARTBEAT_INTERVAL_MS, properties.getTransport().getHeartbeatIntervalMs()); } if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_CLIENT_IP)) && StringUtils.isNotBlank(properties.getTransport().getClientIp())) { System.setProperty(TransportConfig.HEARTBEAT_CLIENT_IP, properties.getTransport().getClientIp()); } if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET)) && StringUtils.isNotBlank(properties.getMetric().getCharset())) { System.setProperty(SentinelConfig.CHARSET, properties.getMetric().getCharset()); } if (StringUtils .isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE)) && StringUtils.isNotBlank(properties.getMetric().getFileSingleSize())) { System.setProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE, properties.getMetric().getFileSingleSize()); } if (StringUtils .isEmpty(System.getProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT)) && StringUtils.isNotBlank(properties.getMetric().getFileTotalCount())) { System.setProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT, properties.getMetric().getFileTotalCount()); } if (StringUtils.isEmpty(System.getProperty(SentinelConfig.COLD_FACTOR)) && StringUtils.isNotBlank(properties.getFlow().getColdFactor())) { System.setProperty(SentinelConfig.COLD_FACTOR, properties.getFlow().getColdFactor()); } if (StringUtils.isNotBlank(properties.getBlockPage())) { setConfig(BLOCK_PAGE_URL_CONF_KEY, properties.getBlockPage()); } // earlier initialize if (properties.isEager()) { InitExecutor.doInit(); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/endpoint/SentinelEndpoint.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.endpoint; import java.util.HashMap; import java.util.Map; import com.alibaba.cloud.sentinel.SentinelProperties; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.util.AppNameUtil; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import static com.alibaba.cloud.sentinel.SentinelConstants.BLOCK_PAGE_URL_CONF_KEY; /** * Endpoint for Sentinel, contains ans properties and rules. * * @author xiaojing */ @Endpoint(id = "sentinel") public class SentinelEndpoint { private final SentinelProperties sentinelProperties; public SentinelEndpoint(SentinelProperties sentinelProperties) { this.sentinelProperties = sentinelProperties; } @ReadOperation public Map invoke() { final Map result = new HashMap<>(); if (sentinelProperties.isEnabled()) { result.put("appName", AppNameUtil.getAppName()); result.put("logDir", LogBase.getLogBaseDir()); result.put("logUsePid", LogBase.isLogNameUsePid()); result.put("blockPage", SentinelConfig.getConfig(BLOCK_PAGE_URL_CONF_KEY)); result.put("metricsFileSize", SentinelConfig.singleMetricFileSize()); result.put("metricsFileCharset", SentinelConfig.charset()); result.put("totalMetricsFileCount", SentinelConfig.totalMetricFileCount()); result.put("consoleServer", TransportConfig.getConsoleServerList()); result.put("clientIp", TransportConfig.getHeartbeatClientIp()); result.put("heartbeatIntervalMs", TransportConfig.getHeartbeatIntervalMs()); result.put("clientPort", TransportConfig.getPort()); result.put("coldFactor", sentinelProperties.getFlow().getColdFactor()); result.put("filter", sentinelProperties.getFilter()); result.put("datasource", sentinelProperties.getDatasource()); final Map rules = new HashMap<>(); result.put("rules", rules); rules.put("flowRules", FlowRuleManager.getRules()); rules.put("degradeRules", DegradeRuleManager.getRules()); rules.put("systemRules", SystemRuleManager.getRules()); rules.put("authorityRule", AuthorityRuleManager.getRules()); rules.put("paramFlowRule", ParamFlowRuleManager.getRules()); } return result; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/endpoint/SentinelEndpointAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.endpoint; import com.alibaba.cloud.sentinel.SentinelProperties; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator; import org.springframework.context.annotation.Bean; /** * @author hengyunabc */ @ConditionalOnClass(Endpoint.class) @EnableConfigurationProperties({ SentinelProperties.class }) public class SentinelEndpointAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnAvailableEndpoint public SentinelEndpoint sentinelEndPoint(SentinelProperties sentinelProperties) { return new SentinelEndpoint(sentinelProperties); } @Bean @ConditionalOnMissingBean @ConditionalOnEnabledHealthIndicator("sentinel") public SentinelHealthIndicator sentinelHealthIndicator( DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties) { return new SentinelHealthIndicator(beanFactory, sentinelProperties); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/endpoint/SentinelHealthIndicator.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.endpoint; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.cloud.sentinel.SentinelProperties; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider; import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.transport.endpoint.Endpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.health.contributor.AbstractHealthIndicator; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.HealthIndicator; import org.springframework.boot.health.contributor.Status; import org.springframework.util.CollectionUtils; /** * A {@link HealthIndicator} for Sentinel, which checks the status of Sentinel Dashboard * and DataSource. * *

* Check the status of Sentinel Dashboard by sending a heartbeat message to it. If return * true, it's OK. * * Check the status of Sentinel DataSource by calling loadConfig method of * {@link AbstractDataSource}. If no Exception thrown, it's OK. * * If Dashboard and DataSource are both OK, the health status is UP. *

* *

* Note: If Sentinel isn't enabled, the health status is up. If Sentinel Dashboard isn't * configured, it's OK and mark the status of Dashboard with UNKNOWN. More informations * are provided in details. *

* * @author cdfive */ public class SentinelHealthIndicator extends AbstractHealthIndicator { private DefaultListableBeanFactory beanFactory; private SentinelProperties sentinelProperties; private static final Logger logger = LoggerFactory.getLogger(SentinelHealthIndicator.class); public SentinelHealthIndicator(DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties) { this.beanFactory = beanFactory; this.sentinelProperties = sentinelProperties; } @Override protected void doHealthCheck(Health.Builder builder) throws Exception { Map detailMap = new HashMap<>(); // If sentinel isn't enabled, set the status up and set the enabled to false in // detail if (!sentinelProperties.isEnabled()) { detailMap.put("enabled", false); builder.up().withDetails(detailMap); return; } detailMap.put("enabled", true); // Check health of Dashboard boolean dashboardUp = true; List consoleServerList = TransportConfig.getConsoleServerList(); logger.info("Find sentinel dashboard server list: {}", consoleServerList); if (CollectionUtils.isEmpty(consoleServerList)) { // If Dashboard isn't configured, it's OK and mark the status of Dashboard // with UNKNOWN. detailMap.put("dashboard", new Status( Status.UNKNOWN.getCode(), "dashboard isn't configured" ) ); } else { // If Dashboard is configured, send a heartbeat message to it and check the HeartbeatSender heartbeatSender = HeartbeatSenderProvider.getHeartbeatSender(); // result is true if the heartbeat message is sent successfully boolean result = heartbeatSender.sendHeartbeat(); if (result) { detailMap.put("dashboard", Status.UP); } else { logger.warn("Sentinel dashboard heartbeat message can't be sent to the dashboard servers {} one of them can't be connected", consoleServerList); // If failed to send heartbeat message, means that the Dashboard is DOWN dashboardUp = false; detailMap.put("dashboard", new Status( Status.UNKNOWN.getCode(), String.format( "the dashboard servers [%s] one of them can't be connected", consoleServerList ) ) ); } } // Check health of DataSource boolean dataSourceUp = true; Map dataSourceDetailMap = new HashMap<>(); detailMap.put("dataSource", dataSourceDetailMap); // Get all DataSources and each call loadConfig to check if it's OK // If no Exception thrown, it's OK // Note: // Even if the dynamic config center is down, the loadConfig() might return // successfully // e.g. for Nacos client, it might retrieve from the local cache) // But in most circumstances it's okay Map dataSourceMap = beanFactory .getBeansOfType(AbstractDataSource.class); for (Map.Entry dataSourceMapEntry : dataSourceMap .entrySet()) { String dataSourceBeanName = dataSourceMapEntry.getKey(); AbstractDataSource dataSource = dataSourceMapEntry.getValue(); try { dataSource.loadConfig(); dataSourceDetailMap.put(dataSourceBeanName, Status.UP); } catch (Exception e) { // If one DataSource failed to loadConfig, means that the DataSource is // DOWN dataSourceUp = false; dataSourceDetailMap.put(dataSourceBeanName, new Status(Status.UNKNOWN.getCode(), e.getMessage())); } } // If Dashboard and DataSource are both OK, the health status is UP if (dashboardUp && dataSourceUp) { builder.up().withDetails(detailMap); } else { builder.unknown().withDetails(detailMap); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/feign/SentinelContractHolder.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.feign; import java.util.HashMap; import java.util.List; import java.util.Map; import feign.Contract; import feign.MethodMetadata; /** * * Using static field {@link SentinelContractHolder#METADATA_MAP} to hold * {@link MethodMetadata} data. * * @author Jim */ public class SentinelContractHolder implements Contract { private final Contract delegate; /** * map key is constructed by ClassFullName + configKey. configKey is constructed by * {@link feign.Feign#configKey} */ public final static Map METADATA_MAP = new HashMap<>(); public SentinelContractHolder(Contract delegate) { this.delegate = delegate; } @Override public List parseAndValidateMetadata(Class targetType) { List metadatas = delegate.parseAndValidateMetadata(targetType); metadatas.forEach(metadata -> METADATA_MAP .put(targetType.getName() + metadata.configKey(), metadata)); return metadatas; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/feign/SentinelFeign.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.feign; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; import feign.Contract; import feign.Feign; import feign.InvocationHandlerFactory; import feign.Target; import org.springframework.beans.BeansException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.cloud.openfeign.FeignClientFactory; import org.springframework.cloud.openfeign.FeignClientFactoryBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.support.GenericApplicationContext; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** * {@link Feign.Builder}. * * @author Jim * @author 黄学敏(huangxuemin) */ public final class SentinelFeign { private static final String FEIGN_LAZY_ATTR_RESOLUTION = "spring.cloud.openfeign.lazy-attributes-resolution"; private SentinelFeign() { } public static Builder builder() { return new Builder(); } public static final class Builder extends Feign.Builder implements ApplicationContextAware { private Contract contract = new Contract.Default(); private ApplicationContext applicationContext; private FeignClientFactory feignClientFactory; @Override public Feign.Builder invocationHandlerFactory( InvocationHandlerFactory invocationHandlerFactory) { throw new UnsupportedOperationException(); } @Override public Builder contract(Contract contract) { this.contract = contract; return this; } @Override public Feign internalBuild() { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map dispatch) { GenericApplicationContext gctx = (GenericApplicationContext) Builder.this.applicationContext; BeanDefinition def = gctx.getBeanDefinition(target.type().getName()); FeignClientFactoryBean feignClientFactoryBean; // If you need the attributes to be resolved lazily, set the property value to true. Boolean isLazyInit = applicationContext.getEnvironment() .getProperty(FEIGN_LAZY_ATTR_RESOLUTION, Boolean.class, false); if (isLazyInit) { /* * Due to the change of the initialization sequence, * BeanFactory.getBean will cause a circular dependency. So * FeignClientFactoryBean can only be obtained from BeanDefinition */ feignClientFactoryBean = (FeignClientFactoryBean) def .getAttribute("feignClientsRegistrarFactoryBean"); } else { feignClientFactoryBean = (FeignClientFactoryBean) applicationContext .getBean("&" + target.type().getName()); } Class fallback = feignClientFactoryBean.getFallback(); Class fallbackFactory = feignClientFactoryBean.getFallbackFactory(); String beanName = feignClientFactoryBean.getContextId(); if (!StringUtils.hasText(beanName)) { beanName = (String) getFieldValue(feignClientFactoryBean, "name"); } Object fallbackInstance; FallbackFactory fallbackFactoryInstance; // check fallback and fallbackFactory properties if (void.class != fallback) { fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type()); return new SentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance)); } if (void.class != fallbackFactory) { fallbackFactoryInstance = (FallbackFactory) getFromContext( beanName, "fallbackFactory", fallbackFactory, FallbackFactory.class); return new SentinelInvocationHandler(target, dispatch, fallbackFactoryInstance); } return new SentinelInvocationHandler(target, dispatch); } private Object getFromContext(String name, String type, Class fallbackType, Class targetType) { Object fallbackInstance = feignClientFactory.getInstance(name, fallbackType); if (fallbackInstance == null) { throw new IllegalStateException(String.format( "No %s instance of type %s found for feign client %s", type, fallbackType, name)); } // when fallback is a FactoryBean, should determine the type of instance if (fallbackInstance instanceof FactoryBean factoryBean) { try { fallbackInstance = factoryBean.getObject(); } catch (Exception e) { throw new IllegalStateException(type + " create fail", e); } fallbackType = fallbackInstance.getClass(); } if (!targetType.isAssignableFrom(fallbackType)) { throw new IllegalStateException(String.format( "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", type, fallbackType, targetType, name)); } return fallbackInstance; } }); super.contract(new SentinelContractHolder(contract)); return super.internalBuild(); } private Object getFieldValue(Object instance, String fieldName) { Field field = ReflectionUtils.findField(instance.getClass(), fieldName); field.setAccessible(true); try { return field.get(instance); } catch (IllegalAccessException e) { // ignore } return null; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; feignClientFactory = this.applicationContext.getBean(FeignClientFactory.class); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/feign/SentinelFeignAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.feign; import com.alibaba.csp.sentinel.SphU; import feign.Feign; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; /** * @author Jim */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ SphU.class, Feign.class }) public class SentinelFeignAutoConfiguration { @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty(name = "feign.sentinel.enabled") public Feign.Builder feignSentinelBuilder() { return SentinelFeign.builder(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/feign/SentinelInvocationHandler.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.feign; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import feign.Feign; import feign.InvocationHandlerFactory.MethodHandler; import feign.MethodMetadata; import feign.Target; import org.springframework.cloud.openfeign.FallbackFactory; import static feign.Util.checkNotNull; /** * {@link InvocationHandler} handle invocation that protected by Sentinel. * * @author Jim */ public class SentinelInvocationHandler implements InvocationHandler { private final Target target; private final Map dispatch; private FallbackFactory fallbackFactory; private Map fallbackMethodMap; SentinelInvocationHandler(Target target, Map dispatch, FallbackFactory fallbackFactory) { this.target = checkNotNull(target, "target"); this.dispatch = checkNotNull(dispatch, "dispatch"); this.fallbackFactory = fallbackFactory; this.fallbackMethodMap = toFallbackMethod(dispatch); } SentinelInvocationHandler(Target target, Map dispatch) { this.target = checkNotNull(target, "target"); this.dispatch = checkNotNull(dispatch, "dispatch"); } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if ("equals".equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } Object result; MethodHandler methodHandler = this.dispatch.get(method); // only handle by HardCodedTarget if (target instanceof Target.HardCodedTarget hardCodedTarget) { MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP .get(hardCodedTarget.type().getName() + Feign.configKey(hardCodedTarget.type(), method)); // resource default is HttpMethod:protocol://url if (methodMetadata == null) { result = methodHandler.invoke(args); } else { String resourceName = methodMetadata.template().method().toUpperCase(Locale.ROOT) + ":" + hardCodedTarget.url() + methodMetadata.template().path(); Entry entry = null; try { ContextUtil.enter(resourceName); entry = SphU.entry(resourceName, EntryType.OUT, 1, args); result = methodHandler.invoke(args); } catch (Throwable ex) { // fallback handle if (!BlockException.isBlockException(ex)) { Tracer.traceEntry(ex, entry); } if (fallbackFactory != null) { try { Object fallbackResult = fallbackMethodMap.get(method) .invoke(fallbackFactory.create(ex), args); return fallbackResult; } catch (IllegalAccessException e) { // shouldn't happen as method is public due to being an // interface throw new AssertionError(e); } catch (InvocationTargetException e) { throw e.getCause(); } } else { // throw exception if fallbackFactory is null throw ex; } } finally { if (entry != null) { entry.exit(1, args); } ContextUtil.exit(); } } } else { // other target type using default strategy result = methodHandler.invoke(args); } return result; } @Override public boolean equals(Object obj) { if (obj instanceof SentinelInvocationHandler sentinelInvocationHandler) { return target.equals(sentinelInvocationHandler.target); } return false; } @Override public int hashCode() { return target.hashCode(); } @Override public String toString() { return target.toString(); } static Map toFallbackMethod(Map dispatch) { Map result = new LinkedHashMap<>(); for (Method method : dispatch.keySet()) { method.setAccessible(true); result.put(method, method); } return result; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/rest/SentinelClientHttpResponse.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.rest; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import com.alibaba.cloud.sentinel.custom.SentinelProtectInterceptor; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; /** * Using by {@link SentinelRestTemplate} and {@link SentinelProtectInterceptor}. * * @author Jim */ public class SentinelClientHttpResponse implements ClientHttpResponse { private String blockResponse = "RestTemplate request block by sentinel"; public SentinelClientHttpResponse() { } public SentinelClientHttpResponse(String blockResponse) { this.blockResponse = blockResponse; } // @Override public int getRawStatusCode() throws IOException { return HttpStatus.OK.value(); } @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public String getStatusText() throws IOException { return blockResponse; } @Override public void close() { // nothing do } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream(blockResponse.getBytes()); } @Override public HttpHeaders getHeaders() { Map> headers = new HashMap<>(); headers.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.APPLICATION_JSON_VALUE)); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.putAll(headers); return httpHeaders; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json ================================================ { "properties": [ { "name": "spring.cloud.sentinel.enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "enable or disable sentinel auto configure." }, { "name": "resttemplate.sentinel.enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "enable or disable @SentinelRestTemplate." }, { "name": "spring.cloud.sentinel.eager", "type": "java.lang.Boolean", "defaultValue": false, "description": "earlier initialize heart-beat when the spring container starts when the transport dependency is on classpath, the configuration is effective." }, { "name": "spring.cloud.sentinel.web-context-unify", "type": "java.lang.Boolean", "defaultValue": true, "description": "Specify whether unify web context(i.e. use the default context name), and is true by default." }, { "name": "spring.cloud.sentinel.transport.port", "type": "java.lang.String", "defaultValue": "8719", "description": "sentinel api port." }, { "name": "spring.cloud.sentinel.transport.clientIp", "type": "java.lang.String", "description": "sentinel client ip connect to dashboard." }, { "name": "spring.cloud.sentinel.transport.dashboard", "type": "java.lang.String", "description": "sentinel dashboard address, won't try to connect dashboard when address is empty." }, { "name": "spring.cloud.sentinel.transport.heartbeatIntervalMs", "type": "java.lang.String", "description": "send heartbeat interval millisecond." }, { "name": "spring.cloud.sentinel.filter.order", "type": "java.lang.Integer", "defaultValue": "Integer.MIN_VALUE", "description": "SentinelWebInterceptor order, will be register to InterceptorRegistry." }, { "name": "spring.cloud.sentinel.filter.enabled", "type": "java.lang.Boolean", "defaultValue": true, "description": "Enable to register com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor." }, { "name": "spring.cloud.sentinel.metric.charset", "type": "java.lang.String", "defaultValue": "UTF-8", "description": "charset when sentinel write or search metric file." }, { "name": "spring.cloud.sentinel.metric.fileSingleSize", "type": "java.lang.String", "description": "the metric file size." }, { "name": "spring.cloud.sentinel.metric.fileTotalCount", "type": "java.lang.String", "description": "the total metric file count." }, { "name": "spring.cloud.sentinel.log.dir", "type": "java.lang.String", "description": "log base directory." }, { "name": "spring.cloud.sentinel.log.switch-pid", "type": "java.lang.Boolean", "defaultValue": false, "description": "log file should with pid." }, { "name": "spring.cloud.sentinel.block-page", "type": "java.lang.String", "description": "the process page when the flow control is triggered." }, { "name": "spring.cloud.sentinel.servlet.block-page", "type": "java.lang.String", "description": "recommend use spring.cloud.sentinel.block-page." }, { "name": "spring.cloud.sentinel.flow.coldFactor", "type": "java.lang.String", "defaultValue": "3", "description": "sentinel the cold factor." }, { "name": "management.health.sentinel.enabled", "type": "java.lang.Boolean", "description": "Whether to enable sentinel health check.", "defaultValue": true }, { "defaultValue": "false", "name": "feign.sentinel.enabled", "description": "If true, an OpenFeign client will be wrapped with a Sentinel circuit breaker.", "type": "java.lang.Boolean" } ] } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/resources/META-INF/native-image/reflect-config.json ================================================ [ { "condition": { "typeReachable": "org.springframework.beans.factory.support.DefaultListableBeanFactory$2" }, "name": "com.alibaba.csp.sentinel.Entry" }, { "condition": { "typeReachable": "org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext" }, "name": "com.alibaba.csp.sentinel.SphU" }, { "condition": { "typeReachable": "org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGroupingHandler" }, "name": "com.alibaba.csp.sentinel.SphU" }, { "condition": { "typeReachable": "org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGroupingHandler" }, "name": "com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer" }, { "condition": { "typeReachable": "org.springframework.beans.factory.support.DefaultListableBeanFactory$2" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "afterCompletion", "parameterTypes": [ "jakarta.servlet.http.HttpServletRequest", "jakarta.servlet.http.HttpServletResponse", "java.lang.Object", "java.lang.Exception" ] }, { "name": "postHandle", "parameterTypes": [ "jakarta.servlet.http.HttpServletRequest", "jakarta.servlet.http.HttpServletResponse", "java.lang.Object", "org.springframework.web.servlet.ModelAndView" ] }, { "name": "preHandle", "parameterTypes": [ "jakarta.servlet.http.HttpServletRequest", "jakarta.servlet.http.HttpServletResponse", "java.lang.Object" ] } ] }, { "condition": { "typeReachable": "org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor", "methods": [ { "name": "afterCompletion", "parameterTypes": [ "jakarta.servlet.http.HttpServletRequest", "jakarta.servlet.http.HttpServletResponse", "java.lang.Object", "java.lang.Exception" ] }, { "name": "postHandle", "parameterTypes": [ "jakarta.servlet.http.HttpServletRequest", "jakarta.servlet.http.HttpServletResponse", "java.lang.Object", "org.springframework.web.servlet.ModelAndView" ] }, { "name": "preHandle", "parameterTypes": [ "jakarta.servlet.http.HttpServletRequest", "jakarta.servlet.http.HttpServletResponse", "java.lang.Object" ] } ] }, { "condition": { "typeReachable": "org.springframework.beans.factory.support.DefaultListableBeanFactory$2" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGroupingHandler" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor" }, { "condition": { "typeReachable": "org.springframework.beans.factory.support.DefaultListableBeanFactory$2" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler" }, { "condition": { "typeReachable": "org.springframework.beans.factory.support.DefaultListableBeanFactory$2" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser" }, { "condition": { "typeReachable": "org.springframework.beans.factory.support.DefaultListableBeanFactory$2" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner" }, { "condition": { "typeReachable": "org.springframework.beans.factory.support.DefaultListableBeanFactory$2" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.config.BaseWebMvcConfig", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "getBlockExceptionHandler", "parameterTypes": [] }, { "name": "getOriginParser", "parameterTypes": [] }, { "name": "getRequestAttributeName", "parameterTypes": [] }, { "name": "getRequestRefName", "parameterTypes": [] }, { "name": "setBlockExceptionHandler", "parameterTypes": [ "com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler" ] }, { "name": "setOriginParser", "parameterTypes": [ "com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser" ] }, { "name": "setRequestAttributeName", "parameterTypes": [ "java.lang.String" ] } ] }, { "condition": { "typeReachable": "org.springframework.beans.factory.support.DefaultListableBeanFactory$2" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "methods": [ { "name": "toString", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext" }, "name": "com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.aspectj.weaver.patterns.ExactAnnotationTypePattern" }, "name": "com.alibaba.csp.sentinel.annotation.SentinelResource" }, { "condition": { "typeReachable": "org.aspectj.weaver.reflect.Java15ReflectionBasedReferenceTypeDelegate" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.AbstractSentinelAspectSupport" }, { "condition": { "typeReachable": "org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.AbstractSentinelAspectSupport", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.aspectj.internal.lang.reflect.AjTypeImpl" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.aspectj.weaver.tools.PointcutParser" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect" }, { "condition": { "typeReachable": "org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect", "allDeclaredFields": true }, { "condition": { "typeReachable": "org.springframework.aop.aspectj.annotation.AspectMetadata" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect" }, { "condition": { "typeReachable": "org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext" }, "name": "com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect", "allDeclaredFields": true, "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.init.InitExecutor" }, "name": "com.alibaba.csp.sentinel.cluster.client.init.DefaultClusterClientInitFunc", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider" }, "name": "com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterFlowRulesCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterMetricCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterParamFlowRulesCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerConfigHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerInfoCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterFlowRulesCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterParamFlowRulesCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerFlowConfigHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerTransportConfigHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyServerNamespaceSetHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.init.InitExecutor" }, "name": "com.alibaba.csp.sentinel.cluster.server.init.DefaultClusterServerInitFunc", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.processor.FlowRequestProcessor", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider" }, "name": "com.alibaba.csp.sentinel.cluster.server.processor.ParamFlowRequestProcessor", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.ApiCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.BasicInfoCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.FetchActiveRuleCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.FetchClusterClientConfigHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.FetchClusterNodeByIdCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.FetchClusterNodeHumanCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.FetchJsonTreeCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.FetchOriginCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.FetchSimpleClusterNodeCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.FetchSystemStatusCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.FetchTreeCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.GetParamFlowRulesCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.ModifyClusterClientConfigHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.ModifyRulesCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.OnOffGetCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.OnOffSetCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.SendMetricCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.VersionCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.cluster.FetchClusterModeCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandHandlerProvider" }, "name": "com.alibaba.csp.sentinel.command.handler.cluster.ModifyClusterModeCommandHandler", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext" }, "name": "com.alibaba.csp.sentinel.datasource.Converter", "queryAllDeclaredMethods": true, "queryAllPublicMethods": true }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.init.InitExecutor" }, "name": "com.alibaba.csp.sentinel.init.ParamFlowStatisticSlotCallbackInit", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.init.InitExecutor" }, "name": "com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.deser.std.CollectionDeserializer" }, "name": "com.alibaba.csp.sentinel.slots.block.AbstractRule", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.deser.std.CollectionDeserializer" }, "name": "com.alibaba.csp.sentinel.slots.block.Rule", "queryAllDeclaredMethods": true }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.fasterxml.jackson.databind.deser.std.CollectionDeserializer" }, "name": "com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.block.flow.FlowSlot", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder" }, "name": "com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot" }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.logger.LogSlot", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder" }, "name": "com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot" }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.statistic.StatisticSlot", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "org.apache.tomcat.websocket.server.WsFilter" }, "name": "com.alibaba.csp.sentinel.slots.system.SystemSlot", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.command.CommandCenterProvider" }, "name": "com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider" }, "name": "com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.init.InitExecutor" }, "name": "com.alibaba.csp.sentinel.transport.init.CommandCenterInitFunc", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "condition": { "typeReachable": "com.alibaba.csp.sentinel.init.InitExecutor" }, "name": "com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc", "methods": [ { "name": "", "parameterTypes": [] } ] } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/resources/META-INF/native-image/resource-config.json ================================================ { "resources": { "includes": [ ], "excludes": [ { "pattern": "\\QMETA-INF/services/com.alibaba.csp.sentinel.cluster.TokenService\\E" }, { "pattern": "\\QMETA-INF/services/com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor\\E" }, { "pattern": "\\QMETA-INF/services/com.alibaba.csp.sentinel.command.CommandHandler\\E" }, { "pattern": "\\QMETA-INF/services/com.alibaba.csp.sentinel.init.InitFunc\\E" }, { "pattern": "\\QMETA-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot\\E" }, { "pattern": "\\QMETA-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder\\E" }, { "pattern": "\\QMETA-INF/services/com.alibaba.csp.sentinel.transport.CommandCenter\\E" }, { "pattern": "\\QMETA-INF/services/com.alibaba.csp.sentinel.transport.HeartbeatSender\\E" } ] } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/resources/META-INF/spring/aot.factories ================================================ org.springframework.aot.hint.RuntimeHintsRegistrar=\ com.alibaba.cloud.sentinel.aot.hint.SentinelProtectInterceptorHints ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration com.alibaba.cloud.sentinel.SentinelWebFluxAutoConfiguration com.alibaba.cloud.sentinel.endpoint.SentinelEndpointAutoConfiguration com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/main/resources/META-INF/spring.factories ================================================ org.springframework.context.ApplicationContextInitializer=\ com.alibaba.cloud.sentinel.custom.context.SentinelApplicationContextInitializer ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/ContextIdSentinelFeignTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import static org.assertj.core.api.Assertions.assertThat; /** * Add this unit test to verify https://github.com/alibaba/spring-cloud-alibaba/pull/838. * * @author Jim */ @RunWith(SpringRunner.class) @SpringBootTest(classes = { ContextIdSentinelFeignTests.TestConfig.class }, properties = { "feign.sentinel.enabled=true" }) public class ContextIdSentinelFeignTests { @Autowired private EchoService echoService; @Autowired private FooService fooService; @Test public void testFeignClient() { assertThat(echoService.echo("test")).isEqualTo("echo fallback"); assertThat(fooService.echo("test")).isEqualTo("foo fallback"); assertThat(fooService.toString()).isNotEqualTo(echoService.toString()); assertThat(fooService.hashCode()).isNotEqualTo(echoService.hashCode()); assertThat(echoService.equals(fooService)).isEqualTo(Boolean.FALSE); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ SentinelFeignAutoConfiguration.class }) @EnableFeignClients public static class TestConfig { } @FeignClient(contextId = "echoService", name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) public interface EchoService { @GetMapping("/echo/{str}") String echo(@PathVariable("str") String str); } @FeignClient(contextId = "fooService", value = "foo-service", fallbackFactory = CustomFallbackFactory.class, configuration = FeignConfiguration.class) public interface FooService { @RequestMapping(path = "echo/{str}") String echo(@RequestParam("str") String param); } public static class FeignConfiguration { @Bean public EchoServiceFallback echoServiceFallback() { return new EchoServiceFallback(); } @Bean public CustomFallbackFactory customFallbackFactory() { return new CustomFallbackFactory(); } } public static class EchoServiceFallback implements EchoService { @Override public String echo(@RequestParam("str") String param) { return "echo fallback"; } } public static class FooServiceFallback implements FooService { @Override public String echo(@RequestParam("str") String param) { return "foo fallback"; } } public static class CustomFallbackFactory implements FallbackFactory { private FooService fooService = new FooServiceFallback(); @Override public FooService create(Throwable throwable) { return fooService; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/SentinelAutoConfigurationTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import java.util.Arrays; import java.util.Map; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration; import com.alibaba.cloud.sentinel.custom.SentinelBeanPostProcessor; import com.alibaba.cloud.sentinel.endpoint.SentinelEndpoint; import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.transport.endpoint.Endpoint; import com.alibaba.csp.sentinel.transport.endpoint.Protocol; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import static com.alibaba.cloud.sentinel.SentinelConstants.BLOCK_PAGE_URL_CONF_KEY; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** * @author Jim * @author jiashuai.xie */ @RunWith(SpringRunner.class) @SpringBootTest(classes = { SentinelAutoConfigurationTests.TestConfig.class }, properties = { "spring.cloud.sentinel.filter.order=123", "spring.cloud.sentinel.filter.urlPatterns=/*,/test", "spring.cloud.sentinel.metric.fileSingleSize=9999", "spring.cloud.sentinel.metric.fileTotalCount=100", "spring.cloud.sentinel.blockPage=/error", "spring.cloud.sentinel.flow.coldFactor=3", "spring.cloud.sentinel.eager=true", "spring.cloud.sentinel.log.switchPid=true", "spring.cloud.sentinel.transport.dashboard=http://localhost:8080,http://localhost:8081", "spring.cloud.sentinel.transport.port=9999", "spring.cloud.sentinel.transport.clientIp=1.1.1.1", "spring.cloud.sentinel.transport.heartbeatIntervalMs=20000" }, webEnvironment = RANDOM_PORT) public class SentinelAutoConfigurationTests { @Autowired private SentinelProperties sentinelProperties; @Autowired private SentinelBeanPostProcessor sentinelBeanPostProcessor; @Autowired private RestTemplate restTemplate; @Autowired private RestTemplate restTemplateWithBlockClass; @Autowired private RestTemplate restTemplateWithoutBlockClass; @Autowired private RestTemplate restTemplateWithFallbackClass; @LocalServerPort private int port; private String flowUrl = "http://localhost:" + port + "/flow"; private String degradeUrl = "http://localhost:" + port + "/degrade"; @Before public void setUp() { FlowRule rule = new FlowRule(); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(0); rule.setResource("GET:" + flowUrl); rule.setLimitApp("default"); rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rule.setStrategy(RuleConstant.STRATEGY_DIRECT); FlowRuleManager.loadRules(Arrays.asList(rule)); } @Test public void contextLoads() throws Exception { assertThat(sentinelBeanPostProcessor).isNotNull(); checkSentinelLog(); checkSentinelEager(); checkSentinelTransport(); checkSentinelColdFactor(); checkSentinelMetric(); checkSentinelFilter(); checkEndpoint(); } private void checkEndpoint() { SentinelEndpoint sentinelEndpoint = new SentinelEndpoint(sentinelProperties); Map map = sentinelEndpoint.invoke(); assertThat(map.get("logUsePid")).isEqualTo(Boolean.TRUE); assertThat(map.get("consoleServer").toString()) .isEqualTo(Arrays .asList(new Endpoint(Protocol.HTTP, "localhost", 8080), new Endpoint(Protocol.HTTP, "localhost", 8081)) .toString()); assertThat(map.get("clientPort")).isEqualTo("9999"); assertThat(map.get("heartbeatIntervalMs")).isEqualTo(20000L); assertThat(map.get("clientIp")).isEqualTo("1.1.1.1"); assertThat(map.get("metricsFileSize")).isEqualTo(9999L); assertThat(map.get("totalMetricsFileCount")).isEqualTo(100); assertThat(map.get("metricsFileCharset")).isEqualTo("UTF-8"); assertThat(map.get("blockPage")).isEqualTo("/error"); } private void checkSentinelFilter() { assertThat(sentinelProperties.getFilter().getOrder()).isEqualTo(123); assertThat(sentinelProperties.getFilter().getUrlPatterns().size()).isEqualTo(2); assertThat(sentinelProperties.getFilter().getUrlPatterns().get(0)) .isEqualTo("/*"); assertThat(sentinelProperties.getFilter().getUrlPatterns().get(1)) .isEqualTo("/test"); } private void checkSentinelMetric() { assertThat(sentinelProperties.getMetric().getCharset()).isEqualTo("UTF-8"); assertThat(sentinelProperties.getMetric().getFileSingleSize()).isEqualTo("9999"); assertThat(sentinelProperties.getMetric().getFileTotalCount()).isEqualTo("100"); } private void checkSentinelColdFactor() { assertThat(sentinelProperties.getFlow().getColdFactor()).isEqualTo("3"); } private void checkSentinelTransport() { assertThat(sentinelProperties.getTransport().getPort()).isEqualTo("9999"); assertThat(sentinelProperties.getTransport().getDashboard()) .isEqualTo("http://localhost:8080,http://localhost:8081"); assertThat(sentinelProperties.getTransport().getClientIp()).isEqualTo("1.1.1.1"); assertThat(sentinelProperties.getTransport().getHeartbeatIntervalMs()) .isEqualTo("20000"); } private void checkSentinelEager() { assertThat(sentinelProperties.isEager()).isEqualTo(true); } private void checkSentinelLog() { assertThat(sentinelProperties.getLog().isSwitchPid()).isEqualTo(true); } @Test public void testSentinelSystemProperties() { assertThat(LogBase.isLogNameUsePid()).isEqualTo(true); assertThat(TransportConfig.getConsoleServerList().toString()) .isEqualTo(Arrays .asList(new Endpoint(Protocol.HTTP, "localhost", 8080), new Endpoint(Protocol.HTTP, "localhost", 8081)) .toString()); assertThat(TransportConfig.getPort()).isEqualTo("9999"); assertThat(TransportConfig.getHeartbeatIntervalMs().longValue()) .isEqualTo(20000L); assertThat(TransportConfig.getHeartbeatClientIp()).isEqualTo("1.1.1.1"); assertThat(SentinelConfig.singleMetricFileSize()).isEqualTo(9999); assertThat(SentinelConfig.totalMetricFileCount()).isEqualTo(100); assertThat(SentinelConfig.charset()).isEqualTo("UTF-8"); assertThat(SentinelConfig.getConfig(BLOCK_PAGE_URL_CONF_KEY)).isEqualTo("/error"); } @Test public void testRestTemplateBlockHandler() { assertThat(restTemplate.getInterceptors().size()).isEqualTo(2); assertThat(restTemplateWithBlockClass.getInterceptors().size()).isEqualTo(1); ResponseEntity responseEntityBlock = restTemplateWithBlockClass .getForEntity(flowUrl, String.class); assertThat(responseEntityBlock.getBody()).isEqualTo("Oops"); assertThat(responseEntityBlock.getStatusCode()).isEqualTo(HttpStatus.OK); ResponseEntity responseEntityRaw = restTemplate.getForEntity(flowUrl, String.class); assertThat(responseEntityRaw.getBody()) .isEqualTo("RestTemplate request block by sentinel"); assertThat(responseEntityRaw.getStatusCode()).isEqualTo(HttpStatus.OK); } @Test public void testNormalRestTemplate() { assertThat(restTemplateWithoutBlockClass.getInterceptors().size()).isEqualTo(0); assertThatThrownBy(() -> { restTemplateWithoutBlockClass.getForEntity(flowUrl, String.class); }).isInstanceOf(RestClientException.class); } @Configuration static class SentinelTestConfiguration { @Bean @SentinelRestTemplate RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.getInterceptors().add(mock(ClientHttpRequestInterceptor.class)); return restTemplate; } @Bean @SentinelRestTemplate(blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException") RestTemplate restTemplateWithBlockClass() { return new RestTemplate(); } @Bean @SentinelRestTemplate(fallbackClass = ExceptionUtil.class, fallback = "fallbackException") RestTemplate restTemplateWithFallbackClass() { return new RestTemplate(); } @Bean RestTemplate restTemplateWithoutBlockClass() { return new RestTemplate(); } } public static class ExceptionUtil { public static SentinelClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); return new SentinelClientHttpResponse("Oops"); } public static SentinelClientHttpResponse fallbackException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); return new SentinelClientHttpResponse("Oops fallback"); } } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ SentinelAutoConfiguration.class, SentinelWebAutoConfiguration.class, SentinelTestConfiguration.class }) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/SentinelBeanAutowiredTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.DefaultBlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.config.SentinelWebMvcConfig; import com.alibaba.csp.sentinel.adapter.web.common.UrlCleaner; import jakarta.servlet.http.HttpServletRequest; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; /** * @author Jim */ @RunWith(SpringRunner.class) @SpringBootTest(classes = { SentinelBeanAutowiredTests.TestConfig.class }, properties = { "spring.cloud.sentinel.filter.order=111" }) public class SentinelBeanAutowiredTests { @Autowired private UrlCleaner urlCleaner; @Autowired private BlockExceptionHandler blockExceptionHandler; @Autowired private RequestOriginParser requestOriginParser; @Autowired private SentinelProperties sentinelProperties; @Autowired private SentinelWebMvcConfig sentinelWebMvcConfig; @Test public void contextLoads() throws Exception { assertThat(urlCleaner).isNotNull(); assertThat(blockExceptionHandler).isNotNull(); assertThat(requestOriginParser).isNotNull(); assertThat(sentinelProperties).isNotNull(); checkUrlPattern(); } private void checkUrlPattern() { assertThat(sentinelProperties.getFilter().getOrder()).isEqualTo(111); assertThat(sentinelProperties.getFilter().getUrlPatterns().size()).isEqualTo(1); assertThat(sentinelProperties.getFilter().getUrlPatterns().get(0)) .isEqualTo("/**"); } @Test public void testBeanAutowired() { assertThat(sentinelWebMvcConfig.getUrlCleaner()).isEqualTo(urlCleaner); assertThat(sentinelWebMvcConfig.getBlockExceptionHandler()) .isEqualTo(blockExceptionHandler); assertThat(sentinelWebMvcConfig.getOriginParser()).isEqualTo(requestOriginParser); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ SentinelAutoConfiguration.class, SentinelWebAutoConfiguration.class }) public static class TestConfig { @Bean public UrlCleaner urlCleaner() { return new UrlCleaner() { @Override public String clean(String s) { return s; } }; } @Bean public RequestOriginParser requestOriginParser() { return new RequestOriginParser() { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { return httpServletRequest.getRemoteAddr(); } }; } @Bean public BlockExceptionHandler blockExceptionHandler() { return new DefaultBlockExceptionHandler(); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/SentinelDataSourceTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration; import com.alibaba.cloud.sentinel.datasource.RuleType; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; /** * @author Jim */ @RunWith(SpringRunner.class) @SpringBootTest(classes = { SentinelDataSourceTests.TestConfig.class }, properties = { "spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json", "spring.cloud.sentinel.datasource.ds1.file.data-type=json", "spring.cloud.sentinel.datasource.ds1.file.rule-type=flow", "spring.cloud.sentinel.datasource.ds2.file.file=classpath: degraderule.json", "spring.cloud.sentinel.datasource.ds2.file.data-type=json", "spring.cloud.sentinel.datasource.ds2.file.rule-type=degrade", "spring.cloud.sentinel.datasource.ds3.file.file=classpath: authority.json", "spring.cloud.sentinel.datasource.ds3.file.rule-type=authority", "spring.cloud.sentinel.datasource.ds4.file.file=classpath: system.json", "spring.cloud.sentinel.datasource.ds4.file.rule-type=system", "spring.cloud.sentinel.datasource.ds5.file.file=classpath: param-flow.json", "spring.cloud.sentinel.datasource.ds5.file.data-type=custom", "spring.cloud.sentinel.datasource.ds5.file.converter-class=TestConverter", "spring.cloud.sentinel.datasource.ds5.file.rule-type=param-flow" }) public class SentinelDataSourceTests { @Autowired private SentinelProperties sentinelProperties; @Test public void contextLoads() throws Exception { assertThat(sentinelProperties).isNotNull(); checkUrlPattern(); } private void checkUrlPattern() { assertThat(sentinelProperties.getFilter().getOrder()) .isEqualTo(Integer.MIN_VALUE); assertThat(sentinelProperties.getFilter().getUrlPatterns().size()).isEqualTo(1); assertThat(sentinelProperties.getFilter().getUrlPatterns().get(0)) .isEqualTo("/**"); } @Test public void testDataSource() { assertThat(sentinelProperties.getDatasource().size()).isEqualTo(5); assertThat(sentinelProperties.getDatasource().get("ds1").getApollo()).isNull(); assertThat(sentinelProperties.getDatasource().get("ds1").getNacos()).isNull(); assertThat(sentinelProperties.getDatasource().get("ds1").getZk()).isNull(); assertThat(sentinelProperties.getDatasource().get("ds1").getFile()).isNotNull(); assertThat(sentinelProperties.getDatasource().get("ds1").getFile().getDataType()) .isEqualTo("json"); assertThat(sentinelProperties.getDatasource().get("ds1").getFile().getRuleType()) .isEqualTo(RuleType.FLOW); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ SentinelAutoConfiguration.class, SentinelWebAutoConfiguration.class }) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/SentinelFallbackSupportFactoryBeanTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.FactoryBean; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FeignAutoConfiguration; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.GetMapping; import static org.assertj.core.api.Assertions.assertThat; /** * @author 黄学敏(huangxuemin) */ public class SentinelFallbackSupportFactoryBeanTests { private static final String FACTORY_BEAN_FALLBACK_MESSAGE = "factoryBean fallback message"; private static final String ORIGINAL_FALLBACK_MESSAGE = "OriginalFeign fallback message"; private final ApplicationContextRunner runner = new ApplicationContextRunner() .withBean(FactoryBeanFallbackFeignFallback.class) .withBean(OriginalFeignFallback.class) .withConfiguration(AutoConfigurations.of(TestConfiguration.class, FeignAutoConfiguration.class)) .withPropertyValues("feign.sentinel.enabled=true"); @Test public void shouldRunFallbackFromBeanOrFactoryBean() { runner.run(ctx -> { assertThat(ctx.getBean(OriginalFeign.class).get()).isEqualTo(ORIGINAL_FALLBACK_MESSAGE); assertThat(ctx.getBean(FactoryBeanFallbackFeign.class).get()).isEqualTo(FACTORY_BEAN_FALLBACK_MESSAGE); }); } @Configuration(proxyBeanMethods = false) @EnableFeignClients(clients = {OriginalFeign.class, FactoryBeanFallbackFeign.class }) @EnableAutoConfiguration public static class TestConfiguration { } @FeignClient(name = "original", url = "https://original", fallback = OriginalFeignFallback.class) interface OriginalFeign { @GetMapping("/") String get(); } @FeignClient(name = "factoryBean", url = "https://factoryBean", fallback = FactoryBeanFallbackFeignFallback.class) interface FactoryBeanFallbackFeign { @GetMapping("/") String get(); } private static final class FactoryBeanFallbackFeignFallback implements FactoryBean { @Override public FactoryBeanFallbackFeign getObject() { return () -> FACTORY_BEAN_FALLBACK_MESSAGE; } @Override public Class getObjectType() { return FactoryBeanFallbackFeign.class; } } private static final class OriginalFeignFallback implements OriginalFeign { @Override public String get() { return ORIGINAL_FALLBACK_MESSAGE; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/SentinelFeignLazilyTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author Jim */ @RunWith(SpringRunner.class) @SpringBootTest(classes = {SentinelFeignTests.TestConfig.class}, properties = {"feign.sentinel.enabled=true", "spring.cloud.openfeign.lazy-attributes-resolution=true"}) public class SentinelFeignLazilyTests { @Autowired private SentinelFeignTests.EchoService echoService; @Autowired private SentinelFeignTests.FooService fooService; @Autowired private SentinelFeignTests.BarService barService; @Autowired private SentinelFeignTests.BazService bazService; @Test public void contextLoads() throws Exception { assertThat(echoService).isNotNull(); assertThat(fooService).isNotNull(); } @Test public void testFeignClient() { assertThat(echoService.echo("test")).isEqualTo("echo fallback"); assertThat(fooService.echo("test")).isEqualTo("foo fallback"); assertThatThrownBy(() -> { barService.bar(); }).isInstanceOf(Exception.class); assertThatThrownBy(() -> { bazService.baz(); }).isInstanceOf(Exception.class); assertThat(fooService.toString()).isNotEqualTo(echoService.toString()); assertThat(fooService.hashCode()).isNotEqualTo(echoService.hashCode()); assertThat(echoService.equals(fooService)).isEqualTo(Boolean.FALSE); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/SentinelFeignTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import java.util.Arrays; import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author Jim */ @RunWith(SpringRunner.class) @SpringBootTest(classes = { SentinelFeignTests.TestConfig.class }, properties = { "feign.sentinel.enabled=true" }) public class SentinelFeignTests { @Autowired private EchoService echoService; @Autowired private FooService fooService; @Autowired private BarService barService; @Autowired private BazService bazService; @Before public void setUp() { FlowRule rule1 = new FlowRule(); rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setCount(0); rule1.setResource("GET:http://test-service/echo/{str}"); rule1.setLimitApp("default"); rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rule1.setStrategy(RuleConstant.STRATEGY_DIRECT); FlowRule rule2 = new FlowRule(); rule2.setGrade(RuleConstant.FLOW_GRADE_QPS); rule2.setCount(0); rule2.setResource("GET:http://foo-service/echo/{str}"); rule2.setLimitApp("default"); rule2.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rule2.setStrategy(RuleConstant.STRATEGY_DIRECT); FlowRule rule3 = new FlowRule(); rule3.setGrade(RuleConstant.FLOW_GRADE_QPS); rule3.setCount(0); rule3.setResource("GET:http://bar-service/bar"); rule3.setLimitApp("default"); rule3.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rule3.setStrategy(RuleConstant.STRATEGY_DIRECT); FlowRule rule4 = new FlowRule(); rule4.setGrade(RuleConstant.FLOW_GRADE_QPS); rule4.setCount(0); rule4.setResource("GET:http://baz-service/baz"); rule4.setLimitApp("default"); rule4.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rule4.setStrategy(RuleConstant.STRATEGY_DIRECT); FlowRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3, rule4)); } @Test public void contextLoads() throws Exception { assertThat(echoService).isNotNull(); assertThat(fooService).isNotNull(); } @Test public void testFeignClient() { assertThat(echoService.echo("test")).isEqualTo("echo fallback"); assertThat(fooService.echo("test")).isEqualTo("foo fallback"); assertThatThrownBy(() -> { barService.bar(); }).isInstanceOf(Exception.class); assertThatThrownBy(() -> { bazService.baz(); }).isInstanceOf(Exception.class); assertThat(fooService.toString()).isNotEqualTo(echoService.toString()); assertThat(fooService.hashCode()).isNotEqualTo(echoService.hashCode()); assertThat(echoService.equals(fooService)).isEqualTo(Boolean.FALSE); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ SentinelFeignAutoConfiguration.class }) @EnableFeignClients public static class TestConfig { @Bean public EchoServiceFallback echoServiceFallback() { return new EchoServiceFallback(); } @Bean public CustomFallbackFactory customFallbackFactory() { return new CustomFallbackFactory(); } } @FeignClient(value = "test-service", fallback = EchoServiceFallback.class) public interface EchoService { @RequestMapping(path = "echo/{str}") String echo(@RequestParam("str") String param); } @FeignClient(value = "foo-service", fallbackFactory = CustomFallbackFactory.class) public interface FooService { @RequestMapping(path = "echo/{str}") String echo(@RequestParam("str") String param); } @FeignClient("bar-service") public interface BarService { @RequestMapping(path = "bar") String bar(); } public interface BazService { @RequestMapping(path = "baz") String baz(); } @FeignClient("baz-service") public interface BazClient extends BazService { } public static class EchoServiceFallback implements EchoService { @Override public String echo(@RequestParam("str") String param) { return "echo fallback"; } } public static class FooServiceFallback implements FooService { @Override public String echo(@RequestParam("str") String param) { return "foo fallback"; } } public static class CustomFallbackFactory implements FallbackFactory { private FooService fooService = new FooServiceFallback(); @Override public FooService create(Throwable throwable) { return fooService; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/SentinelRestTemplateTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import com.alibaba.cloud.sentinel.custom.SentinelBeanPostProcessor; import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; /** * @author Jim */ public class SentinelRestTemplateTests { @Test(expected = BeanCreationException.class) public void testFbkMethod() { new AnnotationConfigApplicationContext(TestConfig1.class); } @Test(expected = BeanCreationException.class) public void testFbkClass() { new AnnotationConfigApplicationContext(TestConfig2.class); } @Test(expected = BeanCreationException.class) public void testblkMethod() { new AnnotationConfigApplicationContext(TestConfig3.class); } @Test(expected = BeanCreationException.class) public void testblkClass() { new AnnotationConfigApplicationContext(TestConfig4.class); } @Test public void testNormal() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( TestConfig5.class); assertThat(context.getBeansOfType(RestTemplate.class).size()).isEqualTo(1); } @Test(expected = BeanCreationException.class) public void testBlkMethodExists() { new AnnotationConfigApplicationContext(TestConfig6.class); } @Test(expected = BeanCreationException.class) public void testFbkMethodExists() { new AnnotationConfigApplicationContext(TestConfig7.class); } @Test(expected = BeanCreationException.class) public void testBlkReturnValue() { new AnnotationConfigApplicationContext(TestConfig8.class); } @Test(expected = BeanCreationException.class) public void testFbkReturnValue() { new AnnotationConfigApplicationContext(TestConfig9.class); } @Test public void testNormalWithoutParam() { new AnnotationConfigApplicationContext(TestConfig10.class); } @Test(expected = BeanCreationException.class) public void testUrlClnMethod() { new AnnotationConfigApplicationContext(TestConfig11.class); } @Test(expected = BeanCreationException.class) public void testUrlClnClass() { new AnnotationConfigApplicationContext(TestConfig12.class); } @Test(expected = BeanCreationException.class) public void testUrlClnMethodExists() { new AnnotationConfigApplicationContext(TestConfig13.class); } @Test(expected = BeanCreationException.class) public void testUrlClnReturnValue() { new AnnotationConfigApplicationContext(TestConfig14.class); } @Configuration public static class TestConfig1 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate(fallback = "fbk") RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig2 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate(fallbackClass = ExceptionUtil.class) RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig3 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate(blockHandler = "blk") RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig4 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate(blockHandlerClass = ExceptionUtil.class) RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig5 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate( blockHandlerClass = SentinelRestTemplateTests.ExceptionUtil.class, blockHandler = "handleException", fallbackClass = SentinelRestTemplateTests.ExceptionUtil.class, fallback = "fallbackException", urlCleanerClass = SentinelRestTemplateTests.UrlCleanUtil.class, urlCleaner = "clean") RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig6 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate( blockHandlerClass = SentinelRestTemplateTests.ExceptionUtil.class, blockHandler = "handleException1") RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig7 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate( fallbackClass = SentinelRestTemplateTests.ExceptionUtil.class, fallback = "fallbackException1") RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig8 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate( blockHandlerClass = SentinelRestTemplateTests.ExceptionUtil.class, blockHandler = "handleException2") RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig9 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate( fallbackClass = SentinelRestTemplateTests.ExceptionUtil.class, fallback = "fallbackException2") RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig10 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate RestTemplate restTemplate() { return new RestTemplate(); } @Bean @SentinelRestTemplate RestTemplate restTemplate2() { return new RestTemplate(); } } @Configuration public static class TestConfig11 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate(urlCleaner = "cln") RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig12 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate(urlCleanerClass = UrlCleanUtil.class) RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig13 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate( urlCleanerClass = SentinelRestTemplateTests.UrlCleanUtil.class, urlCleaner = "clean1") RestTemplate restTemplate() { return new RestTemplate(); } } @Configuration public static class TestConfig14 { @Bean SentinelBeanPostProcessor sentinelBeanPostProcessor( ApplicationContext applicationContext) { return new SentinelBeanPostProcessor(applicationContext); } @Bean @SentinelRestTemplate( urlCleanerClass = SentinelRestTemplateTests.UrlCleanUtil.class, urlCleaner = "clean2") RestTemplate restTemplate() { return new RestTemplate(); } } public static class ExceptionUtil { public static SentinelClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); return new SentinelClientHttpResponse("Oops"); } public static void handleException2(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); } public static SentinelClientHttpResponse fallbackException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); return new SentinelClientHttpResponse("Oops fallback"); } public static void fallbackException2(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); } } public static class UrlCleanUtil { public static String clean(String url) { return url; } public static void clean2(String url) { } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/TestConverter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel; import java.util.List; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; /** * @author Jim */ public class TestConverter implements Converter> { private ObjectMapper objectMapper = new ObjectMapper(); @Override public List convert(String s) { return objectMapper.readValue(s, new TypeReference<>() { }); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/aot/hint/SentinelProtectInterceptorHintsTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.aot.hint; import java.lang.reflect.Constructor; import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate; import com.alibaba.cloud.sentinel.custom.SentinelProtectInterceptor; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; /** * @author ruansheneg */ public class SentinelProtectInterceptorHintsTest { @Test public void shouldRegisterHints() { Constructor constructor; try { constructor = SentinelProtectInterceptor.class.getConstructor(SentinelRestTemplate.class, RestTemplate.class); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } RuntimeHints hints = new RuntimeHints(); new SentinelProtectInterceptorHints().registerHints(hints, getClass().getClassLoader()); assertThat(RuntimeHintsPredicates.reflection().onConstructor(constructor)).accepts(hints); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/custom/SentinelDataSourceHandlerTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.custom; import com.alibaba.cloud.sentinel.SentinelProperties; import com.alibaba.cloud.sentinel.datasource.RuleType; import com.alibaba.cloud.sentinel.datasource.config.AbstractDataSourceProperties; import com.alibaba.cloud.sentinel.datasource.config.ApolloDataSourceProperties; import org.junit.Before; import org.junit.Test; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.env.Environment; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; /** * Test cases for {@link SentinelDataSourceHandler}. * * @author hnyyghk */ public class SentinelDataSourceHandlerTests { private SentinelDataSourceHandler sentinelDataSourceHandler; private DefaultListableBeanFactory beanFactory; private SentinelProperties sentinelProperties; private Environment env; @Before public void setUp() { beanFactory = mock(DefaultListableBeanFactory.class); sentinelProperties = mock(SentinelProperties.class); env = mock(Environment.class); sentinelDataSourceHandler = new SentinelDataSourceHandler(beanFactory, sentinelProperties, env); } /** * Test cases for {@link SentinelDataSourceHandler#parseBeanDefinition(AbstractDataSourceProperties, String)}. * * @see com.alibaba.cloud.sentinel.datasource.config.ApolloDataSourceProperties * @see com.alibaba.cloud.sentinel.datasource.factorybean.ApolloDataSourceFactoryBean */ @Test public void testParseBeanDefinition() { ApolloDataSourceProperties dataSourceProperties = new ApolloDataSourceProperties(); dataSourceProperties.setNamespaceName("application"); dataSourceProperties.setFlowRulesKey("test-flow-rules"); dataSourceProperties.setDefaultFlowRuleValue("[]"); dataSourceProperties.setDataType("json"); dataSourceProperties.setRuleType(RuleType.FLOW); String dataSourceName = "ds1" + "-sentinel-" + "apollo" + "-datasource"; //init BeanDefinitionBuilder for ApolloDataSourceFactoryBean BeanDefinitionBuilder builder = sentinelDataSourceHandler.parseBeanDefinition(dataSourceProperties, dataSourceName); MutablePropertyValues propertyValues = builder.getBeanDefinition().getPropertyValues(); //ApolloDataSourceFactoryBean has four parameters, $jacocoData should not be included assertThat(propertyValues.size()).isEqualTo(4); assertThat(propertyValues).noneMatch(propertyValue -> "$jacocoData".equals(propertyValue.getName())); assertThat(propertyValues).anyMatch(propertyValue -> "flowRulesKey".equals(propertyValue.getName()) && dataSourceProperties.getFlowRulesKey().equals(propertyValue.getValue())); assertThat(propertyValues).anyMatch(propertyValue -> "defaultFlowRuleValue".equals(propertyValue.getName()) && dataSourceProperties.getDefaultFlowRuleValue().equals(propertyValue.getValue())); assertThat(propertyValues).anyMatch(propertyValue -> "namespaceName".equals(propertyValue.getName()) && dataSourceProperties.getNamespaceName().equals(propertyValue.getValue())); assertThat(propertyValues).anyMatch(propertyValue -> "converter".equals(propertyValue.getName()) && propertyValue.getValue() instanceof RuntimeBeanReference value && value.getBeanName().equals("sentinel-" + dataSourceProperties.getDataType() + "-" + dataSourceProperties.getRuleType().getName() + "-converter")); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/java/com/alibaba/cloud/sentinel/endpoint/SentinelHealthIndicatorTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sentinel.endpoint; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import com.alibaba.cloud.sentinel.SentinelProperties; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider; import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.Status; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Test cases for {@link SentinelHealthIndicator}. * * @author cdfive */ public class SentinelHealthIndicatorTests { private SentinelHealthIndicator sentinelHealthIndicator; private DefaultListableBeanFactory beanFactory; private SentinelProperties sentinelProperties; private HeartbeatSender heartbeatSender; @Before public void setUp() { beanFactory = mock(DefaultListableBeanFactory.class); sentinelProperties = mock(SentinelProperties.class); sentinelHealthIndicator = new SentinelHealthIndicator(beanFactory, sentinelProperties); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, ""); heartbeatSender = mock(HeartbeatSender.class); Field heartbeatSenderField = ReflectionUtils .findField(HeartbeatSenderProvider.class, "heartbeatSender"); heartbeatSenderField.setAccessible(true); ReflectionUtils.setField(heartbeatSenderField, null, heartbeatSender); } @Test public void testSentinelNotEnabled() { when(sentinelProperties.isEnabled()).thenReturn(false); Health health = sentinelHealthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getDetails().get("enabled")).isEqualTo(false); } @Test public void testSentinelDashboardNotConfigured() { when(sentinelProperties.isEnabled()).thenReturn(true); Health health = sentinelHealthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getDetails().get("dashboard")).isEqualTo(Status.UNKNOWN); } @Test public void testSentinelDashboardConfiguredSuccess() throws Exception { when(sentinelProperties.isEnabled()).thenReturn(true); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); when(heartbeatSender.sendHeartbeat()).thenReturn(true); Health health = sentinelHealthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); } @Test public void testSentinelDashboardConfiguredFailed() throws Exception { when(sentinelProperties.isEnabled()).thenReturn(true); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); when(heartbeatSender.sendHeartbeat()).thenReturn(false); Health health = sentinelHealthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UNKNOWN); assertThat(health.getDetails().get("dashboard")).isEqualTo(new Status( Status.UNKNOWN.getCode(), "localhost:8080 can't be connected")); } @Test public void testSentinelDataSourceSuccess() throws Exception { when(sentinelProperties.isEnabled()).thenReturn(true); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); when(heartbeatSender.sendHeartbeat()).thenReturn(true); Map dataSourceMap = new HashMap<>(); FileRefreshableDataSource fileDataSource1 = mock(FileRefreshableDataSource.class); dataSourceMap.put("ds1-sentinel-file-datasource", fileDataSource1); FileRefreshableDataSource fileDataSource2 = mock(FileRefreshableDataSource.class); dataSourceMap.put("ds2-sentinel-file-datasource", fileDataSource2); when(beanFactory.getBeansOfType(AbstractDataSource.class)) .thenReturn(dataSourceMap); Health health = sentinelHealthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); Map dataSourceDetailMap = (Map) health .getDetails().get("dataSource"); assertThat(dataSourceDetailMap.get("ds1-sentinel-file-datasource")) .isEqualTo(Status.UP); assertThat(dataSourceDetailMap.get("ds2-sentinel-file-datasource")) .isEqualTo(Status.UP); } @Test public void testSentinelDataSourceFailed() throws Exception { when(sentinelProperties.isEnabled()).thenReturn(true); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); when(heartbeatSender.sendHeartbeat()).thenReturn(true); Map dataSourceMap = new HashMap<>(); FileRefreshableDataSource fileDataSource1 = mock(FileRefreshableDataSource.class); dataSourceMap.put("ds1-sentinel-file-datasource", fileDataSource1); FileRefreshableDataSource fileDataSource2 = mock(FileRefreshableDataSource.class); when(fileDataSource2.loadConfig()) .thenThrow(new RuntimeException("fileDataSource2 error")); dataSourceMap.put("ds2-sentinel-file-datasource", fileDataSource2); when(beanFactory.getBeansOfType(AbstractDataSource.class)) .thenReturn(dataSourceMap); Health health = sentinelHealthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UNKNOWN); Map dataSourceDetailMap = (Map) health .getDetails().get("dataSource"); assertThat(dataSourceDetailMap.get("ds1-sentinel-file-datasource")) .isEqualTo(Status.UP); assertThat(dataSourceDetailMap.get("ds2-sentinel-file-datasource")) .isEqualTo(new Status(Status.UNKNOWN.getCode(), "fileDataSource2 error")); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/resources/authority.json ================================================ [ { "resource": "good", "limitApp": "abc", "strategy": 0 }, { "resource": "bad", "limitApp": "bcd", "strategy": 1 }, { "resource": "terrible", "limitApp": "aaa", "strategy": 1 } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/resources/degraderule.json ================================================ [ { "resource": "abc0", "count": 20.0, "grade": 0, "passCount": 0, "timeWindow": 10 }, { "resource": "abc1", "count": 15.0, "grade": 0, "passCount": 0, "timeWindow": 10 } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/resources/flowrule.json ================================================ [ { "resource": "resource", "controlBehavior": 0, "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 }, { "resource": "p", "controlBehavior": 0, "count": 1, "grade": 1, "limitApp": "default", "strategy": 0 }, { "resource": "http://www.taobao.com", "controlBehavior": 0, "count": 0, "grade": 1, "limitApp": "default", "strategy": 0 } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/resources/param-flow.json ================================================ [ { "resource": "hotResource", "count": 0, "grade": 1, "limitApp": "default", "paramIdx": 0, "paramFlowItemList": [ { "object": "2", "classType": "int", "count": 1 } ] } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sentinel/src/test/resources/system.json ================================================ [ { "highestSystemLoad": -1, "qps": 100, "avgRt": -1, "maxThread": 10 } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml 4.0.0 spring-cloud-starter-alibaba-sidecar Spring Cloud Starter Alibaba Sidecar org.springframework.boot spring-boot-starter true org.springframework.cloud spring-cloud-starter-gateway-server-webflux true org.springframework.boot spring-boot-starter-actuator true com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery true org.springframework.cloud spring-cloud-starter-consul-discovery true org.springframework.boot spring-boot-configuration-processor true ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/CustomHealthCheckHandler.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar; /** * @author yuhuangbin */ public interface CustomHealthCheckHandler { void handler(String applicationName, SidecarInstanceInfo sidecarInstanceInfo); } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.web.client.RestTemplate; /** * @author www.itmuch.com */ @Configuration(proxyBeanMethods = false) public class SidecarAutoConfiguration { @Bean @ConditionalOnMissingBean public RestTemplate restTemplate() { return new RestTemplate(); } @Bean @ConditionalOnEnabledHealthIndicator("sidecar") public SidecarHealthIndicator sidecarHealthIndicator( SidecarProperties sidecarProperties, RestTemplate restTemplate) { return new SidecarHealthIndicator(sidecarProperties, restTemplate); } @Bean public SidecarHealthChecker sidecarHealthChecker( SidecarDiscoveryClient sidecarDiscoveryClient, SidecarHealthIndicator sidecarHealthIndicator, SidecarProperties sidecarProperties, ConfigurableEnvironment environment) { SidecarHealthChecker cleaner = new SidecarHealthChecker(sidecarDiscoveryClient, sidecarHealthIndicator, sidecarProperties, environment); cleaner.check(); return cleaner; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarDiscoveryClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar; /** * @author www.itmuch.com */ public interface SidecarDiscoveryClient { /** * register instance. * @param applicationName applicationName * @param ip ip * @param port port */ void registerInstance(String applicationName, String ip, Integer port); /** * deregister instance. * @param applicationName applicationName * @param ip ip * @param port port */ void deregisterInstance(String applicationName, String ip, Integer port); } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarHealthChecker.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.scheduler.Schedulers; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.health.contributor.HealthIndicator; import org.springframework.boot.health.contributor.Status; import org.springframework.core.env.ConfigurableEnvironment; /** * @author www.itmuch.com * @author yuhuangbin */ public class SidecarHealthChecker { private static final Logger log = LoggerFactory.getLogger(SidecarHealthChecker.class); private final Map sidecarInstanceCacheMap = new ConcurrentHashMap<>(); private final SidecarDiscoveryClient sidecarDiscoveryClient; private final HealthIndicator healthIndicator; private final SidecarProperties sidecarProperties; private final ConfigurableEnvironment environment; @Autowired private ObjectProvider customHealthCheckHandlerObjectProvider; public SidecarHealthChecker(SidecarDiscoveryClient sidecarDiscoveryClient, HealthIndicator healthIndicator, SidecarProperties sidecarProperties, ConfigurableEnvironment environment) { this.sidecarDiscoveryClient = sidecarDiscoveryClient; this.healthIndicator = healthIndicator; this.sidecarProperties = sidecarProperties; this.environment = environment; } public void check() { Schedulers.single().schedulePeriodically(() -> { String applicationName = environment.getProperty("spring.application.name"); String ip = sidecarProperties.getIp(); Integer port = sidecarProperties.getPort(); Status status = healthIndicator.health().getStatus(); SidecarInstanceInfo sidecarInstanceInfo = instanceCache(applicationName, ip, port, status); if (status.equals(Status.UP)) { if (needRegister(applicationName, sidecarInstanceInfo)) { this.sidecarDiscoveryClient.registerInstance(applicationName, ip, port); log.info( "Polyglot service changed and Health check success. register the new instance. applicationName = {}, ip = {}, port = {}, status = {}", applicationName, ip, port, status); } } else { log.warn( "Health check failed. unregister this instance. applicationName = {}, ip = {}, port = {}, status = {}", applicationName, ip, port, status); this.sidecarDiscoveryClient.deregisterInstance(applicationName, ip, port); sidecarInstanceCacheMap.put(applicationName, buildCache(ip, port, status)); } try { customHealthCheckHandlerObjectProvider .ifAvailable(customHealthCheckHandler -> customHealthCheckHandler .handler(applicationName, sidecarInstanceInfo)); } catch (Exception e) { // ignore } }, 0, sidecarProperties.getHealthCheckInterval(), TimeUnit.MILLISECONDS); } private SidecarInstanceInfo instanceCache(String applicationName, String ip, Integer port, Status status) { SidecarInstanceInfo sidecarInstanceInfo = buildCache(ip, port, status); sidecarInstanceCacheMap.putIfAbsent(applicationName, sidecarInstanceInfo); return sidecarInstanceInfo; } private boolean needRegister(String applicationName, SidecarInstanceInfo sidecarInstanceInfo) { SidecarInstanceInfo cacheRecord = sidecarInstanceCacheMap.get(applicationName); if (!Objects.equals(sidecarInstanceInfo, cacheRecord)) { // modify the cache info sidecarInstanceCacheMap.put(applicationName, sidecarInstanceInfo); return true; } return false; } private SidecarInstanceInfo buildCache(String ip, Integer port, Status status) { SidecarInstanceInfo cache = new SidecarInstanceInfo(); cache.setIp(ip); cache.setPort(port); cache.setStatus(status); return cache; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarHealthIndicator.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar; import java.net.URI; import java.util.Map; import org.springframework.boot.health.contributor.AbstractHealthIndicator; import org.springframework.boot.health.contributor.Health; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; /** * @author www.itmuch.com */ public class SidecarHealthIndicator extends AbstractHealthIndicator { private final SidecarProperties sidecarProperties; private final RestTemplate restTemplate; public SidecarHealthIndicator(SidecarProperties sidecarProperties, RestTemplate restTemplate) { this.sidecarProperties = sidecarProperties; this.restTemplate = restTemplate; } @Override protected void doHealthCheck(Health.Builder builder) throws Exception { try { URI uri = this.sidecarProperties.getHealthCheckUrl(); if (uri == null) { builder.up(); return; } ResponseEntity> exchange = this.restTemplate.exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference>() { }); Map map = exchange.getBody(); if (map == null) { this.getWarning(builder); return; } Object status = map.get("status"); if (status instanceof String strStatus) { builder.status(strStatus); } else { this.getWarning(builder); } } catch (Exception e) { builder.down().withDetail("error", e.getMessage()); } } private void getWarning(Health.Builder builder) { builder.unknown().withDetail("warning", "no status field in response"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarInstanceInfo.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar; import java.util.Objects; import org.springframework.boot.health.contributor.Status; /** * @author yuhuangbin */ public class SidecarInstanceInfo { private String ip; private Integer port; private Status status; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public Status getStatus() { return status; } public void setStatus(Status status) { this.status = status; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SidecarInstanceInfo that = (SidecarInstanceInfo) o; return Objects.equals(ip, that.ip) && Objects.equals(port, that.port) && Objects.equals(status, that.status); } @Override public int hashCode() { return Objects.hash(ip, port, status); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/SidecarProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar; import java.net.URI; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.annotation.Validated; /** * @author www.itmuch.com */ @ConfigurationProperties("sidecar") @Validated public class SidecarProperties { /** * polyglot service's ip. */ private String ip; /** * polyglot service's port. */ @NotNull @Max(65535) @Min(1) private Integer port; /** * polyglot service's health check url. this endpoint must return json and the format * must follow spring boot actuator's health endpoint. eg. {"status": "UP"}. */ private URI healthCheckUrl; /** * interval of health check. */ private long healthCheckInterval = 30000L; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public URI getHealthCheckUrl() { return healthCheckUrl; } public void setHealthCheckUrl(URI healthCheckUrl) { this.healthCheckUrl = healthCheckUrl; } public long getHealthCheckInterval() { return healthCheckInterval; } public void setHealthCheckInterval(long healthCheckInterval) { this.healthCheckInterval = healthCheckInterval; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar.consul; import java.util.List; import com.alibaba.cloud.sidecar.SidecarAutoConfiguration; import com.alibaba.cloud.sidecar.SidecarDiscoveryClient; import com.alibaba.cloud.sidecar.SidecarProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties; import org.springframework.cloud.consul.discovery.HeartbeatProperties; import org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration; import org.springframework.cloud.consul.serviceregistry.ConsulAutoServiceRegistrationAutoConfiguration; import org.springframework.cloud.consul.serviceregistry.ConsulManagementRegistrationCustomizer; import org.springframework.cloud.consul.serviceregistry.ConsulRegistrationCustomizer; import org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry; import org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistryAutoConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author www.itmuch.com */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ConsulServiceRegistryAutoConfiguration.class) @AutoConfigureBefore({ ConsulAutoServiceRegistrationAutoConfiguration.class, SidecarAutoConfiguration.class }) public class SidecarConsulAutoConfiguration { @Bean public ConsulAutoRegistration consulRegistration( AutoServiceRegistrationProperties autoServiceRegistrationProperties, ConsulDiscoveryProperties properties, ApplicationContext applicationContext, ObjectProvider> registrationCustomizers, ObjectProvider> managementRegistrationCustomizers, HeartbeatProperties heartbeatProperties, SidecarProperties sidecarProperties) { return SidecarConsulAutoRegistration.registration( autoServiceRegistrationProperties, properties, applicationContext, registrationCustomizers.getIfAvailable(), managementRegistrationCustomizers.getIfAvailable(), heartbeatProperties, sidecarProperties); } @Bean public SidecarDiscoveryClient sidecarDiscoveryClient( ConsulDiscoveryProperties properties, ConsulServiceRegistry serviceRegistry, ConsulAutoRegistration registration) { return new SidecarConsulDiscoveryClient(properties, serviceRegistry, registration); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulAutoRegistration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar.consul; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.alibaba.cloud.sidecar.SidecarProperties; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties; import org.springframework.cloud.consul.discovery.HeartbeatProperties; import org.springframework.cloud.consul.model.http.agent.NewService; import org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration; import org.springframework.cloud.consul.serviceregistry.ConsulManagementRegistrationCustomizer; import org.springframework.cloud.consul.serviceregistry.ConsulRegistrationCustomizer; import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * @author www.itmuch.com */ public class SidecarConsulAutoRegistration extends ConsulAutoRegistration { public SidecarConsulAutoRegistration(NewService service, AutoServiceRegistrationProperties autoServiceRegistrationProperties, ConsulDiscoveryProperties properties, ApplicationContext context, HeartbeatProperties heartbeatProperties, List managementRegistrationCustomizers) { super(service, autoServiceRegistrationProperties, properties, context, heartbeatProperties, managementRegistrationCustomizers); } public static ConsulAutoRegistration registration( AutoServiceRegistrationProperties autoServiceRegistrationProperties, ConsulDiscoveryProperties properties, ApplicationContext context, List registrationCustomizers, List managementRegistrationCustomizers, HeartbeatProperties heartbeatProperties, SidecarProperties sidecarProperties) { NewService service = new NewService(); String appName = getAppName(properties, context.getEnvironment()); service.setId(getInstanceId(sidecarProperties, context.getEnvironment())); if (!properties.isPreferAgentAddress()) { service.setAddress(sidecarProperties.getIp()); } service.setName(normalizeForDns(appName)); service.setTags(new ArrayList<>(properties.getTags())); service.setEnableTagOverride(properties.getEnableTagOverride()); service.setMeta(getMetadata(properties)); if (sidecarProperties.getPort() != null && sidecarProperties.getPort() > 0) { service.setPort(properties.getPort()); } else if (properties.getPort() != null && properties.getPort() > 0) { service.setPort(properties.getPort()); } else if (context.getEnvironment().getProperty("server.port") != null) { // set health check, use alibaba sidecar self's port rather than polyglot // app's port. service.setPort( Integer.valueOf(context.getEnvironment().getProperty("server.port"))); } if (service.getPort() != null) { // we know the port and can set the check setCheck(service, autoServiceRegistrationProperties, properties, context, heartbeatProperties); } ConsulAutoRegistration registration = new ConsulAutoRegistration(service, autoServiceRegistrationProperties, properties, context, heartbeatProperties, managementRegistrationCustomizers); customize(registrationCustomizers, registration); return registration; } /** * copyed from * org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration#getMetadata. */ private static Map getMetadata(ConsulDiscoveryProperties properties) { LinkedHashMap metadata = new LinkedHashMap<>(); if (!CollectionUtils.isEmpty(properties.getMetadata())) { metadata.putAll(properties.getMetadata()); } // add metadata from other properties. See createTags above. if (StringUtils.hasLength(properties.getInstanceZone())) { metadata.put(properties.getDefaultZoneMetadataName(), properties.getInstanceZone()); } if (StringUtils.hasLength(properties.getInstanceGroup())) { metadata.put("group", properties.getInstanceGroup()); } // store the secure flag in the tags so that clients will be able to figure // out whether to use http or https automatically metadata.put("secure", Boolean.toString(properties.getScheme().equalsIgnoreCase("https"))); return metadata; } public static String getInstanceId(SidecarProperties sidecarProperties, Environment environment) { return String.format("%s-%s-%s", environment.getProperty("spring.application.name"), sidecarProperties.getIp(), sidecarProperties.getPort()); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/consul/SidecarConsulDiscoveryClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar.consul; import com.alibaba.cloud.sidecar.SidecarDiscoveryClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties; import org.springframework.cloud.consul.serviceregistry.ConsulAutoRegistration; import org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistry; /** * @author www.itmuch.com */ public class SidecarConsulDiscoveryClient implements SidecarDiscoveryClient { private static final Logger log = LoggerFactory .getLogger(SidecarConsulDiscoveryClient.class); private final ConsulDiscoveryProperties properties; private final ConsulServiceRegistry serviceRegistry; private final ConsulAutoRegistration registration; public SidecarConsulDiscoveryClient(ConsulDiscoveryProperties properties, ConsulServiceRegistry serviceRegistry, ConsulAutoRegistration registration) { this.properties = properties; this.serviceRegistry = serviceRegistry; this.registration = registration; } @Override public void registerInstance(String applicationName, String ip, Integer port) { if (!this.properties.isRegister()) { log.debug("Registration disabled."); return; } serviceRegistry.register(registration); } @Override public void deregisterInstance(String applicationName, String ip, Integer port) { if (!this.properties.isRegister() || !this.properties.isDeregister()) { return; } serviceRegistry.deregister(registration); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar.nacos; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration; import com.alibaba.cloud.sidecar.SidecarAutoConfiguration; import com.alibaba.cloud.sidecar.SidecarDiscoveryClient; import com.alibaba.cloud.sidecar.SidecarProperties; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author www.itmuch.com */ @Configuration(proxyBeanMethods = false) @AutoConfigureBefore({ NacosDiscoveryAutoConfiguration.class, SidecarAutoConfiguration.class }) @ConditionalOnClass(NacosDiscoveryProperties.class) @EnableConfigurationProperties(SidecarProperties.class) public class SidecarNacosAutoConfiguration { @Bean @ConditionalOnMissingBean public SidecarNacosDiscoveryProperties sidecarNacosDiscoveryProperties( SidecarProperties sidecarProperties) { return new SidecarNacosDiscoveryProperties(sidecarProperties); } @Bean @ConditionalOnMissingBean public SidecarDiscoveryClient sidecarDiscoveryClient( NacosServiceManager nacosServiceManager, SidecarNacosDiscoveryProperties sidecarNacosDiscoveryProperties) { return new SidecarNacosDiscoveryClient(nacosServiceManager, sidecarNacosDiscoveryProperties); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosDiscoveryClient.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar.nacos; import com.alibaba.cloud.nacos.NacosServiceManager; import com.alibaba.cloud.sidecar.SidecarDiscoveryClient; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author www.itmuch.com */ public class SidecarNacosDiscoveryClient implements SidecarDiscoveryClient { private static final Logger log = LoggerFactory .getLogger(SidecarNacosDiscoveryClient.class); private NacosServiceManager nacosServiceManager; private final SidecarNacosDiscoveryProperties sidecarNacosDiscoveryProperties; public SidecarNacosDiscoveryClient(NacosServiceManager nacosServiceManager, SidecarNacosDiscoveryProperties sidecarNacosDiscoveryProperties) { this.nacosServiceManager = nacosServiceManager; this.sidecarNacosDiscoveryProperties = sidecarNacosDiscoveryProperties; } @Override public void registerInstance(String applicationName, String ip, Integer port) { try { this.namingService().registerInstance(applicationName, sidecarNacosDiscoveryProperties.getGroup(), ip, port); } catch (NacosException e) { log.warn("nacos exception happens", e); } } @Override public void deregisterInstance(String applicationName, String ip, Integer port) { try { this.namingService().deregisterInstance(applicationName, sidecarNacosDiscoveryProperties.getGroup(), ip, port); } catch (NacosException e) { log.warn("nacos exception happens", e); } } private NamingService namingService() { return nacosServiceManager .getNamingService(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/java/com/alibaba/cloud/sidecar/nacos/SidecarNacosDiscoveryProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.sidecar.nacos; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.sidecar.SidecarProperties; import org.springframework.util.StringUtils; /** * @author yuhuangbin */ public class SidecarNacosDiscoveryProperties extends NacosDiscoveryProperties { SidecarProperties sidecarProperties; public SidecarNacosDiscoveryProperties(SidecarProperties sidecarProperties) { this.sidecarProperties = sidecarProperties; } @Override public void init() throws Exception { super.init(); String ip = sidecarProperties.getIp(); if (StringUtils.hasText(ip)) { this.setIp(ip); } Integer port = sidecarProperties.getPort(); this.setPort(port); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-sidecar/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.sidecar.nacos.SidecarNacosAutoConfiguration com.alibaba.cloud.sidecar.SidecarAutoConfiguration com.alibaba.cloud.sidecar.consul.SidecarConsulAutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-bus-rocketmq/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml 4.0.0 spring-cloud-starter-bus-rocketmq Spring Cloud Alibaba Bus RocketMQ com.alibaba.cloud spring-cloud-starter-stream-rocketmq org.springframework.cloud spring-cloud-bus ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-bus-rocketmq/src/main/java/com/alibaba/cloud/bus/rocketmq/autoconfigurate/RocketMQBusAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.bus.rocketmq.autoconfigurate; import com.alibaba.cloud.stream.binder.rocketmq.convert.RocketMQMessageConverter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.bus.BusEnvironmentPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.converter.CompositeMessageConverter; /** * BusAutoConfiguration of rocketmq. * * @author Sorie * @see BusEnvironmentPostProcessor */ @Configuration(proxyBeanMethods = false) public class RocketMQBusAutoConfiguration { /** * issue: https://github.com/alibaba/spring-cloud-alibaba/issues/2742 * if you want to customize a bean, please use this BeanName {@code RocketMQMessageConverter.DEFAULT_NAME}. */ @Bean(RocketMQMessageConverter.DEFAULT_NAME) @ConditionalOnMissingBean(name = { RocketMQMessageConverter.DEFAULT_NAME }) public CompositeMessageConverter rocketMQMessageConverter() { return new RocketMQMessageConverter().getMessageConverter(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-bus-rocketmq/src/main/java/com/alibaba/cloud/bus/rocketmq/env/RocketMQBusEnvironmentPostProcessor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.bus.rocketmq.env; import java.util.HashMap; import java.util.Map; import org.springframework.boot.EnvironmentPostProcessor; import org.springframework.boot.SpringApplication; import org.springframework.cloud.bus.BusEnvironmentPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import static org.springframework.cloud.bus.BusConstants.INPUT; /** * The lowest precedence {@link EnvironmentPostProcessor} configures default RocketMQ Bus * Properties that will be appended into {@link SpringApplication#defaultProperties}. * * @author Mercy * @since 0.2.1 * @see BusEnvironmentPostProcessor */ public class RocketMQBusEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { /** * The name of {@link PropertySource} of {@link SpringApplication#defaultProperties}. */ private static final String PROPERTY_SOURCE_NAME = "defaultProperties"; @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addDefaultPropertySource(environment); } private void addDefaultPropertySource(ConfigurableEnvironment environment) { Map map = new HashMap(); configureDefaultProperties(map); addOrReplace(environment.getPropertySources(), map); } private void configureDefaultProperties(Map source) { // Required Properties String groupBindingPropertyName = createBindingPropertyName(INPUT, "group"); String broadcastingPropertyName = createRocketMQPropertyName(INPUT, "broadcasting"); source.put(groupBindingPropertyName, "rocketmq-bus-group"); source.put(broadcastingPropertyName, "true"); } private String createRocketMQPropertyName(String channel, String propertyName) { return "spring.cloud.stream.rocketmq.bindings." + INPUT + ".consumer." + propertyName; } private String createBindingPropertyName(String channel, String propertyName) { return "spring.cloud.stream.bindings." + channel + "." + propertyName; } /** * Copy from. * {@link BusEnvironmentPostProcessor#addOrReplace(MutablePropertySources, Map)} * @param propertySources {@link MutablePropertySources} * @param map Default RocketMQ Bus Properties */ private void addOrReplace(MutablePropertySources propertySources, Map map) { MapPropertySource target = null; if (propertySources.contains(PROPERTY_SOURCE_NAME)) { PropertySource source = propertySources.get(PROPERTY_SOURCE_NAME); if (source instanceof MapPropertySource mapPropertySource) { target = mapPropertySource; for (String key : map.keySet()) { if (!target.containsProperty(key)) { target.getSource().put(key, map.get(key)); } } } } if (target == null) { target = new MapPropertySource(PROPERTY_SOURCE_NAME, map); } if (!propertySources.contains(PROPERTY_SOURCE_NAME)) { propertySources.addLast(target); } } @Override public int getOrder() { return LOWEST_PRECEDENCE; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-bus-rocketmq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.bus.rocketmq.autoconfigurate.RocketMQBusAutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-bus-rocketmq/src/main/resources/META-INF/spring.factories ================================================ # EnvironmentPostProcessor org.springframework.boot.EnvironmentPostProcessor=\ com.alibaba.cloud.bus.rocketmq.env.RocketMQBusEnvironmentPostProcessor ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/pom.xml ================================================ 4.0.0 com.alibaba.cloud spring-cloud-alibaba-starters ${revision} ../pom.xml spring-cloud-starter-stream-rocketmq Spring Cloud Starter Stream RocketMQ org.springframework.boot spring-boot-health provided org.springframework.cloud spring-cloud-stream org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot-actuator-autoconfigure true org.apache.rocketmq rocketmq-client org.apache.rocketmq rocketmq-acl com.alibaba fastjson com.alibaba fastjson org.springframework.boot spring-boot-starter-test test ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/RocketMQMessageChannelBinder.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq; import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache; import com.alibaba.cloud.stream.binder.rocketmq.extend.ErrorAcknowledgeHandler; import com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.RocketMQInboundChannelAdapter; import com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull.DefaultErrorAcknowledgeHandler; import com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull.RocketMQMessageSource; import com.alibaba.cloud.stream.binder.rocketmq.integration.outbound.RocketMQProducerMessageHandler; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindingProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties; import com.alibaba.cloud.stream.binder.rocketmq.provisioning.RocketMQTopicProvisioner; import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.springframework.cloud.stream.binder.AbstractMessageChannelBinder; import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider; import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; import org.springframework.cloud.stream.binder.ExtendedProducerProperties; import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder; import org.springframework.cloud.stream.binding.MessageConverterConfigurer; import org.springframework.cloud.stream.provisioning.ConsumerDestination; import org.springframework.cloud.stream.provisioning.ProducerDestination; import org.springframework.integration.StaticMessageHeaderAccessor; import org.springframework.integration.acks.AcknowledgmentCallback; import org.springframework.integration.channel.AbstractMessageChannel; import org.springframework.integration.core.MessageProducer; import org.springframework.integration.support.DefaultErrorMessageStrategy; import org.springframework.integration.support.ErrorMessageStrategy; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; import org.springframework.util.StringUtils; /** * A {@link org.springframework.cloud.stream.binder.Binder} that uses RocketMQ as the * underlying middleware. * * @author Jim */ public class RocketMQMessageChannelBinder extends AbstractMessageChannelBinder, ExtendedProducerProperties, RocketMQTopicProvisioner> implements ExtendedPropertiesBinder { private final RocketMQExtendedBindingProperties extendedBindingProperties; private final RocketMQBinderConfigurationProperties binderConfigurationProperties; public RocketMQMessageChannelBinder( RocketMQBinderConfigurationProperties binderConfigurationProperties, RocketMQExtendedBindingProperties extendedBindingProperties, RocketMQTopicProvisioner provisioningProvider) { super(new String[0], provisioningProvider); this.extendedBindingProperties = extendedBindingProperties; this.binderConfigurationProperties = binderConfigurationProperties; } @Override protected MessageHandler createProducerMessageHandler(ProducerDestination destination, ExtendedProducerProperties extendedProducerProperties, MessageChannel channel, MessageChannel errorChannel) throws Exception { if (!extendedProducerProperties.getExtension().getEnabled()) { throw new RuntimeException("Binding for channel " + destination.getName() + " has been disabled, message can't be delivered"); } RocketMQProducerProperties mqProducerProperties = RocketMQUtils .mergeRocketMQProperties(binderConfigurationProperties, extendedProducerProperties.getExtension()); RocketMQProducerMessageHandler messageHandler = new RocketMQProducerMessageHandler( destination, extendedProducerProperties, mqProducerProperties); messageHandler.setApplicationContext(this.getApplicationContext()); if (errorChannel != null) { messageHandler.setSendFailureChannel(errorChannel); } MessageConverterConfigurer.PartitioningInterceptor partitioningInterceptor = ((AbstractMessageChannel) channel) .getInterceptors().stream() .filter(channelInterceptor -> channelInterceptor instanceof MessageConverterConfigurer.PartitioningInterceptor) .map(channelInterceptor -> ((MessageConverterConfigurer.PartitioningInterceptor) channelInterceptor)) .findFirst().orElse(null); messageHandler.setPartitioningInterceptor(partitioningInterceptor); messageHandler.setBeanFactory(this.getApplicationContext().getBeanFactory()); messageHandler.setErrorMessageStrategy(this.getErrorMessageStrategy()); return messageHandler; } @Override protected MessageHandler createProducerMessageHandler(ProducerDestination destination, ExtendedProducerProperties producerProperties, MessageChannel errorChannel) throws Exception { throw new UnsupportedOperationException( "The abstract binder should not call this method"); } @Override protected MessageProducer createConsumerEndpoint(ConsumerDestination destination, String group, ExtendedConsumerProperties extendedConsumerProperties) throws Exception { boolean anonymous = !StringUtils.hasLength(group); /*** * When using DLQ, at least the group property must be provided for proper naming of the DLQ destination * According to https://docs.spring.io/spring-cloud-stream/docs/3.2.1/reference/html/spring-cloud-stream.html#spring-cloud-stream-reference */ if (anonymous && NamespaceUtil.isDLQTopic(destination.getName())) { throw new RuntimeException( "group must be configured for DLQ" + destination.getName()); } group = anonymous ? RocketMQUtils.anonymousGroup(destination.getName()) : group; RocketMQUtils.mergeRocketMQProperties(binderConfigurationProperties, extendedConsumerProperties.getExtension()); extendedConsumerProperties.getExtension().setGroup(group); RocketMQInboundChannelAdapter inboundChannelAdapter = new RocketMQInboundChannelAdapter( destination.getName(), extendedConsumerProperties); inboundChannelAdapter.setBeanFactory(this.getApplicationContext().getBeanFactory()); ErrorInfrastructure errorInfrastructure = registerErrorInfrastructure(destination, group, extendedConsumerProperties); if (extendedConsumerProperties.getMaxAttempts() > 1) { inboundChannelAdapter .setRetryTemplate(buildRetryTemplate(extendedConsumerProperties)); inboundChannelAdapter.setRecoveryCallback(errorInfrastructure.getRecoverer()); } else { inboundChannelAdapter.setErrorChannel(errorInfrastructure.getErrorChannel()); } return inboundChannelAdapter; } @Override protected PolledConsumerResources createPolledConsumerResources(String name, String group, ConsumerDestination destination, ExtendedConsumerProperties extendedConsumerProperties) { RocketMQUtils.mergeRocketMQProperties(binderConfigurationProperties, extendedConsumerProperties.getExtension()); extendedConsumerProperties.getExtension().setGroup(group); RocketMQMessageSource messageSource = new RocketMQMessageSource(name, extendedConsumerProperties); return new PolledConsumerResources(messageSource, registerErrorInfrastructure( destination, group, extendedConsumerProperties, true)); } @Override protected MessageHandler getPolledConsumerErrorMessageHandler( ConsumerDestination destination, String group, ExtendedConsumerProperties properties) { return message -> { if (message.getPayload() instanceof MessagingException payload) { AcknowledgmentCallback ack = StaticMessageHeaderAccessor .getAcknowledgmentCallback( payload.getFailedMessage()); if (ack != null) { ErrorAcknowledgeHandler handler = RocketMQBeanContainerCache.getBean( properties.getExtension().getPull().getErrAcknowledge(), ErrorAcknowledgeHandler.class, new DefaultErrorAcknowledgeHandler()); ack.acknowledge( handler.handler(payload.getFailedMessage())); } } }; } /** * Binders can return an {@link ErrorMessageStrategy} for building error messages; * binder implementations typically might add extra headers to the error message. * @return the implementation - may be null. */ @Override protected ErrorMessageStrategy getErrorMessageStrategy() { // It can be extended to custom if necessary. return new DefaultErrorMessageStrategy(); } @Override public RocketMQConsumerProperties getExtendedConsumerProperties(String channelName) { return this.extendedBindingProperties.getExtendedConsumerProperties(channelName); } @Override public RocketMQProducerProperties getExtendedProducerProperties(String channelName) { return this.extendedBindingProperties.getExtendedProducerProperties(channelName); } @Override public String getDefaultsPrefix() { return this.extendedBindingProperties.getDefaultsPrefix(); } @Override public Class getExtendedPropertiesEntryClass() { return this.extendedBindingProperties.getExtendedPropertiesEntryClass(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/actuator/RocketMQBinderHealthIndicator.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.actuator; import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation; import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager; import org.springframework.boot.health.contributor.AbstractHealthIndicator; import org.springframework.boot.health.contributor.Health; /** * @author Timur Valiev * @author Jim */ public class RocketMQBinderHealthIndicator extends AbstractHealthIndicator { @Override protected void doHealthCheck(Health.Builder builder) throws Exception { if (InstrumentationManager.getHealthInstrumentations().stream() .allMatch(Instrumentation::isUp)) { builder.up(); return; } if (InstrumentationManager.getHealthInstrumentations().stream() .allMatch(Instrumentation::isOutOfService)) { builder.outOfService(); return; } builder.down(); InstrumentationManager.getHealthInstrumentations().stream() .filter(instrumentation -> !instrumentation.isStarted()) .forEach(instrumentation1 -> builder .withException(instrumentation1.getStartException())); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/aot/hint/RocketMQConsumerPropertiesHints.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.aot.hint; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.util.ReflectionUtils; /** * @author ChengPu raozihao */ public class RocketMQConsumerPropertiesHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { Constructor constructor; try { constructor = RocketMQConsumerProperties.class.getConstructor(); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } hints.reflection().registerConstructor(constructor, ExecutableMode.INVOKE); // setMessageModel Method setMessageModel = ReflectionUtils.findMethod(RocketMQConsumerProperties.class, "setMessageModel", String.class); hints.reflection().registerMethod(setMessageModel, ExecutableMode.INVOKE); // getPush Method getPush = ReflectionUtils.findMethod(RocketMQConsumerProperties.class, "getPush"); hints.reflection().registerMethod(getPush, ExecutableMode.INVOKE); // setSubscription Method setSubscription = ReflectionUtils.findMethod(RocketMQConsumerProperties.class, "setSubscription", String.class); hints.reflection().registerMethod(setSubscription, ExecutableMode.INVOKE); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/aot/hint/RocketMQSpecificPropertiesProviderHints.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.aot.hint; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQSpecificPropertiesProvider; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.util.ReflectionUtils; /** * @author ChengPu raozihao */ public class RocketMQSpecificPropertiesProviderHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { Constructor constructor; try { constructor = RocketMQSpecificPropertiesProvider.class.getConstructor(); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } hints.reflection().registerConstructor(constructor, ExecutableMode.INVOKE); // getConsumer Method getConsumer = ReflectionUtils.findMethod(RocketMQSpecificPropertiesProvider.class, "getConsumer"); hints.reflection().registerMethod(getConsumer, ExecutableMode.INVOKE); // setConsumer Method setConsumer = ReflectionUtils.findMethod(RocketMQSpecificPropertiesProvider.class, "setConsumer", RocketMQConsumerProperties.class); hints.reflection().registerMethod(setConsumer, ExecutableMode.INVOKE); // getProducer Method getProducer = ReflectionUtils.findMethod(RocketMQSpecificPropertiesProvider.class, "getProducer"); hints.reflection().registerMethod(getProducer, ExecutableMode.INVOKE); // setProducer Method setProducer = ReflectionUtils.findMethod(RocketMQSpecificPropertiesProvider.class, "setProducer", RocketMQProducerProperties.class); hints.reflection().registerMethod(setProducer, ExecutableMode.INVOKE); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/autoconfigurate/ExtendedBindingHandlerMappingsProviderConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate; import java.util.HashMap; import java.util.Map; import com.alibaba.cloud.stream.binder.rocketmq.convert.RocketMQMessageConverter; import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQConfigBeanPostProcessor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.cloud.stream.config.BindingHandlerAdvise.MappingsProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.converter.MessageConverter; @Configuration public class ExtendedBindingHandlerMappingsProviderConfiguration { @Bean public MappingsProvider rocketExtendedPropertiesDefaultMappingsProvider() { return () -> { Map mappings = new HashMap<>(); mappings.put( ConfigurationPropertyName.of("spring.cloud.stream.rocketmq.bindings"), ConfigurationPropertyName.of("spring.cloud.stream.rocketmq.default")); mappings.put( ConfigurationPropertyName.of("spring.cloud.stream.rocketmq.streams"), ConfigurationPropertyName .of("spring.cloud.stream.rocketmq.streams.default")); return mappings; }; } @Bean public static RocketMQConfigBeanPostProcessor rocketMQConfigBeanPostProcessor() { return new RocketMQConfigBeanPostProcessor(); } /** * if you want to customize a bean, please use this BeanName {@code RocketMQMessageConverter.DEFAULT_NAME}. */ @Bean(RocketMQMessageConverter.DEFAULT_NAME) @ConditionalOnMissingBean(name = { RocketMQMessageConverter.DEFAULT_NAME }) public CompositeMessageConverter rocketMQMessageConverter() { return new RocketMQMessageConverter().getMessageConverter(); } /** * Register message converter to adapte Spring Cloud Stream. * Refer to https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/spring-cloud-stream.html#spring-cloud-stream-overview-user-defined-message-converters . * @return message converter. */ @Bean public MessageConverter rocketMQCustomMessageConverter() { return new RocketMQMessageConverter(); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/autoconfigurate/RocketMQBinderAutoConfiguration.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate; import com.alibaba.cloud.stream.binder.rocketmq.RocketMQMessageChannelBinder; import com.alibaba.cloud.stream.binder.rocketmq.actuator.RocketMQBinderHealthIndicator; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindingProperties; import com.alibaba.cloud.stream.binder.rocketmq.provisioning.RocketMQTopicProvisioner; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.health.contributor.HealthIndicator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * issue:https://github.com/alibaba/spring-cloud-alibaba/issues/1681 . * * @author Timur Valiev * @author Jim * @author freeman */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties({ RocketMQExtendedBindingProperties.class, RocketMQBinderConfigurationProperties.class }) public class RocketMQBinderAutoConfiguration { @Autowired private RocketMQExtendedBindingProperties extendedBindingProperties; @Autowired private RocketMQBinderConfigurationProperties rocketBinderConfigurationProperties; @Bean public RocketMQTopicProvisioner rocketMQTopicProvisioner() { return new RocketMQTopicProvisioner(); } @Bean public RocketMQMessageChannelBinder rocketMQMessageChannelBinder( RocketMQTopicProvisioner provisioningProvider) { return new RocketMQMessageChannelBinder(rocketBinderConfigurationProperties, extendedBindingProperties, provisioningProvider); } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(HealthIndicator.class) @ConditionalOnEnabledHealthIndicator("rocketmq") static class RocketMQBinderHealthIndicatorConfiguration { @Bean public RocketMQBinderHealthIndicator rocketMQBinderHealthIndicator() { return new RocketMQBinderHealthIndicator(); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/constant/RocketMQConst.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.constant; import org.apache.rocketmq.common.message.MessageConst; /** * @author zkzlx */ public class RocketMQConst extends MessageConst { /** * Default NameServer value. */ public static final String DEFAULT_NAME_SERVER = "127.0.0.1:9876"; /** * Default group for SCS RocketMQ Binder. */ public static final String DEFAULT_GROUP = "anonymous"; /** * user args for SCS RocketMQ Binder. */ public static final String USER_TRANSACTIONAL_ARGS = "TRANSACTIONAL_ARGS"; /** * It is mainly provided for conversion between rocketMq-message and Spring-message, * and parameters are passed through HEADERS. */ public static class Headers { /** * keys for SCS RocketMQ Headers. */ public static final String KEYS = MessageConst.PROPERTY_KEYS; /** * tags for SCS RocketMQ Headers. */ public static final String TAGS = MessageConst.PROPERTY_TAGS; /** * topic for SCS RocketMQ Headers. */ public static final String TOPIC = "MQ_TOPIC"; /** * The ID of the message. */ public static final String MESSAGE_ID = "MQ_MESSAGE_ID"; /** * The timestamp that the message producer invokes the message sending API. */ public static final String BORN_TIMESTAMP = "MQ_BORN_TIMESTAMP"; /** * The IP and port number of the message producer. */ public static final String BORN_HOST = "MQ_BORN_HOST"; /** * Message flag, MQ is not processed and is available for use by applications. */ public static final String FLAG = "MQ_FLAG"; /** * Message consumption queue ID. */ public static final String QUEUE_ID = "MQ_QUEUE_ID"; /** * Message system Flag, such as whether or not to compress, whether or not to * transactional messages. */ public static final String SYS_FLAG = "MQ_SYS_FLAG"; /** * The transaction ID of the transaction message. */ public static final String TRANSACTION_ID = "MQ_TRANSACTION_ID"; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/convert/RocketMQMessageConverter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.convert; import java.util.ArrayList; import java.util.List; import org.springframework.messaging.Message; import org.springframework.messaging.converter.AbstractMessageConverter; import org.springframework.messaging.converter.ByteArrayMessageConverter; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.converter.JacksonJsonMessageConverter; import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.util.ClassUtils; /** * The default message converter of rocketMq,its bean name is {@link #DEFAULT_NAME} . * * @author zkzlx */ public class RocketMQMessageConverter extends AbstractMessageConverter { /** * if you want to customize a bean, please use the BeanName. */ public static final String DEFAULT_NAME = "com.alibaba.cloud.stream.binder.rocketmq.convert.RocketMQMessageConverter"; private static final boolean JACKSON_PRESENT; private static final boolean FASTJSON_PRESENT; static { ClassLoader classLoader = RocketMQMessageConverter.class.getClassLoader(); JACKSON_PRESENT = ClassUtils .isPresent("tools.jackson.core.JsonToken", classLoader) && ClassUtils.isPresent("tools.jackson.core.JsonGenerator", classLoader); FASTJSON_PRESENT = ClassUtils.isPresent("com.alibaba.fastjson.JSON", classLoader) && ClassUtils.isPresent( "com.alibaba.fastjson.support.config.FastJsonConfig", classLoader); } private CompositeMessageConverter messageConverter; public RocketMQMessageConverter() { List messageConverters = new ArrayList<>(); ByteArrayMessageConverter byteArrayMessageConverter = new ByteArrayMessageConverter(); byteArrayMessageConverter.setContentTypeResolver(null); messageConverters.add(byteArrayMessageConverter); messageConverters.add(new StringMessageConverter()); if (JACKSON_PRESENT) { messageConverters.add(new JacksonJsonMessageConverter()); } if (FASTJSON_PRESENT) { try { messageConverters.add((MessageConverter) ClassUtils.forName( "com.alibaba.fastjson.support.spring.messaging.MappingFastJsonMessageConverter", ClassUtils.getDefaultClassLoader()).newInstance()); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException ignored) { // ignore this exception } } messageConverter = new CompositeMessageConverter(messageConverters); } public CompositeMessageConverter getMessageConverter() { return messageConverter; } public void setMessageConverter(CompositeMessageConverter messageConverter) { this.messageConverter = messageConverter; } /** * support all classes. * @param clazz classes. * @return awayls true. */ @Override protected boolean supports(Class clazz) { return true; } /** * Convert the message payload from serialized form to an Object by RocketMQMessageConverter. * @param message the input message * @param targetClass the target class for the conversion * @param conversionHint an extra object passed to the {@link MessageConverter}, * e.g. the associated {@code MethodParameter} (may be {@code null}} * @return the result of the conversion, or {@code null} if the converter cannot * perform the conversion * @since 4.2 */ @Override protected Object convertFromInternal(Message message, Class targetClass, Object conversionHint) { Object payload = null; for (MessageConverter converter : getMessageConverter().getConverters()) { try { payload = converter.fromMessage(message, targetClass); } catch (Exception ignore) { } if (payload != null) { return payload; } } if (payload == null && logger.isDebugEnabled()) { logger.debug("Can convert message " + message.toString()); } return payload; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/custom/RocketMQBeanContainerCache.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.custom; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.cloud.stream.binder.rocketmq.extend.ErrorAcknowledgeHandler; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.consumer.listener.MessageListener; import org.apache.rocketmq.client.hook.CheckForbiddenHook; import org.apache.rocketmq.client.hook.SendMessageHook; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.TransactionListener; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.util.StringUtils; /** * Gets the beans configured in the configuration file. * * @author junboXiang */ public final class RocketMQBeanContainerCache { private RocketMQBeanContainerCache() { } private static final Class[] CLASSES = new Class[] { CompositeMessageConverter.class, AllocateMessageQueueStrategy.class, MessageQueueSelector.class, MessageListener.class, TransactionListener.class, SendCallback.class, CheckForbiddenHook.class, SendMessageHook.class, ErrorAcknowledgeHandler.class }; private static final Map BEANS_CACHE = new ConcurrentHashMap<>(); static void putBean(String beanName, Object beanObj) { BEANS_CACHE.put(beanName, beanObj); } static Class[] getClassAry() { return CLASSES; } public static T getBean(String beanName, Class clazz) { return getBean(beanName, clazz, null); } public static T getBean(String beanName, Class clazz, T defaultObj) { if (!StringUtils.hasLength(beanName)) { return defaultObj; } Object obj = BEANS_CACHE.get(beanName); if (null == obj) { return defaultObj; } if (clazz.isAssignableFrom(obj.getClass())) { return (T) obj; } return defaultObj; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/custom/RocketMQConfigBeanPostProcessor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.custom; import java.util.stream.Stream; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; /** * find RocketMQ bean by annotations. * * @author junboXiang * */ public class RocketMQConfigBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Stream.of(RocketMQBeanContainerCache.getClassAry()).forEach(clazz -> { if (clazz.isAssignableFrom(bean.getClass())) { RocketMQBeanContainerCache.putBean(beanName, bean); } }); return bean; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/extend/ErrorAcknowledgeHandler.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.extend; import org.springframework.integration.acks.AcknowledgmentCallback.Status; import org.springframework.messaging.Message; /** * @author zkzlx */ public interface ErrorAcknowledgeHandler { /** * Ack state handling, including receive, reject, and retry, when a consumption * exception occurs. * @param message message * @return see {@link Status} */ Status handler(Message message); } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/integration/inbound/RocketMQConsumerFactory.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound; import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils; import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.NamespaceUtil; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Extended function related to producer . eg:initial * * @author zkzlx */ public final class RocketMQConsumerFactory { private RocketMQConsumerFactory() { } private final static Logger log = LoggerFactory .getLogger(RocketMQConsumerFactory.class); public static DefaultMQPushConsumer initPushConsumer( ExtendedConsumerProperties extendedConsumerProperties) { RocketMQConsumerProperties consumerProperties = extendedConsumerProperties .getExtension(); Assert.notNull(consumerProperties.getGroup(), "Property 'group' is required - consumerGroup"); Assert.notNull(consumerProperties.getNameServer(), "Property 'nameServer' is required"); AllocateMessageQueueStrategy allocateMessageQueueStrategy = RocketMQBeanContainerCache .getBean(consumerProperties.getAllocateMessageQueueStrategy(), AllocateMessageQueueStrategy.class, new AllocateMessageQueueAveragely()); RPCHook rpcHook = null; if (StringUtils.hasLength(consumerProperties.getAccessKey()) && StringUtils.hasLength(consumerProperties.getSecretKey())) { rpcHook = new AclClientRPCHook( new SessionCredentials(consumerProperties.getAccessKey(), consumerProperties.getSecretKey())); } DefaultMQPushConsumer consumer = new DefaultMQPushConsumer( consumerProperties.getGroup(), rpcHook, allocateMessageQueueStrategy, consumerProperties.getEnableMsgTrace(), consumerProperties.getCustomizedTraceTopic()); consumer.setVipChannelEnabled( null == rpcHook && consumerProperties.getVipChannelEnabled()); consumer.setInstanceName( RocketMQUtils.getInstanceName(rpcHook, consumerProperties.getGroup())); consumer.setNamespace(consumerProperties.getNamespace()); consumer.setNamespaceV2(consumerProperties.getNamespaceV2()); consumer.setNamesrvAddr(consumerProperties.getNameServer()); consumer.setMessageModel(getMessageModel(consumerProperties.getMessageModel())); consumer.setUseTLS(consumerProperties.getUseTLS()); consumer.setPullTimeDelayMillsWhenException( consumerProperties.getPullTimeDelayMillsWhenException()); consumer.setPullBatchSize(consumerProperties.getPullBatchSize()); consumer.setConsumeFromWhere(consumerProperties.getConsumeFromWhere()); consumer.setHeartbeatBrokerInterval( consumerProperties.getHeartbeatBrokerInterval()); consumer.setPersistConsumerOffsetInterval( consumerProperties.getPersistConsumerOffsetInterval()); consumer.setPullInterval(consumerProperties.getPush().getPullInterval()); consumer.setConsumeThreadMin(extendedConsumerProperties.getConcurrency()); consumer.setConsumeThreadMax(extendedConsumerProperties.getConcurrency()); consumer.setUnitName(consumerProperties.getUnitName()); consumer.setMaxReconsumeTimes( consumerProperties.getPush().getMaxReconsumeTimes()); consumer.setConsumeTimeout(consumerProperties.getPush().getConsumeTimeout()); consumer.setAccessChannel(AccessChannel.valueOf(consumerProperties.getAccessChannel())); return consumer; } /** * todo Compatible with versions less than 4.6 ? * @param topic consumer topic. * @param extendedConsumerProperties extendedConsumerProperties * @return DefaultLitePullConsumer */ public static DefaultLitePullConsumer initPullConsumer( String topic, ExtendedConsumerProperties extendedConsumerProperties) { RocketMQConsumerProperties consumerProperties = extendedConsumerProperties .getExtension(); boolean anonymous = !StringUtils.hasLength(consumerProperties.getGroup()); /*** * When using DLQ, at least the group property must be provided for proper naming of the DLQ destination * According to https://docs.spring.io/spring-cloud-stream/docs/3.2.1/reference/html/spring-cloud-stream.html#spring-cloud-stream-reference */ if (anonymous && NamespaceUtil.isDLQTopic(topic)) { throw new RuntimeException( "group must be configured for DLQ" + topic); } if (anonymous) { consumerProperties.setGroup(RocketMQUtils.anonymousGroup(topic)); } Assert.notNull(consumerProperties.getNameServer(), "Property 'nameServer' is required"); AllocateMessageQueueStrategy allocateMessageQueueStrategy = RocketMQBeanContainerCache .getBean(consumerProperties.getAllocateMessageQueueStrategy(), AllocateMessageQueueStrategy.class); RPCHook rpcHook = null; if (StringUtils.hasLength(consumerProperties.getAccessKey()) && StringUtils.hasLength(consumerProperties.getSecretKey())) { rpcHook = new AclClientRPCHook( new SessionCredentials(consumerProperties.getAccessKey(), consumerProperties.getSecretKey())); } DefaultLitePullConsumer consumer = new DefaultLitePullConsumer( consumerProperties.getNamespace(), consumerProperties.getGroup(), rpcHook); consumer.setVipChannelEnabled( null == rpcHook && consumerProperties.getVipChannelEnabled()); consumer.setInstanceName( RocketMQUtils.getInstanceName(rpcHook, consumerProperties.getGroup())); if (null != allocateMessageQueueStrategy) { consumer.setAllocateMessageQueueStrategy(allocateMessageQueueStrategy); } consumer.setNamesrvAddr(consumerProperties.getNameServer()); consumer.setMessageModel(getMessageModel(consumerProperties.getMessageModel())); consumer.setNamespaceV2(consumerProperties.getNamespaceV2()); consumer.setUseTLS(consumerProperties.getUseTLS()); consumer.setPullTimeDelayMillsWhenException( consumerProperties.getPullTimeDelayMillsWhenException()); consumer.setConsumerTimeoutMillisWhenSuspend( consumerProperties.getPull().getConsumerTimeoutMillisWhenSuspend()); consumer.setPullBatchSize(consumerProperties.getPullBatchSize()); consumer.setConsumeFromWhere(consumerProperties.getConsumeFromWhere()); consumer.setHeartbeatBrokerInterval( consumerProperties.getHeartbeatBrokerInterval()); consumer.setPersistConsumerOffsetInterval( consumerProperties.getPersistConsumerOffsetInterval()); consumer.setPollTimeoutMillis( consumerProperties.getPull().getPollTimeoutMillis()); consumer.setPullThreadNums(extendedConsumerProperties.getConcurrency()); // The internal queues are cached by a maximum of 1000 consumer.setPullThresholdForAll(extendedConsumerProperties.getExtension() .getPull().getPullThresholdForAll()); consumer.setUnitName(consumerProperties.getUnitName()); consumer.setAccessChannel(AccessChannel.valueOf(consumerProperties.getAccessChannel())); return consumer; } private static MessageModel getMessageModel(String messageModel) { for (MessageModel model : MessageModel.values()) { if (model.getModeCN().equalsIgnoreCase(messageModel)) { return model; } } return MessageModel.CLUSTERING; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/integration/inbound/RocketMQInboundChannelAdapter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound; import java.util.List; import java.util.function.Supplier; import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation; import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport; import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.common.message.MessageExt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; import org.springframework.core.retry.RetryException; import org.springframework.core.retry.RetryListener; import org.springframework.core.retry.RetryPolicy; import org.springframework.core.retry.RetryTemplate; import org.springframework.core.retry.Retryable; import org.springframework.integration.context.OrderlyShutdownCapable; import org.springframework.integration.core.RecoveryCallback; import org.springframework.integration.endpoint.MessageProducerSupport; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * @author Jim */ public class RocketMQInboundChannelAdapter extends MessageProducerSupport implements OrderlyShutdownCapable { private static final Logger log = LoggerFactory .getLogger(RocketMQInboundChannelAdapter.class); private RetryTemplate retryTemplate; private RecoveryCallback recoveryCallback; private DefaultMQPushConsumer pushConsumer; private final String topic; private final ExtendedConsumerProperties extendedConsumerProperties; public RocketMQInboundChannelAdapter(String topic, ExtendedConsumerProperties extendedConsumerProperties) { this.topic = topic; this.extendedConsumerProperties = extendedConsumerProperties; } @Override protected void onInit() { if (extendedConsumerProperties.getExtension() == null || !extendedConsumerProperties.getExtension().getEnabled()) { return; } try { super.onInit(); if (this.retryTemplate != null) { Assert.state(getErrorChannel() == null, "Cannot have an 'errorChannel' property when a 'RetryTemplate' is " + "provided; use an 'ErrorMessageSendingRecoverer' in the 'recoveryCallback' property to " + "send an error message when retries are exhausted"); this.retryTemplate.setRetryListener(new RetryListener() { }); } pushConsumer = RocketMQConsumerFactory .initPushConsumer(extendedConsumerProperties); // prepare register consumer message listener,the next step is to be // compatible with a custom MessageListener. if (extendedConsumerProperties.getExtension().getPush().getOrderly()) { pushConsumer.registerMessageListener((MessageListenerOrderly) (msgs, context) -> RocketMQInboundChannelAdapter.this .consumeMessage(msgs, () -> { context.setSuspendCurrentQueueTimeMillis( extendedConsumerProperties.getExtension() .getPush() .getSuspendCurrentQueueTimeMillis()); return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; }, () -> ConsumeOrderlyStatus.SUCCESS)); } else { pushConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> RocketMQInboundChannelAdapter.this .consumeMessage(msgs, () -> { context.setDelayLevelWhenNextConsume( extendedConsumerProperties.getExtension() .getPush() .getDelayLevelWhenNextConsume()); return ConsumeConcurrentlyStatus.RECONSUME_LATER; }, () -> ConsumeConcurrentlyStatus.CONSUME_SUCCESS)); } } catch (Exception e) { log.error("DefaultMQPushConsumer init failed, Caused by " + e.getMessage()); throw new MessagingException(MessageBuilder.withPayload( "DefaultMQPushConsumer init failed, Caused by " + e.getMessage()) .build(), e); } } /** * The actual execution of a user-defined input consumption service method. * @param messageExtList rocket mq message list * @param failSupplier {@link ConsumeConcurrentlyStatus} or * {@link ConsumeOrderlyStatus} * @param sucSupplier {@link ConsumeConcurrentlyStatus} or * {@link ConsumeOrderlyStatus} * @param object * @return R */ private R consumeMessage(List messageExtList, Supplier failSupplier, Supplier sucSupplier) { if (CollectionUtils.isEmpty(messageExtList)) { throw new MessagingException( "DefaultMQPushConsumer consuming failed, Caused by messageExtList is empty"); } for (MessageExt messageExt : messageExtList) { try { Message message = RocketMQMessageConverterSupport .convertMessage2Spring(messageExt); if (this.retryTemplate != null) { this.retryTemplate.setRetryListener(new RetryListener() { @Override public void onRetryPolicyExhaustion(RetryPolicy retryPolicy, Retryable retryable, RetryException exception) { recoveryCallback.recover(null, exception); } }); this.retryTemplate.execute(() -> { try { this.sendMessage(message); } catch (Exception e) { return e; } return null; }); } else { this.sendMessage(message); } } catch (Exception e) { log.warn("consume message failed. messageExt:{}", messageExt, e); return failSupplier.get(); } } return sucSupplier.get(); } @Override protected void doStart() { if (extendedConsumerProperties.getExtension() == null || !extendedConsumerProperties.getExtension().getEnabled()) { return; } Instrumentation instrumentation = new Instrumentation(topic, this); try { pushConsumer.subscribe(topic, RocketMQUtils.getMessageSelector( extendedConsumerProperties.getExtension().getSubscription())); pushConsumer.start(); instrumentation.markStartedSuccessfully(); } catch (Exception e) { instrumentation.markStartFailed(e); log.error("DefaultMQPushConsumer init failed, Caused by " + e.getMessage()); throw new MessagingException(MessageBuilder.withPayload( "DefaultMQPushConsumer init failed, Caused by " + e.getMessage()) .build(), e); } finally { InstrumentationManager.addHealthInstrumentation(instrumentation); } } @Override protected void doStop() { if (pushConsumer != null) { pushConsumer.shutdown(); } } public void setRetryTemplate(RetryTemplate retryTemplate) { this.retryTemplate = retryTemplate; } public void setRecoveryCallback(RecoveryCallback recoveryCallback) { this.recoveryCallback = recoveryCallback; } @Override public int beforeShutdown() { this.stop(); return 0; } @Override public int afterShutdown() { return 0; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/integration/inbound/pull/DefaultErrorAcknowledgeHandler.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull; import com.alibaba.cloud.stream.binder.rocketmq.extend.ErrorAcknowledgeHandler; import org.springframework.integration.acks.AcknowledgmentCallback.Status; import org.springframework.messaging.Message; /** * By default, if consumption fails, the corresponding MessageQueue will always be * retried, that is, the consumption of other messages in the MessageQueue will be * blocked. * * @author zkzlx */ public class DefaultErrorAcknowledgeHandler implements ErrorAcknowledgeHandler { /** * Ack state handling, including receive, reject, and retry, when a consumption * exception occurs. * @param message message * @return see {@link Status} */ @Override public Status handler(Message message) { return Status.REQUEUE; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/integration/inbound/pull/RocketMQAckCallback.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull; import java.util.Collections; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.integration.acks.AcknowledgmentCallback; import org.springframework.util.Assert; /** * A pollable {@link org.springframework.integration.core.MessageSource} for RocketMQ. * * @author zkzlx */ public class RocketMQAckCallback implements AcknowledgmentCallback { private final static Logger log = LoggerFactory.getLogger(RocketMQAckCallback.class); private boolean acknowledged; private boolean autoAckEnabled = true; private MessageExt messageExt; private DefaultLitePullConsumer consumer; private final MessageQueue messageQueue; public RocketMQAckCallback(DefaultLitePullConsumer consumer, MessageQueue messageQueue, MessageExt messageExt) { this.messageExt = messageExt; this.consumer = consumer; this.messageQueue = messageQueue; } @Override public boolean isAcknowledged() { return this.acknowledged; } @Override public void noAutoAck() { this.autoAckEnabled = false; } @Override public boolean isAutoAck() { return this.autoAckEnabled; } @Override public void acknowledge(Status status) { Assert.notNull(status, "'status' cannot be null"); if (this.acknowledged) { throw new IllegalStateException("Already acknowledged"); } synchronized (messageQueue) { try { long offset = messageExt.getQueueOffset(); switch (status) { case REJECT, ACCEPT -> consumer.commit(Collections.singleton(messageQueue), false); case REQUEUE -> consumer.seek(messageQueue, offset); } } catch (MQClientException e) { throw new IllegalStateException(e); } finally { this.acknowledged = true; } } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/integration/inbound/pull/RocketMQMessageSource.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.pull; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.cloud.stream.binder.rocketmq.integration.inbound.RocketMQConsumerFactory; import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation; import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport; import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils; import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; import org.springframework.context.Lifecycle; import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.integration.endpoint.AbstractMessageSource; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; /** * @author Jim */ public class RocketMQMessageSource extends AbstractMessageSource implements DisposableBean, Lifecycle { private final static Logger log = LoggerFactory .getLogger(RocketMQMessageSource.class); private DefaultLitePullConsumer consumer; private final Map> messageQueuesForTopic = new ConcurrentHashMap<>(); private volatile boolean running; private final String topic; private final MessageSelector messageSelector; private final ExtendedConsumerProperties extendedConsumerProperties; private volatile Iterator messageExtIterator = null; public RocketMQMessageSource(String name, ExtendedConsumerProperties extendedConsumerProperties) { this.topic = name; this.messageSelector = RocketMQUtils.getMessageSelector( extendedConsumerProperties.getExtension().getSubscription()); this.extendedConsumerProperties = extendedConsumerProperties; } @Override public synchronized void start() { Instrumentation instrumentation = new Instrumentation(topic, this); try { if (this.isRunning()) { throw new IllegalStateException( "pull consumer already running. " + this.toString()); } this.consumer = RocketMQConsumerFactory .initPullConsumer(topic, extendedConsumerProperties); // This parameter must be 1, otherwise doReceive cannot be handled singly. // this.consumer.setPullBatchSize(1); this.consumer.subscribe(topic, messageSelector); this.consumer.setAutoCommit(false); // register TopicMessageQueueChangeListener for messageQueuesForTopic consumer.registerTopicMessageQueueChangeListener(topic, messageQueuesForTopic::put); this.consumer.start(); // Initialize messageQueuesForTopic immediately messageQueuesForTopic.put(topic, consumer.fetchMessageQueues(topic)); instrumentation.markStartedSuccessfully(); } catch (MQClientException e) { instrumentation.markStartFailed(e); log.error("DefaultMQPullConsumer startup error: " + e.getMessage(), e); } finally { InstrumentationManager.addHealthInstrumentation(instrumentation); } this.running = true; } private MessageQueue acquireCurrentMessageQueue(String topic, int queueId, String brokerName) { Collection messageQueueSet = messageQueuesForTopic.get(topic); if (CollectionUtils.isEmpty(messageQueueSet)) { return null; } for (MessageQueue messageQueue : messageQueueSet) { if (messageQueue.getQueueId() == queueId && ObjectUtils .nullSafeEquals(brokerName, messageQueue.getBrokerName())) { return messageQueue; } } return null; } @Override public synchronized void stop() { if (this.isRunning() && null != consumer) { consumer.unsubscribe(topic); consumer.shutdown(); this.running = false; } } @Override public synchronized boolean isRunning() { return running; } @Override protected synchronized Object doReceive() { if (messageExtIterator == null) { List messageExtList = consumer.poll(); if (CollectionUtils.isEmpty(messageExtList)) { return null; } messageExtIterator = messageExtList.iterator(); } MessageExt messageExt = messageExtIterator.next(); if (!messageExtIterator.hasNext()) { messageExtIterator = null; } if (null == messageExt) { return null; } MessageQueue messageQueue = this.acquireCurrentMessageQueue(messageExt.getTopic(), messageExt.getQueueId(), messageExt.getBrokerName()); if (messageQueue == null) { throw new IllegalArgumentException( "The message queue is not in assigned list"); } Message message = RocketMQMessageConverterSupport .convertMessage2Spring(messageExt); return MessageBuilder.fromMessage(message) .setHeader(IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, new RocketMQAckCallback(this.consumer, messageQueue, messageExt)) .build(); } @Override public String getComponentType() { return "rocketmq:message-source"; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/integration/outbound/RocketMQProduceFactory.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.integration.outbound; import java.lang.reflect.Field; import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst; import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties; import com.alibaba.cloud.stream.binder.rocketmq.utils.RocketMQUtils; import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.hook.CheckForbiddenHook; import org.apache.rocketmq.client.hook.SendMessageHook; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.hook.SendMessageTraceHookImpl; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.RPCHook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Extended function related to producer . eg:initial . * * @author zkzlx */ public final class RocketMQProduceFactory { private RocketMQProduceFactory() { } private final static Logger log = LoggerFactory .getLogger(RocketMQProduceFactory.class); /** * init for the producer,including convert producer params. * @param topic topic * @param producerProperties producerProperties * @return DefaultMQProducer */ public static DefaultMQProducer initRocketMQProducer(String topic, RocketMQProducerProperties producerProperties) { if (!StringUtils.hasLength(producerProperties.getGroup())) { producerProperties.setGroup(RocketMQConst.DEFAULT_GROUP); } Assert.notNull(producerProperties.getNameServer(), "Property 'nameServer' is required"); RPCHook rpcHook = null; if (StringUtils.hasLength(producerProperties.getAccessKey()) && StringUtils.hasLength(producerProperties.getSecretKey())) { rpcHook = new AclClientRPCHook( new SessionCredentials(producerProperties.getAccessKey(), producerProperties.getSecretKey())); } DefaultMQProducer producer; if (RocketMQProducerProperties.ProducerType.Trans .equalsName(producerProperties.getProducerType())) { producer = new TransactionMQProducer(producerProperties.getNamespace(), producerProperties.getGroup(), rpcHook, producerProperties.getEnableMsgTrace(), producerProperties.getCustomizedTraceTopic()); if (producerProperties.getEnableMsgTrace()) { try { AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher( producerProperties.getGroup(), TraceDispatcher.Type.PRODUCE, 10, producerProperties.getCustomizedTraceTopic(), rpcHook); dispatcher.setHostProducer(producer.getDefaultMQProducerImpl()); Field field = DefaultMQProducer.class .getDeclaredField("traceDispatcher"); field.setAccessible(true); field.set(producer, dispatcher); producer.getDefaultMQProducerImpl().registerSendMessageHook( new SendMessageTraceHookImpl(dispatcher)); } catch (Throwable e) { log.error( "system mq-trace hook init failed ,maybe can't send msg trace data"); } } } else { producer = new DefaultMQProducer(producerProperties.getNamespace(), producerProperties.getGroup(), rpcHook, producerProperties.getEnableMsgTrace(), producerProperties.getCustomizedTraceTopic()); } producer.setVipChannelEnabled( null == rpcHook && producerProperties.getVipChannelEnabled()); producer.setInstanceName( RocketMQUtils.getInstanceName(rpcHook, topic + "|" + UtilAll.getPid())); producer.setNamesrvAddr(producerProperties.getNameServer()); producer.setNamespaceV2(producerProperties.getNamespaceV2()); producer.setSendMsgTimeout(producerProperties.getSendMsgTimeout()); producer.setRetryTimesWhenSendFailed( producerProperties.getRetryTimesWhenSendFailed()); producer.setRetryTimesWhenSendAsyncFailed( producerProperties.getRetryTimesWhenSendAsyncFailed()); producer.setCompressMsgBodyOverHowmuch( producerProperties.getCompressMsgBodyThreshold()); producer.setRetryAnotherBrokerWhenNotStoreOK( producerProperties.getRetryAnotherBroker()); producer.setMaxMessageSize(producerProperties.getMaxMessageSize()); producer.setUseTLS(producerProperties.getUseTLS()); producer.setUnitName(producerProperties.getUnitName()); producer.setAccessChannel(AccessChannel.valueOf(producerProperties.getAccessChannel())); CheckForbiddenHook checkForbiddenHook = RocketMQBeanContainerCache.getBean( producerProperties.getCheckForbiddenHook(), CheckForbiddenHook.class); if (null != checkForbiddenHook) { producer.getDefaultMQProducerImpl() .registerCheckForbiddenHook(checkForbiddenHook); } SendMessageHook sendMessageHook = RocketMQBeanContainerCache .getBean(producerProperties.getSendMessageHook(), SendMessageHook.class); if (null != sendMessageHook) { producer.getDefaultMQProducerImpl().registerSendMessageHook(sendMessageHook); } return producer; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/integration/outbound/RocketMQProducerMessageHandler.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.integration.outbound; import java.util.List; import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst; import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache; import com.alibaba.cloud.stream.binder.rocketmq.metrics.Instrumentation; import com.alibaba.cloud.stream.binder.rocketmq.metrics.InstrumentationManager; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties; import com.alibaba.cloud.stream.binder.rocketmq.provisioning.selector.PartitionMessageQueueSelector; import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.remoting.exception.RemotingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.stream.binder.ExtendedProducerProperties; import org.springframework.cloud.stream.binding.MessageConverterConfigurer; import org.springframework.cloud.stream.binding.MessageConverterConfigurer.PartitioningInterceptor; import org.springframework.cloud.stream.provisioning.ProducerDestination; import org.springframework.context.Lifecycle; import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.integration.support.ErrorMessageStrategy; import org.springframework.integration.support.ErrorMessageUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessagingException; /** * @author Jim */ public class RocketMQProducerMessageHandler extends AbstractMessageHandler implements Lifecycle { private final static Logger log = LoggerFactory .getLogger(RocketMQProducerMessageHandler.class); private volatile boolean running = false; private volatile boolean isTrans = false; private ErrorMessageStrategy errorMessageStrategy; private MessageChannel sendFailureChannel; private MessageConverterConfigurer.PartitioningInterceptor partitioningInterceptor; private DefaultMQProducer defaultMQProducer; private MessageQueueSelector messageQueueSelector; private final ProducerDestination destination; private final ExtendedProducerProperties extendedProducerProperties; private final RocketMQProducerProperties mqProducerProperties; public RocketMQProducerMessageHandler(ProducerDestination destination, ExtendedProducerProperties extendedProducerProperties, RocketMQProducerProperties mqProducerProperties) { this.destination = destination; this.extendedProducerProperties = extendedProducerProperties; this.mqProducerProperties = mqProducerProperties; } @Override protected void onInit() { if (null == mqProducerProperties || !mqProducerProperties.getEnabled()) { return; } super.onInit(); this.defaultMQProducer = RocketMQProduceFactory .initRocketMQProducer(destination.getName(), mqProducerProperties); this.isTrans = defaultMQProducer instanceof TransactionMQProducer; // Use the default if the partition is on and no customization is available. this.messageQueueSelector = RocketMQBeanContainerCache.getBean( mqProducerProperties.getMessageQueueSelector(), MessageQueueSelector.class, extendedProducerProperties.isPartitioned() ? new PartitionMessageQueueSelector() : null); } @Override public void start() { Instrumentation instrumentation = new Instrumentation(destination.getName(), this); try { defaultMQProducer.start(); // TransactionMQProducer does not currently support custom // MessageQueueSelector. if (!isTrans && extendedProducerProperties.isPartitioned()) { List messageQueues = defaultMQProducer .fetchPublishMessageQueues(destination.getName()); if (extendedProducerProperties.getPartitionCount() != messageQueues .size()) { log.info(String.format( "The partition count of topic '%s' will change from '%s' to '%s'", destination.getName(), extendedProducerProperties.getPartitionCount(), messageQueues.size())); extendedProducerProperties.setPartitionCount(messageQueues.size()); // may be npe! partitioningInterceptor.setPartitionCount( extendedProducerProperties.getPartitionCount()); } } running = true; instrumentation.markStartedSuccessfully(); } catch (MQClientException | NullPointerException e) { instrumentation.markStartFailed(e); log.error("The defaultMQProducer startup failure !!!", e); } finally { InstrumentationManager.addHealthInstrumentation(instrumentation); } } @Override public void stop() { if (running && null != defaultMQProducer) { defaultMQProducer.shutdown(); } running = false; } @Override public boolean isRunning() { return running; } @Override protected void handleMessageInternal(Message message) { try { org.apache.rocketmq.common.message.Message mqMessage = RocketMQMessageConverterSupport .convertMessage2MQ(destination.getName(), message); SendResult sendResult; if (defaultMQProducer instanceof TransactionMQProducer translateMQProducer) { TransactionListener transactionListener = RocketMQBeanContainerCache .getBean(mqProducerProperties.getTransactionListener(), TransactionListener.class); if (transactionListener == null) { throw new MessagingException( "TransactionMQProducer must have a TransactionListener !!! "); } translateMQProducer.setTransactionListener(transactionListener); if (log.isDebugEnabled()) { log.debug("send transaction message ->{}", mqMessage); } sendResult = defaultMQProducer.sendMessageInTransaction(mqMessage, message.getHeaders().get(RocketMQConst.USER_TRANSACTIONAL_ARGS)); } else { if (log.isDebugEnabled()) { log.debug("send message ->{}", mqMessage); } sendResult = this.send(mqMessage, this.messageQueueSelector, message.getHeaders(), message); } if (log.isDebugEnabled()) { log.debug("the message has sent,message={},sendResult={}", mqMessage, sendResult); } if (sendResult == null || !SendStatus.SEND_OK.equals(sendResult.getSendStatus())) { log.error("message send fail.SendStatus is not OK.the message={}", mqMessage); this.doFail(message, new MessagingException( "message send fail.SendStatus is not OK.")); } } catch (Exception e) { log.error("RocketMQ Message hasn't been sent. Caused by " + e.getMessage(), e); this.doFail(message, e); } } private SendResult send(org.apache.rocketmq.common.message.Message mqMessage, MessageQueueSelector selector, Object args, Message message) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { SendResult sendResult = new SendResult(); sendResult.setSendStatus(SendStatus.SEND_OK); if (RocketMQProducerProperties.SendType.OneWay .equalsName(mqProducerProperties.getSendType())) { if (null != selector) { defaultMQProducer.sendOneway(mqMessage, selector, args); } else { defaultMQProducer.sendOneway(mqMessage); } return sendResult; } if (RocketMQProducerProperties.SendType.Sync .equalsName(mqProducerProperties.getSendType())) { if (null != selector) { return defaultMQProducer.send(mqMessage, selector, args); } return defaultMQProducer.send(mqMessage); } if (RocketMQProducerProperties.SendType.Async .equalsName(mqProducerProperties.getSendType())) { if (null != selector) { defaultMQProducer.send(mqMessage, selector, args, this.getSendCallback(message)); } else { defaultMQProducer.send(mqMessage, this.getSendCallback(message)); } return sendResult; } throw new MessagingException( "message hasn't been sent,cause by : the SendType must be in this values[OneWay, Async, Sync]"); } /** * https://github.com/alibaba/spring-cloud-alibaba/issues/1408 . * @param message message * @return SendCallback */ private SendCallback getSendCallback(Message message) { SendCallback sendCallback = RocketMQBeanContainerCache .getBean(mqProducerProperties.getSendCallBack(), SendCallback.class); if (null == sendCallback) { sendCallback = new SendCallback() { @Override public void onSuccess(SendResult sendResult) { } @Override public void onException(Throwable e) { RocketMQProducerMessageHandler.this.doFail(message, e); } }; } return sendCallback; } private void doFail(Message message, Throwable e) { if (getSendFailureChannel() != null) { getSendFailureChannel().send(getErrorMessageStrategy().buildErrorMessage(e, ErrorMessageUtils.getAttributeAccessor(message, message))); } else { throw new MessagingException(message, e); } } public MessageChannel getSendFailureChannel() { return sendFailureChannel; } public void setSendFailureChannel(MessageChannel sendFailureChannel) { this.sendFailureChannel = sendFailureChannel; } public ErrorMessageStrategy getErrorMessageStrategy() { return errorMessageStrategy; } public void setErrorMessageStrategy(ErrorMessageStrategy errorMessageStrategy) { this.errorMessageStrategy = errorMessageStrategy; } public PartitioningInterceptor getPartitioningInterceptor() { return partitioningInterceptor; } public RocketMQProducerMessageHandler setPartitioningInterceptor( PartitioningInterceptor partitioningInterceptor) { this.partitioningInterceptor = partitioningInterceptor; return this; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/metrics/Instrumentation.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.metrics; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import org.springframework.context.Lifecycle; /** * @author Timur Valiev * @author Jim */ public class Instrumentation { private final String name; private Lifecycle actuator; protected final AtomicBoolean started = new AtomicBoolean(false); protected Exception startException = null; public Instrumentation(String name) { this.name = name; } public Instrumentation(String name, Lifecycle actuator) { this.name = name; this.actuator = actuator; } public Lifecycle getActuator() { return actuator; } public void setActuator(Lifecycle actuator) { this.actuator = actuator; } public boolean isDown() { return startException != null; } public boolean isUp() { return started.get(); } public boolean isOutOfService() { return !started.get() && startException == null; } public void markStartedSuccessfully() { started.set(true); } public void markStartFailed(Exception e) { started.set(false); startException = e; } public String getName() { return name; } public boolean isStarted() { return started.get(); } public Exception getStartException() { return startException; } @Override public int hashCode() { return Objects.hash(getName(), getActuator()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Instrumentation that = (Instrumentation) o; return name.equals(that.name) && actuator.equals(that.actuator); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/metrics/InstrumentationManager.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.metrics; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * @author Timur Valiev * @author Jim */ public final class InstrumentationManager { private InstrumentationManager() { } private static final Map HEALTH_INSTRUMENTATIONS = new HashMap<>(); public static Collection getHealthInstrumentations() { return HEALTH_INSTRUMENTATIONS.values(); } public static void addHealthInstrumentation(Instrumentation instrumentation) { if (null != instrumentation) { HEALTH_INSTRUMENTATIONS.computeIfPresent(instrumentation.hashCode(), (k, v) -> { if (instrumentation.getActuator() != null) { instrumentation.getActuator().stop(); } throw new IllegalArgumentException( "The current actuator exists, please confirm if there is a repeat operation!!!"); }); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQBinderConfigurationProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.properties; import org.springframework.boot.context.properties.ConfigurationProperties; /** * binding rocketMq properties. * * @author Jim */ @ConfigurationProperties(prefix = "spring.cloud.stream.rocketmq.binder") public class RocketMQBinderConfigurationProperties extends RocketMQCommonProperties { } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQCommonProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.properties; import java.io.Serializable; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.remoting.netty.TlsSystemConfig; /** * @author zkzlx */ public class RocketMQCommonProperties implements Serializable { private static final long serialVersionUID = -6724870154343284715L; private boolean enabled = true; private String nameServer; /** * The property of "access-key". */ private String accessKey; /** * The property of "secret-key". */ private String secretKey; /** * Consumers of the same role is required to have exactly same subscriptions and * consumerGroup to correctly achieve load balance. It's required and needs to be * globally unique. Producer group conceptually aggregates all producer instances of * exactly same role, which is particularly important when transactional messages are * involved. For non-transactional messages, it does not matter as long as it's unique * per process. See here * for further discussion. */ private String group; private String namespace; private String namespaceV2; /** * The property of "unitName". */ private String unitName; private String accessChannel = AccessChannel.LOCAL.name(); /** * Pulling topic information interval from the named server. * see{@link MQClientInstance#startScheduledTask()},eg:ScheduledTask * updateTopicRouteInfoFromNameServer. */ private int pollNameServerInterval = 1000 * 30; /** * Heartbeat interval in microseconds with message broker. * see{@link MQClientInstance#startScheduledTask()},eg:ScheduledTask * sendHeartbeatToAllBroker . */ private int heartbeatBrokerInterval = 1000 * 30; /** * Offset persistent interval for consumer. * see{@link MQClientInstance#startScheduledTask()},eg:ScheduledTask * sendHeartbeatToAllBroker . */ private int persistConsumerOffsetInterval = 1000 * 5; private boolean vipChannelEnabled = false; private boolean useTLS = TlsSystemConfig.tlsEnable; private boolean enableMsgTrace = true; private String customizedTraceTopic; public boolean getEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getNameServer() { return nameServer; } public void setNameServer(String nameServer) { this.nameServer = nameServer; } public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getNamespace() { return namespace; } public void setNamespace(String namespace) { this.namespace = namespace; } public String getNamespaceV2() { return namespaceV2; } public void setNamespaceV2(String namespaceV2) { this.namespaceV2 = namespaceV2; } public String getAccessChannel() { return accessChannel; } public void setAccessChannel(String accessChannel) { this.accessChannel = accessChannel; } public int getPollNameServerInterval() { return pollNameServerInterval; } public void setPollNameServerInterval(int pollNameServerInterval) { this.pollNameServerInterval = pollNameServerInterval; } public int getHeartbeatBrokerInterval() { return heartbeatBrokerInterval; } public void setHeartbeatBrokerInterval(int heartbeatBrokerInterval) { this.heartbeatBrokerInterval = heartbeatBrokerInterval; } public int getPersistConsumerOffsetInterval() { return persistConsumerOffsetInterval; } public void setPersistConsumerOffsetInterval(int persistConsumerOffsetInterval) { this.persistConsumerOffsetInterval = persistConsumerOffsetInterval; } public boolean getVipChannelEnabled() { return vipChannelEnabled; } public void setVipChannelEnabled(boolean vipChannelEnabled) { this.vipChannelEnabled = vipChannelEnabled; } public boolean getUseTLS() { return useTLS; } public void setUseTLS(boolean useTLS) { this.useTLS = useTLS; } public boolean getEnableMsgTrace() { return enableMsgTrace; } public void setEnableMsgTrace(boolean enableMsgTrace) { this.enableMsgTrace = enableMsgTrace; } public String getCustomizedTraceTopic() { return customizedTraceTopic; } public void setCustomizedTraceTopic(String customizedTraceTopic) { this.customizedTraceTopic = customizedTraceTopic; } public String getUnitName() { return unitName; } public void setUnitName(String unitName) { this.unitName = unitName; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQConsumerProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.properties; import java.io.Serializable; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService; import org.apache.rocketmq.client.impl.consumer.ConsumeMessageOrderlyService; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; /** * Extended consumer properties for RocketMQ binder. * * @author Jim */ public class RocketMQConsumerProperties extends RocketMQCommonProperties { /** * Message model defines the way how messages are delivered to each consumer clients. * This field defaults to clustering. */ private String messageModel = MessageModel.CLUSTERING.getModeCN(); /** * Queue allocation algorithm specifying how message queues are allocated to each * consumer clients. */ private String allocateMessageQueueStrategy; /** * The expressions include tags or SQL,as follow: *

* tag: {@code tag1||tag2||tag3 }; sql: {@code 'color'='blue' AND 'price'>100 } . *

* Determines whether there are specific characters "{@code ||}" in the expression to * determine how the message is filtered,tags or SQL. */ private String subscription; /** * Delay some time when exception occur . */ private long pullTimeDelayMillsWhenException = 1000; /** * Consuming point on consumer booting. * * There are three consuming points: *
    *
  • CONSUME_FROM_LAST_OFFSET: consumer clients pick up where it * stopped previously. If it were a newly booting up consumer client, according aging * of the consumer group, there are two cases: *
      *
    1. if the consumer group is created so recently that the earliest message being * subscribed has yet expired, which means the consumer group represents a lately * launched business, consuming will start from the very beginning;
    2. *
    3. if the earliest message being subscribed has expired, consuming will start from * the latest messages, meaning messages born prior to the booting timestamp would be * ignored.
    4. *
    *
  • *
  • CONSUME_FROM_FIRST_OFFSET: Consumer client will start from * earliest messages available.
  • *
  • CONSUME_FROM_TIMESTAMP: Consumer client will start from specified * timestamp, which means messages born prior to {@link #consumeTimestamp} will be * ignored
  • *
*/ private ConsumeFromWhere consumeFromWhere = ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET; /** * Backtracking consumption time with second precision. Time format is * 20131223171201
* Implying Seventeen twelve and 01 seconds on December 23, 2013 year
* Default backtracking consumption time Half an hour ago. */ private String consumeTimestamp = UtilAll .timeMillisToHumanString3(System.currentTimeMillis() - (1000 * 60 * 30)); /** * Flow control threshold on queue level, each message queue will cache at most 1000 * messages by default, Consider the {@link #pullBatchSize}, the instantaneous value * may exceed the limit . */ private int pullThresholdForQueue = 1000; /** * Limit the cached message size on queue level, each message queue will cache at most * 100 MiB messages by default, Consider the {@link #pullBatchSize}, the instantaneous * value may exceed the limit . * *

* The size of a message only measured by message body, so it's not accurate */ private int pullThresholdSizeForQueue = 100; /** * Maximum number of messages pulled each time. */ private int pullBatchSize = 10; /** * Consume max span offset.it has no effect on sequential consumption. */ private int consumeMaxSpan = 2000; private Push push = new Push(); private Pull pull = new Pull(); public String getMessageModel() { return messageModel; } public RocketMQConsumerProperties setMessageModel(String messageModel) { this.messageModel = messageModel; return this; } public String getAllocateMessageQueueStrategy() { return allocateMessageQueueStrategy; } public void setAllocateMessageQueueStrategy(String allocateMessageQueueStrategy) { this.allocateMessageQueueStrategy = allocateMessageQueueStrategy; } public String getSubscription() { return subscription; } public void setSubscription(String subscription) { this.subscription = subscription; } public Push getPush() { return push; } public void setPush(Push push) { this.push = push; } public long getPullTimeDelayMillsWhenException() { return pullTimeDelayMillsWhenException; } public RocketMQConsumerProperties setPullTimeDelayMillsWhenException( long pullTimeDelayMillsWhenException) { this.pullTimeDelayMillsWhenException = pullTimeDelayMillsWhenException; return this; } public ConsumeFromWhere getConsumeFromWhere() { return consumeFromWhere; } public RocketMQConsumerProperties setConsumeFromWhere( ConsumeFromWhere consumeFromWhere) { this.consumeFromWhere = consumeFromWhere; return this; } public String getConsumeTimestamp() { return consumeTimestamp; } public RocketMQConsumerProperties setConsumeTimestamp(String consumeTimestamp) { this.consumeTimestamp = consumeTimestamp; return this; } public int getPullThresholdForQueue() { return pullThresholdForQueue; } public RocketMQConsumerProperties setPullThresholdForQueue( int pullThresholdForQueue) { this.pullThresholdForQueue = pullThresholdForQueue; return this; } public int getPullThresholdSizeForQueue() { return pullThresholdSizeForQueue; } public RocketMQConsumerProperties setPullThresholdSizeForQueue( int pullThresholdSizeForQueue) { this.pullThresholdSizeForQueue = pullThresholdSizeForQueue; return this; } public int getPullBatchSize() { return pullBatchSize; } public RocketMQConsumerProperties setPullBatchSize(int pullBatchSize) { this.pullBatchSize = pullBatchSize; return this; } public Pull getPull() { return pull; } public RocketMQConsumerProperties setPull(Pull pull) { this.pull = pull; return this; } public int getConsumeMaxSpan() { return consumeMaxSpan; } public RocketMQConsumerProperties setConsumeMaxSpan(int consumeMaxSpan) { this.consumeMaxSpan = consumeMaxSpan; return this; } public static class Push implements Serializable { private static final long serialVersionUID = -7398468554978817630L; /** * if orderly is true, using {@link MessageListenerOrderly} else if orderly if * false, using {@link MessageListenerConcurrently}. */ private boolean orderly = false; /** * Suspending pulling time for cases requiring slow pulling like flow-control * scenario. see{@link ConsumeMessageOrderlyService#processConsumeResult}. * see{@link ConsumeOrderlyContext#getSuspendCurrentQueueTimeMillis}. */ private int suspendCurrentQueueTimeMillis = 1000; /** * https://github.com/alibaba/spring-cloud-alibaba/issues/1866 Max re-consume * times. -1 means 16 times. If messages are re-consumed more than * {@link #maxReconsumeTimes} before success, it's be directed to a deletion queue * waiting. */ private int maxReconsumeTimes; /** * for concurrently listener. message consume retry strategy. -1 means dlq(or * discard. see {@link ConsumeMessageConcurrentlyService#processConsumeResult}. * see {@link ConsumeConcurrentlyContext#getDelayLevelWhenNextConsume}. */ private int delayLevelWhenNextConsume = 0; /** * Flow control threshold on topic level, default value is -1(Unlimited) *

* The value of {@code pullThresholdForQueue} will be overwrote and calculated * based on {@code pullThresholdForTopic} if it is't unlimited *

* For example, if the value of pullThresholdForTopic is 1000 and 10 message * queues are assigned to this consumer, then pullThresholdForQueue will be set to * 100. */ private int pullThresholdForTopic = -1; /** * Limit the cached message size on topic level, default value is -1 * MiB(Unlimited) *

* The value of {@code pullThresholdSizeForQueue} will be overwrote and calculated * based on {@code pullThresholdSizeForTopic} if it is't unlimited . *

* For example, if the value of pullThresholdSizeForTopic is 1000 MiB and 10 * message queues are assigned to this consumer, then pullThresholdSizeForQueue * will be set to 100 MiB . */ private int pullThresholdSizeForTopic = -1; /** * Message pull Interval. */ private long pullInterval = 0; /** * Batch consumption size. */ private int consumeMessageBatchMaxSize = 1; /** * Maximum amount of time in minutes a message may block the consuming thread. * Unit: Minutes */ private long consumeTimeout = 15; public boolean getOrderly() { return orderly; } public void setOrderly(boolean orderly) { this.orderly = orderly; } public int getSuspendCurrentQueueTimeMillis() { return suspendCurrentQueueTimeMillis; } public void setSuspendCurrentQueueTimeMillis(int suspendCurrentQueueTimeMillis) { this.suspendCurrentQueueTimeMillis = suspendCurrentQueueTimeMillis; } public int getMaxReconsumeTimes() { return maxReconsumeTimes; } public void setMaxReconsumeTimes(int maxReconsumeTimes) { this.maxReconsumeTimes = maxReconsumeTimes; } public int getDelayLevelWhenNextConsume() { return delayLevelWhenNextConsume; } public void setDelayLevelWhenNextConsume(int delayLevelWhenNextConsume) { this.delayLevelWhenNextConsume = delayLevelWhenNextConsume; } public int getPullThresholdForTopic() { return pullThresholdForTopic; } public void setPullThresholdForTopic(int pullThresholdForTopic) { this.pullThresholdForTopic = pullThresholdForTopic; } public int getPullThresholdSizeForTopic() { return pullThresholdSizeForTopic; } public void setPullThresholdSizeForTopic(int pullThresholdSizeForTopic) { this.pullThresholdSizeForTopic = pullThresholdSizeForTopic; } public long getPullInterval() { return pullInterval; } public void setPullInterval(long pullInterval) { this.pullInterval = pullInterval; } public int getConsumeMessageBatchMaxSize() { return consumeMessageBatchMaxSize; } public void setConsumeMessageBatchMaxSize(int consumeMessageBatchMaxSize) { this.consumeMessageBatchMaxSize = consumeMessageBatchMaxSize; } public long getConsumeTimeout() { return consumeTimeout; } public void setConsumeTimeout(long consumeTimeout) { this.consumeTimeout = consumeTimeout; } } public static class Pull implements Serializable { /** * The poll timeout in milliseconds. */ private long pollTimeoutMillis = 1000 * 5; /** * Pull thread number. */ private int pullThreadNums = 20; /** * Interval time in in milliseconds for checking changes in topic metadata. */ private long topicMetadataCheckIntervalMillis = 30 * 1000; /** * Long polling mode, the Consumer connection timeout(must greater than * brokerSuspendMaxTimeMillis), it is not recommended to modify. */ private long consumerTimeoutMillisWhenSuspend = 1000 * 30; /** * Ack state handling, including receive, reject, and retry, when a consumption * exception occurs. */ private String errAcknowledge; private long pullThresholdForAll = 1000L; public long getPollTimeoutMillis() { return pollTimeoutMillis; } public void setPollTimeoutMillis(long pollTimeoutMillis) { this.pollTimeoutMillis = pollTimeoutMillis; } public int getPullThreadNums() { return pullThreadNums; } public void setPullThreadNums(int pullThreadNums) { this.pullThreadNums = pullThreadNums; } public long getTopicMetadataCheckIntervalMillis() { return topicMetadataCheckIntervalMillis; } public void setTopicMetadataCheckIntervalMillis( long topicMetadataCheckIntervalMillis) { this.topicMetadataCheckIntervalMillis = topicMetadataCheckIntervalMillis; } public long getConsumerTimeoutMillisWhenSuspend() { return consumerTimeoutMillisWhenSuspend; } public void setConsumerTimeoutMillisWhenSuspend( long consumerTimeoutMillisWhenSuspend) { this.consumerTimeoutMillisWhenSuspend = consumerTimeoutMillisWhenSuspend; } public String getErrAcknowledge() { return errAcknowledge; } public void setErrAcknowledge(String errAcknowledge) { this.errAcknowledge = errAcknowledge; } public long getPullThresholdForAll() { return pullThresholdForAll; } public void setPullThresholdForAll(long pullThresholdForAll) { this.pullThresholdForAll = pullThresholdForAll; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQExtendedBindingProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.properties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.stream.binder.AbstractExtendedBindingProperties; import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider; /** * rocketMQ specific extended binding properties class that extends from * {@link AbstractExtendedBindingProperties}. * * @author Jim */ @ConfigurationProperties("spring.cloud.stream.rocketmq") public class RocketMQExtendedBindingProperties extends AbstractExtendedBindingProperties { private static final String DEFAULTS_PREFIX = "spring.cloud.stream.rocketmq.default"; @Override public String getDefaultsPrefix() { return DEFAULTS_PREFIX; } @Override public Class getExtendedPropertiesEntryClass() { return RocketMQSpecificPropertiesProvider.class; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQProducerProperties.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.properties; /** * Extended producer properties for RocketMQ binder. * * @author Jim */ public class RocketMQProducerProperties extends RocketMQCommonProperties { /** * Timeout for sending messages. */ private int sendMsgTimeout = 3000; /** * Compress message body threshold, namely, message body larger than 4k will be * compressed on default. */ private int compressMsgBodyThreshold = 1024 * 4; /** * Maximum number of retry to perform internally before claiming sending failure in * synchronous mode. * * This may potentially cause message duplication which is up to application * developers to resolve. */ private int retryTimesWhenSendFailed = 2; /** * Maximum number of retry to perform internally before claiming sending failure in * asynchronous mode. * * This may potentially cause message duplication which is up to application * developers to resolve. */ private int retryTimesWhenSendAsyncFailed = 2; /** * Indicate whether to retry another broker on sending failure internally. */ private boolean retryAnotherBroker = false; /** * Maximum allowed message size in bytes. */ private int maxMessageSize = 1024 * 1024 * 4; private String producerType = ProducerType.Normal.name(); private String sendType = SendType.Sync.name(); private String sendCallBack; private String transactionListener; private String messageQueueSelector; private String errorMessageStrategy; private String sendFailureChannel; private String checkForbiddenHook; private String sendMessageHook; public int getSendMsgTimeout() { return sendMsgTimeout; } public void setSendMsgTimeout(int sendMsgTimeout) { this.sendMsgTimeout = sendMsgTimeout; } public int getCompressMsgBodyThreshold() { return compressMsgBodyThreshold; } public void setCompressMsgBodyThreshold(int compressMsgBodyThreshold) { this.compressMsgBodyThreshold = compressMsgBodyThreshold; } public int getRetryTimesWhenSendFailed() { return retryTimesWhenSendFailed; } public void setRetryTimesWhenSendFailed(int retryTimesWhenSendFailed) { this.retryTimesWhenSendFailed = retryTimesWhenSendFailed; } public int getRetryTimesWhenSendAsyncFailed() { return retryTimesWhenSendAsyncFailed; } public void setRetryTimesWhenSendAsyncFailed(int retryTimesWhenSendAsyncFailed) { this.retryTimesWhenSendAsyncFailed = retryTimesWhenSendAsyncFailed; } public boolean getRetryAnotherBroker() { return retryAnotherBroker; } public void setRetryAnotherBroker(boolean retryAnotherBroker) { this.retryAnotherBroker = retryAnotherBroker; } public int getMaxMessageSize() { return maxMessageSize; } public void setMaxMessageSize(int maxMessageSize) { this.maxMessageSize = maxMessageSize; } public String getProducerType() { return producerType; } public void setProducerType(String producerType) { this.producerType = producerType; } public String getSendType() { return sendType; } public void setSendType(String sendType) { this.sendType = sendType; } public String getSendCallBack() { return sendCallBack; } public void setSendCallBack(String sendCallBack) { this.sendCallBack = sendCallBack; } public String getTransactionListener() { return transactionListener; } public void setTransactionListener(String transactionListener) { this.transactionListener = transactionListener; } public String getMessageQueueSelector() { return messageQueueSelector; } public void setMessageQueueSelector(String messageQueueSelector) { this.messageQueueSelector = messageQueueSelector; } public String getErrorMessageStrategy() { return errorMessageStrategy; } public void setErrorMessageStrategy(String errorMessageStrategy) { this.errorMessageStrategy = errorMessageStrategy; } public String getSendFailureChannel() { return sendFailureChannel; } public void setSendFailureChannel(String sendFailureChannel) { this.sendFailureChannel = sendFailureChannel; } public String getCheckForbiddenHook() { return checkForbiddenHook; } public void setCheckForbiddenHook(String checkForbiddenHook) { this.checkForbiddenHook = checkForbiddenHook; } public String getSendMessageHook() { return sendMessageHook; } public void setSendMessageHook(String sendMessageHook) { this.sendMessageHook = sendMessageHook; } public enum ProducerType { /** * Is not a transaction. */ Normal, /** * a transaction. */ Trans; public boolean equalsName(String name) { return this.name().equalsIgnoreCase(name); } } public enum SendType { /** * one way. */ OneWay, /** * Asynchronization Model. */ Async, /** * synchronization. */ Sync,; public boolean equalsName(String name) { return this.name().equalsIgnoreCase(name); } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/properties/RocketMQSpecificPropertiesProvider.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.properties; import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider; /** * Container object for RocketMQ specific extended producer and consumer binding * properties. * * @author Jim */ public class RocketMQSpecificPropertiesProvider implements BinderSpecificPropertiesProvider { /** * Consumer specific binding properties. @see {@link RocketMQConsumerProperties}. */ private RocketMQConsumerProperties consumer = new RocketMQConsumerProperties(); /** * Producer specific binding properties. @see {@link RocketMQProducerProperties}. */ private RocketMQProducerProperties producer = new RocketMQProducerProperties(); /** * @return {@link RocketMQConsumerProperties} Consumer specific binding * properties. @see {@link RocketMQConsumerProperties}. */ @Override public RocketMQConsumerProperties getConsumer() { return this.consumer; } public void setConsumer(RocketMQConsumerProperties consumer) { this.consumer = consumer; } /** * @return {@link RocketMQProducerProperties} Producer specific binding * properties. @see {@link RocketMQProducerProperties}. */ @Override public RocketMQProducerProperties getProducer() { return this.producer; } public void setProducer(RocketMQProducerProperties producer) { this.producer = producer; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/provisioning/RocketMQTopicProvisioner.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.provisioning; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.exception.MQClientException; import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; import org.springframework.cloud.stream.binder.ExtendedProducerProperties; import org.springframework.cloud.stream.provisioning.ConsumerDestination; import org.springframework.cloud.stream.provisioning.ProducerDestination; import org.springframework.cloud.stream.provisioning.ProvisioningException; import org.springframework.cloud.stream.provisioning.ProvisioningProvider; /** * @author Timur Valiev * @author Jim */ public class RocketMQTopicProvisioner implements ProvisioningProvider, ExtendedProducerProperties> { @Override public ProducerDestination provisionProducerDestination(String name, ExtendedProducerProperties properties) throws ProvisioningException { checkTopic(name); return new RocketProducerDestination(name); } @Override public ConsumerDestination provisionConsumerDestination(String name, String group, ExtendedConsumerProperties properties) throws ProvisioningException { checkTopic(name); return new RocketConsumerDestination(name); } private void checkTopic(String topic) { try { Validators.checkTopic(topic); } catch (MQClientException e) { throw new AssertionError(e); } } private static final class RocketProducerDestination implements ProducerDestination { private final String producerDestinationName; RocketProducerDestination(String destinationName) { this.producerDestinationName = destinationName; } @Override public String getName() { return producerDestinationName; } @Override public String getNameForPartition(int partition) { return producerDestinationName; } } private static final class RocketConsumerDestination implements ConsumerDestination { private final String consumerDestinationName; RocketConsumerDestination(String consumerDestinationName) { this.consumerDestinationName = consumerDestinationName; } @Override public String getName() { return this.consumerDestinationName; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/provisioning/selector/PartitionMessageQueueSelector.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.provisioning.selector; import java.util.List; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.stream.binder.BinderHeaders; /** * @author wangxing */ public class PartitionMessageQueueSelector implements MessageQueueSelector { private static final Logger LOGGER = LoggerFactory .getLogger(PartitionMessageQueueSelector.class); @Override public MessageQueue select(List mqs, Message msg, Object arg) { int partition = 0; try { partition = Math.abs( Integer.parseInt(msg.getProperty(BinderHeaders.PARTITION_HEADER))); if (partition >= mqs.size()) { LOGGER.warn( "the partition '{}' is greater than the number of queues '{}'.", partition, mqs.size()); partition = partition % mqs.size(); } } catch (NumberFormatException ignored) { } return mqs.get(partition); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/support/AbstractRocketMQHeaderMapper.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.support; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.apache.rocketmq.common.message.MessageConst; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; /** * Base for RocketMQ header mappers. * * @author caotc * @since 2.1.1.RELEASE */ public abstract class AbstractRocketMQHeaderMapper implements RocketMQHeaderMapper { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private Charset charset; public AbstractRocketMQHeaderMapper() { this(DEFAULT_CHARSET); } public AbstractRocketMQHeaderMapper(Charset charset) { Assert.notNull(charset, "'charset' cannot be null"); this.charset = charset; } protected boolean matches(String headerName) { return !MessageConst.STRING_HASH_SET.contains(headerName) && !MessageHeaders.ID.equals(headerName) && !MessageHeaders.TIMESTAMP.equals(headerName) && !MessageHeaders.CONTENT_TYPE.equals(headerName) && !MessageHeaders.REPLY_CHANNEL.equals(headerName) && !MessageHeaders.ERROR_CHANNEL.equals(headerName); } public Charset getCharset() { return charset; } public void setCharset(Charset charset) { Assert.notNull(charset, "'charset' cannot be null"); this.charset = charset; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/support/JacksonRocketMQHeaderMapper.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.support; import java.io.IOException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import org.springframework.messaging.MessageHeaders; import org.springframework.util.ClassUtils; /** * jackson header mapper for RocketMQ. Header types are added to a special header * {@link #JSON_TYPES}. * * @author caotc * @since 2.1.1.RELEASE */ public class JacksonRocketMQHeaderMapper extends AbstractRocketMQHeaderMapper { private final static Logger log = LoggerFactory .getLogger(JacksonRocketMQHeaderMapper.class); private static final List DEFAULT_TRUSTED_PACKAGES = Arrays .asList("java.lang", "java.net", "java.util", "org.springframework.util"); /** * Header name for java types of other headers. */ public static final String JSON_TYPES = "spring_json_header_types"; private final ObjectMapper objectMapper; private final Set trustedPackages = new LinkedHashSet<>( DEFAULT_TRUSTED_PACKAGES); public JacksonRocketMQHeaderMapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } public JacksonRocketMQHeaderMapper(Charset charset, ObjectMapper objectMapper) { super(charset); this.objectMapper = objectMapper; } @Override public Map fromHeaders(MessageHeaders headers) { final Map target = new HashMap<>(); final Map jsonHeaders = new HashMap<>(); headers.forEach((key, value) -> { if (matches(key)) { if (value instanceof String strValue) { target.put(key, strValue); } else { try { String className = value.getClass().getName(); target.put(key, objectMapper.writeValueAsString(value)); jsonHeaders.put(key, className); } catch (Exception e) { log.debug("Could not map " + key + " with type " + value.getClass().getName(), e); } } } }); if (jsonHeaders.size() > 0) { try { target.put(JSON_TYPES, objectMapper.writeValueAsString(jsonHeaders)); } catch (IllegalStateException e) { log.error("Could not add json types header", e); } } return target; } @Override public MessageHeaders toHeaders(Map source) { final Map target = new HashMap<>(); final Map jsonTypes = decodeJsonTypes(source); source.forEach((key, value) -> { if (matches(key) && !(key.equals(JSON_TYPES))) { if (jsonTypes.containsKey(key)) { Class type = Object.class; String requestedType = jsonTypes.get(key); boolean trusted = trusted(requestedType); if (trusted) { try { type = ClassUtils.forName(requestedType, null); } catch (Exception e) { log.error("Could not load class for header: " + key, e); } } if (trusted) { try { Object val = decodeValue(value, type); target.put(key, val); } catch (IOException e) { log.error("Could not decode json type: " + value + " for key: " + key, e); target.put(key, value); } } else { target.put(key, new NonTrustedHeaderType(value, requestedType)); } } else { target.put(key, value); } } }); return new MessageHeaders(target); } /** * @param packagesToTrust the packages to trust. * @see #addTrustedPackages(Collection) */ public void addTrustedPackages(String... packagesToTrust) { if (Objects.nonNull(packagesToTrust)) { addTrustedPackages(Arrays.asList(packagesToTrust)); } } /** * Add packages to the trusted packages list (default {@code java.util, java.lang}) * used when constructing objects from JSON. If any of the supplied packages is * {@code "*"}, all packages are trusted. If a class for a non-trusted package is * encountered, the header is returned to the application with value of type * {@link NonTrustedHeaderType}. * @param packagesToTrust the packages to trust. */ public void addTrustedPackages(Collection packagesToTrust) { if (packagesToTrust != null) { for (String whiteList : packagesToTrust) { if ("*".equals(whiteList)) { this.trustedPackages.clear(); break; } else { this.trustedPackages.add(whiteList); } } } } public Set getTrustedPackages() { return this.trustedPackages; } public ObjectMapper getObjectMapper() { return objectMapper; } private Object decodeValue(String jsonString, Class type) throws IOException, LinkageError { Object value = objectMapper.readValue(jsonString, type); if (type.equals(NonTrustedHeaderType.class)) { // Upstream NTHT propagated; may be trusted here... NonTrustedHeaderType nth = (NonTrustedHeaderType) value; if (trusted(nth.getUntrustedType())) { try { value = objectMapper.readValue(nth.getHeaderValue(), ClassUtils.forName(nth.getUntrustedType(), null)); } catch (Exception e) { log.error("Could not decode header: " + nth, e); } } } return value; } private Map decodeJsonTypes(Map source) { if (source.containsKey(JSON_TYPES)) { String value = source.get(JSON_TYPES); try { return objectMapper.readValue(value, new TypeReference>() { }); } catch (Exception e) { log.error("Could not decode json types: " + value, e); } } return Collections.emptyMap(); } protected boolean trusted(String requestedType) { if (requestedType.equals(NonTrustedHeaderType.class.getName())) { return true; } if (!this.trustedPackages.isEmpty()) { int lastDot = requestedType.lastIndexOf('.'); if (lastDot < 0) { return false; } String packageName = requestedType.substring(0, lastDot); for (String trustedPackage : this.trustedPackages) { if (packageName.equals(trustedPackage) || packageName.startsWith(trustedPackage + ".")) { return true; } } return false; } return true; } /** * Represents a header that could not be decoded due to an untrusted type. */ public static class NonTrustedHeaderType { private String headerValue; private String untrustedType; public NonTrustedHeaderType() { super(); } NonTrustedHeaderType(String headerValue, String untrustedType) { this.headerValue = headerValue; this.untrustedType = untrustedType; } public void setHeaderValue(String headerValue) { this.headerValue = headerValue; } public String getHeaderValue() { return this.headerValue; } public void setUntrustedType(String untrustedType) { this.untrustedType = untrustedType; } public String getUntrustedType() { return this.untrustedType; } @Override public String toString() { return "NonTrustedHeaderType [headerValue=" + headerValue + ", untrustedType=" + this.untrustedType + "]"; } } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/support/RocketMQHeaderMapper.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.support; import java.util.Map; import org.springframework.messaging.MessageHeaders; /** * header value mapper for RocketMQ. * * @author caotc * @since 2.1.1.RELEASE */ public interface RocketMQHeaderMapper { /** * Map from the given {@link MessageHeaders} to the specified target message. * @param headers the abstracted MessageHeaders. * @return the native target message. */ Map fromHeaders(MessageHeaders headers); /** * Map from the given target message to abstracted {@link MessageHeaders}. * @param source the native target message. * @return the target headers. */ MessageHeaders toHeaders(Map source); } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/support/RocketMQMessageConverterSupport.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.support; import java.nio.charset.Charset; import java.util.Map; import java.util.Objects; import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst; import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst.Headers; import com.alibaba.cloud.stream.binder.rocketmq.convert.RocketMQMessageConverter; import com.alibaba.cloud.stream.binder.rocketmq.custom.RocketMQBeanContainerCache; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeTypeUtils; import org.springframework.util.ObjectUtils; /** * @author zkzlx */ public final class RocketMQMessageConverterSupport { private RocketMQMessageConverterSupport() { } private static final CompositeMessageConverter MESSAGE_CONVERTER = RocketMQBeanContainerCache .getBean(RocketMQMessageConverter.DEFAULT_NAME, CompositeMessageConverter.class, new RocketMQMessageConverter().getMessageConverter()); public static Message convertMessage2Spring(MessageExt message) { MessageBuilder messageBuilder = MessageBuilder.withPayload(message.getBody()) .setHeader(toRocketHeaderKey(Headers.KEYS), message.getKeys()) .setHeader(toRocketHeaderKey(Headers.TAGS), message.getTags()) .setHeader(toRocketHeaderKey(Headers.TOPIC), message.getTopic()) .setHeader(toRocketHeaderKey(Headers.MESSAGE_ID), message.getMsgId()) .setHeader(toRocketHeaderKey(Headers.BORN_TIMESTAMP), message.getBornTimestamp()) .setHeader(toRocketHeaderKey(Headers.BORN_HOST), message.getBornHostString()) .setHeader(toRocketHeaderKey(Headers.FLAG), message.getFlag()) .setHeader(toRocketHeaderKey(Headers.QUEUE_ID), message.getQueueId()) .setHeader(toRocketHeaderKey(Headers.SYS_FLAG), message.getSysFlag()) .setHeader(toRocketHeaderKey(Headers.TRANSACTION_ID), message.getTransactionId()); addUserProperties(message.getProperties(), messageBuilder); return messageBuilder.build(); } public static String toRocketHeaderKey(String rawKey) { return "ROCKET_" + rawKey; } private static void addUserProperties(Map properties, MessageBuilder messageBuilder) { if (!CollectionUtils.isEmpty(properties)) { properties.forEach((key, val) -> { if (!MessageConst.STRING_HASH_SET.contains(key) && !MessageHeaders.ID.equals(key) && !MessageHeaders.TIMESTAMP.equals(key)) { messageBuilder.setHeader(key, val); } }); } } public static org.apache.rocketmq.common.message.Message convertMessage2MQ( String destination, Message source) { Message message = MESSAGE_CONVERTER.toMessage(source.getPayload(), source.getHeaders()); assert message != null; MessageBuilder builder = MessageBuilder.fromMessage(message); builder.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN); message = builder.build(); return doConvert(destination, message); } private static org.apache.rocketmq.common.message.Message doConvert(String topic, Message message) { Charset charset = Charset.defaultCharset(); Object payloadObj = message.getPayload(); byte[] payloads; try { if (payloadObj instanceof String payload) { payloads = payload.getBytes(charset); } else if (payloadObj instanceof byte[] payload) { payloads = payload; } else { String jsonObj = (String) MESSAGE_CONVERTER.fromMessage(message, payloadObj.getClass()); if (null == jsonObj) { throw new RuntimeException(String.format( "empty after conversion [messageConverter:%s,payloadClass:%s,payloadObj:%s]", MESSAGE_CONVERTER.getClass(), payloadObj.getClass(), payloadObj)); } payloads = jsonObj.getBytes(charset); } } catch (Exception e) { throw new RuntimeException("convert to RocketMQ message failed.", e); } return getAndWrapMessage(topic, message.getHeaders(), payloads); } private static org.apache.rocketmq.common.message.Message getAndWrapMessage( String topic, MessageHeaders headers, byte[] payloads) { if (topic == null || topic.length() < 1) { return null; } if (payloads == null || payloads.length < 1) { return null; } org.apache.rocketmq.common.message.Message rocketMsg = new org.apache.rocketmq.common.message.Message( topic, payloads); if (Objects.nonNull(headers) && !headers.isEmpty()) { Object tag = headers.getOrDefault(Headers.TAGS, headers.get(toRocketHeaderKey(Headers.TAGS))); if (!ObjectUtils.isEmpty(tag)) { rocketMsg.setTags(String.valueOf(tag)); } Object keys = headers.getOrDefault(Headers.KEYS, headers.get(toRocketHeaderKey(Headers.KEYS))); if (!ObjectUtils.isEmpty(keys)) { rocketMsg.setKeys(keys.toString()); } Object flagObj = headers.getOrDefault(Headers.FLAG, headers.get(toRocketHeaderKey(Headers.FLAG))); int flag = 0; int delayLevel = 0; try { flagObj = flagObj == null ? 0 : flagObj; Object delayLevelObj = headers.getOrDefault( RocketMQConst.PROPERTY_DELAY_TIME_LEVEL, headers.get(toRocketHeaderKey( RocketMQConst.PROPERTY_DELAY_TIME_LEVEL))); delayLevelObj = delayLevelObj == null ? 0 : delayLevelObj; delayLevel = Integer.parseInt(String.valueOf(delayLevelObj)); flag = Integer.parseInt(String.valueOf(flagObj)); } catch (Exception ignored) { } if (delayLevel > 0) { rocketMsg.setDelayTimeLevel(delayLevel); } rocketMsg.setFlag(flag); Object waitStoreMsgOkObj = headers .getOrDefault(RocketMQConst.PROPERTY_WAIT_STORE_MSG_OK, "true"); rocketMsg.setWaitStoreMsgOK( Boolean.parseBoolean(String.valueOf(waitStoreMsgOkObj))); headers.entrySet().stream() .filter(entry -> !Objects.equals(entry.getKey(), Headers.FLAG)) .forEach(entry -> { if (!MessageConst.STRING_HASH_SET.contains(entry.getKey())) { String val = String.valueOf(entry.getValue()); // Remove All blank header(rocketmq not support). if (org.apache.commons.lang3.StringUtils.isNotBlank(val)) { rocketMsg.putUserProperty(entry.getKey(), val); } } }); } return rocketMsg; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/utils/RocketMQUtils.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.utils; import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQCommonProperties; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.RPCHook; /** * @author Jim */ public final class RocketMQUtils { private RocketMQUtils() { } public static T mergeRocketMQProperties( RocketMQBinderConfigurationProperties binderConfigurationProperties, T mqProperties) { if (null == binderConfigurationProperties || mqProperties == null) { return mqProperties; } if (StringUtils.isEmpty(mqProperties.getNameServer())) { mqProperties.setNameServer(binderConfigurationProperties.getNameServer()); } if (StringUtils.isEmpty(mqProperties.getSecretKey())) { mqProperties.setSecretKey(binderConfigurationProperties.getSecretKey()); } if (StringUtils.isEmpty(mqProperties.getAccessKey())) { mqProperties.setAccessKey(binderConfigurationProperties.getAccessKey()); } if (StringUtils.isEmpty(mqProperties.getAccessChannel())) { mqProperties .setAccessChannel(binderConfigurationProperties.getAccessChannel()); } if (StringUtils.isEmpty(mqProperties.getNamespace())) { mqProperties.setNamespace(binderConfigurationProperties.getNamespace()); } if (StringUtils.isEmpty(mqProperties.getNamespaceV2())) { mqProperties.setNamespaceV2(binderConfigurationProperties.getNamespaceV2()); } if (StringUtils.isEmpty(mqProperties.getGroup())) { mqProperties.setGroup(binderConfigurationProperties.getGroup()); } if (StringUtils.isEmpty(mqProperties.getCustomizedTraceTopic())) { mqProperties.setCustomizedTraceTopic( binderConfigurationProperties.getCustomizedTraceTopic()); } if (StringUtils.isEmpty(mqProperties.getUnitName())) { mqProperties.setUnitName(binderConfigurationProperties.getUnitName()); } mqProperties.setNameServer(getNameServerStr(mqProperties.getNameServer())); return mqProperties; } public static String getInstanceName(RPCHook rpcHook, String identify) { String separator = "|"; StringBuilder instanceName = new StringBuilder(); if (null != rpcHook) { SessionCredentials sessionCredentials = ((AclClientRPCHook) rpcHook) .getSessionCredentials(); instanceName.append(sessionCredentials.getAccessKey()).append(separator); } instanceName.append(identify).append(separator).append(UtilAll.getPid()) .append(separator).append(Long.toString(System.nanoTime(), 36)); return instanceName.toString(); } public static String getNameServerStr(String nameServer) { if (StringUtils.isEmpty(nameServer)) { return RocketMQConst.DEFAULT_NAME_SERVER; } return nameServer.replaceAll(",", ";"); } private static final String SQL = "sql:"; public static MessageSelector getMessageSelector(String expression) { if (StringUtils.isNotBlank(expression) && expression.startsWith(SQL)) { return MessageSelector.bySql(expression.replaceFirst(SQL, "")); } return MessageSelector.byTag(expression); } /** * generate anonymous group. * @param destination not null * @return anonymous group name. */ public static String anonymousGroup(final String destination) { return RocketMQConst.DEFAULT_GROUP + "_" + destination; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/resources/META-INF/native-image/com.alibaba.cloud/spring-cloud-starter-stream-rocketmq/native-image.properties ================================================ Args = --initialize-at-build-time=org.apache.commons.logging.LogFactoryService \ ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/resources/META-INF/native-image/reflect-config.json ================================================ [ { "name": "org.apache.rocketmq.client.consumer.store.OffsetSerializeWrapper", "allDeclaredFields": true, "queryAllPublicMethods": true }, { "name": "org.apache.rocketmq.common.consumer.ConsumeFromWhere", "allPublicFields": true, "queryAllPublicMethods": true }, { "name": "org.apache.rocketmq.common.message.MessageQueue", "allDeclaredFields": true, "queryAllPublicMethods": true }, { "name": "org.apache.rocketmq.common.protocol.header.GetMaxOffsetRequestHeader", "allDeclaredFields": true }, { "name": "org.apache.rocketmq.common.protocol.header.GetMaxOffsetResponseHeader", "allDeclaredFields": true, "methods": [ { "name": "", "parameterTypes": [] } ] }, { "name": "org.apache.rocketmq.common.protocol.header.NotifyConsumerIdsChangedRequestHeader", "allDeclaredFields": true, "methods": [ { "name": "", "parameterTypes": [] } ] }, { "name": "org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader", "allDeclaredFields": true }, { "name": "org.apache.rocketmq.common.protocol.header.PullMessageResponseHeader", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "name": "org.apache.rocketmq.common.protocol.header.SendMessageRequestHeaderV2", "allDeclaredFields": true }, { "name": "org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader", "methods": [ { "name": "", "parameterTypes": [] } ] }, { "name": "org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader", "allDeclaredFields": true }, { "name": "org.apache.rocketmq.common.protocol.header.namesrv.GetRouteInfoRequestHeader", "allDeclaredFields": true }, { "name": "org.apache.rocketmq.common.protocol.heartbeat.ConsumeType", "allPublicFields": true, "queryAllPublicMethods": true }, { "name": "org.apache.rocketmq.common.protocol.heartbeat.ConsumerData", "allDeclaredFields": true, "allPublicMethods": true }, { "name": "org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData", "allDeclaredFields": true, "allPublicMethods": true }, { "name": "org.apache.rocketmq.common.protocol.heartbeat.MessageModel", "allPublicFields": true, "queryAllPublicMethods": true }, { "name": "org.apache.rocketmq.common.protocol.heartbeat.ProducerData", "allDeclaredFields": true, "allPublicMethods": true }, { "name": "org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData", "allDeclaredFields": true, "allPublicMethods": true }, { "name": "org.apache.rocketmq.common.protocol.route.BrokerData", "allDeclaredFields": true, "allPublicMethods": true, "allPublicConstructors": true }, { "name": "org.apache.rocketmq.common.protocol.route.QueueData", "allDeclaredFields": true, "allPublicMethods": true, "allPublicConstructors": true }, { "name": "org.apache.rocketmq.common.protocol.route.TopicRouteData", "allDeclaredFields": true, "allPublicMethods": true, "allPublicConstructors": true }, { "name": "org.apache.rocketmq.remoting.netty.NettyDecoder" }, { "name": "org.apache.rocketmq.remoting.netty.NettyEncoder" }, { "name": "org.apache.rocketmq.remoting.netty.NettyRemotingClient$4" }, { "name": "org.apache.rocketmq.remoting.netty.NettyRemotingClient$NettyClientHandler" }, { "name": "org.apache.rocketmq.remoting.netty.NettyRemotingClient$NettyConnectManageHandler", "methods": [ { "name": "close", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext", "io.netty.channel.ChannelPromise" ] }, { "name": "connect", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext", "java.net.SocketAddress", "java.net.SocketAddress", "io.netty.channel.ChannelPromise" ] }, { "name": "disconnect", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext", "io.netty.channel.ChannelPromise" ] }, { "name": "exceptionCaught", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext", "java.lang.Throwable" ] }, { "name": "userEventTriggered", "parameterTypes": [ "io.netty.channel.ChannelHandlerContext", "java.lang.Object" ] } ] }, { "name": "org.apache.rocketmq.remoting.protocol.LanguageCode", "allPublicFields": true, "queryAllPublicMethods": true, "queryAllDeclaredConstructors": true }, { "name": "org.apache.rocketmq.remoting.protocol.RemotingCommand", "allDeclaredFields": true, "allDeclaredMethods": true, "allDeclaredConstructors": true, "queryAllPublicMethods": true }, { "name": "org.apache.rocketmq.remoting.protocol.RemotingSerializable", "allDeclaredFields": true }, { "name": "org.apache.rocketmq.remoting.protocol.SerializeType", "allPublicFields": true, "queryAllPublicMethods": true, "queryAllDeclaredConstructors": true }, { "name": "org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupResponseBody", "allDeclaredFields": true, "allPublicFields": true, "allPublicMethods": true, "allDeclaredConstructors": true }, { "name": "org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupRequestHeader", "allDeclaredFields": true }, { "name": "org.apache.rocketmq.common.protocol.body.CheckClientRequestBody", "allDeclaredFields": true, "allPublicMethods": true }, { "name": "org.apache.rocketmq.common.protocol.header.QueryConsumerOffsetRequestHeader", "allDeclaredFields": true }, { "name": "org.apache.rocketmq.common.protocol.header.QueryConsumerOffsetResponseHeader", "allDeclaredFields": true, "methods": [ { "name": "", "parameterTypes": [] } ] }, { "name": "org.apache.rocketmq.client.consumer.store.OffsetSerializeWrapper", "allPublicMethods": true }, { "name": "com.alibaba.fastjson.support.spring.messaging.MappingFastJsonMessageConverter", "allDeclaredConstructors": true, "allDeclaredFields": true, "allDeclaredMethods": true }, { "name": "org.springframework.integration.config.ConverterRegistrar$IntegrationConverterRegistration", "allDeclaredFields": true, "queryAllDeclaredMethods": true, "queryAllDeclaredConstructors": true, "methods": [ { "name": "", "parameterTypes": [ "java.lang.Object" ] }, { "name": "converter", "parameterTypes": [] }, { "name": "equals", "parameterTypes": [ "java.lang.Object" ] }, { "name": "hashCode", "parameterTypes": [] }, { "name": "toString", "parameterTypes": [] } ] } ] ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/resources/META-INF/spring/aot.factories ================================================ org.springframework.aot.hint.RuntimeHintsRegistrar=\ com.alibaba.cloud.stream.binder.rocketmq.aot.hint.RocketMQSpecificPropertiesProviderHints,\ com.alibaba.cloud.stream.binder.rocketmq.aot.hint.RocketMQConsumerPropertiesHints ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.ExtendedBindingHandlerMappingsProviderConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/resources/META-INF/spring.binders ================================================ rocketmq:com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.RocketMQBinderAutoConfiguration ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/RocketMQAutoConfigurationTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq; import com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.RocketMQBinderAutoConfiguration; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindingProperties; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; /** * @author Jim */ public class RocketMQAutoConfigurationTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration( AutoConfigurations.of(RocketMQBinderAutoConfiguration.class)) .withPropertyValues( "spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876,127.0.0.1:9877", "spring.cloud.stream.bindings.output.destination=TopicOrderTest", "spring.cloud.stream.bindings.output.content-type=application/json", "spring.cloud.stream.bindings.input1.destination=TopicOrderTest", "spring.cloud.stream.bindings.input1.content-type=application/json", "spring.cloud.stream.bindings.input1.group=test-group1", "spring.cloud.stream.rocketmq.bindings.input1.consumer.push.orderly=true", "spring.cloud.stream.bindings.input1.consumer.maxAttempts=1", "spring.cloud.stream.bindings.input2.destination=TopicOrderTest", "spring.cloud.stream.bindings.input2.content-type=application/json", "spring.cloud.stream.bindings.input2.group=test-group2", "spring.cloud.stream.rocketmq.bindings.input2.consumer.push.orderly=false", "spring.cloud.stream.rocketmq.bindings.input2.consumer.subscription=tag1"); @Test public void testProperties() { this.contextRunner.run(context -> { RocketMQBinderConfigurationProperties binderConfigurationProperties = context .getBean(RocketMQBinderConfigurationProperties.class); assertThat(binderConfigurationProperties.getNameServer()) .isEqualTo("127.0.0.1:9876,127.0.0.1:9877"); RocketMQExtendedBindingProperties bindingProperties = context .getBean(RocketMQExtendedBindingProperties.class); assertThat(bindingProperties.getExtendedConsumerProperties("input2") .getSubscription()).isEqualTo("tag1"); assertThat(bindingProperties.getExtendedConsumerProperties("input2").getPush() .getOrderly()).isFalse(); assertThat(bindingProperties.getExtendedConsumerProperties("input1").getPush() .getOrderly()).isTrue(); }); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/RocketMQMessageChannelBinderTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq; import com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.ExtendedBindingHandlerMappingsProviderConfiguration; import com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.RocketMQBinderAutoConfiguration; import com.alibaba.cloud.stream.binder.rocketmq.constant.RocketMQConst; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import jakarta.annotation.Resource; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; import org.springframework.context.annotation.Configuration; import org.springframework.integration.core.MessageProducer; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE; @SpringBootTest(classes = RocketMQMessageChannelBinderTest.TestConfig.class, webEnvironment = NONE, properties = { "spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876", "spring.cloud.stream.bindings.output.destination=TopicOrderTest", "spring.cloud.stream.bindings.output.content-type=application/json", "spring.cloud.stream.bindings.input1.destination=TopicOrderTest", "spring.cloud.stream.bindings.input1.content-type=application/json", "spring.cloud.stream.bindings.input1.group=test-group1", "spring.cloud.stream.rocketmq.bindings.input1.consumer.push.orderly=true", "spring.cloud.stream.bindings.input1.consumer.maxAttempts=1", "spring.cloud.stream.bindings.input2.destination=TopicOrderTest", "spring.cloud.stream.bindings.input2.content-type=application/json", "spring.cloud.stream.bindings.input2.group=test-group2", "spring.cloud.stream.rocketmq.bindings.input2.consumer.push.orderly=false", "spring.cloud.stream.rocketmq.bindings.input2.consumer.subscription=tag1" }) public class RocketMQMessageChannelBinderTest { @Resource RocketMQMessageChannelBinder binder; @Test public void createConsumerEndpoint() throws Exception { TestConsumerDestination destination = new TestConsumerDestination("test"); MessageProducer consumerEndpoint = binder.createConsumerEndpoint(destination, "test", new ExtendedConsumerProperties<>(new RocketMQConsumerProperties())); Assertions.assertThat(consumerEndpoint).isNotNull(); } @Test public void createAnymousConsumerEndpoint() throws Exception { ExtendedConsumerProperties extendedConsumerProperties = new ExtendedConsumerProperties<>(new RocketMQConsumerProperties()); extendedConsumerProperties.populateBindingName("input1"); TestConsumerDestination destination = new TestConsumerDestination("test"); MessageProducer consumerEndpoint = binder.createConsumerEndpoint(destination, null, extendedConsumerProperties); Assertions.assertThat(consumerEndpoint).isNotNull(); Assertions.assertThat(extendedConsumerProperties.getExtension().getGroup()) .isEqualTo(RocketMQConst.DEFAULT_GROUP + "_test"); } @Test public void createDLQAnymousConsumerEndpoint() throws Exception { TestConsumerDestination destination = new TestConsumerDestination("%DLQ%test"); Assertions.assertThatThrownBy(() -> { MessageProducer consumerEndpoint = binder.createConsumerEndpoint(destination, null, new ExtendedConsumerProperties<>(new RocketMQConsumerProperties())); }); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({ ExtendedBindingHandlerMappingsProviderConfiguration.class, RocketMQBinderAutoConfiguration.class}) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/RocketMQMessageConverterSupportTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq; import com.alibaba.cloud.stream.binder.rocketmq.support.RocketMQMessageConverterSupport; import org.apache.rocketmq.common.message.MessageConst; import org.junit.jupiter.api.Test; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import static org.assertj.core.api.Assertions.assertThat; /** * @author Sorie */ public class RocketMQMessageConverterSupportTest { @Test public void convertMessage2MQBlankHeaderTest() { String destination = "test"; Message message = MessageBuilder.withPayload("msg") .setHeader(MessageConst.PROPERTY_TAGS, "a") .setHeader("test", "") .build(); org.apache.rocketmq.common.message.Message rkmqMsg = RocketMQMessageConverterSupport.convertMessage2MQ(destination, message); String testProp = rkmqMsg.getProperty("test"); String tagProp = rkmqMsg.getProperty(MessageConst.PROPERTY_TAGS); assertThat(testProp).isNull(); assertThat(tagProp).isEqualTo("a"); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/TestConsumerDestination.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq; import org.springframework.cloud.stream.provisioning.ConsumerDestination; public class TestConsumerDestination implements ConsumerDestination { private String name; public TestConsumerDestination(String name) { this.name = name; } @Override public String getName() { return name; } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/aot/hint/RocketMQConsumerPropertiesHintsTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.aot.hint; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; /** * @author ChengPu raozihao */ public class RocketMQConsumerPropertiesHintsTests { @Test public void shouldRegisterHints() { Constructor constructor; try { constructor = RocketMQConsumerProperties.class.getConstructor(); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } Method setMessageModel = ReflectionUtils.findMethod(RocketMQConsumerProperties.class, "setMessageModel", String.class); Method getPush = ReflectionUtils.findMethod(RocketMQConsumerProperties.class, "getPush"); Method setSubscription = ReflectionUtils.findMethod(RocketMQConsumerProperties.class, "setSubscription", String.class); RuntimeHints hints = new RuntimeHints(); new RocketMQConsumerPropertiesHints().registerHints(hints, getClass().getClassLoader()); assertThat(RuntimeHintsPredicates.reflection().onConstructor(constructor)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onMethod(setMessageModel)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onMethod(getPush)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onMethod(setSubscription)).accepts(hints); } } ================================================ FILE: spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/aot/hint/RocketMQSpecificPropertiesProviderHintsTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.aot.hint; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQSpecificPropertiesProvider; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; /** * @author ChengPu raozihao */ public class RocketMQSpecificPropertiesProviderHintsTests { @Test public void shouldRegisterHints() { Constructor constructor; try { constructor = RocketMQSpecificPropertiesProvider.class.getConstructor(); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } Method getConsumer = ReflectionUtils.findMethod(RocketMQSpecificPropertiesProvider.class, "getConsumer"); Method setConsumer = ReflectionUtils.findMethod(RocketMQSpecificPropertiesProvider.class, "setConsumer", RocketMQConsumerProperties.class); Method getProducer = ReflectionUtils.findMethod(RocketMQSpecificPropertiesProvider.class, "getProducer"); Method setProducer = ReflectionUtils.findMethod(RocketMQSpecificPropertiesProvider.class, "setProducer", RocketMQProducerProperties.class); RuntimeHints hints = new RuntimeHints(); new RocketMQSpecificPropertiesProviderHints().registerHints(hints, getClass().getClassLoader()); assertThat(RuntimeHintsPredicates.reflection().onConstructor(constructor)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onMethod(getConsumer)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onMethod(setConsumer)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onMethod(getProducer)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onMethod(setProducer)).accepts(hints); } } ================================================ FILE: spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/test/resources/docker/nacos-compose-test.yml ================================================ ================================================ FILE: spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/pom.xml ================================================ nacos-tests com.alibaba.cloud ${revision} 4.0.0 nacos-discovery-test Nacos Discovery Test org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-loadbalancer org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.projectlombok lombok com.alibaba.cloud spring-cloud-alibaba-test-support test ================================================ FILE: spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/main/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTestApp.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.tests.nacos.discovery; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.server.servlet.context.ServletWebServerInitializedEvent; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * * @author freeman */ @SpringBootApplication @EnableFeignClients public class NacosDiscoveryTestApp { public static void main(String[] args) { SpringApplication.run(NacosDiscoveryTestApp.class, args); } @Bean @LoadBalanced @Profile("service-1") RestTemplate restTemplate() { return new RestTemplate(); } @RestController @Profile("service-2") static class Controller { int port; @GetMapping public Object get() { return port; } @GetMapping("/{ok}") public Object pass(@PathVariable Boolean ok) { if (ok) { return "ok"; } throw new RuntimeException("not ok!"); } @EventListener public void onApplicationEvent(ServletWebServerInitializedEvent event) { port = event.getWebServer().getPort(); } } @FeignClient(value = "service-2", fallback = Fallback.class) interface Service2Client { @GetMapping Object get(); @GetMapping("/{ok}") String pass(@PathVariable Boolean ok); } @Component static class Fallback implements Service2Client { @Override public Object get() { return "shouldn't use this !"; } @Override public String pass(Boolean ok) { return "fallback"; } } } ================================================ FILE: spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.tests.nacos.discovery; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import com.alibaba.cloud.testsupport.ContainerStarter; import com.alibaba.cloud.testsupport.HasDockerAndItEnabled; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.testcontainers.containers.GenericContainer; import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.server.context.WebServerApplicationContext; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import static com.alibaba.cloud.tests.nacos.discovery.NacosDiscoveryTestApp.Service2Client; import static com.alibaba.cloud.testsupport.Tester.justDo; import static com.alibaba.cloud.testsupport.Tester.testFunction; import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * * * @author freeman */ @HasDockerAndItEnabled public class NacosDiscoveryTest { static GenericContainer nacos; static ConfigurableApplicationContext service1 = new SpringApplicationBuilder( NacosDiscoveryTestApp.class).profiles("service-1").run(); static ConfigurableApplicationContext service2_0 = new SpringApplicationBuilder( NacosDiscoveryTestApp.class).profiles("service-2").run(); static ConfigurableApplicationContext service2_1 = new SpringApplicationBuilder( NacosDiscoveryTestApp.class).profiles("service-2").run(); static { nacos = ContainerStarter.startNacos(); // wait nacos started justDo(() -> Thread.sleep(4000L)); } static { String serverAddr = "localhost:" + nacos.getMappedPort(8848); System.setProperty("spring.cloud.nacos.discovery.server-addr", serverAddr); } @BeforeAll static void init() { // give nacos a break justDo(() -> Thread.sleep(1000L)); } @AfterAll static void stop() { service1.stop(); service2_0.stop(); service2_1.stop(); } @Test public void testServiceDiscovery() { testFunction("Service discovery", () -> { DiscoveryClient discoveryClient = service1.getBean(DiscoveryClient.class); List instances = discoveryClient.getInstances("service-2"); assertThat(instances).hasSize(2); }); } @Test public void testServiceDiscoveryWithRestTemplate() { testFunction("Service discovery with RestTemplate", () -> { RestTemplate restTemplate = service1.getBean(RestTemplate.class); assertThatThrownBy(() -> service2_0.getBean(RestTemplate.class)); // default using RR List list1 = new ArrayList<>(); List list2 = new ArrayList<>(); for (int i = 0; i < 20; i++) { ResponseEntity response = restTemplate .getForEntity("http://service-2", String.class); if (i % 2 != 0) { list1.add(response.getBody()); } else { list2.add(response.getBody()); } } assertThat(list1).hasSize(10); assertThat(list1.stream().allMatch(isEqual(list1.get(0)))).isTrue(); assertThat(list2).hasSize(10); assertThat(list2.stream().allMatch(isEqual(list2.get(0)))).isTrue(); }); } @Test public void testServiceDiscoveryWithFeignClient() { testFunction("Service discovery with FeignClient", () -> { Service2Client service2Client = service1.getBean(Service2Client.class); // default using RR List list1 = new ArrayList<>(); List list2 = new ArrayList<>(); for (int i = 0; i < 20; i++) { Object result = service2Client.get(); if (i % 2 != 0) { list1.add(result); } else { list2.add(result); } } assertThat(list1).hasSize(10); assertThat(list1.stream().allMatch(isEqual(list1.get(0)))).isTrue(); assertThat(list2).hasSize(10); assertThat(list2.stream().allMatch(isEqual(list2.get(0)))).isTrue(); }); } @Test public void testServiceDiscoveryWithFeignClientUsingSentinelCircuitBreaker() { testFunction("Service discovery with FeignClient using Sentinel Circuit Breaker", () -> { Service2Client service2Client = service1 .getBean(Service2Client.class); List passResult = new ArrayList<>(); List fallbackResult = new ArrayList<>(); for (int i = 0; i < 10; i++) { passResult.add(service2Client.pass(true)); fallbackResult.add(service2Client.pass(false)); } assertThat(passResult.stream().allMatch(isEqual("ok"))).isTrue(); assertThat(fallbackResult.stream().allMatch(isEqual("fallback"))) .isTrue(); }); } @Test public void testServiceDiscoveryActuatorEndpoint() { testFunction("Service discovery actuator endpoint", () -> { WebServerApplicationContext webServerApplicationContext = (WebServerApplicationContext) service1; int port = webServerApplicationContext.getWebServer().getPort(); String response = new RestTemplate().getForEntity( String.format("http://127.0.0.1:%d/actuator/nacosdiscovery", port), String.class).getBody(); LinkedHashMap map = new ObjectMapper().readValue(response, LinkedHashMap.class); assertThat(map.containsKey("subscribe")).isTrue(); assertThat(map.containsKey("NacosDiscoveryProperties")).isTrue(); }); } } ================================================ FILE: spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/resources/application-service-1.yml ================================================ server: port: 0 tomcat: threads: min-spare: 2 max: 10 spring: application: name: service-1 cloud: nacos: discovery: server-addr: localhost:8848 feign: circuitbreaker: enabled: true management: endpoints: web: exposure: include: '*' ================================================ FILE: spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/resources/application-service-2.yml ================================================ server: port: 0 tomcat: threads: min-spare: 4 max: 10 spring: application: name: service-2 cloud: nacos: discovery: server-addr: localhost:8848 ================================================ FILE: spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/resources/docker/nacos-compose-test.yml ================================================ services: nacos: image: nacos/nacos-server:v3.1.0 container_name: nacos-standalone environment: - PREFER_HOST_MODE=hostname - MODE=standalone ports: - "8848:8848" - "9848:9848" healthcheck: test: "curl --fail http://127.0.0.1:8848/nacos/v1/console/health/liveness || exit 1" interval: 5s ================================================ FILE: spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/resources/nacos/nacos-config-refresh.yml ================================================ configdata: user: age: 22 name: freeman1123 map: hobbies: - art - programming - movie intro: Hello, I'm freeman extra: yo~ users: - name: dad age: 20 - name: mom age: 18 ================================================ FILE: spring-cloud-alibaba-tests/nacos-tests/pom.xml ================================================ 4.0.0 nacos-discovery-test spring-cloud-alibaba-tests com.alibaba.cloud ${revision} pom nacos-tests Nacos Tests org.springframework.cloud spring-cloud-starter-bootstrap compile ================================================ FILE: spring-cloud-alibaba-tests/pom.xml ================================================ 4.0.0 spring-cloud-alibaba com.alibaba.cloud ${revision} spring-cloud-alibaba-tests Spring Cloud Alibaba Tests pom spring-cloud-alibaba-test-support nacos-tests sentinel-tests 1.16.3 com.alibaba.cloud spring-cloud-alibaba-test-support ${project.version} org.testcontainers testcontainers-bom ${testcontainers.version} pom import org.apache.maven.plugins maven-deploy-plugin ${maven-deploy-plugin.version} true ================================================ FILE: spring-cloud-alibaba-tests/rocketmq-tests/pom.xml ================================================ 4.0.0 Rocketmq Test rocketmq-stream-test spring-cloud-alibaba-tests com.alibaba.cloud ${revision} pom rocketmq-tests UTF-8 4.0.0 junit junit 4.13.1 test org.springframework.cloud spring-cloud-stream-test-support test org.springframework.cloud spring-cloud-stream-test-support ${spring-cloud-stream-test-support.version} ================================================ FILE: spring-cloud-alibaba-tests/rocketmq-tests/rocketmq-stream-test/pom.xml ================================================ rocketmq-tests com.alibaba.cloud ${revision} rocketmq-stream-test 4.0.0 Rocketmq Stream Test org.springframework.boot spring-boot-configuration-processor test com.alibaba.cloud spring-cloud-alibaba-test-support test com.alibaba.cloud spring-cloud-starter-stream-rocketmq maven-jar-plugin 3.0.3 test-jar ================================================ FILE: spring-cloud-alibaba-tests/rocketmq-tests/rocketmq-stream-test/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/RocketmqStreamApplication.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RocketmqStreamApplication { public static void main(String[] args) { SpringApplication.run(RocketmqStreamApplication.class, args); } } ================================================ FILE: spring-cloud-alibaba-tests/rocketmq-tests/rocketmq-stream-test/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/RocketMQAutoConfigurationTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq; import com.alibaba.cloud.stream.binder.rocketmq.autoconfigurate.RocketMQBinderAutoConfiguration; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties; import com.alibaba.cloud.stream.binder.rocketmq.properties.RocketMQExtendedBindingProperties; import com.alibaba.cloud.testsupport.SpringCloudAlibaba; import com.alibaba.cloud.testsupport.TestExtend; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Configuration; import static com.alibaba.cloud.testsupport.Constant.TIME_OUT; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE; @SpringCloudAlibaba(composeFiles = "docker/rocket-compose-test.yml", serviceName = "rocketmq-standalone") @TestExtend(time = 6 * TIME_OUT) @SpringBootTest(classes = RocketMQAutoConfigurationTests.TestConfig.class, webEnvironment = NONE, properties = { "spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876,127.0.0.1:9877", "spring.cloud.stream.bindings.output.destination=TopicOrderTest", "spring.cloud.stream.bindings.output.content-type=application/json", "spring.cloud.stream.bindings.input1.destination=TopicOrderTest", "spring.cloud.stream.bindings.input1.content-type=application/json", "spring.cloud.stream.bindings.input1.group=test-group1", "spring.cloud.stream.rocketmq.bindings.input1.consumer.push.orderly=true", "spring.cloud.stream.bindings.input1.consumer.maxAttempts=1", "spring.cloud.stream.bindings.input2.destination=TopicOrderTest", "spring.cloud.stream.bindings.input2.content-type=application/json", "spring.cloud.stream.bindings.input2.group=test-group2", "spring.cloud.stream.rocketmq.bindings.input2.consumer.push.orderly=false", "spring.cloud.stream.rocketmq.bindings.input2.consumer.subscription=tag1" }) public class RocketMQAutoConfigurationTests { @Autowired private RocketMQBinderConfigurationProperties binderConfigurationProperties; @Autowired private RocketMQExtendedBindingProperties bindingProperties; private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration( AutoConfigurations.of(RocketMQBinderAutoConfiguration.class)); @BeforeAll public static void setUp() { } @BeforeEach public void prepare() { } @Test public void testProperties() { this.contextRunner.run(context -> { assertThat(binderConfigurationProperties.getNameServer()) .isEqualTo("127.0.0.1:9876,127.0.0.1:9877"); assertThat(bindingProperties.getExtendedConsumerProperties("input2") .getSubscription()).isEqualTo("tag1"); assertThat(bindingProperties.getExtendedConsumerProperties("input2").getPush() .getOrderly()).isFalse(); assertThat(bindingProperties.getExtendedConsumerProperties("input1").getPush() .getOrderly()).isTrue(); }); } @Configuration @EnableAutoConfiguration @ImportAutoConfiguration({RocketMQBinderAutoConfiguration.class }) public static class TestConfig { } } ================================================ FILE: spring-cloud-alibaba-tests/rocketmq-tests/rocketmq-stream-test/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/RocketmqProduceAndConsumerTests.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq; import java.util.HashMap; import java.util.Map; import java.util.concurrent.BlockingQueue; import com.alibaba.cloud.stream.binder.rocketmq.fixture.RocketmqBinderProcessor; import com.alibaba.cloud.testsupport.SpringCloudAlibaba; import com.alibaba.cloud.testsupport.TestExtend; import com.alibaba.fastjson.JSON; import org.apache.rocketmq.common.message.MessageConst; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.stream.test.binder.MessageCollector; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.GenericMessage; import org.springframework.test.annotation.DirtiesContext; import static com.alibaba.cloud.testsupport.Constant.TIME_OUT; import static org.hamcrest.CoreMatchers.is; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE; import static org.springframework.cloud.stream.test.matcher.MessageQueueMatcher.receivesPayloadThat; @SpringCloudAlibaba(composeFiles = "docker/rocket-compose-test.yml", serviceName = "rocketmq-standalone") @TestExtend(time = 6 * TIME_OUT) @DirtiesContext @ImportAutoConfiguration(value = {}, exclude = { DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class }) @SpringBootTest(classes = RocketmqBinderProcessor.class, webEnvironment = NONE, properties = { "spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876,127.0.0.1:9877", "spring.cloud.stream.rocketmq.binder.group=flaky-group", // "spring.cloud.stream.rocketmq.binder.consumer-group=flaky-group", // "spring.cloud.stream.pollable-source=pollable", "spring.cloud.stream.bindings.uppercaseFunction-out-0.destination=TopicOrderTest", "spring.cloud.stream.bindings.uppercaseFunction-out-0.content-type=application/json", "spring.cloud.stream.bindings.uppercaseFunction-out-0.group=test-group1", "spring.cloud.stream.bindings.uppercaseFunction-in-0.destination=TopicOrderTest", "spring.cloud.stream.bindings.uppercaseFunction-in-0.content-type=application/json", "spring.cloud.stream.bindings.uppercaseFunction-in-0.group=test-group1", "spring.cloud.stream.bindings.uppercaseFunction-in-0.consumer.push.orderly=true", "spring.cloud.stream.bindings.uppercaseFunction-in-0.consumer.maxAttempts=1" }) public class RocketmqProduceAndConsumerTests { @Autowired private MessageCollector collector; @Autowired @Qualifier("uppercaseFunction-in-0") private MessageChannel input1; @Autowired @Qualifier("uppercaseFunction-out-0") private MessageChannel output; @BeforeAll public static void prepare() { } @BeforeEach public void setup() { String key = "KEY"; String messageId = "1"; Map headers = new HashMap<>(); headers.put(MessageConst.PROPERTY_KEYS, key); headers.put(MessageConst.PROPERTY_TAGS, "TagA"); headers.put(MessageConst.PROPERTY_ORIGIN_MESSAGE_ID, messageId); Message msg = new GenericMessage(JSON.toJSONString("Hello RocketMQ"), headers); input1.send(msg); } @Test public void testConsumeAndProduce() throws Exception { BlockingQueue> messages = this.collector.forChannel(this.output); MatcherAssert.assertThat(messages, receivesPayloadThat(is("\"HELLO ROCKETMQ\""))); } } ================================================ FILE: spring-cloud-alibaba-tests/rocketmq-tests/rocketmq-stream-test/src/test/java/com/alibaba/cloud/stream/binder/rocketmq/fixture/RocketmqBinderProcessor.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.stream.binder.rocketmq.fixture; import java.util.function.Function; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class RocketmqBinderProcessor { public static void main(String[] args) { SpringApplication.run(RocketmqBinderProcessor.class, args); } @Bean public Function uppercaseFunction() { return String::toUpperCase; } } ================================================ FILE: spring-cloud-alibaba-tests/rocketmq-tests/rocketmq-stream-test/src/test/resources/docker/data/broker/conf/broker.conf ================================================ brokerClusterName = DefaultCluster brokerName = broker-a brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = ASYNC_MASTER flushDiskType = ASYNC_FLUSH ================================================ FILE: spring-cloud-alibaba-tests/rocketmq-tests/rocketmq-stream-test/src/test/resources/docker/data1/broker/conf/broker.conf ================================================ brokerClusterName = DefaultCluster brokerName = broker-b brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = ASYNC_MASTER flushDiskType = ASYNC_FLUSH ================================================ FILE: spring-cloud-alibaba-tests/rocketmq-tests/rocketmq-stream-test/src/test/resources/docker/rocket-compose-test.yml ================================================ services: #Service for nameserver namesrv: image: apache/rocketmq:4.9.3 container_name: rmqnamesrv ports: - 9876:9876 volumes: - ./data/namesrv/logs:/home/rocketmq/logs command: sh mqnamesrv #Service for broker broker: image: apache/rocketmq:4.9.3 container_name: rmqbroker links: - namesrv ports: - 10909:10909 - 10911:10911 - 10912:10912 environment: - NAMESRV_ADDR=namesrv:9876 volumes: - ./data/broker/logs:/home/rocketmq/logs - ./data/broker/store:/home/rocketmq/store - ./data/broker/conf/broker.conf:/opt/rocketmq-4.9.3/conf/broker.conf command: sh mqbroker -c /opt/rocketmq-4.9.3/conf/broker.conf #Service for another broker -- broker1 broker1: image: apache/rocketmq:4.9.3 container_name: rmqbroker-b links: - namesrv ports: - 10929:10909 - 10931:10911 - 10932:10912 environment: - NAMESRV_ADDR=namesrv:9876 volumes: - ./data1/broker/logs:/home/rocketmq/logs - ./data1/broker/store:/home/rocketmq/store - ./data1/broker/conf/broker.conf:/opt/rocketmq-4.9.3/conf/broker.conf command: sh mqbroker -c /opt/rocketmq-4.9.3/conf/broker.conf ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-tests ${revision} 4.0.0 sentinel-tests pom Sentinel Tests sentinel-flowcontrol-test sentinel-degrade-test ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/sentinel-degrade-test/pom.xml ================================================ sentinel-tests com.alibaba.cloud ${revision} 4.0.0 sentinel-degrade-test Sentinel Degrade Test com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-alibaba-test-support test org.springframework.boot spring-boot-restclient test org.springframework.boot spring-boot-resttestclient test ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/sentinel-degrade-test/src/main/java/com/alibaba/cloud/tests/sentinel/degrade/SentinelDegradeTestApp.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.tests.sentinel.degrade; import java.util.Arrays; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Freeman */ @SpringBootApplication @RestController public class SentinelDegradeTestApp { public static void main(String[] args) { SpringApplication.run(SentinelDegradeTestApp.class, args); } @RequestMapping("/degrade") @SentinelResource(value = "/degrade", fallback = "fallback") public String degrade() { throw new RuntimeException("Ops, something wrong!"); } public static String fallback() { return "fallback"; } @EventListener(ApplicationReadyEvent.class) public void onApplicationReady() { DegradeRule degradeRule = new DegradeRule(); degradeRule.setResource("/degrade"); degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); degradeRule.setMinRequestAmount(1); degradeRule.setStatIntervalMs(2); degradeRule.setTimeWindow(1); degradeRule.setCount(1); DegradeRuleManager.loadRules(Arrays.asList(degradeRule)); } } ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/sentinel-degrade-test/src/main/resources/application.yml ================================================ server: port: 8080 spring: application: name: sentinel-flowcontrol-test ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/sentinel-degrade-test/src/test/java/com/alibaba/cloud/tests/sentinel/degrade/SentinelDegradeTestAppTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.tests.sentinel.degrade; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.resttestclient.TestRestTemplate; import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.ResponseEntity; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @AutoConfigureTestRestTemplate @SpringBootTest(webEnvironment = RANDOM_PORT) class SentinelDegradeTestAppTest { @LocalServerPort int port; @Autowired TestRestTemplate rest; @Test public void testDegradeRule() { ResponseEntity res = rest .getForEntity("http://localhost:" + port + "/degrade", String.class); assertThat(res.getBody()).contains("fallback"); } } ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/sentinel-flowcontrol-test/pom.xml ================================================ com.alibaba.cloud sentinel-tests ${revision} 4.0.0 sentinel-flowcontrol-test Sentinel Flowcontrol Test com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-alibaba-test-support test org.springframework.boot spring-boot-restclient test org.springframework.boot spring-boot-resttestclient test ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/sentinel-flowcontrol-test/src/main/java/com/alibaba/cloud/tests/sentinel/degrade/SentinelFlowControlTestApp.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.tests.sentinel.degrade; import java.util.Arrays; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import static com.alibaba.cloud.tests.sentinel.degrade.Util.FLOW_CONTROL_NOT_TRIGGERED; import static com.alibaba.cloud.tests.sentinel.degrade.Util.FLOW_CONTROL_TRIGGERED; /** * @author Freeman */ @SpringBootApplication @RestController public class SentinelFlowControlTestApp { public static void main(String[] args) { SpringApplication.run(SentinelFlowControlTestApp.class, args); } @RequestMapping(FLOW_CONTROL_NOT_TRIGGERED) @SentinelResource(value = FLOW_CONTROL_NOT_TRIGGERED, defaultFallback = "fallback") public String flowControlNotTriggered() { return "OK"; } @RequestMapping(FLOW_CONTROL_TRIGGERED) @SentinelResource(value = FLOW_CONTROL_TRIGGERED, defaultFallback = "fallback") public String flowControlTriggered() { return "OK"; } private static String fallback() { return "fallback"; } @EventListener(ApplicationReadyEvent.class) public void onApplicationReady() { FlowRule notTriggeredRule = new FlowRule(); notTriggeredRule.setResource(FLOW_CONTROL_NOT_TRIGGERED); notTriggeredRule.setGrade(RuleConstant.FLOW_GRADE_QPS); notTriggeredRule.setCount(4); FlowRule triggeredRule = new FlowRule(); triggeredRule.setResource(FLOW_CONTROL_TRIGGERED); triggeredRule.setGrade(RuleConstant.FLOW_GRADE_QPS); triggeredRule.setCount(3); FlowRuleManager.loadRules(Arrays.asList(notTriggeredRule, triggeredRule)); } } ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/sentinel-flowcontrol-test/src/main/java/com/alibaba/cloud/tests/sentinel/degrade/Util.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.tests.sentinel.degrade; /** * @author Freeman */ public final class Util { private Util() { throw new AssertionError("No Util instances for you!"); } /** * Flow control not triggered. */ public static final String FLOW_CONTROL_NOT_TRIGGERED = "/flowControlNotTriggered"; /** * Flow control triggered. */ public static final String FLOW_CONTROL_TRIGGERED = "/flowControlTriggered"; } ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/sentinel-flowcontrol-test/src/main/resources/application.yml ================================================ server: port: 8080 spring: application: name: sentinel-flowcontrol-test ================================================ FILE: spring-cloud-alibaba-tests/sentinel-tests/sentinel-flowcontrol-test/src/test/java/com/alibaba/cloud/tests/sentinel/degrade/SentinelFlowControlTestAppTest.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.tests.sentinel.degrade; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.resttestclient.TestRestTemplate; import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.ResponseEntity; import static com.alibaba.cloud.tests.sentinel.degrade.Util.FLOW_CONTROL_NOT_TRIGGERED; import static com.alibaba.cloud.tests.sentinel.degrade.Util.FLOW_CONTROL_TRIGGERED; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @AutoConfigureTestRestTemplate @SpringBootTest(webEnvironment = RANDOM_PORT) class SentinelFlowControlTestAppTest { @LocalServerPort int port; @Autowired TestRestTemplate rest; @Test void testFlowControl_whenNotTriggered() { final int count = 3; List result = new ArrayList<>(); for (int i = 0; i < count; i++) { ResponseEntity res = rest.getForEntity( "http://localhost:" + port + FLOW_CONTROL_NOT_TRIGGERED, String.class); result.add(res.getBody()); } assertThat(result).doesNotContain("fallback"); } @Test void testFlowControl_whenTriggered() { final int count = 3; List result = new ArrayList<>(); for (int i = 0; i < count; i++) { ResponseEntity res = rest.getForEntity( "http://localhost:" + port + FLOW_CONTROL_TRIGGERED, String.class); result.add(res.getBody()); } assertThat(result).containsSequence("fallback"); } } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/pom.xml ================================================ com.alibaba.cloud spring-cloud-alibaba-tests ${revision} 4.0.0 spring-cloud-alibaba-test-support Spring Cloud Alibaba Test Support 17 17 UTF-8 5.10.2 3.141.59 3.25.3 4.2.2 1.9.24 3.0 4.6.1 2.1.2 org.springframework.boot spring-boot-starter-test compile org.slf4j slf4j-api org.apache.logging.log4j log4j-slf4j2-impl org.hamcrest hamcrest-library ${hamcrest.version} test org.hamcrest hamcrest-core ${hamcrest.version} test org.junit.jupiter junit-jupiter org.testcontainers testcontainers org.testcontainers junit-jupiter org.assertj assertj-core ${assertj-core.version} test org.awaitility awaitility ${awaitility.version} test org.mockito mockito-core ${mockito-core.version} org.rnorth.visible-assertions visible-assertions ${visible-assertions.version} org.projectlombok lombok provided org.junit junit-bom ${junit.version} import pom org.testcontainers testcontainers-bom ${testcontainers.version} import pom org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/Constant.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; public class Constant { /** * container timeout. */ public static final long TIME_OUT = 5000; /** * nacos refresh config. */ public static final String REFRESH_CONFIG = "nacos-config-refresh.yml"; } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/ContainerStarter.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.testcontainers.containers.FixedHostPortGenericContainer; import org.testcontainers.containers.GenericContainer; import org.testcontainers.shaded.org.apache.commons.io.FileUtils; import org.springframework.core.io.ClassPathResource; /** * Util class for starting the related container. * * @author freeman * @since 2021.0.1.0 */ public class ContainerStarter { private static final String ROCKETMQ_BROKER_CONFIG_PATH = "rocketmq/conf/broker.conf"; private static final String NACOS_VERSION = "1.4.2"; private static final String SENTINEL_VERSION = "1.8.3"; private static final String ROCKETMQ_VERSION = "4.9.2"; private static final String SEATA_VERSION = "1.4.2"; private static final Map nacosMap = new ConcurrentHashMap<>( 4); private static final Map rocketmqMap = new ConcurrentHashMap<>( 4); /** * Start Nacos container, using default version. */ public static GenericContainer startNacos() { return startNacos(NACOS_VERSION); } /** * Start RocketMQ container, using default version. */ public static GenericContainer startRocketmq() { return startRocketmq(ROCKETMQ_VERSION); } /** * Start Nacos container, using specific version. * @param version Nacos version */ public static GenericContainer startNacos(String version) { if (!nacosMap.containsKey(version)) { GenericContainer nacos = new GenericContainer("freemanlau/nacos:" + version) .withExposedPorts(8848).withEnv("MODE", "standalone") .withEnv("JVM_XMS", "256m").withEnv("JVM_XMX", "256m") .withEnv("JVM_XMN", "128m"); nacos.start(); nacosMap.put(version, nacos); } return nacosMap.get(version); } /** * Start RocketMQ container, using specific version. * @param version RocketMQ version */ public static GenericContainer startRocketmq(String version) { if (!rocketmqMap.containsKey(version)) { loadHostIp2BrokerConf(); // this image exposes 4 ports, include namesrv and broker // we need use FixedHostPortGenericContainer ! GenericContainer rocketmq = new FixedHostPortGenericContainer( "freemanlau/rocketmq:" + version).withFixedExposedPort(9876, 9876) .withFixedExposedPort(10909, 10909) .withFixedExposedPort(10911, 10911) .withFixedExposedPort(10912, 10912); rocketmq.withFileSystemBind(getAbsolutePath4BrokerConf(), "/home/rocketmq/rocketmq-" + version + "/conf/broker.conf"); rocketmq.start(); rocketmqMap.put(version, rocketmq); } return rocketmqMap.get(version); } private static void loadHostIp2BrokerConf() { try { ClassPathResource resource = new ClassPathResource( ROCKETMQ_BROKER_CONFIG_PATH); FileUtils.writeLines(resource.getFile(), Collections.singletonList("brokerIP1 = " + InetUtil.getHostIp()), true); } catch (IOException e) { throw new RuntimeException("load host ip to 'broker.conf' err !", e); } } private static String getAbsolutePath4BrokerConf() { try { ClassPathResource resource = new ClassPathResource( ROCKETMQ_BROKER_CONFIG_PATH); return resource.getFile().getAbsolutePath(); } catch (IOException e) { throw new RuntimeException(e); } } } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/Func.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; /** * * @author freeman * @since 2021.0.1.0 */ public interface Func { /** * just do, don't care the exception. * @throws Throwable e */ void justDo() throws Throwable; } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/HasDockerAndItEnabled.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.extension.ExtendWith; /** * Disables test execution if Docker is unavailable. *

* We don't want to run integration tests on local machine, but still give a chance to run * it. *

* Typically, used for CI and local integration test. *

* Set system property * {@link HasDockerAndItEnabledCondition#RUN_INTEGRATION_TESTS_PROPERTY} to 'true' *

* general usage: {@code mvn -Dit.enabled=true test} *

* `it` means integration test * * @author freeman * @since 2021.0.1.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(HasDockerAndItEnabledCondition.class) public @interface HasDockerAndItEnabled { } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/HasDockerAndItEnabledCondition.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.testcontainers.DockerClientFactory; /** * An {@link ExecutionCondition} that disables execution if Docker is unavailable. * * @author freeman * @since 2021.0.1.0 */ class HasDockerAndItEnabledCondition implements ExecutionCondition { private static final String RUN_INTEGRATION_TESTS_PROPERTY = "it.enabled"; private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult .enabled("Docker available."); private static final ConditionEvaluationResult DISABLED = ConditionEvaluationResult .disabled("Default not run integration tests, you can set '" + RUN_INTEGRATION_TESTS_PROPERTY + "=true' to enable."); private static final ConditionEvaluationResult DOCKER_DISABLED = ConditionEvaluationResult .disabled("Docker unavailable."); @Override public ConditionEvaluationResult evaluateExecutionCondition( ExtensionContext context) { try { if (Boolean .parseBoolean(System.getProperty(RUN_INTEGRATION_TESTS_PROPERTY))) { DockerClientFactory.instance().client(); return ENABLED; } else { return DISABLED; } } catch (Throwable ignored) { return DOCKER_DISABLED; } } } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/InetUtil.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; import java.net.InetAddress; import java.net.UnknownHostException; /** * * @author freeman * @since 2021.0.1.0 */ public final class InetUtil { /** * Get host ip. * *

* It's very useful for the container test. * * @return host ip */ public static String getHostIp() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { throw new RuntimeException(e); } } /** * Get host name. * * @return host name */ public static String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { throw new RuntimeException(e); } } } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/SpringCloudAlibaba.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtendWith; import org.testcontainers.junit.jupiter.Testcontainers; @Inherited @Testcontainers @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @TestMethodOrder(OrderAnnotation.class) @ExtendWith(SpringCloudAlibabaExtension.class) public @interface SpringCloudAlibaba { String[] composeFiles(); String serviceName(); } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/SpringCloudAlibabaExtension.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; import java.io.File; import java.io.IOException; import java.net.URL; import java.time.Duration; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.testcontainers.Testcontainers; import org.testcontainers.containers.DockerComposeContainer; import org.testcontainers.shaded.org.awaitility.Awaitility; @Slf4j final class SpringCloudAlibabaExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback { private final boolean LOCAL_MODE = Objects.equals(System.getProperty("local"), "true"); private DockerComposeContainer compose; @Override @SuppressWarnings("UnstableApiUsage") public void beforeAll(ExtensionContext context) throws IOException { Awaitility.setDefaultTimeout(Duration.ofSeconds(60)); Awaitility.setDefaultPollInterval(Duration.ofSeconds(10)); if (LOCAL_MODE) { runInLocal(); } else { runInDockerContainer(context); } } private void runInLocal() { Testcontainers.exposeHostPorts(3000); } private void runInDockerContainer(ExtensionContext context) { compose = createDockerCompose(context); compose.start(); } @Override public void afterAll(ExtensionContext context) { if (compose != null) { compose.stop(); } } @Override public void beforeEach(ExtensionContext context) { final Object instance = context.getRequiredTestInstance(); Stream.of(instance.getClass().getDeclaredFields()); } private DockerComposeContainer createDockerCompose(ExtensionContext context) { final Class clazz = context.getRequiredTestClass(); final SpringCloudAlibaba annotation = clazz .getAnnotation(SpringCloudAlibaba.class); final List files = Stream.of(annotation.composeFiles()) .map(it -> SpringCloudAlibaba.class.getClassLoader().getResource(it)) .filter(Objects::nonNull).map(URL::getPath).map(File::new) .collect(Collectors.toList()); compose = new DockerComposeContainer<>(files).withPull(true) .withTailChildContainers(true); return compose; } } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/TestExtend.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.extension.ExtendWith; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @ExtendWith(TestTimeoutExtension.class) public @interface TestExtend { long time(); } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/TestTimeoutExtension.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; public class TestTimeoutExtension implements BeforeAllCallback, BeforeEachCallback, AfterAllCallback { @Override public void afterAll(ExtensionContext context) throws Exception { } @Override public void beforeAll(ExtensionContext context) throws Exception { } @Override public void beforeEach(ExtensionContext context) throws Exception { final Class clazz = context.getRequiredTestClass(); final TestExtend annotation = clazz.getAnnotation(TestExtend.class); ScheduledExecutorService singletonThread = Executors .newSingleThreadScheduledExecutor(); while (!singletonThread.awaitTermination(annotation.time(), TimeUnit.MILLISECONDS)) { singletonThread.shutdown(); } } } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/Tester.java ================================================ /* * Copyright 2013-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.cloud.testsupport; /** * * @author freeman * @since 2021.0.1.0 */ public class Tester { /** * Test function. * @param function function name * @param func func */ public static void testFunction(String function, Func func) { try { System.out.println( "============================================================================"); System.out.println("Testing '" + function + "' ......"); System.out.println( "============================================================================"); func.justDo(); System.out.println( "============================================================================"); System.out.println("Function '" + function + "' OK !"); System.out.println( "============================================================================\n"); } catch (Throwable e) { System.err.println( "============================================================================"); System.err.println("Function '" + function + "' err !"); System.err.println( "============================================================================\n"); throw new RuntimeException(e); } } /** * Do it, don't care exception. * @param func func */ public static void justDo(Func func) { try { func.justDo(); } catch (Throwable e) { throw new RuntimeException(e); } } } ================================================ FILE: spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/resources/rocketmq/conf/broker.conf ================================================ brokerClusterName = DefaultCluster brokerName = broker-a brokerId = 0 deleteWhen = 04 fileReservedTime = 48 brokerRole = ASYNC_MASTER flushDiskType = ASYNC_FLUSH