├── .gitignore ├── LICENSE ├── README.md ├── dubbo ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── dubbo │ │ ├── api │ │ └── DemoService.java │ │ ├── client │ │ └── Consumer.java │ │ └── service │ │ ├── DemoServiceImpl.java │ │ └── Server.java │ └── resources │ ├── META-INF │ └── app.properties │ ├── consumer.xml │ ├── log4j2.xml │ └── server.xml ├── dynamic-datasource ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── dynamic │ │ └── datasource │ │ ├── Application.java │ │ ├── RefreshableDataSourceConfiguration.java │ │ ├── ds │ │ └── DynamicDataSource.java │ │ ├── entity │ │ └── User.java │ │ ├── repository │ │ └── UserRepository.java │ │ └── util │ │ ├── CustomizedConfigurationPropertiesBinder.java │ │ ├── DataSourceManager.java │ │ ├── DataSourceRefresher.java │ │ └── DataSourceTerminationTask.java │ └── resources │ ├── application.properties │ └── sql │ ├── test1.sql │ └── test2.sql ├── netflix-archaius ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── netflix │ │ └── archaius │ │ └── Application.java │ └── resources │ └── log4j2.xml ├── pom.xml ├── properties-keeper ├── README.md ├── fetch_properties.sh └── pom.xml ├── sentinel ├── README.md └── pom.xml ├── spring-boot-agent ├── README.md ├── apollo-agent │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── agent │ │ └── ApolloAgent.java ├── app │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com.ctrip.framevowrk.apollo.use.cases.agent │ │ │ └── Application.java │ │ └── resources │ │ └── application.properties └── pom.xml ├── spring-boot-dubbo ├── README.md ├── pom.xml ├── spring-boot-dubbo-api │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── spring │ │ └── boot │ │ └── starter │ │ └── dubbo │ │ └── api │ │ └── DemoService.java ├── spring-boot-dubbo-consumer │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── ctrip │ │ │ └── framework │ │ │ └── apollo │ │ │ └── use │ │ │ └── cases │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ └── dubbo │ │ │ └── consumer │ │ │ └── Consumer.java │ │ └── resources │ │ ├── application.properties │ │ └── log4j2.xml └── spring-boot-dubbo-provider │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── spring │ │ └── boot │ │ └── starter │ │ └── dubbo │ │ └── provider │ │ ├── DemoServiceImpl.java │ │ └── Server.java │ └── resources │ ├── application.properties │ └── logback-spring.xml ├── spring-boot-encrypt ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── spring │ │ └── boot │ │ └── encrypt │ │ ├── Application.java │ │ └── EncryptUtil.java │ └── resources │ └── application.properties ├── spring-boot-logger ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── spring │ │ └── boot │ │ └── logger │ │ ├── Application.java │ │ ├── LoggerConfiguration.java │ │ └── PrintLogger.java │ └── resources │ └── application.properties ├── spring-cloud-gateway ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── spring │ │ └── cloud │ │ └── gateway │ │ ├── GatewayPropertiesRefresher.java │ │ └── SpringCloudGatewayApplication.java │ └── resources │ └── application.properties ├── spring-cloud-logger ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── spring │ │ └── cloud │ │ └── logger │ │ ├── Application.java │ │ ├── LoggerLevelRefresher.java │ │ └── PrintLogger.java │ └── resources │ └── application.properties ├── spring-cloud-zuul-ratelimit ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── spring │ │ └── cloud │ │ └── zuul │ │ ├── Application.java │ │ └── ZuulRateLimitPropertiesRefreshConfig.java │ └── resources │ ├── application.properties │ └── log4j2.xml ├── spring-cloud-zuul ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── ctrip │ │ └── framework │ │ └── apollo │ │ └── use │ │ └── cases │ │ └── spring │ │ └── cloud │ │ └── zuul │ │ ├── Application.java │ │ └── ZuulPropertiesRefresher.java │ └── resources │ ├── application.properties │ └── log4j2.xml └── spring-mvc-logger ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── ctrip │ └── framework │ └── apollo │ └── use │ └── cases │ └── spring │ └── mvc │ └── logger │ └── LoggerStartupListener.java ├── resources ├── META-INF │ └── app.properties ├── logback.xml └── spring-web.xml └── webapp └── WEB-INF └── web.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | .DS_Store 3 | application.pid 4 | 5 | # Mobile Tools for Java (J2ME) 6 | .mtj.tmp/ 7 | 8 | # Package Files # 9 | *.jar 10 | *.war 11 | *.ear 12 | 13 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 14 | hs_err_pid* 15 | 16 | # Eclipse 17 | .classpath 18 | .project 19 | target 20 | .settings 21 | 22 | # Idea 23 | .idea 24 | *.iml 25 | 26 | # git 27 | *.orig 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 展示Apollo配置中心的各种使用场景和示例代码,目前包含了以下示例项目: 4 | 5 | * [spring-boot-logger](spring-boot-logger):演示[Spring Boot Logging](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html)如何通过Apollo配置中心实现动态调整Logging Level 6 | * [spring-cloud-logger](spring-cloud-logger):演示[Spring Boot Logging](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html)在Spring Cloud环境下如何通过Apollo配置中心方便地实现动态调整Logging Level 7 | * [spring-cloud-zuul](spring-cloud-zuul):演示[Spring Cloud Zuul](https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#netflix-zuul-reverse-proxy)如何通过Apollo配置中心实现动态路由 8 | * [spring-cloud-zuul-ratelimit](spring-cloud-zuul-ratelimit):演示[Spring Cloud Zuul](https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#netflix-zuul-reverse-proxy)的第三方限流插件[marcosbarbero/spring-cloud-zuul-ratelimit](https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit)如何通过Apollo配置中心实现动态限流 9 | * [spring-cloud-gateway](spring-cloud-gateway):演示[Spring Cloud Gateway](https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.3.RELEASE/single/spring-cloud-gateway.html)如何通过Apollo配置中心实现动态路由 10 | * [spring-boot-encrypt](spring-boot-encrypt):演示如何结合[jasypt-spring-boot](https://github.com/ulisesbocchio/jasypt-spring-boot)实现Apollo中存储加密配置 11 | * [dynamic-datasource](dynamic-datasource):演示[Spring Boot默认的HikariCP DataSource](https://github.com/brettwooldridge/HikariCP)如何通过Apollo配置中心实现动态切换数据源(其它类型的DataSource也是类似的,可以依样画葫芦) 12 | * [dubbo](dubbo): 演示[Dubbo](https://github.com/apache/incubator-dubbo)如何通过Apollo配置中心实现中心化配置 13 | * [spring-boot-dubbo](spring-boot-dubbo): 演示[Dubbo Spring Boot Starter](https://github.com/apache/incubator-dubbo-spring-boot-project)如何通过Apollo配置中心实现中心化配置 14 | * 该项目同时也演示了如何通过apollo管理logback的配置,详见[logback-spring.xml](https://github.com/ctripcorp/apollo-use-cases/blob/master/spring-boot-dubbo/spring-boot-dubbo-provider/src/main/resources/logback-spring.xml) 15 | * [netflix-archaius](netflix-archaius): 演示[Netflix Archaius](https://github.com/Netflix/archaius)如何使用Apollo配置中心作为其服务端使用 16 | * [sentinel](sentinel): 演示[Sentinel](https://github.com/alibaba/Sentinel)如何通过Apollo配置中心实现中心化流控规则配置 17 | * [properties-keeper](properties-keeper): 演示如何通过apollo管理启动前需要加载的properties文件配置 18 | * [spring-boot-agent](spring-boot-agent): 演示如何通过java agent探针技术实现应用无缝接入Apollo配置中心 19 | * [spring-mvc-logger](spring-mvc-logger): 演示Spring/SpringMVC项目下如何通过Apollo配置中心实现动态调整日志的属性值 20 | 21 | 欢迎大家把日常工作中的更多配置使用案例分享出来,提交Pull Request即可! 22 | 23 | # Instructions 24 | 25 | 1. 部署并启动Apollo配置中心 26 | * 请参考[分布式部署指南](https://github.com/ctripcorp/apollo/wiki/%E5%88%86%E5%B8%83%E5%BC%8F%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97) 27 | * 如果只是Demo用途的话,可以参考[Quick Start](https://github.com/ctripcorp/apollo/wiki/Quick-Start)文档快速地在本地启动一套Apollo配置中心,或者参考[Apollo开发指南](https://github.com/ctripcorp/apollo/wiki/Apollo%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97)通过IDE在本地启动一套Apollo配置中心 28 | 2. 配置Apollo Meta信息 29 | * Apollo支持应用在不同的环境有不同的配置,所以需要配置Apollo Meta信息 30 | * 示例代码在`application.properties`或System Property配置了`apollo.meta=http://localhost:8080`,请根据实际部署情况调整该配置 31 | 3. 以上步骤都完成后,就可以参考各子模块的README.md来运行示例项目了 32 | -------------------------------------------------------------------------------- /dubbo/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示[Dubbo](https://github.com/apache/incubator-dubbo)如何通过Apollo配置中心实现中心化配置 4 | 5 | # Instructions 6 | 7 | 1. 在Apollo配置中心创建AppId为`dubbo`的项目 8 | 2. 在默认的`application`下配置zookeerper的地址,注意key为`zookeeper.address` 9 | * 如:zookeeper.address = 127.0.0.1:2181 10 | 3. 启动zookeeper 11 | 4. 运行`com.ctrip.framework.apollo.use.cases.dubbo.service.Server`启动Demo服务端 12 | 5. 运行`com.ctrip.framework.apollo.use.cases.dubbo.client.Consumer`启动Demo调用端 13 | 6. 在调用端输入任意字符后按回车,即可发起一次Dubbo服务请求并输出服务端的响应 14 | * 如输入`dubbo`,服务端会响应`Hello dubbo` 15 | -------------------------------------------------------------------------------- /dubbo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | dubbo 13 | 14 | 15 | 16 | com.alibaba 17 | dubbo 18 | 19 | 20 | org.apache.zookeeper 21 | zookeeper 22 | 23 | 24 | log4j 25 | log4j 26 | 27 | 28 | slf4j-log4j12 29 | org.slf4j 30 | 31 | 32 | 33 | 34 | com.101tec 35 | zkclient 36 | 37 | 38 | org.apache.curator 39 | curator-framework 40 | 41 | 42 | org.apache.logging.log4j 43 | log4j-slf4j-impl 44 | 45 | 46 | org.apache.logging.log4j 47 | log4j-api 48 | 49 | 50 | org.apache.logging.log4j 51 | log4j-core 52 | 53 | 54 | org.slf4j 55 | log4j-over-slf4j 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /dubbo/src/main/java/com/ctrip/framework/apollo/use/cases/dubbo/api/DemoService.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dubbo.api; 2 | 3 | public interface DemoService { 4 | 5 | String sayHello(String name); 6 | } 7 | -------------------------------------------------------------------------------- /dubbo/src/main/java/com/ctrip/framework/apollo/use/cases/dubbo/client/Consumer.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dubbo.client; 2 | 3 | import com.ctrip.framework.apollo.core.ConfigConsts; 4 | import com.ctrip.framework.apollo.use.cases.dubbo.api.DemoService; 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.nio.charset.StandardCharsets; 9 | import org.springframework.context.support.ClassPathXmlApplicationContext; 10 | 11 | public class Consumer { 12 | 13 | public static void main(String[] args) throws IOException { 14 | // set apollo meta server address, adjust to actual address if necessary 15 | System.setProperty(ConfigConsts.APOLLO_META_KEY, "http://localhost:8080"); 16 | 17 | // run with -Djava.net.preferIPv4Stack=true if met 'Can't assign requested address' error 18 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml"); 19 | context.start(); 20 | 21 | DemoService demoService = (DemoService) context.getBean("demoService"); 22 | 23 | System.out.println("Please input any key to test. Input quit to exit."); 24 | while (true) { 25 | System.out.print("> "); 26 | String input = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)).readLine(); 27 | if (input == null || input.length() == 0) { 28 | continue; 29 | } 30 | input = input.trim(); 31 | if (input.equalsIgnoreCase("quit")) { 32 | System.exit(0); 33 | } 34 | System.out.println(demoService.sayHello(input)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /dubbo/src/main/java/com/ctrip/framework/apollo/use/cases/dubbo/service/DemoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dubbo.service; 2 | 3 | import com.ctrip.framework.apollo.use.cases.dubbo.api.DemoService; 4 | 5 | public class DemoServiceImpl implements DemoService { 6 | 7 | @Override 8 | public String sayHello(String name) { 9 | return "Hello " + name; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /dubbo/src/main/java/com/ctrip/framework/apollo/use/cases/dubbo/service/Server.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dubbo.service; 2 | 3 | import com.ctrip.framework.apollo.core.ConfigConsts; 4 | import java.io.IOException; 5 | import org.springframework.context.support.ClassPathXmlApplicationContext; 6 | 7 | public class Server { 8 | 9 | public static void main(String[] args) throws IOException { 10 | // set apollo meta server address, adjust to actual address if necessary 11 | System.setProperty(ConfigConsts.APOLLO_META_KEY, "http://localhost:8080"); 12 | 13 | // run with -Djava.net.preferIPv4Stack=true if met 'Can't assign requested address' error 14 | new ClassPathXmlApplicationContext("server.xml").start(); 15 | 16 | System.in.read(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /dubbo/src/main/resources/META-INF/app.properties: -------------------------------------------------------------------------------- 1 | app.id=dubbo 2 | -------------------------------------------------------------------------------- /dubbo/src/main/resources/consumer.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | -------------------------------------------------------------------------------- /dubbo/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /dubbo/src/main/resources/server.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | -------------------------------------------------------------------------------- /dynamic-datasource/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示[Spring Boot默认的HikariCP DataSource](https://github.com/brettwooldridge/HikariCP)如何通过Apollo配置中心实现动态切换数据源(其它类型的DataSource也是类似的,可以依样画葫芦) 4 | 5 | # Instructions 6 | 7 | 1. 创建test1数据库,导入test1.sql 8 | 2. 创建test2数据库,导入test2.sql 9 | 3. 在Apollo配置中心创建AppId为`dynamic-datasource`的项目 10 | 2. 在默认的`application`下做如下配置(按照实际的数据库连接信息填写): 11 | 12 | ```properties 13 | spring.datasource.url = jdbc:mysql://127.0.0.1:3306/test1?autoReconnect=true&useUnicode=true&characterEncoding=utf-8 14 | spring.datasource.username = xxx-user 15 | spring.datasource.password = xxx-password 16 | # hikari specific settings 17 | spring.datasource.hikari.maximumPoolSize = 1 18 | ``` 19 | 3. 运行`com.ctrip.framework.apollo.use.cases.dynamic.datasource.Application`启动Demo 20 | 4. 程序启动后会持续打印kl 21 | 5. 在Apollo配置中心修改配置,把`spring.datasource.url`的值切换到`test2`并发布配置 22 | 6. 程序会持续打印ckl,说明动态切换数据源生效了 23 | 7. 更多信息可以参见博文:http://www.kailing.pub/article/index/arcid/198.html 24 | -------------------------------------------------------------------------------- /dynamic-datasource/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | dynamic-datasource 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter 17 | 18 | 19 | org.springframework.cloud 20 | spring-cloud-context 21 | 22 | 23 | mysql 24 | mysql-connector-java 25 | 26 | 27 | com.zaxxer 28 | HikariCP 29 | 2.7.8 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-data-jpa 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/java/com/ctrip/framework/apollo/use/cases/dynamic/datasource/Application.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dynamic.datasource; 2 | 3 | import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; 4 | import com.ctrip.framework.apollo.use.cases.dynamic.datasource.repository.UserRepository; 5 | import java.util.concurrent.Executors; 6 | import java.util.concurrent.TimeUnit; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.SpringBootApplication; 11 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 12 | 13 | /** 14 | * Created by kl on 2018/6/25. Content :动态数据源实例 15 | */ 16 | @EnableApolloConfig 17 | @SpringBootApplication 18 | public class Application implements CommandLineRunner { 19 | 20 | public static void main(String[] args) { 21 | SpringApplication.run(Application.class, args); 22 | } 23 | 24 | @Autowired 25 | private UserRepository userRepository; 26 | 27 | @Override 28 | public void run(String... args) throws Exception { 29 | Executors.newSingleThreadExecutor().submit(() -> { 30 | while (true) { 31 | try { 32 | System.err.println(userRepository.findById(1).get().getName()); 33 | TimeUnit.SECONDS.sleep(1); 34 | } catch (Throwable ex) { 35 | ex.printStackTrace(); 36 | } 37 | } 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/java/com/ctrip/framework/apollo/use/cases/dynamic/datasource/RefreshableDataSourceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dynamic.datasource; 2 | 3 | import com.ctrip.framework.apollo.use.cases.dynamic.datasource.ds.DynamicDataSource; 4 | import com.ctrip.framework.apollo.use.cases.dynamic.datasource.util.DataSourceManager; 5 | import javax.sql.DataSource; 6 | import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.cloud.context.config.annotation.RefreshScope; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Primary; 12 | 13 | @Configuration 14 | public class RefreshableDataSourceConfiguration { 15 | 16 | @Bean 17 | public DynamicDataSource dataSource(DataSourceManager dataSourceManager) { 18 | DataSource actualDataSource = dataSourceManager.createDataSource(); 19 | return new DynamicDataSource(actualDataSource); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/java/com/ctrip/framework/apollo/use/cases/dynamic/datasource/ds/DynamicDataSource.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dynamic.datasource.ds; 2 | 3 | import java.io.PrintWriter; 4 | import java.sql.Connection; 5 | import java.sql.SQLException; 6 | import java.sql.SQLFeatureNotSupportedException; 7 | import java.util.concurrent.atomic.AtomicReference; 8 | import javax.sql.DataSource; 9 | 10 | /** 11 | * A sample refreshable data source 12 | */ 13 | public class DynamicDataSource implements DataSource { 14 | private final AtomicReference dataSourceAtomicReference; 15 | 16 | public DynamicDataSource(DataSource dataSource) { 17 | dataSourceAtomicReference = new AtomicReference<>(dataSource); 18 | } 19 | 20 | /** 21 | * set the new data source and return the previous one 22 | */ 23 | public DataSource setDataSource(DataSource newDataSource){ 24 | return dataSourceAtomicReference.getAndSet(newDataSource); 25 | } 26 | 27 | @Override 28 | public Connection getConnection() throws SQLException { 29 | return dataSourceAtomicReference.get().getConnection(); 30 | } 31 | 32 | @Override 33 | public Connection getConnection(String username, String password) throws SQLException { 34 | return dataSourceAtomicReference.get().getConnection(username, password); 35 | } 36 | 37 | @Override 38 | public T unwrap(Class iface) throws SQLException { 39 | return dataSourceAtomicReference.get().unwrap(iface); 40 | } 41 | 42 | @Override 43 | public boolean isWrapperFor(Class iface) throws SQLException { 44 | return dataSourceAtomicReference.get().isWrapperFor(iface); 45 | } 46 | 47 | @Override 48 | public PrintWriter getLogWriter() throws SQLException { 49 | return dataSourceAtomicReference.get().getLogWriter(); 50 | } 51 | 52 | @Override 53 | public void setLogWriter(PrintWriter out) throws SQLException { 54 | dataSourceAtomicReference.get().setLogWriter(out); 55 | } 56 | 57 | @Override 58 | public void setLoginTimeout(int seconds) throws SQLException { 59 | dataSourceAtomicReference.get().setLoginTimeout(seconds); 60 | } 61 | 62 | @Override 63 | public int getLoginTimeout() throws SQLException { 64 | return dataSourceAtomicReference.get().getLoginTimeout(); 65 | } 66 | 67 | @Override 68 | public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { 69 | return dataSourceAtomicReference.get().getParentLogger(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/java/com/ctrip/framework/apollo/use/cases/dynamic/datasource/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dynamic.datasource.entity; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.Id; 5 | 6 | /** 7 | * Created by kl on 2018/6/25. Content : 8 | */ 9 | @Entity 10 | public class User { 11 | 12 | @Id 13 | private int id; 14 | private String name; 15 | 16 | public int getId() { 17 | return id; 18 | } 19 | 20 | public void setId(int id) { 21 | this.id = id; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/java/com/ctrip/framework/apollo/use/cases/dynamic/datasource/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dynamic.datasource.repository; 2 | 3 | import com.ctrip.framework.apollo.use.cases.dynamic.datasource.entity.User; 4 | import org.springframework.data.repository.PagingAndSortingRepository; 5 | 6 | public interface UserRepository extends PagingAndSortingRepository { 7 | } 8 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/java/com/ctrip/framework/apollo/use/cases/dynamic/datasource/util/CustomizedConfigurationPropertiesBinder.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dynamic.datasource.util; 2 | 3 | import java.util.function.Consumer; 4 | import org.springframework.beans.BeansException; 5 | import org.springframework.beans.PropertyEditorRegistry; 6 | import org.springframework.boot.context.properties.bind.BindHandler; 7 | import org.springframework.boot.context.properties.bind.Bindable; 8 | import org.springframework.boot.context.properties.bind.Binder; 9 | import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; 10 | import org.springframework.boot.context.properties.bind.handler.IgnoreTopLevelConverterNotFoundBindHandler; 11 | import org.springframework.boot.context.properties.source.ConfigurationPropertySource; 12 | import org.springframework.boot.context.properties.source.ConfigurationPropertySources; 13 | import org.springframework.context.ApplicationContext; 14 | import org.springframework.context.ApplicationContextAware; 15 | import org.springframework.context.ConfigurableApplicationContext; 16 | import org.springframework.core.convert.ConversionService; 17 | import org.springframework.core.env.PropertySources; 18 | import org.springframework.stereotype.Component; 19 | 20 | /** 21 | * @see org.springframework.boot.context.properties.ConfigurationPropertiesBinder 22 | */ 23 | @Component 24 | public class CustomizedConfigurationPropertiesBinder implements ApplicationContextAware { 25 | 26 | private ConfigurableApplicationContext applicationContext; 27 | private PropertySources propertySources; 28 | private Binder binder; 29 | 30 | public void bind(String configPrefix, Bindable bean) { 31 | BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler(); 32 | this.binder.bind(configPrefix, bean, handler); 33 | } 34 | 35 | @Override 36 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 37 | this.applicationContext = (ConfigurableApplicationContext) applicationContext; 38 | this.propertySources = this.applicationContext.getEnvironment().getPropertySources(); 39 | this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(), 40 | getConversionService(), getPropertyEditorInitializer()); 41 | } 42 | 43 | private Iterable getConfigurationPropertySources() { 44 | return ConfigurationPropertySources.from(this.propertySources); 45 | } 46 | 47 | private PropertySourcesPlaceholdersResolver getPropertySourcesPlaceholdersResolver() { 48 | return new PropertySourcesPlaceholdersResolver(this.propertySources); 49 | } 50 | 51 | private ConversionService getConversionService() { 52 | return this.applicationContext.getBeanFactory().getConversionService(); 53 | } 54 | 55 | private Consumer getPropertyEditorInitializer() { 56 | return this.applicationContext.getBeanFactory()::copyRegisteredEditorsTo; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/java/com/ctrip/framework/apollo/use/cases/dynamic/datasource/util/DataSourceManager.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dynamic.datasource.util; 2 | 3 | import com.zaxxer.hikari.HikariDataSource; 4 | import java.sql.Connection; 5 | import java.sql.SQLException; 6 | import javax.sql.DataSource; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; 11 | import org.springframework.boot.context.properties.bind.Bindable; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.StringUtils; 14 | 15 | @Component 16 | public class DataSourceManager { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(DataSourceManager.class); 19 | 20 | @Autowired 21 | private CustomizedConfigurationPropertiesBinder binder; 22 | 23 | @Autowired 24 | private DataSourceProperties dataSourceProperties; 25 | 26 | /** 27 | * create a hikari data source 28 | * @see org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.Hikari#dataSource 29 | */ 30 | public HikariDataSource createDataSource() { 31 | HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); 32 | if (StringUtils.hasText(dataSourceProperties.getName())) { 33 | dataSource.setPoolName(dataSourceProperties.getName()); 34 | } 35 | Bindable target = Bindable.of(HikariDataSource.class).withExistingValue(dataSource); 36 | this.binder.bind("spring.datasource.hikari", target); 37 | return dataSource; 38 | } 39 | 40 | public HikariDataSource createAndTestDataSource() throws SQLException { 41 | HikariDataSource newDataSource = createDataSource(); 42 | try { 43 | testConnection(newDataSource); 44 | } catch (SQLException ex) { 45 | logger.error("Testing connection for data source failed: {}", newDataSource.getJdbcUrl(), ex); 46 | newDataSource.close(); 47 | throw ex; 48 | } 49 | 50 | return newDataSource; 51 | } 52 | 53 | private void testConnection(DataSource dataSource) throws SQLException { 54 | //borrow a connection 55 | Connection connection = dataSource.getConnection(); 56 | //return the connection 57 | connection.close(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/java/com/ctrip/framework/apollo/use/cases/dynamic/datasource/util/DataSourceRefresher.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dynamic.datasource.util; 2 | 3 | import com.ctrip.framework.apollo.model.ConfigChangeEvent; 4 | import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; 5 | import com.ctrip.framework.apollo.use.cases.dynamic.datasource.ds.DynamicDataSource; 6 | import java.util.Set; 7 | import java.util.concurrent.Executors; 8 | import java.util.concurrent.ScheduledExecutorService; 9 | import java.util.concurrent.TimeUnit; 10 | import javax.sql.DataSource; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.beans.BeansException; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.cloud.context.environment.EnvironmentChangeEvent; 16 | import org.springframework.context.ApplicationContext; 17 | import org.springframework.context.ApplicationContextAware; 18 | import org.springframework.stereotype.Component; 19 | 20 | @Component 21 | public class DataSourceRefresher implements ApplicationContextAware { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(DataSourceRefresher.class); 24 | 25 | private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); 26 | 27 | @Autowired 28 | private DynamicDataSource dynamicDataSource; 29 | 30 | @Autowired 31 | private DataSourceManager dataSourceManager; 32 | 33 | @Autowired 34 | private ApplicationContext applicationContext; 35 | 36 | @ApolloConfigChangeListener(interestedKeyPrefixes = "spring.datasource.") 37 | public void onChange(ConfigChangeEvent changeEvent) { 38 | refreshDataSource(changeEvent.changedKeys()); 39 | } 40 | 41 | private synchronized void refreshDataSource(Set changedKeys) { 42 | try { 43 | logger.info("Refreshing data source"); 44 | 45 | /** 46 | * rebind configuration beans, e.g. DataSourceProperties 47 | * @see org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent 48 | */ 49 | this.applicationContext.publishEvent(new EnvironmentChangeEvent(changedKeys)); 50 | 51 | DataSource newDataSource = dataSourceManager.createAndTestDataSource(); 52 | DataSource oldDataSource = dynamicDataSource.setDataSource(newDataSource); 53 | asyncTerminate(oldDataSource); 54 | 55 | logger.info("Finished refreshing data source"); 56 | } catch (Throwable ex) { 57 | logger.error("Refreshing data source failed", ex); 58 | } 59 | } 60 | 61 | private void asyncTerminate(DataSource dataSource) { 62 | DataSourceTerminationTask task = new DataSourceTerminationTask(dataSource, scheduledExecutorService); 63 | 64 | //start now 65 | scheduledExecutorService.schedule(task, 0, TimeUnit.MILLISECONDS); 66 | } 67 | 68 | @Override 69 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 70 | this.applicationContext = applicationContext; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/java/com/ctrip/framework/apollo/use/cases/dynamic/datasource/util/DataSourceTerminationTask.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.dynamic.datasource.util; 2 | 3 | import com.zaxxer.hikari.HikariDataSource; 4 | import com.zaxxer.hikari.HikariPoolMXBean; 5 | import java.util.concurrent.ScheduledExecutorService; 6 | import java.util.concurrent.TimeUnit; 7 | import javax.sql.DataSource; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | public class DataSourceTerminationTask implements Runnable { 12 | 13 | private static final Logger logger = LoggerFactory.getLogger(DataSourceTerminationTask.class); 14 | private static final int MAX_RETRY_TIMES = 10; 15 | private static final int RETRY_DELAY_IN_MILLISECONDS = 5000; 16 | 17 | private final DataSource dataSourceToTerminate; 18 | private final ScheduledExecutorService scheduledExecutorService; 19 | 20 | private volatile int retryTimes; 21 | 22 | public DataSourceTerminationTask(DataSource dataSourceToTerminate, 23 | ScheduledExecutorService scheduledExecutorService) { 24 | this.dataSourceToTerminate = dataSourceToTerminate; 25 | this.scheduledExecutorService = scheduledExecutorService; 26 | this.retryTimes = 0; 27 | } 28 | 29 | @Override 30 | public void run() { 31 | if (terminate(dataSourceToTerminate)) { 32 | logger.info("Data source {} terminated successfully!", dataSourceToTerminate); 33 | } else { 34 | scheduledExecutorService.schedule(this, RETRY_DELAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS); 35 | } 36 | } 37 | 38 | private boolean terminate(DataSource dataSource) { 39 | logger.info("Trying to terminate data source: {}", dataSource); 40 | 41 | try { 42 | if (dataSource instanceof HikariDataSource) { 43 | return terminateHikariDataSource((HikariDataSource) dataSource); 44 | } 45 | 46 | logger.error("Not supported data source: {}", dataSource); 47 | 48 | return true; 49 | } catch (Throwable ex) { 50 | logger.warn("Terminating data source {} failed, will retry in {} ms, error message: {}", dataSource, 51 | RETRY_DELAY_IN_MILLISECONDS, ex.getMessage()); 52 | return false; 53 | } finally { 54 | retryTimes++; 55 | } 56 | } 57 | 58 | /** 59 | * @see Support graceful shutdown of connection 60 | * pool 61 | */ 62 | private boolean terminateHikariDataSource(HikariDataSource dataSource) { 63 | HikariPoolMXBean poolMXBean = dataSource.getHikariPoolMXBean(); 64 | 65 | if (poolMXBean != null) { 66 | //evict idle connections 67 | poolMXBean.softEvictConnections(); 68 | 69 | if (poolMXBean.getActiveConnections() > 0 && retryTimes < MAX_RETRY_TIMES) { 70 | logger.warn("Data source {} still has {} active connections, will retry in {} ms.", 71 | dataSource, 72 | poolMXBean.getActiveConnections(), RETRY_DELAY_IN_MILLISECONDS); 73 | return false; 74 | } 75 | 76 | if (poolMXBean.getActiveConnections() > 0) { 77 | logger.warn( 78 | "Retry times({}) >= {}, force closing data source {}, with {} active connections!", 79 | retryTimes, 80 | MAX_RETRY_TIMES, dataSource, poolMXBean.getActiveConnections()); 81 | } 82 | } 83 | 84 | dataSource.close(); 85 | 86 | return true; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | app.id=dynamic-datasource 2 | # set apollo meta server address, adjust to actual address if necessary 3 | apollo.meta=http://localhost:8080 4 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/resources/sql/test1.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : 本地数据库 5 | Source Server Version : 50624 6 | Source Host : 127.0.0.1:3306 7 | Source Database : test1 8 | 9 | Target Server Type : MYSQL 10 | Target Server Version : 50624 11 | File Encoding : 65001 12 | 13 | Date: 2018-06-25 17:56:09 14 | */ 15 | 16 | SET FOREIGN_KEY_CHECKS=0; 17 | 18 | -- ---------------------------- 19 | -- Table structure for user 20 | -- ---------------------------- 21 | DROP TABLE IF EXISTS `user`; 22 | CREATE TABLE `user` ( 23 | `id` int(11) NOT NULL, 24 | `name` varchar(255) DEFAULT NULL, 25 | PRIMARY KEY (`id`) 26 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 27 | 28 | -- ---------------------------- 29 | -- Records of user 30 | -- ---------------------------- 31 | INSERT INTO `user` VALUES ('1', 'kl'); 32 | -------------------------------------------------------------------------------- /dynamic-datasource/src/main/resources/sql/test2.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : 本地数据库 5 | Source Server Version : 50624 6 | Source Host : 127.0.0.1:3306 7 | Source Database : test2 8 | 9 | Target Server Type : MYSQL 10 | Target Server Version : 50624 11 | File Encoding : 65001 12 | 13 | Date: 2018-06-25 17:56:16 14 | 15 | 16 | 17 | 18 | 19 | 20 | */ 21 | 22 | SET FOREIGN_KEY_CHECKS=0; 23 | 24 | -- ---------------------------- 25 | -- Table structure for user 26 | -- ---------------------------- 27 | DROP TABLE IF EXISTS `user`; 28 | CREATE TABLE `user` ( 29 | `id` int(11) NOT NULL, 30 | `name` varchar(255) DEFAULT NULL, 31 | PRIMARY KEY (`id`) 32 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 33 | 34 | -- ---------------------------- 35 | -- Records of user 36 | -- ---------------------------- 37 | INSERT INTO `user` VALUES ('1', 'ckl'); 38 | -------------------------------------------------------------------------------- /netflix-archaius/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | Netflix开源的一系列组件(Zuul, Hystrix, Eureka, Ribbon)的配置管理使用的是自家的[Archaius](https://github.com/Netflix/archaius),遗憾的是Netflix没有开源Archaius的服务端,现在可以使用Apollo作为Archaius的服务端使用。 4 | 5 | - Apollo可以为Netflix微服务全家桶提供集中化的配置管理 6 | - Archaius可以做为Apollo Client的另一种选择 7 | 8 | # Instructions 9 | 10 | 1. 在Apollo配置中心创建AppId为`netflix-archaius`的项目 11 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 12 | 13 | ```properties 14 | hystrix.command.default.circuitBreaker.forceClosed = false 15 | ``` 16 | 3. 运行`com.ctrip.framework.apollo.use.cases.netflix.archaius.Application`启动Demo 17 | 4. 程序会在控制台输出hystrix.command.default.circuitBreaker.forceClosed参数值的变化 18 | 5. 在Apollo配置中心修改配置,把`hystrix.command.default.circuitBreaker.forceClosed`的值改为`true`并发布配置 19 | * Archaius 默认30秒从服务端更新一次配置信息,所以需要等待30秒配置生效 20 | -------------------------------------------------------------------------------- /netflix-archaius/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | netflix-archaius 13 | 14 | 15 | 16 | com.netflix.archaius 17 | archaius-core 18 | 19 | 20 | org.slf4j 21 | jcl-over-slf4j 22 | 23 | 24 | org.slf4j 25 | log4j-over-slf4j 26 | 27 | 28 | org.apache.logging.log4j 29 | log4j-slf4j-impl 30 | 31 | 32 | org.apache.logging.log4j 33 | log4j-api 34 | 35 | 36 | org.apache.logging.log4j 37 | log4j-core 38 | 39 | 40 | -------------------------------------------------------------------------------- /netflix-archaius/src/main/java/com/ctrip/framework/apollo/use/cases/netflix/archaius/Application.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.netflix.archaius; 2 | 3 | import com.netflix.config.DynamicBooleanProperty; 4 | import com.netflix.config.DynamicPropertyFactory; 5 | import java.time.LocalDateTime; 6 | 7 | public class Application { 8 | 9 | private static final String ARCHAIUS_ADD_ITIONAL_URLS = "archaius.configurationSource.additionalUrls"; 10 | 11 | public static void main(String[] args) throws Exception { 12 | // set apollo meta server address, adjust to actual address if necessary 13 | String apolloConfigServiceUrl = "http://localhost:8080"; 14 | 15 | String appId = "netflix-archaius"; 16 | System.setProperty(ARCHAIUS_ADD_ITIONAL_URLS, 17 | apolloConfigServiceUrl + "/configfiles/" + appId + "/default/application"); 18 | 19 | DynamicBooleanProperty hystrixForceClosedProperty = DynamicPropertyFactory.getInstance() 20 | .getBooleanProperty("hystrix.command.default.circuitBreaker.forceClosed", false); 21 | 22 | //Archaius 默认30秒从服务端更新一次配置信息 23 | hystrixForceClosedProperty.addCallback( 24 | () -> System.out.println("[" + LocalDateTime.now().toString() + "] update forceClosed :" + hystrixForceClosedProperty.get())); 25 | 26 | while (true) { 27 | System.in.read(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /netflix-archaius/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.ctrip.framework.apollo 8 | apollo-use-cases 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | dubbo 13 | spring-boot-dubbo 14 | spring-cloud-zuul 15 | spring-cloud-zuul-ratelimit 16 | spring-boot-logger 17 | dynamic-datasource 18 | spring-boot-encrypt 19 | spring-cloud-logger 20 | netflix-archaius 21 | sentinel 22 | properties-keeper 23 | spring-boot-agent 24 | spring-cloud-gateway 25 | spring-mvc-logger 26 | 27 | 28 | 29 | 1.8 30 | UTF-8 31 | 1.9.2 32 | Cairo-SR2 33 | Finchley.RELEASE 34 | 2.6.12 35 | 0.2.0 36 | 3.4.14 37 | 0.10 38 | 2.12.0 39 | 0.7.6 40 | 2.2.4.RELEASE 41 | 2.17.1 42 | 43 | 44 | 45 | 46 | 47 | 48 | io.spring.platform 49 | platform-bom 50 | ${platform.bom.version} 51 | pom 52 | import 53 | 54 | 55 | 56 | org.springframework.cloud 57 | spring-cloud-dependencies 58 | ${spring.cloud.version} 59 | pom 60 | import 61 | 62 | 63 | 64 | com.ctrip.framework.apollo 65 | apollo-client 66 | ${apollo.version} 67 | 68 | 69 | 70 | com.alibaba 71 | dubbo 72 | ${dubbo.version} 73 | 74 | 75 | com.alibaba.boot 76 | dubbo-spring-boot-starter 77 | ${dubbo.spring.boot.starter.version} 78 | 79 | 80 | 81 | org.apache.zookeeper 82 | zookeeper 83 | ${zookeeper.version} 84 | 85 | 86 | com.101tec 87 | zkclient 88 | ${zkclient.version} 89 | 90 | 91 | org.apache.curator 92 | curator-framework 93 | ${curator.version} 94 | 95 | 96 | org.apache.curator 97 | curator-recipes 98 | ${curator.version} 99 | 100 | 101 | org.apache.curator 102 | curator-x-discovery 103 | ${curator.version} 104 | 105 | 106 | org.apache.curator 107 | curator-test 108 | ${curator.version} 109 | 110 | 111 | 112 | org.apache.logging.log4j 113 | log4j-api 114 | ${log4j2.version} 115 | 116 | 117 | org.apache.logging.log4j 118 | log4j-slf4j-impl 119 | ${log4j2.version} 120 | 121 | 122 | org.apache.logging.log4j 123 | log4j-core 124 | ${log4j2.version} 125 | 126 | 127 | org.apache.logging.log4j 128 | log4j-jul 129 | ${log4j2.version} 130 | 131 | 132 | 133 | com.netflix.archaius 134 | archaius-core 135 | ${archaius.version} 136 | 137 | 138 | commons-logging 139 | commons-logging 140 | 141 | 142 | com.google.code.findbugs 143 | annotations 144 | 145 | 146 | 147 | 148 | com.marcosbarbero.cloud 149 | spring-cloud-zuul-ratelimit 150 | ${spring.cloud.zuul.ratelimit.version} 151 | 152 | 153 | 154 | 155 | 156 | 157 | com.ctrip.framework.apollo 158 | apollo-client 159 | 160 | 161 | 162 | 163 | 164 | 165 | org.apache.maven.plugins 166 | maven-compiler-plugin 167 | 3.3 168 | 169 | true 170 | ${java.version} 171 | ${java.version} 172 | ${project.encoding} 173 | 174 | 175 | 176 | org.apache.maven.plugins 177 | maven-jar-plugin 178 | 2.6 179 | 180 | ${project.artifactId}-${project.version} 181 | 182 | 183 | 184 | org.apache.maven.plugins 185 | maven-source-plugin 186 | 2.4 187 | 188 | ${project.artifactId}-${project.version} 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /properties-keeper/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示如何通过apollo管理将启动前需要加载的properties文件配置也管理起来, 常见于数据源或者一些不怎么变更的文件properties配置, 主要抽离文件配置的重复内容和屏蔽环境差异, 一般用于容器环境, 通过ENV注入所需变量配置. 4 | 5 | # Instructions 6 | - 约定应用加载的配置文件名称, 放到公共namespace抽离公共配置, 项目有需要特别修改的, 直接关联公共namespace覆盖或增加响应配置. 示例中使用`config.properties`和`db-config.properties`, 对应公共namespace `PLATFORM.config.properties`和`PLATFORM.db-config.properties` 7 | - config.properties: 不经常修改或者非数据源但启动前需要加载的配置项 8 | - db-config.properties: 数据源相关的配置 9 | - 确保需要托管文件在项目中存在, 空文件也可. 10 | - 选择apollo托管则全部以apollo上配置为准, 文件内已有内容会被覆盖. 11 | - 除了公共properties文件外, 其他properties文件默认也会被扫到并从apollo拉取, 如果不存在会报错退出, 建立对应文件名的私有namespace即可, 如果不想404直接退出,可以传入环境变量 `APOLLO_FORCE_PROPERTIES=false`. 12 | -------------------------------------------------------------------------------- /properties-keeper/fetch_properties.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CONFIG_SERVER_BASE_URL=${CONFIG_SERVER_BASE_URL:?"need env param [CONFIG_SERVER_BASE_URL]"} 3 | APOLLO_APP_ID=${APOLLO_APP_ID:?"need env param [APOLLO_APP_ID]"} 4 | PUBLIC_PREFIX=${PUBLIC_PREFIX:-"PLATFORM"} 5 | APOLLO_CLUSTER=${APOLLO_CLUSTER:-"default"} 6 | CONFIG_FILES="$(find /data/tomcat/webapps/ROOT/WEB-INF/classes/ -name "*.properties" ! -path "*static*" | awk -vFS="/" '{print $NF}')" 7 | ECS_IP=$(curl http://100.100.100.200/latest/meta-data/private-ipv4) 8 | APOLLO_FORCE_PROPERTIES=${APOLLO_FORCE_PROPERTIES:-"true"} 9 | 10 | 11 | for _config_file in ${CONFIG_FILES[@]} 12 | do 13 | export result= 14 | echo "## $_config_file" 15 | _abs_config_file_path=`find /data/tomcat/webapps/ROOT/WEB-INF/classes/ -name "$_config_file" ! -path "*static*"` 16 | _namespace=${_config_file%%.*} 17 | 18 | if [ x"$_namespace" = x"config" ] || [ x"$_namespace" = x"db-config" ];then 19 | _namespace="${PUBLIC_PREFIX}.${_namespace}" 20 | fi 21 | 22 | result=$(curl -s ${CONFIG_SERVER_BASE_URL}/configfiles/${APOLLO_APP_ID}/${APOLLO_CLUSTER}/${_namespace}?ip=${ECS_IP} | tr -d '\\' ) 23 | if echo $result | grep -q 'Not Found' ;then 24 | echo "################# $_config_file not found in apollo!!! #######################" 25 | if [ x"$APOLLO_FORCE_PROPERTIES" = x"true" ];then 26 | exit 404 27 | fi 28 | continue 29 | fi 30 | echo "### init ${_abs_config_file_path} ..." 31 | echo "$result" | sort > ${_abs_config_file_path} 32 | done -------------------------------------------------------------------------------- /properties-keeper/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | properties-keeper 13 | 14 | 15 | -------------------------------------------------------------------------------- /sentinel/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示[Sentinel](https://github.com/alibaba/Sentinel)如何通过Apollo配置中心实现中心化流控规则配置 4 | 5 | # Instructions 6 | 7 | - [基于Apollo配置中心的Sentinel流控规则配置实战(一)](https://mp.weixin.qq.com/s?__biz=MzA4NzA0NjAzOQ==&mid=2257484007&idx=1&sn=26e228c98d0743df098969be4a86f106&chksm=9345ba1fa43233090885ba37b601ed9f28e278c6585b5c73c5e6d801615240defd48786a79f5&token=159781885&lang=zh_CN&scene=21#wechat_redirect) 8 | - [基于Apollo配置中心的Sentinel流控规则配置实战(二) ](https://mp.weixin.qq.com/s?__biz=MzA4NzA0NjAzOQ==&mid=2257484017&idx=1&sn=cce834ad61e172a439f86e188e243144&chksm=9345ba09a432331fe5652240c6bebe50d687e58e7a82e1fcd2911a4b43f14c86a5c426c1c731&scene=0&xtrack=1#rd) 9 | -------------------------------------------------------------------------------- /sentinel/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | sentinel 13 | 14 | 15 | -------------------------------------------------------------------------------- /spring-boot-agent/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示如何通过java agent技术无缝集成Apollo配置中心,典型使用场如: 4 | 5 | 1. 有些配置已经打到jar包里了,而源码不方便修改 6 | 2. 不想改动项目代码直接集成Apollo 7 | 8 | PS: 通过java agent的偷懒方式有缺陷,很难使用到配置变更动态生效功能,这里只是提供场景实例思路,最好还是按照官方wiki的方式正确接入,也非常简单 9 | # Instructions 10 | 11 | 1. 在Apollo配置中心创建AppId为`spring-boot-agent`的项目 12 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 13 | 14 | ```properties 15 | test.input = 666 16 | ``` 17 | 3. 运行`com.ctrip.framework.apollo.use.cases.agent.Application`启动Demo,程序会打印application.properties配置的`888` 18 | 4. 编译apollo-agent模块,得到apollo-agent-1.0-SNAPSHOT.jar,然后在VM options中,添加如下javaagent配置: 19 | ``` 20 | -javaagent:xxx\apollo-agent-1.0-SNAPSHOT.jar 21 | -Ddev_meta=http://127.0.0.1:8801 22 | -Denv=DEV 23 | -Dapp.id=spring-boot-agent 24 | ``` 25 | javaagent需要自行替换apollo-agent-1.0-SNAPSHOT.jar的决定路径 26 | 5. 重新运行`com.ctrip.framework.apollo.use.cases.agent.Application`启动Demo,这个时候就会输出apollo中配置的`666`了 27 | -------------------------------------------------------------------------------- /spring-boot-agent/apollo-agent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-boot-agent 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | apollo-agent 13 | 14 | 15 | com.ctrip.framework.apollo.use.cases.agent.ApolloAgent 16 | 17 | 18 | 19 | 20 | 21 | org.apache.maven.plugins 22 | maven-jar-plugin 23 | 2.6 24 | 25 | 26 | 27 | ${premain.class} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /spring-boot-agent/apollo-agent/src/main/java/com/ctrip/framework/apollo/use/cases/agent/ApolloAgent.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.agent; 2 | 3 | import com.ctrip.framework.apollo.Config; 4 | import com.ctrip.framework.apollo.ConfigChangeListener; 5 | import com.ctrip.framework.apollo.ConfigService; 6 | import com.ctrip.framework.apollo.model.ConfigChangeEvent; 7 | import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.lang.instrument.Instrumentation; 12 | 13 | /** 14 | * @author: kl @kailing.pub 15 | * @date: 2019/5/8 16 | */ 17 | public class ApolloAgent { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(ApolloAgent.class); 20 | 21 | public static void premain(String agentArgs, Instrumentation inst) { 22 | Config config = ConfigService.getAppConfig(); 23 | for (String key : config.getPropertyNames()) { 24 | System.getProperties().put(key, config.getProperty(key, "")); 25 | } 26 | LOGGER.debug("Apollo Configure load complete"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-agent/app/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-boot-agent 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | app 13 | 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /spring-boot-agent/app/src/main/java/com.ctrip.framevowrk.apollo.use.cases.agent/Application.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framevowrk.apollo.use.cases.agent; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.boot.CommandLineRunner; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * @author: kl @kailing.pub 12 | * @date: 2019/5/8 13 | */ 14 | @SpringBootApplication 15 | public class Application implements CommandLineRunner{ 16 | 17 | 18 | @Value("${test.input:777}") 19 | private String input; 20 | 21 | @Override 22 | public void run(String... args) { 23 | while (true){ 24 | System.err.println(input); 25 | try { 26 | TimeUnit.SECONDS.sleep(2); 27 | }catch (InterruptedException e){ 28 | e.printStackTrace(); 29 | } 30 | } 31 | 32 | } 33 | 34 | public static void main(String[] args) { 35 | SpringApplication.run(Application.class, args); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spring-boot-agent/app/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | test.input = 888 2 | -------------------------------------------------------------------------------- /spring-boot-agent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | pom 12 | spring-boot-agent 13 | 14 | 15 | apollo-agent 16 | app 17 | 18 | 19 | -------------------------------------------------------------------------------- /spring-boot-dubbo/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示[Dubbo Spring Boot Starter](https://github.com/apache/incubator-dubbo-spring-boot-project)如何通过Apollo配置中心实现中心化配置 4 | 5 | # Instructions 6 | 7 | ## 服务端配置 8 | 1. 在Apollo配置中心创建AppId为`spring-boot-dubbo-provider`的项目 9 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 10 | 11 | ```properties 12 | # Base packages to scan Dubbo Components (e.g @Service , @Reference) 13 | dubbo.scan.basePackages = com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.provider 14 | 15 | ## ApplicationConfig Bean 16 | dubbo.application.name = spring-boot-dubbo-provider 17 | 18 | ## RegistryConfig Bean 19 | dubbo.registry.protocol = zookeeper 20 | dubbo.registry.address = 127.0.0.1:2181 21 | ``` 22 | 3. 启动zookeeper 23 | 4. 运行`com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.provider.Server`启动Demo服务端 24 | 25 | ## 调用端配置 26 | 1. 在Apollo配置中心创建AppId为`spring-boot-dubbo-consumer`的项目 27 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 28 | 29 | ```properties 30 | ## ApplicationConfig Bean 31 | dubbo.application.name = spring-boot-dubbo-consumer 32 | 33 | ## RegistryConfig Bean 34 | dubbo.registry.protocol = zookeeper 35 | dubbo.registry.address = 127.0.0.1:2181 36 | ``` 37 | 3. 运行`com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.consumer.Consumer`启动Demo调用端 38 | 4. 在调用端输入任意字符后按回车,即可发起一次Dubbo服务请求并输出服务端的响应 39 | * 如输入`dubbo`,服务端会响应`Hello dubbo` 40 | -------------------------------------------------------------------------------- /spring-boot-dubbo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-boot-dubbo 13 | pom 14 | 15 | spring-boot-dubbo-api 16 | spring-boot-dubbo-consumer 17 | spring-boot-dubbo-provider 18 | 19 | 20 | 21 | 22 | 23 | com.ctrip.framework.apollo 24 | spring-boot-dubbo-api 25 | ${project.version} 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-boot-dubbo 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-boot-dubbo-api 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-api/src/main/java/com/ctrip/framework/apollo/use/cases/spring/boot/starter/dubbo/api/DemoService.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.api; 2 | 3 | public interface DemoService { 4 | 5 | String sayHello(String name); 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-consumer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-boot-dubbo 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-boot-dubbo-consumer 13 | 14 | 15 | 16 | com.ctrip.framework.apollo 17 | spring-boot-dubbo-api 18 | 19 | 20 | com.alibaba.boot 21 | dubbo-spring-boot-starter 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-logging 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-log4j2 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-consumer/src/main/java/com/ctrip/framework/apollo/use/cases/spring/boot/starter/dubbo/consumer/Consumer.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.consumer; 2 | 3 | import com.alibaba.dubbo.config.annotation.Reference; 4 | import com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.api.DemoService; 5 | import java.io.BufferedReader; 6 | import java.io.InputStreamReader; 7 | import java.nio.charset.StandardCharsets; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.CommandLineRunner; 10 | import org.springframework.boot.WebApplicationType; 11 | import org.springframework.boot.autoconfigure.SpringBootApplication; 12 | import org.springframework.boot.builder.SpringApplicationBuilder; 13 | import org.springframework.stereotype.Component; 14 | 15 | /** 16 | * config the following properties in the spring-boot-dubbo-consumer app of apollo 17 | *
    18 | *
  • dubbo.application.name = spring-boot-dubbo-consumer
  • 19 | *
  • dubbo.registry.protocol = zookeeper
  • 20 | *
  • dubbo.registry.address = 127.0.0.1:2181
  • 21 | *
22 | */ 23 | @SpringBootApplication 24 | public class Consumer implements CommandLineRunner { 25 | 26 | @Autowired 27 | private ConsumerService consumerService; 28 | 29 | public static void main(String[] args) throws Exception { 30 | new SpringApplicationBuilder(Consumer.class).web(WebApplicationType.NONE).run(args); 31 | } 32 | 33 | @Override 34 | public void run(String... args) throws Exception { 35 | System.out.println("Please input any key to test. Input quit to exit."); 36 | while (true) { 37 | System.out.print("> "); 38 | String input = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)).readLine(); 39 | if (input == null || input.length() == 0) { 40 | continue; 41 | } 42 | input = input.trim(); 43 | if (input.equalsIgnoreCase("quit")) { 44 | System.exit(0); 45 | } 46 | System.out.println(consumerService.sayHello(input)); 47 | } 48 | } 49 | 50 | @Component 51 | private static class ConsumerService { 52 | 53 | @Reference 54 | private DemoService demoService; 55 | 56 | public String sayHello(String message) { 57 | return demoService.sayHello(message); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-consumer/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | app.id=spring-boot-dubbo-consumer 2 | # set apollo meta server address, adjust to actual address if necessary 3 | apollo.meta=http://localhost:8080 4 | # will inject 'application' namespace in bootstrap phase 5 | apollo.bootstrap.enabled=true 6 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-consumer/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-provider/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-boot-dubbo 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-boot-dubbo-provider 13 | 14 | 15 | 16 | com.ctrip.framework.apollo 17 | spring-boot-dubbo-api 18 | 19 | 20 | com.alibaba.boot 21 | dubbo-spring-boot-starter 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-provider/src/main/java/com/ctrip/framework/apollo/use/cases/spring/boot/starter/dubbo/provider/DemoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.provider; 2 | 3 | import com.alibaba.dubbo.config.annotation.Service; 4 | import com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.api.DemoService; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | @Service 9 | public class DemoServiceImpl implements DemoService { 10 | private static final Logger LOGGER = LoggerFactory.getLogger(DemoServiceImpl.class); 11 | 12 | @Override 13 | public String sayHello(String name) { 14 | LOGGER.info("Say hello to {}", name); 15 | return "Hello " + name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-provider/src/main/java/com/ctrip/framework/apollo/use/cases/spring/boot/starter/dubbo/provider/Server.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.provider; 2 | 3 | import org.springframework.boot.WebApplicationType; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | 7 | /** 8 | * config the following properties in the spring-boot-dubbo-provider app of apollo 9 | *
    10 | *
  • dubbo.application.name = spring-boot-dubbo-provider
  • 11 | *
  • dubbo.registry.protocol = zookeeper
  • 12 | *
  • dubbo.registry.address = 127.0.0.1:2181
  • 13 | *
  • dubbo.scan.basePackages = com.ctrip.framework.apollo.use.cases.spring.boot.starter.dubbo.provider
  • 14 | *
15 | */ 16 | @SpringBootApplication 17 | public class Server { 18 | 19 | public static void main(String[] args) throws Exception { 20 | new SpringApplicationBuilder(Server.class).web(WebApplicationType.NONE).run(args); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-provider/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | app.id=spring-boot-dubbo-provider 2 | # set apollo meta server address, adjust to actual address if necessary 3 | apollo.meta=http://localhost:8080 4 | # will inject 'application' namespace in bootstrap phase 5 | apollo.bootstrap.enabled=true 6 | apollo.bootstrap.eagerLoad.enabled=true 7 | -------------------------------------------------------------------------------- /spring-boot-dubbo/spring-boot-dubbo-provider/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | [${logName}]%magenta([%thread]) %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %logger{36}.%M - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /spring-boot-encrypt/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示如何结合[jasypt-spring-boot](https://github.com/ulisesbocchio/jasypt-spring-boot)实现Apollo中存储加密配置 4 | 5 | # Instructions 6 | 7 | 1. 在Apollo配置中心创建AppId为`spring-boot-encrypt`的项目 8 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 9 | 10 | ```properties 11 | jasypt.encryptor.password = klklklklklklklkl 12 | test.input = ENC(Ore69lUopDHL5R8Bw/G3bQ==) 13 | test.input1 = ckl 14 | ``` 15 | 3. 运行`com.ctrip.framework.apollo.use.cases.spring.boot.encrypt.Application`启动Demo 16 | 4. 程序会输出解密后的明文配置 17 | 5. 使用EncryptUtil小工具输出加密后的配置,加解密的keyjasypt.encryptor.password自己指定,添加配置时使用ENC()包含配置,如加密配置为xxx,则ENC(xxx) 18 | -------------------------------------------------------------------------------- /spring-boot-encrypt/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-boot-encrypt 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter 18 | 19 | 20 | 21 | com.github.ulisesbocchio 22 | jasypt-spring-boot-starter 23 | 1.16 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /spring-boot-encrypt/src/main/java/com/ctrip/framework/apollo/use/cases/spring/boot/encrypt/Application.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.boot.encrypt; 2 | 3 | import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; 4 | import java.util.concurrent.TimeUnit; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.boot.CommandLineRunner; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | 10 | import javax.annotation.PostConstruct; 11 | import org.springframework.context.EnvironmentAware; 12 | import org.springframework.core.env.Environment; 13 | 14 | /** 15 | * Created by kl on 2018/6/25. 16 | * Content : 配置加解密实例 17 | */ 18 | @SpringBootApplication 19 | @EnableApolloConfig 20 | public class Application implements CommandLineRunner, EnvironmentAware { 21 | 22 | public static void main(String[] args) { 23 | SpringApplication.run(Application.class, args); 24 | } 25 | 26 | @Value("${test.input}") 27 | private String input; 28 | 29 | @Value("${test.input1}") 30 | private String input1; 31 | 32 | private Environment environment; 33 | 34 | @Override 35 | public void run(String... args) throws Exception { 36 | while (true) { 37 | System.err.println("test.input 值 ENC(Ore69lUopDHL5R8Bw/G3bQ==) 解密后:" + input); 38 | System.err.println("test.input from environment: " + environment.getProperty("test.input")); 39 | System.err.println("test.input1 不需要解密:" + input1); 40 | TimeUnit.SECONDS.sleep(1); 41 | } 42 | } 43 | 44 | @Override 45 | public void setEnvironment(Environment environment) { 46 | this.environment = environment; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-boot-encrypt/src/main/java/com/ctrip/framework/apollo/use/cases/spring/boot/encrypt/EncryptUtil.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.boot.encrypt; 2 | 3 | import com.ctrip.framework.apollo.core.utils.StringUtils; 4 | import org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.PrintStream; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * Created by kl on 2018/6/25. 15 | * Content :加密工具 16 | */ 17 | public class EncryptUtil { 18 | 19 | /** 20 | * 制表符、空格、换行符 PATTERN 21 | */ 22 | private static Pattern BLANK_PATTERN = Pattern.compile("\\s*|\t|\r|\n"); 23 | 24 | /** 25 | * 可以理解为加密salt 26 | */ 27 | private static String PASSWORD = "klklklklklklklkl"; 28 | 29 | /** 30 | * 加密算法 31 | */ 32 | private static String ALGORITHM = "PBEWithMD5AndDES"; 33 | 34 | public static Map getEncryptedParams(String input) { 35 | //输出流 36 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024); 37 | PrintStream cacheStream = new PrintStream(byteArrayOutputStream); 38 | //更换数据输出位置 39 | System.setOut(cacheStream); 40 | 41 | //加密参数组装 42 | String[] args = {"input=" + input, "password=" + PASSWORD, "algorithm=" + ALGORITHM}; 43 | JasyptPBEStringEncryptionCLI.main(args); 44 | 45 | //执行加密后的输出 46 | String message = byteArrayOutputStream.toString(); 47 | String str = replaceBlank(message); 48 | int index = str.lastIndexOf("-"); 49 | 50 | //返回加密后的数据 51 | Map result = new HashMap(); 52 | result.put("input", str.substring(index + 1)); 53 | result.put("password", PASSWORD); 54 | return result; 55 | } 56 | 57 | public static void main(String[] args) { 58 | System.out.println(getEncryptedParams("kl"));//print : {input=Ore69lUopDHL5R8Bw/G3bQ==, password=klklklklklklklkl} 59 | } 60 | 61 | /** 62 | * 替换制表符、空格、换行符 63 | * 64 | * @param str 65 | * @return 66 | */ 67 | private static String replaceBlank(String str) { 68 | String dest = ""; 69 | if (!StringUtils.isEmpty(str)) { 70 | Matcher matcher = BLANK_PATTERN.matcher(str); 71 | dest = matcher.replaceAll(""); 72 | } 73 | return dest; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /spring-boot-encrypt/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | app.id=spring-boot-encrypt 2 | # set apollo meta server address, adjust to actual address if necessary 3 | apollo.meta=http://localhost:8080 4 | -------------------------------------------------------------------------------- /spring-boot-logger/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示[Spring Boot Logging](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html)如何通过Apollo配置中心实现动态调整Logging Level 4 | 5 | # Instructions 6 | 7 | 1. 在Apollo配置中心创建AppId为`spring-boot-logger`的项目 8 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 9 | 10 | ```properties 11 | logging.level.com.ctrip.framework.apollo.use.cases.spring.boot.logger = warn 12 | ``` 13 | 3. 运行`com.ctrip.framework.apollo.use.cases.spring.boot.logger.Application`启动Demo 14 | 4. 程序只会持续打印error级别日志 15 | 5. 在Apollo配置中心修改配置,把`logging.level.com.ctrip.framework.apollo.use.cases.spring.boot.logger`的值改为`debug`并发布配置 16 | 6. 程序会输出debug, info, warn, error等级别日志,说明动态调整Logging Level生效了 17 | 7. 更多信息可以参见博文:http://www.kailing.pub/article/index/arcid/189.html 18 | -------------------------------------------------------------------------------- /spring-boot-logger/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-boot-logger 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /spring-boot-logger/src/main/java/com/ctrip/framework/apollo/use/cases/spring/boot/logger/Application.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.boot.logger; 2 | 3 | import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | /** 8 | * Created by kl on 2018/6/25. 9 | * Content :动态日志实例 10 | */ 11 | @SpringBootApplication 12 | @EnableApolloConfig 13 | public class Application { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(Application.class, args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-logger/src/main/java/com/ctrip/framework/apollo/use/cases/spring/boot/logger/LoggerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.boot.logger; 2 | 3 | import com.ctrip.framework.apollo.Config; 4 | import com.ctrip.framework.apollo.model.ConfigChangeEvent; 5 | import com.ctrip.framework.apollo.spring.annotation.ApolloConfig; 6 | import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; 7 | import java.util.Set; 8 | import javax.annotation.PostConstruct; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.logging.LogLevel; 13 | import org.springframework.boot.logging.LoggingSystem; 14 | import org.springframework.stereotype.Service; 15 | 16 | /** 17 | * Created by kl on 2018/6/25. Content :动态日志配置 18 | */ 19 | @Service 20 | public class LoggerConfiguration { 21 | private static final Logger logger = LoggerFactory.getLogger(LoggerConfiguration.class); 22 | private static final String LOGGER_TAG = "logging.level."; 23 | 24 | @Autowired 25 | private LoggingSystem loggingSystem; 26 | 27 | @ApolloConfig 28 | private Config config; 29 | 30 | @ApolloConfigChangeListener 31 | private void onChange(ConfigChangeEvent changeEvent) { 32 | refreshLoggingLevels(); 33 | } 34 | 35 | @PostConstruct 36 | private void refreshLoggingLevels() { 37 | Set keyNames = config.getPropertyNames(); 38 | for (String key : keyNames) { 39 | if (containsIgnoreCase(key, LOGGER_TAG)) { 40 | String strLevel = config.getProperty(key, "info"); 41 | LogLevel level = LogLevel.valueOf(strLevel.toUpperCase()); 42 | loggingSystem.setLogLevel(key.replace(LOGGER_TAG, ""), level); 43 | logger.info("{}:{}", key, strLevel); 44 | } 45 | } 46 | } 47 | 48 | private static boolean containsIgnoreCase(String str, String searchStr) { 49 | if (str == null || searchStr == null) { 50 | return false; 51 | } 52 | int len = searchStr.length(); 53 | int max = str.length() - len; 54 | for (int i = 0; i <= max; i++) { 55 | if (str.regionMatches(true, i, searchStr, 0, len)) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spring-boot-logger/src/main/java/com/ctrip/framework/apollo/use/cases/spring/boot/logger/PrintLogger.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.boot.logger; 2 | 3 | import java.util.concurrent.Executors; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Service; 7 | 8 | import javax.annotation.PostConstruct; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * Created by kl on 2018/6/25. 13 | * Content : 14 | */ 15 | @Service 16 | public class PrintLogger { 17 | private static Logger logger = LoggerFactory.getLogger(PrintLogger.class); 18 | 19 | @PostConstruct 20 | public void printLogger() throws Exception{ 21 | Executors.newSingleThreadExecutor().submit(() -> { 22 | while (true) { 23 | logger.info("我是info级别日志"); 24 | logger.error("我是error级别日志"); 25 | logger.warn("我是warn级别日志"); 26 | logger.debug("我是debug级别日志"); 27 | TimeUnit.SECONDS.sleep(1); 28 | } 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-boot-logger/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | app.id=spring-boot-logger 2 | # set apollo meta server address, adjust to actual address if necessary 3 | apollo.meta=http://localhost:8080 4 | -------------------------------------------------------------------------------- /spring-cloud-gateway/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示[Spring Cloud Gateway](https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.3.RELEASE/single/spring-cloud-gateway.html)如何通过Apollo配置中心实现动态路由 4 | 5 | # Instructions 6 | 7 | 1. 在Apollo配置中心创建AppId为`spring-cloud-Gateway`的项目 8 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 9 | 10 | ```properties 11 | server.port = 9090 12 | spring.cloud.gateway.routes[0].id = github_route 13 | spring.cloud.gateway.routes[0].uri = https://github.com/ 14 | #spring.cloud.gateway.routes[0].uri = https://github.com/ctripcorp/apollo 15 | spring.cloud.gateway.routes[0].predicates[0] = Path=/** 16 | ``` 17 | 3. 运行`com.ctrip.framework.apollo.use.cases.spring.cloud.gateway.SpringCloudGatewayApplication`启动Demo 18 | 4. 打开`http://localhost:9090`,显式内容为GitHub首页 19 | 5. 在Apollo配置中心修改配置,把`spring.cloud.gateway.routes[0].uri`的值改为`https://github.com/ctripcorp/apollo`并发布配置 20 | * 可以以文本模式在原来生效的`spring.cloud.gateway.routes[0].uri`前面加上`#`注释掉,同时把原来注释掉的指向`https://github.com/ctripcorp/apollo`的配置反注释掉来快速修改 21 | 6. 刷新`http://localhost:9090`页面,显式的内容会变成Apollo配置中心的GitHub首页,说明动态路由生效了 22 | 7. 如果浏览器缓存影响测试,可以配置`actuator`使用`org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint`提供的端点查看路由配置 -------------------------------------------------------------------------------- /spring-cloud-gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | apollo-use-cases 6 | com.ctrip.framework.apollo 7 | 1.0-SNAPSHOT 8 | 9 | 4.0.0 10 | 11 | spring-cloud-gateway 12 | 13 | 14 | 15 | org.springframework.cloud 16 | spring-cloud-starter-gateway 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-test 22 | test 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /spring-cloud-gateway/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/gateway/GatewayPropertiesRefresher.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.cloud.gateway; 2 | 3 | import com.ctrip.framework.apollo.enums.PropertyChangeType; 4 | import com.ctrip.framework.apollo.model.ConfigChange; 5 | import com.ctrip.framework.apollo.model.ConfigChangeEvent; 6 | import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.BeansException; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.cloud.context.environment.EnvironmentChangeEvent; 12 | import org.springframework.cloud.gateway.config.GatewayProperties; 13 | import org.springframework.cloud.gateway.event.RefreshRoutesEvent; 14 | import org.springframework.cloud.gateway.route.RouteDefinitionWriter; 15 | import org.springframework.context.ApplicationContext; 16 | import org.springframework.context.ApplicationContextAware; 17 | import org.springframework.context.ApplicationEventPublisher; 18 | import org.springframework.context.ApplicationEventPublisherAware; 19 | import org.springframework.stereotype.Component; 20 | 21 | import java.util.ArrayList; 22 | 23 | /** 24 | * @author ksewen 25 | * @date 2019/5/175:24 PM 26 | */ 27 | @Component 28 | public class GatewayPropertiesRefresher implements ApplicationContextAware,ApplicationEventPublisherAware { 29 | 30 | private static final Logger logger = LoggerFactory.getLogger(GatewayPropertiesRefresher.class); 31 | 32 | private static final String ID_PATTERN = "spring\\.cloud\\.gateway\\.routes\\[\\d+\\]\\.id"; 33 | 34 | private static final String DEFAULT_FILTER_PATTERN = "spring\\.cloud\\.gateway\\.default-filters\\[\\d+\\]\\.name"; 35 | 36 | private ApplicationContext applicationContext; 37 | 38 | private ApplicationEventPublisher publisher; 39 | 40 | @Autowired 41 | private GatewayProperties gatewayProperties; 42 | 43 | @Override 44 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 45 | this.applicationContext = applicationContext; 46 | } 47 | 48 | 49 | @Override 50 | public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { 51 | this.publisher = applicationEventPublisher; 52 | } 53 | 54 | @ApolloConfigChangeListener(interestedKeyPrefixes = "spring.cloud.gateway.") 55 | public void onChange(ConfigChangeEvent changeEvent) { 56 | refreshGatewayProperties(changeEvent); 57 | } 58 | 59 | /*** 60 | * 刷新org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator中定义的routes 61 | * 62 | * @param changeEvent 63 | * @return void 64 | * @author ksewen 65 | * @date 2019/5/21 2:13 PM 66 | */ 67 | private void refreshGatewayProperties(ConfigChangeEvent changeEvent) { 68 | logger.info("Refreshing GatewayProperties!"); 69 | preDestroyGatewayProperties(changeEvent); 70 | this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); 71 | refreshGatewayRouteDefinition(); 72 | logger.info("GatewayProperties refreshed!"); 73 | } 74 | 75 | /*** 76 | * GatewayProperties没有@PreDestroy和destroy方法 77 | * org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#rebind(java.lang.String)中destroyBean时不会销毁当前对象 78 | * 如果把spring.cloud.gateway.前缀的配置项全部删除(例如需要动态删除最后一个路由的场景),initializeBean时也无法创建新的bean,则return当前bean 79 | * 若仍保留有spring.cloud.gateway.routes[n]或spring.cloud.gateway.default-filters[n]等配置,initializeBean时会注入新的属性替换已有的bean 80 | * 这个方法提供了类似@PreDestroy的操作,根据配置文件的实际情况把org.springframework.cloud.gateway.config.GatewayProperties#routes 81 | * 和org.springframework.cloud.gateway.config.GatewayProperties#defaultFilters两个集合清空 82 | * 83 | * @param 84 | * @return void 85 | * @author ksewen 86 | * @date 2019/5/21 2:13 PM 87 | */ 88 | private synchronized void preDestroyGatewayProperties(ConfigChangeEvent changeEvent) { 89 | logger.info("Pre Destroy GatewayProperties!"); 90 | final boolean needClearRoutes = this.checkNeedClear(changeEvent, ID_PATTERN, this.gatewayProperties.getRoutes().size()); 91 | if (needClearRoutes) { 92 | this.gatewayProperties.setRoutes(new ArrayList<>()); 93 | } 94 | final boolean needClearDefaultFilters = this.checkNeedClear(changeEvent, DEFAULT_FILTER_PATTERN, this.gatewayProperties.getDefaultFilters().size()); 95 | if (needClearDefaultFilters) { 96 | this.gatewayProperties.setDefaultFilters(new ArrayList<>()); 97 | } 98 | logger.info("Pre Destroy GatewayProperties finished!"); 99 | } 100 | 101 | private void refreshGatewayRouteDefinition() { 102 | logger.info("Refreshing Gateway RouteDefinition!"); 103 | this.publisher.publishEvent(new RefreshRoutesEvent(this)); 104 | logger.info("Gateway RouteDefinition refreshed!"); 105 | } 106 | 107 | /*** 108 | * 根据changeEvent和定义的pattern匹配key,如果所有对应PropertyChangeType为DELETED则需要清空GatewayProperties里相关集合 109 | * 110 | * @param changeEvent 111 | * @param pattern 112 | * @param existSize 113 | * @return boolean 114 | * @author ksewen 115 | * @date 2019/5/23 2:18 PM 116 | */ 117 | private boolean checkNeedClear(ConfigChangeEvent changeEvent, String pattern, int existSize) { 118 | 119 | return changeEvent.changedKeys().stream().filter(key -> key.matches(pattern)) 120 | .filter(key -> { 121 | ConfigChange change = changeEvent.getChange(key); 122 | return PropertyChangeType.DELETED.equals(change.getChangeType()); 123 | }).count() == existSize; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /spring-cloud-gateway/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/gateway/SpringCloudGatewayApplication.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.cloud.gateway; 2 | 3 | import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableApolloConfig 8 | @SpringBootApplication 9 | public class SpringCloudGatewayApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(SpringCloudGatewayApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-cloud-gateway/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | app.id=spring-cloud-gateway 2 | # set apollo meta server address, adjust to actual address if necessary 3 | apollo.meta=http://localhost:8080 -------------------------------------------------------------------------------- /spring-cloud-logger/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示[Spring Boot Logging](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html)在Spring Cloud环境下如何通过Apollo配置中心方便地实现动态调整Logging Level 4 | 5 | # Instructions 6 | 7 | 1. 在Apollo配置中心创建AppId为`spring-cloud-logger`的项目 8 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 9 | 10 | ```properties 11 | logging.level.com.ctrip.framework.apollo.use.cases.spring.cloud.logger = warn 12 | ``` 13 | 3. 运行`com.ctrip.framework.apollo.use.cases.spring.cloud.logger.Application`启动Demo 14 | 4. 程序只会持续打印error级别日志 15 | 5. 在Apollo配置中心修改配置,把`logging.level.com.ctrip.framework.apollo.use.cases.spring.cloud.logger`的值改为`debug`并发布配置 16 | 6. 程序会输出debug, info, warn, error等级别日志,说明动态调整Logging Level生效了 17 | -------------------------------------------------------------------------------- /spring-cloud-logger/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-cloud-logger 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter 18 | 19 | 20 | org.springframework.cloud 21 | spring-cloud-context 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /spring-cloud-logger/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/logger/Application.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.cloud.logger; 2 | 3 | import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | /** 8 | * Created by kl on 2018/6/25. 9 | * Content :动态日志实例 10 | */ 11 | @SpringBootApplication 12 | @EnableApolloConfig 13 | public class Application { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(Application.class, args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-cloud-logger/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/logger/LoggerLevelRefresher.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.cloud.logger; 2 | 3 | import com.ctrip.framework.apollo.Config; 4 | import com.ctrip.framework.apollo.model.ConfigChangeEvent; 5 | import com.ctrip.framework.apollo.spring.annotation.ApolloConfig; 6 | import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; 7 | import java.util.Set; 8 | import javax.annotation.PostConstruct; 9 | import org.springframework.beans.BeansException; 10 | import org.springframework.cloud.context.environment.EnvironmentChangeEvent; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.ApplicationContextAware; 13 | import org.springframework.stereotype.Service; 14 | 15 | /** 16 | * Created by kl on 2018/6/25. Content :动态日志配置 17 | */ 18 | @Service 19 | public class LoggerLevelRefresher implements ApplicationContextAware { 20 | 21 | private ApplicationContext applicationContext; 22 | 23 | @ApolloConfig 24 | private Config config; 25 | 26 | @PostConstruct 27 | private void initialize() { 28 | refreshLoggingLevels(config.getPropertyNames()); 29 | } 30 | 31 | @ApolloConfigChangeListener(interestedKeyPrefixes = {"logging.level."}) 32 | private void onChange(ConfigChangeEvent changeEvent) { 33 | refreshLoggingLevels(changeEvent.changedKeys()); 34 | } 35 | 36 | private void refreshLoggingLevels(Set changedKeys) { 37 | System.out.println("Refreshing logging levels"); 38 | 39 | /** 40 | * refresh logging levels 41 | * @see org.springframework.cloud.logging.LoggingRebinder#onApplicationEvent 42 | */ 43 | this.applicationContext.publishEvent(new EnvironmentChangeEvent(changedKeys)); 44 | 45 | System.out.println("Logging levels refreshed"); 46 | } 47 | 48 | @Override 49 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 50 | this.applicationContext = applicationContext; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spring-cloud-logger/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/logger/PrintLogger.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.cloud.logger; 2 | 3 | import java.util.concurrent.Executors; 4 | import java.util.concurrent.TimeUnit; 5 | import javax.annotation.PostConstruct; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * Created by kl on 2018/6/25. 12 | * Content : 13 | */ 14 | @Service 15 | public class PrintLogger { 16 | private static Logger logger = LoggerFactory.getLogger(PrintLogger.class); 17 | 18 | @PostConstruct 19 | public void printLogger() throws Exception{ 20 | Executors.newSingleThreadExecutor().submit(() -> { 21 | while (true) { 22 | logger.info("我是info级别日志"); 23 | logger.error("我是error级别日志"); 24 | logger.warn("我是warn级别日志"); 25 | logger.debug("我是debug级别日志"); 26 | TimeUnit.SECONDS.sleep(1); 27 | } 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /spring-cloud-logger/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | app.id=spring-cloud-logger 2 | # set apollo meta server address, adjust to actual address if necessary 3 | apollo.meta=http://localhost:8080 4 | -------------------------------------------------------------------------------- /spring-cloud-zuul-ratelimit/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示[Spring Cloud Zuul](https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#netflix-zuul-reverse-proxy)的第三方限流插件[marcosbarbero/spring-cloud-zuul-ratelimit](https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit)如何通过Apollo配置中心实现动态限流 4 | 5 | # Instructions 6 | 0. 启动本机redis或者手动修改对应配置 7 | 1. 在Apollo配置中心创建AppId为`spring-cloud-zuul-ratelimit`的项目,也可以沿用`spring-cloud-zuul`的项目(注意配置文件中`app.id`配置) 8 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 9 | 10 | ```properties 11 | zuul.routes.test.path = /limit/** 12 | zuul.routes.test.url = forward:/index 13 | zuul.ratelimit.enabled = true 14 | zuul.ratelimit.repository = REDIS 15 | zuul.ratelimit.behind-proxy = true 16 | zuul.ratelimit.add-response-headers = true 17 | zuul.ratelimit.default-policy-list[0].limit = 0 18 | zuul.ratelimit.default-policy-list[0].quota = 1000 19 | zuul.ratelimit.default-policy-list[0].refresh-interval = 5 20 | zuul.ratelimit.default-policy-list[0].type[0] = user 21 | zuul.ratelimit.default-policy-list[0].type[1] = origin 22 | zuul.ratelimit.default-policy-list[0].type[2] = url 23 | # 通过实例配置覆盖默认配置,注意这里的`test`需要和网关对应路由的`test`关联 24 | # zuul.ratelimit.policy-list.test[0].limit = 1 25 | # zuul.ratelimit.policy-list.test[0].quota = 1000 26 | # zuul.ratelimit.policy-list.test[0].refresh-interval = 5 27 | # zuul.ratelimit.policy-list.test[0].type[0] = user 28 | # zuul.ratelimit.policy-list.test[0].type[1] = origin 29 | # zuul.ratelimit.policy-list.test[0].type[2] = url 30 | ``` 31 | 3. 运行`com.ctrip.framework.apollo.use.cases.spring.cloud.zuul.Application`启动Demo 32 | 4. 手动打开`http://localhost:9090/limit`,页面显示`429`访问过载: 33 | ![](http://ww1.sinaimg.cn/large/006tNc79gy1g4fmqgu5rvj311u0eugm7.jpg) 34 | 5. 在Apollo配置中心修改配置,把`zuul.ratelimit.default-policy-list[0].limit`的值改为`1`并发布配置,再次访问,即可在5秒时间窗口内访问到1次`http://localhost:9090/limit`端点,说明动态路由生效了 35 | * 更详细的限流配置,请看[marcosbarbero/spring-cloud-zuul-ratelimit](https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit)或者这里有一篇快速入门[Spring Cloud 入门教程9、服务限流/API限流(Zuul+RateLimiter)](https://ken.io/note/spring-cloud-zuul-ratelimiter-quickstart) 36 | -------------------------------------------------------------------------------- /spring-cloud-zuul-ratelimit/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-cloud-zuul-ratelimit 13 | 14 | 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-netflix-zuul 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-logging 27 | 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-log4j2 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | com.marcosbarbero.cloud 43 | spring-cloud-zuul-ratelimit 44 | 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-data-redis 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /spring-cloud-zuul-ratelimit/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/zuul/Application.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.cloud.zuul; 2 | 3 | import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.builder.SpringApplicationBuilder; 7 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @EnableApolloConfig 12 | @EnableZuulProxy 13 | @SpringBootApplication 14 | @RestController 15 | public class Application { 16 | 17 | @Value("${server.port}") 18 | private int port; 19 | 20 | public static void main(String[] args) throws Exception { 21 | new SpringApplicationBuilder(Application.class).run(args); 22 | } 23 | 24 | @GetMapping("/index") 25 | public String index() { 26 | return String.format("index %s", port); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /spring-cloud-zuul-ratelimit/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/zuul/ZuulRateLimitPropertiesRefreshConfig.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.cloud.zuul; 2 | 3 | import static com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties.PREFIX; 4 | 5 | import com.ctrip.framework.apollo.model.ConfigChangeEvent; 6 | import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.BeansException; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 | import org.springframework.cloud.context.environment.EnvironmentChangeEvent; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.context.ApplicationContextAware; 14 | import org.springframework.stereotype.Component; 15 | 16 | @Component 17 | @ConditionalOnProperty(prefix = PREFIX, name = "enabled", havingValue = "true") 18 | public class ZuulRateLimitPropertiesRefreshConfig implements ApplicationContextAware { 19 | 20 | private static final Logger logger = LoggerFactory.getLogger(ZuulRateLimitPropertiesRefreshConfig.class); 21 | 22 | private ApplicationContext applicationContext; 23 | 24 | @ApolloConfigChangeListener(interestedKeyPrefixes = PREFIX) 25 | public void onChange(ConfigChangeEvent changeEvent) { 26 | logger.info("Refreshing Zuul rateLimit Properties"); 27 | 28 | this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); 29 | 30 | logger.info("Zuul rateLimit Properties refreshed!"); 31 | } 32 | 33 | @Override 34 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 35 | this.applicationContext = applicationContext; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spring-cloud-zuul-ratelimit/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | app.id=spring-cloud-zuul-ratelimit 2 | apollo.meta=http://localhost:8080 3 | apollo.bootstrap.enabled=true 4 | server.port=9090 5 | spring.redis.host=localhost 6 | spring.redis.port=6379 7 | 8 | # 如需要可以配置actuator端点,进行手动刷新,非必须 9 | #management.endpoints.web.exposure.include=refresh,health 10 | #management.endpoints.web.exposure.include=* 11 | #management.endpoint.health.show-details=ALWAYS 12 | -------------------------------------------------------------------------------- /spring-cloud-zuul-ratelimit/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /spring-cloud-zuul/README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | 演示[Spring Cloud Zuul](https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#netflix-zuul-reverse-proxy)如何通过Apollo配置中心实现动态路由 4 | 5 | # Instructions 6 | 7 | 1. 在Apollo配置中心创建AppId为`spring-cloud-zuul`的项目 8 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 9 | 10 | ```properties 11 | server.port = 9090 12 | zuul.routes.test.path = /** 13 | zuul.routes.test.url = https://github.com 14 | #zuul.routes.test.url = https://github.com/ctripcorp/apollo 15 | ``` 16 | 3. 运行`com.ctrip.framework.apollo.use.cases.spring.cloud.zuul.Application`启动Demo 17 | 4. 程序会自动打开`http://localhost:9090`,显式内容为GitHub首页 18 | 5. 在Apollo配置中心修改配置,把`zuul.routes.test.url`的值改为`https://github.com/ctripcorp/apollo`并发布配置 19 | * 可以以文本模式在原来生效的`zuul.routes.test.url`前面加上`#`注释掉,同时把原来注释掉的指向`https://github.com/ctripcorp/apollo`的配置反注释掉来快速修改 20 | 6. 刷新`http://localhost:9090`页面,显式的内容会变成Apollo配置中心的GitHub首页,说明动态路由生效了 21 | -------------------------------------------------------------------------------- /spring-cloud-zuul/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-cloud-zuul 13 | 14 | 15 | 16 | org.springframework.cloud 17 | spring-cloud-starter-netflix-zuul 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-logging 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-log4j2 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /spring-cloud-zuul/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/zuul/Application.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.cloud.zuul; 2 | 3 | import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; 4 | import java.awt.Desktop; 5 | import java.net.URI; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.builder.SpringApplicationBuilder; 8 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 9 | import org.springframework.context.ApplicationContext; 10 | 11 | @EnableApolloConfig 12 | @EnableZuulProxy 13 | @SpringBootApplication 14 | public class Application { 15 | 16 | public static void main(String[] args) throws Exception { 17 | System.setProperty("java.awt.headless", "false"); 18 | ApplicationContext context = new SpringApplicationBuilder(Application.class).run(args); 19 | Desktop.getDesktop().browse( 20 | new URI("http://localhost:" + context.getEnvironment().getProperty("server.port", "8080"))); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /spring-cloud-zuul/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/zuul/ZuulPropertiesRefresher.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.cloud.zuul; 2 | 3 | import com.ctrip.framework.apollo.model.ConfigChangeEvent; 4 | import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.BeansException; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.cloud.context.environment.EnvironmentChangeEvent; 10 | import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent; 11 | import org.springframework.cloud.netflix.zuul.filters.RouteLocator; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.context.ApplicationContextAware; 14 | import org.springframework.stereotype.Component; 15 | 16 | @Component 17 | public class ZuulPropertiesRefresher implements ApplicationContextAware { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(ZuulPropertiesRefresher.class); 20 | 21 | private ApplicationContext applicationContext; 22 | 23 | @Autowired 24 | private RouteLocator routeLocator; 25 | 26 | @ApolloConfigChangeListener(interestedKeyPrefixes = "zuul.") 27 | public void onChange(ConfigChangeEvent changeEvent) { 28 | refreshZuulProperties(changeEvent); 29 | } 30 | 31 | private void refreshZuulProperties(ConfigChangeEvent changeEvent) { 32 | logger.info("Refreshing zuul properties!"); 33 | 34 | /** 35 | * rebind configuration beans, e.g. ZuulProperties 36 | * @see org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent 37 | */ 38 | this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); 39 | 40 | /** 41 | * refresh routes 42 | * @see org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration.ZuulRefreshListener#onApplicationEvent 43 | */ 44 | this.applicationContext.publishEvent(new RoutesRefreshedEvent(routeLocator)); 45 | 46 | logger.info("Zuul properties refreshed!"); 47 | } 48 | 49 | @Override 50 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 51 | this.applicationContext = applicationContext; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spring-cloud-zuul/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | app.id=spring-cloud-zuul 2 | # set apollo meta server address, adjust to actual address if necessary 3 | apollo.meta=http://localhost:8080 4 | -------------------------------------------------------------------------------- /spring-cloud-zuul/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /spring-mvc-logger/README.md: -------------------------------------------------------------------------------- 1 | # Instructions 2 | 3 | 1. 在Apollo配置中心创建AppId为`spring-mvc-logger`的项目 4 | 2. 在默认的`application`下做如下配置(可以通过文本模式直接复制、粘贴下面的内容): 5 | 6 | ``` 7 | apollo.setting.app.name = spring-mvc-logger 8 | ``` 9 | 3. 在项目中的LoggerStartupListener监听器中设置需要动态更新的值`appname`,并且在logback.xml中引用`${appname}` 10 | 4. 用tomcat启动`spring-mvc-logger`项目 11 | 5. 可以看到打印日志: 12 | ``` 13 | [app_name=spring-mvc-logger][timestamp=2021-03-20 13:34:45.406][level=INFO][msg=the value of the logback field from apollo, apollo.setting.app.name is spring-mvc-logger] 14 | ``` 15 | 5. 在Apollo配置中心修改配置,把`apollo.setting.app.name`的值改为`newvalue`并发布配置 16 | 6. 可以看到打印日志已更新: 17 | ``` 18 | [app_name=newvalue][timestamp=2021-03-20 13:38:23.928][level=INFO][msg=reload loggerContext , you can see that the log has been updated, new value from apollo is newvalue] 19 | ``` 20 | 说明logback.xml中app_name的值随着apollo配置的更新而动态更新了 21 | 7. 更多信息可以参见:https://github.com/ctripcorp/apollo/issues/2482#issuecomment-801901167 22 | -------------------------------------------------------------------------------- /spring-mvc-logger/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | apollo-use-cases 7 | com.ctrip.framework.apollo 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | spring-mvc-logger 13 | 14 | 15 | 4.3.4.RELEASE 16 | 17 | 18 | 19 | org.projectlombok 20 | lombok 21 | 1.18.16 22 | 23 | 24 | ch.qos.logback 25 | logback-classic 26 | 1.2.3 27 | 28 | 29 | org.springframework 30 | spring-context 31 | ${spring-version} 32 | 33 | 34 | 35 | org.springframework 36 | spring-web 37 | ${spring-version} 38 | 39 | 40 | org.springframework 41 | spring-webmvc 42 | ${spring-version} 43 | 44 | 45 | org.springframework 46 | spring-test 47 | test 48 | ${spring-version} 49 | 50 | 51 | 52 | 53 | spring-mvc-logger 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-compiler-plugin 58 | 2.5.1 59 | 60 | 1.8 61 | 1.8 62 | UTF8 63 | 64 | 65 | 66 | org.apache.tomcat.maven 67 | tomcat7-maven-plugin 68 | 2.2 69 | 70 | 80 71 | / 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /spring-mvc-logger/src/main/java/com/ctrip/framework/apollo/use/cases/spring/mvc/logger/LoggerStartupListener.java: -------------------------------------------------------------------------------- 1 | package com.ctrip.framework.apollo.use.cases.spring.mvc.logger; 2 | 3 | import ch.qos.logback.classic.Level; 4 | import ch.qos.logback.classic.Logger; 5 | import ch.qos.logback.classic.LoggerContext; 6 | import ch.qos.logback.classic.spi.LoggerContextListener; 7 | import ch.qos.logback.classic.util.ContextInitializer; 8 | import ch.qos.logback.core.Context; 9 | import ch.qos.logback.core.joran.spi.JoranException; 10 | import ch.qos.logback.core.spi.ContextAwareBase; 11 | import ch.qos.logback.core.spi.LifeCycle; 12 | import com.ctrip.framework.apollo.Config; 13 | import com.ctrip.framework.apollo.ConfigChangeListener; 14 | import com.ctrip.framework.apollo.ConfigService; 15 | import com.ctrip.framework.apollo.model.ConfigChange; 16 | import com.ctrip.framework.apollo.model.ConfigChangeEvent; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.stereotype.Component; 19 | 20 | import java.net.URL; 21 | import java.util.concurrent.atomic.AtomicBoolean; 22 | 23 | /** 24 | * @author bugcoder 25 | * @date 2021/3/17 26 | */ 27 | @Component 28 | public class LoggerStartupListener extends ContextAwareBase implements LoggerContextListener, LifeCycle { 29 | 30 | private static final org.slf4j.Logger log = LoggerFactory.getLogger(LoggerStartupListener.class); 31 | private static AtomicBoolean started = new AtomicBoolean(false); 32 | private static String APOLLO_SETTING_NAME = "apollo.setting.app.name"; 33 | private static String LOGBACK_SETTING_NAME = "appname"; 34 | 35 | @Override 36 | public void start() { 37 | if (started.compareAndSet(false,true)){ 38 | //从apollo中获取所有配置信息 39 | Config config = ConfigService.getAppConfig(); 40 | String apolloValue = config.getProperty(APOLLO_SETTING_NAME, "这里设置默认值"); 41 | Context context = getContext(); 42 | //ConfigChangeListener用来监听apollo配置的变化 43 | config.addChangeListener(new ConfigChangeListener() { 44 | @Override 45 | public void onChange(ConfigChangeEvent configChangeEvent) { 46 | for (String key : configChangeEvent.changedKeys()) { 47 | if (APOLLO_SETTING_NAME.equals(key)){ 48 | ConfigChange change = configChangeEvent.getChange(key); 49 | reloadDefaultConfiguration(change); 50 | } 51 | 52 | } 53 | } 54 | }); 55 | context.putProperty(LOGBACK_SETTING_NAME, apolloValue); 56 | log.info("the value of the logback field from apollo, apollo.setting.app.name is {}", apolloValue); 57 | } 58 | } 59 | 60 | @Override 61 | public void stop() { 62 | } 63 | 64 | @Override 65 | public boolean isStarted() { 66 | return started.get(); 67 | } 68 | 69 | @Override 70 | public boolean isResetResistant() { 71 | return true; 72 | } 73 | 74 | @Override 75 | public void onStart(LoggerContext context) { 76 | 77 | } 78 | 79 | @Override 80 | public void onReset(LoggerContext context) { 81 | 82 | } 83 | 84 | @Override 85 | public void onStop(LoggerContext context) { 86 | 87 | } 88 | 89 | @Override 90 | public void onLevelChange(Logger logger, Level level) { 91 | 92 | } 93 | 94 | /** 95 | * 重新加载logback, 并更新logback字段的值 96 | * @param change 这里面包含了apollo的变化值 97 | */ 98 | private void reloadDefaultConfiguration(ConfigChange change) { 99 | LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); 100 | ContextInitializer ci = new ContextInitializer(loggerContext); 101 | URL url = ci.findURLOfDefaultConfigurationFile(true); 102 | loggerContext.reset(); 103 | loggerContext.putProperty(LOGBACK_SETTING_NAME,change.getNewValue()); 104 | try { 105 | ci.configureByResource(url); 106 | } catch (JoranException e) { 107 | e.printStackTrace(); 108 | } 109 | log.info("reload loggerContext , you can see that the log has been updated, new value from apollo is {}",change.getNewValue()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /spring-mvc-logger/src/main/resources/META-INF/app.properties: -------------------------------------------------------------------------------- 1 | app.id=spring-mvc-logger 2 | # set apollo meta server address, adjust to actual address if necessary 3 | apollo.meta=http://localhost:8080 4 | -------------------------------------------------------------------------------- /spring-mvc-logger/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ${logPattern} 17 | 18 | 19 | 20 | 21 | ${logDir}/debug.log 22 | 23 | DEBUG 24 | ACCEPT 25 | DENY 26 | 27 | 28 | ${logDir}/debug/debuglog.%d{yyyyMMdd}.log.gz 29 | ${maxHistory} 30 | 31 | 32 | ${logPattern} 33 | 34 | 35 | 36 | 37 | 38 | ${logDir}/info.log 39 | 40 | INFO 41 | ACCEPT 42 | DENY 43 | 44 | 45 | ${logDir}/info/infolog.%d{yyyyMMdd}.log.gz 46 | ${maxHistory} 47 | 48 | 49 | ${logPattern} 50 | 51 | 52 | 53 | 54 | 55 | ${logDir}/warn.log 56 | 57 | WARN 58 | ACCEPT 59 | DENY 60 | 61 | 62 | ${logDir}/warn/warnlog.%d{yyyyMMdd}.log.gz 63 | ${maxHistory} 64 | 65 | 66 | ${logPattern} 67 | 68 | 69 | 70 | 71 | 72 | ${logDir}/error.log 73 | 74 | ERROR 75 | ACCEPT 76 | DENY 77 | 78 | 79 | ${logDir}/error/errorlog.%d{yyyyMMdd}.log.gz 80 | ${maxHistory} 81 | 82 | 83 | ${logPattern} 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /spring-mvc-logger/src/main/resources/spring-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /spring-mvc-logger/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | dispatcherServlet 9 | org.springframework.web.servlet.DispatcherServlet 10 | 11 | contextConfigLocation 12 | classpath:spring-*.xml 13 | 14 | 1 15 | 16 | 17 | 18 | dispatcherServlet 19 | / 20 | 21 | 22 | --------------------------------------------------------------------------------