├── .gitignore ├── LICENSE ├── README-INTR.md ├── README.md ├── doc └── lemon.png ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── micro │ │ └── lemon │ │ ├── Main.java │ │ ├── common │ │ ├── LemonConfig.java │ │ ├── LemonInvoke.java │ │ ├── LemonStatusCode.java │ │ ├── ServiceMapping.java │ │ ├── config │ │ │ ├── BizTaskConfig.java │ │ │ ├── DubboConfig.java │ │ │ ├── JwtConfig.java │ │ │ └── OriginalConfig.java │ │ ├── support │ │ │ ├── JwtAlgorithm.java │ │ │ └── RejectedStrategy.java │ │ └── utils │ │ │ ├── AntPathMatcher.java │ │ │ ├── NetUtils.java │ │ │ ├── StandardThreadExecutor.java │ │ │ └── URL.java │ │ ├── extension │ │ ├── Extension.java │ │ ├── ExtensionLoader.java │ │ └── SPI.java │ │ ├── filter │ │ ├── AbstractFilter.java │ │ ├── IFilter.java │ │ ├── LemonChain.java │ │ ├── LemonFactory.java │ │ └── support │ │ │ ├── LemonExceptionFilter.java │ │ │ ├── LemonInvokeFilter.java │ │ │ ├── LemonJwtFilter.java │ │ │ └── LemonLogFilter.java │ │ ├── proxy │ │ ├── dubbo │ │ │ ├── DubboInvoke.java │ │ │ ├── MetadataCollector.java │ │ │ ├── RegistryServiceSubscribe.java │ │ │ ├── extension │ │ │ │ ├── AbstractExtensionMetadataReport.java │ │ │ │ ├── RedisMetadataReport.java │ │ │ │ ├── RedisMetadataReportFactory.java │ │ │ │ ├── ZookeeperMetadataReport.java │ │ │ │ └── ZookeeperMetadataReportFactory.java │ │ │ └── metadata │ │ │ │ ├── MetadataCollectorFactory.java │ │ │ │ ├── RedisMetadataCollector.java │ │ │ │ ├── ZookeeperMetadataCollector.java │ │ │ │ └── annotation │ │ │ │ ├── LemonMethod.java │ │ │ │ └── LemonService.java │ │ └── http │ │ │ └── JsoupInvoke.java │ │ └── server │ │ ├── LemonCallback.java │ │ ├── LemonChannelInitializer.java │ │ ├── LemonContext.java │ │ ├── LemonRequest.java │ │ ├── LemonResponse.java │ │ ├── LemonServer.java │ │ └── LemonServerHandler.java └── resources │ ├── META-INF │ ├── dubbo │ │ └── internal │ │ │ └── org.apache.dubbo.metadata.store.MetadataReportFactory │ └── services │ │ ├── org.micro.lemon.common.LemonInvoke │ │ ├── org.micro.lemon.filter.IFilter │ │ └── org.micro.lemon.proxy.dubbo.MetadataCollector │ ├── lemon.yml │ ├── log4j2.xml │ └── tools │ ├── as.bat │ ├── as.sh │ └── assembly.xml └── test ├── java └── org │ └── micro │ └── lemon │ ├── GenericInvokeDemo.java │ ├── ProviderDemo.java │ ├── dubbo │ ├── ConsumerApplication.java │ ├── DemoService.java │ ├── DemoServiceImpl.java │ ├── GenericConsumerApplication.java │ └── ProviderApplication.java │ └── provider │ ├── DemoService.java │ ├── DemoServiceImpl.java │ └── User.java └── resources ├── lemon.yml └── log4j2.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | .idea/* 25 | *.iml 26 | target/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 lemon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-INTR.md: -------------------------------------------------------------------------------- 1 | # lemon 2 | 3 | http://127.0.0.1:9000/lemon/hello/first-dubbo-provider/org.micro.lemon.dubbo.DemoService/sayHi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lemon 2 | **The Micro Service Gateway Framework.** 3 | 4 | Lemon is a high-performance, Java based open source gateway framework. 5 | 6 | 基于Netty4的微服务网关(Micro Service Gateway)。同时支持Dubbo泛化调用和HTTP调用,并支持自定义实现微服务网关请求的代理转发功能。 7 | 8 | 9 | 10 | **GitHub**:https://github.com/yu120/lemon 11 | 12 | **QQ交流群**:微服务基础架构(191958521) 13 | 14 | **微信交流**:请加echo-lry 15 | 16 | 17 | ## Architecture 18 | ![lemon](doc/lemon.png) 19 | 20 | ## Features 21 | - HTTP to Dubbo Gateway 22 | - HTTP to HTTP Gateway 23 | - High Performance 24 | - Dynamic Discovery 25 | - Generalized Agent 26 | - Extensible Chain of Responsibility 27 | - Two-way LOG Printing Filter 28 | 29 | ## TODO 30 | - HTTP proxy Motan 31 | - HTTP proxy SpringCloud 32 | - TCP proxy other 33 | - CircuitBreaker Filter 34 | - Degrade Filter 35 | - Idempotent Filter 36 | - Limiter Filter 37 | - Retry Filter 38 | 39 | 40 | ## Config Introduce 41 | Config Path: `src/main/resources/lemon.yml` 42 | 43 | ``` 44 | port: 8080 45 | application: lemon 46 | # Netty IO/Work thread number 47 | ioThread: 0 48 | workThread: 0 49 | # Body max content length, 64 * 1024 * 1024 = 64 MB 50 | maxContentLength: 67108864 51 | # Max client connection channel number 52 | maxChannel: 100000 53 | # 54 | bizCoreThread: 20 55 | bizMaxThread: 200 56 | bizQueueCapacity: 800 57 | bizKeepAliveTime: 60000 58 | # Custom Fixed Response Header Parameter Configuration 59 | resHeaders: 60 | Connection: keep-alive 61 | Accept-Encoding: gzip,deflate 62 | Content-Type: application/json;charset=UTF-8 63 | originalHeaders: [Connection, Content-Type, Set-Cookie, Call-Code, Call-Message] 64 | # Dubbo lemon config 65 | dubbo: 66 | registryAddress: zookeeper://127.0.0.1:2181 67 | metadataAddress: zookeeper://127.0.0.1:2181 68 | # Direct forwarding HTTP rule 69 | services: 70 | - category: jsoup 71 | service: /baidu/** 72 | url: https://www.baidu.com 73 | - category: jsoup 74 | service: /oschina/** 75 | url: https://www.oschina.net 76 | ``` 77 | 78 | ## Dubbo Lemon 79 | The support apache dubbo 2.7.2 generic service proxy. 80 | 81 | **Format:** 82 | ``` 83 | URL: 84 | http://[host]:[port]/lemon/[application]/[service or serviceSimpleName]/[method]?group=[group]&version=[version] 85 | 86 | Body: 87 | List[Map{...}, Map{...}, ...] 88 | ``` 89 | 90 | **Example:** 91 | ``` 92 | http://localhost:8080/lemon/micro-dubbo-provider/cn.micro.biz.dubbo.provider.DemoService/test 93 | 94 | or 95 | 96 | http://localhost:8080/lemon/micro-dubbo-provider/demo/test 97 | 98 | [{"name":"lemon", "age":23}, {"10001"}] 99 | ``` 100 | 101 | ## HTTP Lemon 102 | Proxy forwarding using jsoup. 103 | 104 | ``` 105 | http://[host]:[port]/lemon/[service]/**?group=[group]&version=[version] 106 | ``` 107 | 108 | ## Motan Lemon 109 | TODO 110 | 111 | ## Spring Cloud Lemon 112 | TODO 113 | 114 | ## Packing and Installation 115 | ``` 116 | mvn clean install -Denforcer.skip=true -Dmaven.test.skip=true 117 | ``` 118 | The target directory: `target/lemon-1.0.0-SNAPSHOT-dist.tar.gz` 119 | 120 | After successful execution, `lemon-1.0.0-SNAPSHOT-dist.tar.gz` will be generated in the directory of `target`. 121 | 122 | ## Start lemon 123 | ``` 124 | > tar -zxvf lemon-1.0.0-SNAPSHOT-dist.tar.gz 125 | > cd bin 126 | > ./app start 127 | # Usage: app { console | start | stop | restart | status | dump } 128 | ``` 129 | 130 | ## Use Arthas 131 | Alibaba [Arthas](https://github.com/alibaba/arthas) 132 | 133 | **arthas-boot**: 134 | ``` 135 | > wget https://alibaba.github.io/arthas/arthas-boot.jar 136 | > java -jar arthas-boot.jar 137 | ``` 138 | 139 | **as.sh**: 140 | ``` 141 | > curl -L https://alibaba.github.io/arthas/install.sh | sh 142 | > ./as.sh 143 | ``` 144 | 可以直接访问:http://127.0.0.1:8563 145 | 146 | ## Start service provider 147 | The metadata report config must be used. 148 | 149 | ``` 150 | import DemoService; 151 | import DemoServiceImpl; 152 | import org.apache.dubbo.config.*; 153 | 154 | public class ProviderDemo { 155 | 156 | public static void main(String[] args) throws Exception { 157 | // Implementation 158 | DemoService demoService = new DemoServiceImpl(); 159 | 160 | // Application Info 161 | ApplicationConfig application = new ApplicationConfig(); 162 | application.setName("micro-dubbo-provider"); 163 | 164 | // Registry Info 165 | RegistryConfig registry = new RegistryConfig(); 166 | registry.setAddress("zookeeper://127.0.0.1:2181"); 167 | 168 | // Metadata Report Info 169 | MetadataReportConfig metadataReportConfig = new MetadataReportConfig(); 170 | metadataReportConfig.setAddress("zookeeper://127.0.0.1:2181"); 171 | 172 | // Protocol 173 | ProtocolConfig protocol = new ProtocolConfig(); 174 | protocol.setName("dubbo"); 175 | protocol.setPort(12345); 176 | protocol.setThreads(200); 177 | 178 | // Exporting: In case of memory leak, please cache. 179 | ServiceConfig service = new ServiceConfig<>(); 180 | service.setApplication(application); 181 | // Use setRegistries() for multi-registry case 182 | service.setRegistry(registry); 183 | // Use setProtocols() for multi-protocol case 184 | service.setProtocol(protocol); 185 | service.setMetadataReportConfig(metadataReportConfig); 186 | service.setInterface(DemoService.class); 187 | service.setRef(demoService); 188 | 189 | // Local export and register 190 | service.export(); 191 | System.in.read(); 192 | } 193 | 194 | } 195 | ``` 196 | 197 | ## License 198 | Lemon is under the MIT license. See the LICENSE file for details. 199 | -------------------------------------------------------------------------------- /doc/lemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yu120/lemon/243ce3feb1ffe882cfb5f7dd75d4f03b12ae591c/doc/lemon.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | lemon 8 | org.micro 9 | 1.0.0-SNAPSHOT 10 | 11 | 12 | UTF-8 13 | 1.8 14 | 3.3.3 15 | 16 | 1.20 17 | 4.1.53.Final 18 | 2.13.2 19 | 1.2.58 20 | 2.7.8 21 | 2.9.0 22 | 1.12.1 23 | 3.3.0 24 | 1.18.2 25 | 26 | 27 | 28 | 29 | 30 | org.yaml 31 | snakeyaml 32 | ${snakeyaml.version} 33 | 34 | 35 | io.netty 36 | netty-all 37 | ${netty.version} 38 | 39 | 40 | 41 | 42 | org.apache.logging.log4j 43 | log4j-core 44 | ${log4j2.version} 45 | 46 | 47 | org.apache.logging.log4j 48 | log4j-slf4j-impl 49 | ${log4j2.version} 50 | 51 | 52 | 53 | 54 | com.alibaba 55 | fastjson 56 | ${fastjson.version} 57 | 58 | 59 | 60 | 61 | org.apache.dubbo 62 | dubbo 63 | ${dubbo.version} 64 | 65 | 66 | spring-context 67 | org.springframework 68 | 69 | 70 | 71 | 72 | org.apache.dubbo 73 | dubbo-dependencies-zookeeper 74 | ${dubbo.version} 75 | pom 76 | 77 | 78 | log4j 79 | log4j 80 | 81 | 82 | slf4j-log4j12 83 | org.slf4j 84 | 85 | 86 | 87 | 88 | org.apache.dubbo 89 | dubbo-metadata-report-zookeeper 90 | ${dubbo.version} 91 | 92 | 93 | redis.clients 94 | jedis 95 | ${jedis.version} 96 | 97 | 98 | 99 | 100 | org.jsoup 101 | jsoup 102 | ${jsoup.version} 103 | 104 | 105 | 106 | com.auth0 107 | java-jwt 108 | ${java-jwt.version} 109 | 110 | 111 | 112 | org.projectlombok 113 | lombok 114 | ${lombok.version} 115 | provided 116 | 117 | 118 | 119 | 120 | 121 | 122 | src/main/resources 123 | true 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-compiler-plugin 131 | 3.7.0 132 | 133 | ${java.version} 134 | ${java.version} 135 | 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-jar-plugin 141 | 2.6 142 | 143 | 144 | 145 | 146 | *.yml 147 | *.xml 148 | *.properties 149 | static/** 150 | 151 | *.conf 152 | tools/** 153 | 154 | 155 | 156 | 157 | package 158 | 159 | jar 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | org.apache.maven.plugins 168 | maven-enforcer-plugin 169 | 3.0.0-M2 170 | 171 | 172 | default-cli 173 | 174 | enforce 175 | 176 | validate 177 | 178 | 179 | 180 | 181 | 182 | 183 | [${maven.version},) 184 | 185 | 186 | 187 | 188 | 189 | [${java.version}.0,) 190 | 191 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | pl.project13.maven 204 | git-commit-id-plugin 205 | 206 | 207 | 208 | revision 209 | 210 | 211 | 212 | 213 | true 214 | yyyy-MM-dd'T'HH:mm:ssZ 215 | true 216 | ${project.build.directory}/dist/jsw/app/conf/git.properties 217 | 218 | 219 | 220 | 221 | 222 | 223 | org.codehaus.mojo 224 | appassembler-maven-plugin 225 | 2.0.0 226 | 227 | UTF-8 228 | bin 229 | tmp 230 | logs 231 | lib 232 | flat 233 | ${project.build.directory}/dist 234 | conf 235 | true 236 | src/main/resources 237 | 238 | 239 | app 240 | 241 | org.micro.lemon.Main 242 | 243 | 244 | jsw 245 | 246 | 247 | 248 | jsw 249 | 250 | aix-ppc-32 251 | aix-ppc-64 252 | linux-ppc-64 253 | linux-x86-32 254 | linux-x86-64 255 | windows-x86-32 256 | windows-x86-64 257 | hpux-parisc-64 258 | solaris-x86-32 259 | solaris-sparc-32 260 | solaris-sparc-64 261 | macosx-ppc-32 262 | macosx-universal-32 263 | macosx-universal-64 264 | 265 | 266 | 267 | configuration.directory.in.classpath.first 268 | conf 269 | 270 | 271 | wrapper.ping.timeout 272 | 120 273 | 274 | 275 | set.default.REPO_DIR 276 | lib 277 | 278 | 279 | wrapper.logfile 280 | logs/wrapper.log 281 | 282 | 283 | 284 | 285 | 286 | 287 | 296 | 297 | 298 | 299 | 300 | -server 301 | 302 | -Xdebug 303 | 306 | 307 | -XX:+HeapDumpOnOutOfMemoryError 308 | -XX:HeapDumpPath=logs/heap-dump.hprof 309 | 310 | -XX:+UseG1GC 311 | -XX:MaxGCPauseMillis=200 312 | -XX:InitiatingHeapOccupancyPercent=45 313 | -XX:G1ReservePercent=10 314 | -XX:NewRatio=2 315 | -XX:SurvivorRatio=8 316 | -XX:MaxTenuringThreshold=15 317 | 318 | -Xloggc:logs/gc.log 319 | -XX:GCLogFileSize=10M 320 | -XX:NumberOfGCLogFiles=10 321 | -XX:+UseGCLogFileRotation 322 | -XX:+PrintGCDateStamps 323 | -XX:+PrintGCTimeStamps 324 | -XX:+PrintGCDetails 325 | -XX:+PrintHeapAtGC 326 | -XX:+PrintGCApplicationStoppedTime 327 | -XX:+DisableExplicitGC 328 | -verbose:gc 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | generate-jsw-scripts 337 | package 338 | 339 | generate-daemons 340 | 341 | 342 | 343 | 344 | 345 | 346 | org.apache.maven.plugins 347 | maven-assembly-plugin 348 | 3.1.0 349 | 350 | 351 | src/main/resources/tools/assembly.xml 352 | 353 | 354 | 355 | 356 | make-assembly 357 | package 358 | 359 | single 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/Main.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon; 2 | 3 | import org.micro.lemon.server.LemonServer; 4 | 5 | /** 6 | * Main 7 | * 8 | * @author lry 9 | */ 10 | public class Main { 11 | 12 | public static void main(String[] args) { 13 | new LemonServer().initialize(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/LemonConfig.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import org.micro.lemon.common.config.BizTaskConfig; 7 | import org.micro.lemon.common.config.JwtConfig; 8 | import org.micro.lemon.common.config.DubboConfig; 9 | import org.micro.lemon.common.config.OriginalConfig; 10 | import lombok.Data; 11 | import lombok.ToString; 12 | import org.yaml.snakeyaml.Yaml; 13 | 14 | import java.io.FileInputStream; 15 | import java.io.Serializable; 16 | import java.util.*; 17 | import java.util.regex.Matcher; 18 | import java.util.regex.Pattern; 19 | 20 | /** 21 | * Lemon Config 22 | * 23 | * @author lry 24 | */ 25 | @Data 26 | @ToString 27 | public class LemonConfig implements Serializable { 28 | 29 | /** 30 | * The operation token 31 | */ 32 | private String token = "lemon"; 33 | 34 | 35 | /** 36 | * The lemon http application path 37 | */ 38 | private String application; 39 | /** 40 | * The lemon http application port 41 | */ 42 | private int port = 8080; 43 | /** 44 | * The hold model: true 45 | */ 46 | private boolean hold = true; 47 | /** 48 | * The IO thread number 49 | */ 50 | private int ioThread = 0; 51 | /** 52 | * The work thread number 53 | */ 54 | private int workThread = 0; 55 | /** 56 | * The default value: 64MB 57 | */ 58 | private int maxContentLength = 64 * 1024 * 1024; 59 | /** 60 | * The max server conn (all clients conn) 61 | **/ 62 | private int maxConnection = 100000; 63 | 64 | 65 | /** 66 | * The call original ignore request header list key 67 | */ 68 | private Set ignoreHeaders = new LinkedHashSet<>(); 69 | 70 | /** 71 | * The biz task config 72 | */ 73 | private BizTaskConfig biz; 74 | /** 75 | * The original config 76 | */ 77 | private OriginalConfig original; 78 | /** 79 | * The jwt config 80 | */ 81 | private JwtConfig jwt; 82 | /** 83 | * The dubbo config 84 | */ 85 | private DubboConfig dubbo; 86 | 87 | 88 | /** 89 | * The registry address 90 | */ 91 | private String registryAddress; 92 | /** 93 | * The exclude filter list 94 | */ 95 | private List excludeFilters = new ArrayList<>(); 96 | /** 97 | * The include filter list 98 | */ 99 | private List includeFilters = new ArrayList<>(); 100 | /** 101 | * The direct connection service mapping list 102 | */ 103 | private List services = new ArrayList<>(); 104 | 105 | private static final Pattern LINE_PATTERN = Pattern.compile("-(\\w)"); 106 | 107 | 108 | /** 109 | * The load config 110 | * 111 | * @return {@link LemonConfig} 112 | */ 113 | public static LemonConfig loadConfig() { 114 | java.net.URL url = LemonConfig.class.getClassLoader().getResource("lemon.yml"); 115 | if (url != null) { 116 | try { 117 | Iterable iterable = new Yaml().loadAll(new FileInputStream(url.getFile())); 118 | for (Object object : iterable) { 119 | JSON json = recursion(object); 120 | return json.toJavaObject(LemonConfig.class); 121 | } 122 | } catch (Exception e) { 123 | throw new RuntimeException("The load as yaml is exception", e); 124 | } 125 | } 126 | 127 | throw new RuntimeException("Not found lemon.yml"); 128 | } 129 | 130 | /** 131 | * 递归解析 132 | */ 133 | @SuppressWarnings("unchecked") 134 | private static JSON recursion(Object object) { 135 | if (object instanceof Map) { 136 | JSONObject jsonObject = new JSONObject(); 137 | for (Map.Entry entry : ((Map) object).entrySet()) { 138 | String key = String.valueOf(entry.getKey()); 139 | if (key.startsWith("-D") || key.startsWith("-d")) { 140 | key = key.substring(2); 141 | } else if (key.contains("-")) { 142 | key = lineToHump(key); 143 | } 144 | if (entry.getValue() instanceof Map || entry.getValue() instanceof Collection) { 145 | jsonObject.put(key, recursion(entry.getValue())); 146 | } else { 147 | jsonObject.put(key, entry.getValue()); 148 | } 149 | } 150 | 151 | return jsonObject; 152 | } else if (object instanceof Collection) { 153 | JSONArray jsonArray = new JSONArray(); 154 | for (Object tempObject : (List) object) { 155 | if (tempObject instanceof Map || tempObject instanceof Collection) { 156 | jsonArray.add(recursion(tempObject)); 157 | } else { 158 | jsonArray.add(tempObject); 159 | } 160 | } 161 | 162 | return jsonArray; 163 | } else { 164 | throw new RuntimeException("未知数据类型" + object); 165 | } 166 | } 167 | 168 | /** 169 | * 减号转驼峰 170 | */ 171 | private static String lineToHump(String str) { 172 | Matcher matcher = LINE_PATTERN.matcher(str.toLowerCase()); 173 | StringBuffer sb = new StringBuffer(); 174 | while (matcher.find()) { 175 | matcher.appendReplacement(sb, matcher.group(1).toUpperCase()); 176 | } 177 | matcher.appendTail(sb); 178 | return sb.toString(); 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/LemonInvoke.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common; 2 | 3 | import org.micro.lemon.server.LemonContext; 4 | import org.micro.lemon.extension.SPI; 5 | 6 | import java.util.concurrent.CompletableFuture; 7 | 8 | /** 9 | * LemonInvoke 10 | * 11 | * @author lry 12 | */ 13 | @SPI(value = "dubbo", single = true) 14 | public interface LemonInvoke { 15 | 16 | /** 17 | * The initialize 18 | * 19 | * @param lemonConfig {@link LemonConfig} 20 | */ 21 | void initialize(LemonConfig lemonConfig); 22 | 23 | /** 24 | * The sync invoke 25 | * 26 | * @param lemonContext {@link LemonContext} 27 | */ 28 | LemonContext invoke(LemonContext lemonContext); 29 | 30 | /** 31 | * The async invoke 32 | * 33 | * @param lemonContext {@link LemonContext} 34 | * @return result object {@link CompletableFuture} 35 | */ 36 | default CompletableFuture invokeAsync(LemonContext lemonContext) { 37 | return CompletableFuture.completedFuture(invoke(lemonContext)); 38 | } 39 | 40 | /** 41 | * The build failure status code 42 | * 43 | * @param context {@link LemonContext} 44 | * @param throwable throw exception 45 | * @return {@link LemonStatusCode} 46 | */ 47 | LemonStatusCode failure(LemonContext context, Throwable throwable); 48 | 49 | /** 50 | * The destroy 51 | */ 52 | void destroy(); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/LemonStatusCode.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | * Lemon Status Code 10 | * 11 | * @author lry 12 | */ 13 | @Slf4j 14 | @Getter 15 | @ToString 16 | @AllArgsConstructor 17 | public enum LemonStatusCode { 18 | 19 | // ======= Lemon Framework Exception 20 | 21 | SUCCESS(200, "Success"), 22 | 23 | BAD_REQUEST(400, "Bad Request"), 24 | UNAUTHORIZED(401, "Unauthorized"), 25 | PAYMENT_REQUIRED(402, "Token Expired"), 26 | FORBIDDEN(403, "Forbidden"), 27 | NOT_FOUND(404, "Not Found"), 28 | REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"), 29 | EXPECTATION_FAILED(417, "Expectation Failed"), 30 | TOO_MANY_REQUESTS(429, "Too Many Requests"), 31 | 32 | INTERNAL_SERVER_ERROR(500, "Internal Server Error"), 33 | BAD_GATEWAY(502, "Bad Gateway"), 34 | SERVICE_UNAVAILABLE(503, "Service Unavailable"), 35 | GATEWAY_TIMEOUT(504, "Gateway Timeout"), 36 | 37 | CALL_ORIGINAL_TIMEOUT(601, "Call Original Timeout"), 38 | CALL_ORIGINAL_BIZ_ERROR(602, "Call Original BIZ Error"), 39 | CALL_ORIGINAL_NETWORK_ERROR(603, "Call Original Network Error"), 40 | CALL_ORIGINAL_SERIALIZATION(604, "Call Original Serialization"), 41 | CALL_ORIGINAL_UNKNOWN(699, "Call Original Unknown"); 42 | 43 | private final int code; 44 | private final String message; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/ServiceMapping.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * Service Mapping 10 | * 11 | * @author lry 12 | */ 13 | @Data 14 | @ToString 15 | public class ServiceMapping implements Serializable { 16 | 17 | /** 18 | * The protocol 19 | */ 20 | private String protocol; 21 | /** 22 | * The application of service 23 | */ 24 | private String application; 25 | /** 26 | * The service name 27 | */ 28 | private String service; 29 | /** 30 | * The real service name 31 | */ 32 | private String serviceName; 33 | /** 34 | * The group of service 35 | */ 36 | private String group; 37 | /** 38 | * The version of service 39 | */ 40 | private String version; 41 | /** 42 | * The url of service direct connection 43 | */ 44 | private String url; 45 | /** 46 | * The full url is true 47 | */ 48 | private boolean fullUrl = false; 49 | /** 50 | * The current call original server timeout(ms) 51 | */ 52 | private Long timeout; 53 | 54 | /** 55 | * The method name 56 | */ 57 | private String method; 58 | /** 59 | * The parameter type list 60 | */ 61 | private String[] paramTypes; 62 | /** 63 | * The parameter value list 64 | */ 65 | private Object[] paramValues; 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/config/BizTaskConfig.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common.config; 2 | 3 | import org.micro.lemon.common.support.RejectedStrategy; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * Biz Task Config 11 | * 12 | * @author lry 13 | */ 14 | @Data 15 | @ToString 16 | public class BizTaskConfig implements Serializable { 17 | 18 | /** 19 | * The biz core thread number 20 | */ 21 | private int coreThread = 20; 22 | /** 23 | * The biz max thread number 24 | */ 25 | private int maxThread = 200; 26 | /** 27 | * The biz queue capacity 28 | */ 29 | private int queueCapacity = 800; 30 | /** 31 | * The biz keep alive time(ms) 32 | */ 33 | private long keepAliveTime = 60000L; 34 | /** 35 | * The biz thread rejected strategy 36 | */ 37 | private RejectedStrategy rejectedStrategy = RejectedStrategy.ABORT_POLICY; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/config/DubboConfig.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common.config; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * Dubbo Config 10 | * 11 | * @author lry 12 | */ 13 | @Data 14 | @ToString 15 | public class DubboConfig implements Serializable { 16 | 17 | /** 18 | * The service name simple 19 | */ 20 | private Boolean serviceSimpleName = true; 21 | /** 22 | * The registry address 23 | */ 24 | private String registryAddress; 25 | /** 26 | * The metadata address 27 | */ 28 | private String metadataAddress; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/config/JwtConfig.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common.config; 2 | 3 | import org.micro.lemon.common.support.JwtAlgorithm; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * Jwt Config 11 | * 12 | * @author lry 13 | */ 14 | @Data 15 | @ToString 16 | public class JwtConfig implements Serializable { 17 | 18 | /** 19 | * The jwt enable 20 | */ 21 | private boolean enable = true; 22 | /** 23 | * The jwt key 24 | */ 25 | private String key = "Token"; 26 | /** 27 | * The jwt secret 28 | */ 29 | private String secret = "lemon"; 30 | /** 31 | * The jwt algorithm 32 | */ 33 | private JwtAlgorithm algorithm = JwtAlgorithm.HMAC256; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/config/OriginalConfig.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common.config; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.io.Serializable; 7 | import java.util.LinkedHashSet; 8 | import java.util.Set; 9 | 10 | /** 11 | * Jwt Config 12 | * 13 | * @author lry 14 | */ 15 | @Data 16 | @ToString 17 | public class OriginalConfig implements Serializable { 18 | 19 | /** 20 | * The global call original server timeout(ms) 21 | */ 22 | private long timeout = 30000L; 23 | /** 24 | * The call original request header list key 25 | */ 26 | private Set reqHeaders = new LinkedHashSet<>(); 27 | /** 28 | * The call original response header list key 29 | */ 30 | private Set resHeaders = new LinkedHashSet<>(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/support/JwtAlgorithm.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common.support; 2 | 3 | public enum JwtAlgorithm { 4 | 5 | // === 6 | 7 | HMAC256, HMAC384, HMAC512; 8 | 9 | } -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/support/RejectedStrategy.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common.support; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | import java.util.concurrent.RejectedExecutionHandler; 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | 9 | /** 10 | * Rejected Strategy 11 | * 12 | * @author lry 13 | */ 14 | @Getter 15 | @AllArgsConstructor 16 | public enum RejectedStrategy { 17 | 18 | // === 19 | 20 | ABORT_POLICY(new ThreadPoolExecutor.AbortPolicy()), 21 | CALLER_RUNS_POLICY(new ThreadPoolExecutor.CallerRunsPolicy()), 22 | DISCARD_OLDEST_POLICY(new ThreadPoolExecutor.DiscardOldestPolicy()), 23 | DISCARD_POLICY(new ThreadPoolExecutor.DiscardPolicy()); 24 | 25 | private RejectedExecutionHandler handler; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/utils/AntPathMatcher.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common.utils; 2 | 3 | import java.util.*; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * The Ant Path Matcher 10 | * 11 | * @author lry 12 | * @link org.springframework.util.AntPathMatcher 13 | */ 14 | public class AntPathMatcher { 15 | 16 | public static final String DEFAULT_PATH_SEPARATOR = "/"; 17 | private static final int CACHE_TURNOFF_THRESHOLD = 65536; 18 | private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}"); 19 | private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; 20 | 21 | private String pathSeparator; 22 | private PathSeparatorPatternCache pathSeparatorPatternCache; 23 | 24 | private boolean caseSensitive = true; 25 | private boolean trimTokens = false; 26 | private volatile Boolean cachePatterns; 27 | 28 | private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); 29 | private final Map stringMatcherCache = new ConcurrentHashMap<>(256); 30 | 31 | public AntPathMatcher() { 32 | this.pathSeparator = DEFAULT_PATH_SEPARATOR; 33 | this.pathSeparatorPatternCache = new PathSeparatorPatternCache(DEFAULT_PATH_SEPARATOR); 34 | } 35 | 36 | public AntPathMatcher(String pathSeparator) { 37 | if (pathSeparator == null) { 38 | throw new IllegalArgumentException("'pathSeparator' is required"); 39 | } 40 | 41 | this.pathSeparator = pathSeparator; 42 | this.pathSeparatorPatternCache = new PathSeparatorPatternCache(pathSeparator); 43 | } 44 | 45 | public void setPathSeparator(String pathSeparator) { 46 | this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR); 47 | this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); 48 | } 49 | 50 | public void setCaseSensitive(boolean caseSensitive) { 51 | this.caseSensitive = caseSensitive; 52 | } 53 | 54 | public void setTrimTokens(boolean trimTokens) { 55 | this.trimTokens = trimTokens; 56 | } 57 | 58 | public void setCachePatterns(boolean cachePatterns) { 59 | this.cachePatterns = cachePatterns; 60 | } 61 | 62 | private void deactivatePatternCache() { 63 | this.cachePatterns = false; 64 | this.tokenizedPatternCache.clear(); 65 | this.stringMatcherCache.clear(); 66 | } 67 | 68 | 69 | public boolean isPattern(String path) { 70 | return (path.indexOf('*') != -1 || path.indexOf('?') != -1); 71 | } 72 | 73 | public boolean match(String pattern, String path) { 74 | return doMatch(pattern, path, true, null); 75 | } 76 | 77 | public boolean matchStart(String pattern, String path) { 78 | return doMatch(pattern, path, false, null); 79 | } 80 | 81 | protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { 82 | if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { 83 | return false; 84 | } 85 | 86 | String[] pattDirs = tokenizePattern(pattern); 87 | if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) { 88 | return false; 89 | } 90 | 91 | String[] pathDirs = tokenizePath(path); 92 | 93 | int pattIdxStart = 0; 94 | int pattIdxEnd = pattDirs.length - 1; 95 | int pathIdxStart = 0; 96 | int pathIdxEnd = pathDirs.length - 1; 97 | 98 | while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { 99 | String pattDir = pattDirs[pattIdxStart]; 100 | if ("**".equals(pattDir)) { 101 | break; 102 | } 103 | if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { 104 | return false; 105 | } 106 | pattIdxStart++; 107 | pathIdxStart++; 108 | } 109 | 110 | if (pathIdxStart > pathIdxEnd) { 111 | if (pattIdxStart > pattIdxEnd) { 112 | return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); 113 | } 114 | if (!fullMatch) { 115 | return true; 116 | } 117 | if (pattIdxStart == pattIdxEnd && "*".equals(pattDirs[pattIdxStart]) && path.endsWith(this.pathSeparator)) { 118 | return true; 119 | } 120 | for (int i = pattIdxStart; i <= pattIdxEnd; i++) { 121 | if (!"**".equals(pattDirs[i])) { 122 | return false; 123 | } 124 | } 125 | return true; 126 | } else if (pattIdxStart > pattIdxEnd) { 127 | return false; 128 | } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) { 129 | return true; 130 | } 131 | 132 | while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { 133 | String pattDir = pattDirs[pattIdxEnd]; 134 | if ("**".equals(pattDir)) { 135 | break; 136 | } 137 | if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { 138 | return false; 139 | } 140 | pattIdxEnd--; 141 | pathIdxEnd--; 142 | } 143 | if (pathIdxStart > pathIdxEnd) { 144 | for (int i = pattIdxStart; i <= pattIdxEnd; i++) { 145 | if (!"**".equals(pattDirs[i])) { 146 | return false; 147 | } 148 | } 149 | return true; 150 | } 151 | 152 | while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { 153 | int patIdxTmp = -1; 154 | for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { 155 | if ("**".equals(pattDirs[i])) { 156 | patIdxTmp = i; 157 | break; 158 | } 159 | } 160 | if (patIdxTmp == pattIdxStart + 1) { 161 | pattIdxStart++; 162 | continue; 163 | } 164 | 165 | int patLength = (patIdxTmp - pattIdxStart - 1); 166 | int strLength = (pathIdxEnd - pathIdxStart + 1); 167 | int foundIdx = -1; 168 | 169 | strLoop: 170 | for (int i = 0; i <= strLength - patLength; i++) { 171 | for (int j = 0; j < patLength; j++) { 172 | String subPat = pattDirs[pattIdxStart + j + 1]; 173 | String subStr = pathDirs[pathIdxStart + i + j]; 174 | if (!matchStrings(subPat, subStr, uriTemplateVariables)) { 175 | continue strLoop; 176 | } 177 | } 178 | foundIdx = pathIdxStart + i; 179 | break; 180 | } 181 | 182 | if (foundIdx == -1) { 183 | return false; 184 | } 185 | 186 | pattIdxStart = patIdxTmp; 187 | pathIdxStart = foundIdx + patLength; 188 | } 189 | 190 | for (int i = pattIdxStart; i <= pattIdxEnd; i++) { 191 | if (!"**".equals(pattDirs[i])) { 192 | return false; 193 | } 194 | } 195 | 196 | return true; 197 | } 198 | 199 | private boolean isPotentialMatch(String path, String[] pattDirs) { 200 | if (!this.trimTokens) { 201 | int pos = 0; 202 | for (String pattDir : pattDirs) { 203 | int skipped = skipSeparator(path, pos, this.pathSeparator); 204 | pos += skipped; 205 | skipped = skipSegment(path, pos, pattDir); 206 | if (skipped < pattDir.length()) { 207 | return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); 208 | } 209 | pos += skipped; 210 | } 211 | } 212 | return true; 213 | } 214 | 215 | private int skipSegment(String path, int pos, String prefix) { 216 | int skipped = 0; 217 | for (int i = 0; i < prefix.length(); i++) { 218 | char c = prefix.charAt(i); 219 | if (isWildcardChar(c)) { 220 | return skipped; 221 | } 222 | int currPos = pos + skipped; 223 | if (currPos >= path.length()) { 224 | return 0; 225 | } 226 | if (c == path.charAt(currPos)) { 227 | skipped++; 228 | } 229 | } 230 | return skipped; 231 | } 232 | 233 | private int skipSeparator(String path, int pos, String separator) { 234 | int skipped = 0; 235 | while (path.startsWith(separator, pos + skipped)) { 236 | skipped += separator.length(); 237 | } 238 | return skipped; 239 | } 240 | 241 | private boolean isWildcardChar(char c) { 242 | for (char candidate : WILDCARD_CHARS) { 243 | if (c == candidate) { 244 | return true; 245 | } 246 | } 247 | return false; 248 | } 249 | 250 | protected String[] tokenizePattern(String pattern) { 251 | String[] tokenized = null; 252 | Boolean cachePatterns = this.cachePatterns; 253 | if (cachePatterns == null || cachePatterns) { 254 | tokenized = this.tokenizedPatternCache.get(pattern); 255 | } 256 | if (tokenized == null) { 257 | tokenized = tokenizePath(pattern); 258 | if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { 259 | deactivatePatternCache(); 260 | return tokenized; 261 | } 262 | if (cachePatterns == null || cachePatterns) { 263 | this.tokenizedPatternCache.put(pattern, tokenized); 264 | } 265 | } 266 | return tokenized; 267 | } 268 | 269 | protected String[] tokenizePath(String path) { 270 | return tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true); 271 | } 272 | 273 | public static String[] tokenizeToStringArray( 274 | String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { 275 | 276 | if (str == null) { 277 | return null; 278 | } 279 | 280 | StringTokenizer st = new StringTokenizer(str, delimiters); 281 | List tokens = new ArrayList<>(); 282 | while (st.hasMoreTokens()) { 283 | String token = st.nextToken(); 284 | if (trimTokens) { 285 | token = token.trim(); 286 | } 287 | if (!ignoreEmptyTokens || token.length() > 0) { 288 | tokens.add(token); 289 | } 290 | } 291 | 292 | return tokens.toArray(new String[0]); 293 | } 294 | 295 | private boolean matchStrings(String pattern, String str, Map uriTemplateVariables) { 296 | return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); 297 | } 298 | 299 | protected AntPathStringMatcher getStringMatcher(String pattern) { 300 | AntPathStringMatcher matcher = null; 301 | Boolean cachePatterns = this.cachePatterns; 302 | if (cachePatterns == null || cachePatterns) { 303 | matcher = this.stringMatcherCache.get(pattern); 304 | } 305 | if (matcher == null) { 306 | matcher = new AntPathStringMatcher(pattern, this.caseSensitive); 307 | if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { 308 | deactivatePatternCache(); 309 | return matcher; 310 | } 311 | if (cachePatterns == null || cachePatterns) { 312 | this.stringMatcherCache.put(pattern, matcher); 313 | } 314 | } 315 | return matcher; 316 | } 317 | 318 | public String extractPathWithinPattern(String pattern, String path) { 319 | String[] patternParts = tokenizeToStringArray(pattern, this.pathSeparator, this.trimTokens, true); 320 | String[] pathParts = tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true); 321 | StringBuilder builder = new StringBuilder(); 322 | boolean pathStarted = false; 323 | 324 | for (int segment = 0; segment < patternParts.length; segment++) { 325 | String patternPart = patternParts[segment]; 326 | if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { 327 | for (; segment < pathParts.length; segment++) { 328 | if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { 329 | builder.append(this.pathSeparator); 330 | } 331 | builder.append(pathParts[segment]); 332 | pathStarted = true; 333 | } 334 | } 335 | } 336 | 337 | return builder.toString(); 338 | } 339 | 340 | public Map extractUriTemplateVariables(String pattern, String path) { 341 | Map variables = new LinkedHashMap(); 342 | boolean result = doMatch(pattern, path, true, variables); 343 | if (!result) { 344 | throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); 345 | } 346 | return variables; 347 | } 348 | 349 | public String combine(String pattern1, String pattern2) { 350 | if (!hasText(pattern1) && !hasText(pattern2)) { 351 | return ""; 352 | } 353 | if (!hasText(pattern1)) { 354 | return pattern2; 355 | } 356 | if (!hasText(pattern2)) { 357 | return pattern1; 358 | } 359 | 360 | boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); 361 | if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { 362 | return pattern2; 363 | } 364 | 365 | if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { 366 | return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); 367 | } 368 | 369 | if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { 370 | return concat(pattern1, pattern2); 371 | } 372 | 373 | int starDotPos1 = pattern1.indexOf("*."); 374 | if (pattern1ContainsUriVar || starDotPos1 == -1 || ".".equals(this.pathSeparator)) { 375 | return concat(pattern1, pattern2); 376 | } 377 | 378 | String ext1 = pattern1.substring(starDotPos1 + 1); 379 | int dotPos2 = pattern2.indexOf('.'); 380 | String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); 381 | String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); 382 | boolean ext1All = (".*".equals(ext1) || "".equals(ext1)); 383 | boolean ext2All = (".*".equals(ext2) || "".equals(ext2)); 384 | if (!ext1All && !ext2All) { 385 | throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); 386 | } 387 | String ext = (ext1All ? ext2 : ext1); 388 | return file2 + ext; 389 | } 390 | 391 | public static boolean hasText(String str) { 392 | if (str == null || str.isEmpty()) { 393 | return false; 394 | } 395 | 396 | int strLen = str.length(); 397 | for (int i = 0; i < strLen; i++) { 398 | if (!Character.isWhitespace(str.charAt(i))) { 399 | return true; 400 | } 401 | } 402 | 403 | return false; 404 | } 405 | 406 | private String concat(String path1, String path2) { 407 | boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); 408 | boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); 409 | 410 | if (path1EndsWithSeparator && path2StartsWithSeparator) { 411 | return path1 + path2.substring(1); 412 | } else if (path1EndsWithSeparator || path2StartsWithSeparator) { 413 | return path1 + path2; 414 | } else { 415 | return path1 + this.pathSeparator + path2; 416 | } 417 | } 418 | 419 | public Comparator getPatternComparator(String path) { 420 | return new AntPatternComparator(path); 421 | } 422 | 423 | protected static class AntPathStringMatcher { 424 | 425 | private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}"); 426 | 427 | private static final String DEFAULT_VARIABLE_PATTERN = "(.*)"; 428 | 429 | private final Pattern pattern; 430 | 431 | private final List variableNames = new LinkedList(); 432 | 433 | public AntPathStringMatcher(String pattern) { 434 | this(pattern, true); 435 | } 436 | 437 | public AntPathStringMatcher(String pattern, boolean caseSensitive) { 438 | StringBuilder patternBuilder = new StringBuilder(); 439 | Matcher matcher = GLOB_PATTERN.matcher(pattern); 440 | int end = 0; 441 | while (matcher.find()) { 442 | patternBuilder.append(quote(pattern, end, matcher.start())); 443 | String match = matcher.group(); 444 | if ("?".equals(match)) { 445 | patternBuilder.append('.'); 446 | } else if ("*".equals(match)) { 447 | patternBuilder.append(".*"); 448 | } else if (match.startsWith("{") && match.endsWith("}")) { 449 | int colonIdx = match.indexOf(':'); 450 | if (colonIdx == -1) { 451 | patternBuilder.append(DEFAULT_VARIABLE_PATTERN); 452 | this.variableNames.add(matcher.group(1)); 453 | } else { 454 | String variablePattern = match.substring(colonIdx + 1, match.length() - 1); 455 | patternBuilder.append('('); 456 | patternBuilder.append(variablePattern); 457 | patternBuilder.append(')'); 458 | String variableName = match.substring(1, colonIdx); 459 | this.variableNames.add(variableName); 460 | } 461 | } 462 | end = matcher.end(); 463 | } 464 | patternBuilder.append(quote(pattern, end, pattern.length())); 465 | this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) : 466 | Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); 467 | } 468 | 469 | private String quote(String s, int start, int end) { 470 | if (start == end) { 471 | return ""; 472 | } 473 | return Pattern.quote(s.substring(start, end)); 474 | } 475 | 476 | public boolean matchStrings(String str, Map uriTemplateVariables) { 477 | Matcher matcher = this.pattern.matcher(str); 478 | if (matcher.matches()) { 479 | if (uriTemplateVariables != null) { 480 | // SPR-8455 481 | if (this.variableNames.size() != matcher.groupCount()) { 482 | throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + 483 | this.pattern + " does not match the number of URI template variables it defines, " + 484 | "which can occur if capturing groups are used in a URI template regex. " + 485 | "Use non-capturing groups instead."); 486 | } 487 | for (int i = 1; i <= matcher.groupCount(); i++) { 488 | String name = this.variableNames.get(i - 1); 489 | String value = matcher.group(i); 490 | uriTemplateVariables.put(name, value); 491 | } 492 | } 493 | return true; 494 | } else { 495 | return false; 496 | } 497 | } 498 | } 499 | 500 | protected static class AntPatternComparator implements Comparator { 501 | 502 | private final String path; 503 | 504 | public AntPatternComparator(String path) { 505 | this.path = path; 506 | } 507 | 508 | @Override 509 | public int compare(String pattern1, String pattern2) { 510 | PatternInfo info1 = new PatternInfo(pattern1); 511 | PatternInfo info2 = new PatternInfo(pattern2); 512 | 513 | if (info1.isLeastSpecific() && info2.isLeastSpecific()) { 514 | return 0; 515 | } else if (info1.isLeastSpecific()) { 516 | return 1; 517 | } else if (info2.isLeastSpecific()) { 518 | return -1; 519 | } 520 | 521 | boolean pattern1EqualsPath = pattern1.equals(path); 522 | boolean pattern2EqualsPath = pattern2.equals(path); 523 | if (pattern1EqualsPath && pattern2EqualsPath) { 524 | return 0; 525 | } else if (pattern1EqualsPath) { 526 | return -1; 527 | } else if (pattern2EqualsPath) { 528 | return 1; 529 | } 530 | 531 | if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { 532 | return 1; 533 | } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { 534 | return -1; 535 | } 536 | 537 | if (info1.getTotalCount() != info2.getTotalCount()) { 538 | return info1.getTotalCount() - info2.getTotalCount(); 539 | } 540 | 541 | if (info1.getLength() != info2.getLength()) { 542 | return info2.getLength() - info1.getLength(); 543 | } 544 | 545 | if (info1.getSingleWildcards() < info2.getSingleWildcards()) { 546 | return -1; 547 | } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { 548 | return 1; 549 | } 550 | 551 | if (info1.getUriVars() < info2.getUriVars()) { 552 | return -1; 553 | } else if (info2.getUriVars() < info1.getUriVars()) { 554 | return 1; 555 | } 556 | 557 | return 0; 558 | } 559 | 560 | /** 561 | * Value class that holds information about the pattern, e.g. number of 562 | * occurrences of "*", "**", and "{" pattern elements. 563 | */ 564 | private static class PatternInfo { 565 | 566 | private final String pattern; 567 | 568 | private int uriVars; 569 | 570 | private int singleWildcards; 571 | 572 | private int doubleWildcards; 573 | 574 | private boolean catchAllPattern; 575 | 576 | private boolean prefixPattern; 577 | 578 | private Integer length; 579 | 580 | public PatternInfo(String pattern) { 581 | this.pattern = pattern; 582 | if (this.pattern != null) { 583 | initCounters(); 584 | this.catchAllPattern = "/**".equals(this.pattern); 585 | this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); 586 | } 587 | if (this.uriVars == 0) { 588 | this.length = (this.pattern != null ? this.pattern.length() : 0); 589 | } 590 | } 591 | 592 | protected void initCounters() { 593 | int pos = 0; 594 | while (pos < this.pattern.length()) { 595 | if (this.pattern.charAt(pos) == '{') { 596 | this.uriVars++; 597 | pos++; 598 | } else if (this.pattern.charAt(pos) == '*') { 599 | if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { 600 | this.doubleWildcards++; 601 | pos += 2; 602 | } else if (pos > 0 && !".*".equals(this.pattern.substring(pos - 1))) { 603 | this.singleWildcards++; 604 | pos++; 605 | } else { 606 | pos++; 607 | } 608 | } else { 609 | pos++; 610 | } 611 | } 612 | } 613 | 614 | public int getUriVars() { 615 | return this.uriVars; 616 | } 617 | 618 | public int getSingleWildcards() { 619 | return this.singleWildcards; 620 | } 621 | 622 | public int getDoubleWildcards() { 623 | return this.doubleWildcards; 624 | } 625 | 626 | public boolean isLeastSpecific() { 627 | return (this.pattern == null || this.catchAllPattern); 628 | } 629 | 630 | public boolean isPrefixPattern() { 631 | return this.prefixPattern; 632 | } 633 | 634 | public int getTotalCount() { 635 | return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); 636 | } 637 | 638 | public int getLength() { 639 | if (this.length == null) { 640 | this.length = VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length(); 641 | } 642 | return this.length; 643 | } 644 | } 645 | } 646 | 647 | /** 648 | * A simple cache for patterns that depend on the configured path separator. 649 | */ 650 | private static class PathSeparatorPatternCache { 651 | 652 | private final String endsOnWildCard; 653 | 654 | private final String endsOnDoubleWildCard; 655 | 656 | public PathSeparatorPatternCache(String pathSeparator) { 657 | this.endsOnWildCard = pathSeparator + "*"; 658 | this.endsOnDoubleWildCard = pathSeparator + "**"; 659 | } 660 | 661 | public String getEndsOnWildCard() { 662 | return this.endsOnWildCard; 663 | } 664 | 665 | public String getEndsOnDoubleWildCard() { 666 | return this.endsOnDoubleWildCard; 667 | } 668 | 669 | } 670 | 671 | } 672 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/utils/NetUtils.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.IOException; 6 | import java.lang.management.ManagementFactory; 7 | import java.net.*; 8 | import java.util.Enumeration; 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | import java.util.Random; 12 | import java.util.regex.Pattern; 13 | 14 | /** 15 | * IP and Port Helper 16 | * 17 | * @author lry 18 | */ 19 | @Slf4j 20 | public class NetUtils { 21 | 22 | public static final String LOCALHOST = "127.0.0.1"; 23 | public static final String ANY_HOST = "0.0.0.0"; 24 | private static final int RND_PORT_START = 30000; 25 | private static final int RND_PORT_RANGE = 10000; 26 | 27 | private static final Random RANDOM = new Random(System.currentTimeMillis()); 28 | private static final int MIN_PORT = 0; 29 | private static final int MAX_PORT = 65535; 30 | private static final Pattern ADDRESS_PATTERN = Pattern.compile("^\\d{1,3}(\\.\\d{1,3}){3}\\:\\d{1,5}$"); 31 | private static final Pattern LOCAL_IP_PATTERN = Pattern.compile("127(\\.\\d{1,3}){3}$"); 32 | private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$"); 33 | private static final Map HOST_NAME_CACHE = new LinkedHashMap<>(); 34 | private static volatile InetAddress LOCAL_ADDRESS = null; 35 | private static volatile String PROCESS_ID = null; 36 | 37 | public static int getAvailablePort() { 38 | try (ServerSocket ss = new ServerSocket()) { 39 | ss.bind(null); 40 | return ss.getLocalPort(); 41 | } catch (IOException e) { 42 | return RND_PORT_START + RANDOM.nextInt(RND_PORT_RANGE); 43 | } 44 | } 45 | 46 | public static int getAvailablePort(int port) { 47 | if (port <= 0) { 48 | return getAvailablePort(); 49 | } 50 | for (int i = port; i < MAX_PORT; i++) { 51 | try (ServerSocket ss = new ServerSocket(i)) { 52 | return i; 53 | } catch (IOException e) { 54 | // continue 55 | } 56 | } 57 | return port; 58 | } 59 | 60 | public static boolean isInvalidPort(int port) { 61 | return port <= MIN_PORT || port > MAX_PORT; 62 | } 63 | 64 | public static boolean isValidAddress(String address) { 65 | return ADDRESS_PATTERN.matcher(address).matches(); 66 | } 67 | 68 | public static boolean isLocalHost(String host) { 69 | return host != null 70 | && (LOCAL_IP_PATTERN.matcher(host).matches() 71 | || "localhost".equalsIgnoreCase(host)); 72 | } 73 | 74 | public static boolean isAnyHost(String host) { 75 | return "0.0.0.0".equals(host); 76 | } 77 | 78 | public static boolean isInvalidLocalHost(String host) { 79 | return host == null 80 | || host.length() == 0 81 | || "localhost".equalsIgnoreCase(host) 82 | || "0.0.0.0".equals(host) 83 | || (LOCAL_IP_PATTERN.matcher(host).matches()); 84 | } 85 | 86 | public static boolean isValidLocalHost(String host) { 87 | return !isInvalidLocalHost(host); 88 | } 89 | 90 | public static InetSocketAddress getLocalSocketAddress(String host, int port) { 91 | return isInvalidLocalHost(host) ? 92 | new InetSocketAddress(port) : new InetSocketAddress(host, port); 93 | } 94 | 95 | private static boolean isValidAddress(InetAddress address) { 96 | if (address == null || address.isLoopbackAddress()) { 97 | return false; 98 | } 99 | String name = address.getHostAddress(); 100 | return (name != null 101 | && !ANY_HOST.equals(name) 102 | && !LOCALHOST.equals(name) 103 | && IP_PATTERN.matcher(name).matches()); 104 | } 105 | 106 | public static String getLocalHost() { 107 | InetAddress address = getLocalAddress(); 108 | return address == null ? LOCALHOST : address.getHostAddress(); 109 | } 110 | 111 | /** 112 | * Find first valid IP from local network card 113 | * 114 | * @return first valid local IP 115 | */ 116 | public static InetAddress getLocalAddress() { 117 | if (LOCAL_ADDRESS != null) { 118 | return LOCAL_ADDRESS; 119 | } 120 | InetAddress localAddress = getLocalAddress0(); 121 | LOCAL_ADDRESS = localAddress; 122 | return localAddress; 123 | } 124 | 125 | private static InetAddress getLocalAddress0() { 126 | InetAddress localAddress = null; 127 | try { 128 | localAddress = InetAddress.getLocalHost(); 129 | if (isValidAddress(localAddress)) { 130 | return localAddress; 131 | } 132 | } catch (Throwable e) { 133 | log.warn("Failed to retrieving ip address, " + e.getMessage(), e); 134 | } 135 | 136 | try { 137 | Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); 138 | if (interfaces != null) { 139 | while (interfaces.hasMoreElements()) { 140 | try { 141 | NetworkInterface network = interfaces.nextElement(); 142 | Enumeration addresses = network.getInetAddresses(); 143 | while (addresses.hasMoreElements()) { 144 | try { 145 | InetAddress address = addresses.nextElement(); 146 | if (isValidAddress(address)) { 147 | return address; 148 | } 149 | } catch (Throwable e) { 150 | log.warn("Failed to retrieving ip address, " + e.getMessage(), e); 151 | } 152 | } 153 | } catch (Throwable e) { 154 | log.warn("Failed to retrieving ip address, " + e.getMessage(), e); 155 | } 156 | } 157 | } 158 | } catch (Throwable e) { 159 | log.warn("Failed to retrieving ip address, " + e.getMessage(), e); 160 | } 161 | 162 | log.error("Could not get local host ip address, will use 127.0.0.1 instead."); 163 | return localAddress; 164 | } 165 | 166 | public static String getHostName(String address) { 167 | try { 168 | int i = address.indexOf(':'); 169 | if (i > -1) { 170 | address = address.substring(0, i); 171 | } 172 | String hostname = HOST_NAME_CACHE.get(address); 173 | if (hostname != null && hostname.length() > 0) { 174 | return hostname; 175 | } 176 | InetAddress inetAddress = InetAddress.getByName(address); 177 | if (inetAddress != null) { 178 | hostname = inetAddress.getHostName(); 179 | HOST_NAME_CACHE.put(address, hostname); 180 | return hostname; 181 | } 182 | } catch (Throwable e) { 183 | // ignore 184 | } 185 | 186 | return address; 187 | } 188 | 189 | public static String getIpByHost(String hostName) { 190 | try { 191 | return InetAddress.getByName(hostName).getHostAddress(); 192 | } catch (UnknownHostException e) { 193 | return hostName; 194 | } 195 | } 196 | 197 | public static String toAddressString(InetSocketAddress address) { 198 | return address.getAddress().getHostAddress() + ":" + address.getPort(); 199 | } 200 | 201 | public static InetSocketAddress toAddress(String address) { 202 | int i = address.indexOf(':'); 203 | String host; 204 | int port; 205 | if (i > -1) { 206 | host = address.substring(0, i); 207 | port = Integer.parseInt(address.substring(i + 1)); 208 | } else { 209 | host = address; 210 | port = 0; 211 | } 212 | 213 | return new InetSocketAddress(host, port); 214 | } 215 | 216 | public static String getProcessId() { 217 | if (PROCESS_ID != null) { 218 | return PROCESS_ID; 219 | } 220 | 221 | return PROCESS_ID = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 222 | } 223 | 224 | } -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/common/utils/StandardThreadExecutor.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.common.utils; 2 | 3 | import java.util.concurrent.*; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | /** 7 | * The Standard Thread Executor 8 | *

9 | * ThreadPoolExecutor: coreThread -> queue -> maxThread -> reject: 10 | * 场景优势:比较适合于CPU密集型应用(比如runnable内部执行的操作都在JVM内部: memory copy or compute) 11 | *

12 | * StandardThreadExecutor:coreThread -> maxThread -> queue -> reject 13 | * 场景优势:比较适合于业务处理需要远程资源的场景 14 | *

15 | * 思路来源:tomcat : org.apache.catalina.core.StandardThreadExecutor 16 | * 17 | * @author lry 18 | */ 19 | public class StandardThreadExecutor extends ThreadPoolExecutor { 20 | 21 | private static final int DEFAULT_MIN_THREADS = 20; 22 | private static final int DEFAULT_MAX_THREADS = 200; 23 | private static final int DEFAULT_MAX_IDLE_TIME = 60 * 1000; 24 | 25 | /** 26 | * Number of tasks being processed 27 | */ 28 | private AtomicInteger submittedTasksCount; 29 | /** 30 | * Maximum number of tasks allowed to be processed simultaneously 31 | */ 32 | private int maxSubmittedTaskCount; 33 | 34 | public StandardThreadExecutor() { 35 | this(DEFAULT_MIN_THREADS, DEFAULT_MAX_THREADS); 36 | } 37 | 38 | public StandardThreadExecutor(int coreThread, int maxThreads) { 39 | this(coreThread, maxThreads, maxThreads); 40 | } 41 | 42 | public StandardThreadExecutor(int coreThread, int maxThreads, long keepAliveTime, TimeUnit unit) { 43 | this(coreThread, maxThreads, keepAliveTime, unit, maxThreads); 44 | } 45 | 46 | public StandardThreadExecutor(int coreThreads, int maxThreads, int queueCapacity) { 47 | this(coreThreads, maxThreads, queueCapacity, Executors.defaultThreadFactory()); 48 | } 49 | 50 | public StandardThreadExecutor(int coreThreads, int maxThreads, int queueCapacity, ThreadFactory threadFactory) { 51 | this(coreThreads, maxThreads, DEFAULT_MAX_IDLE_TIME, TimeUnit.MILLISECONDS, queueCapacity, threadFactory); 52 | } 53 | 54 | public StandardThreadExecutor(int coreThreads, int maxThreads, long keepAliveTime, TimeUnit unit, int queueCapacity) { 55 | this(coreThreads, maxThreads, keepAliveTime, unit, queueCapacity, Executors.defaultThreadFactory()); 56 | } 57 | 58 | public StandardThreadExecutor(int coreThreads, int maxThreads, long keepAliveTime, TimeUnit unit, 59 | int queueCapacity, ThreadFactory threadFactory) { 60 | this(coreThreads, maxThreads, keepAliveTime, unit, queueCapacity, threadFactory, new AbortPolicy()); 61 | } 62 | 63 | public StandardThreadExecutor(int coreThreads, int maxThreads, long keepAliveTime, TimeUnit unit, 64 | int queueCapacity, ThreadFactory threadFactory, RejectedExecutionHandler handler) { 65 | super(coreThreads, maxThreads, keepAliveTime, unit, new StandardExecutorQueue(), threadFactory, handler); 66 | ((StandardExecutorQueue) getQueue()).setStandardThreadExecutor(this); 67 | 68 | submittedTasksCount = new AtomicInteger(0); 69 | 70 | // 最大并发任务限制: 队列buffer数 + 最大线程数 71 | maxSubmittedTaskCount = queueCapacity + maxThreads; 72 | } 73 | 74 | @Override 75 | public void execute(Runnable command) { 76 | int count = submittedTasksCount.incrementAndGet(); 77 | 78 | // 超过最大的并发任务限制,进行 reject 79 | // 依赖的LinkedTransferQueue没有长度限制,因此这里进行控制 80 | if (count > maxSubmittedTaskCount) { 81 | submittedTasksCount.decrementAndGet(); 82 | getRejectedExecutionHandler().rejectedExecution(command, this); 83 | } 84 | 85 | try { 86 | super.execute(command); 87 | } catch (RejectedExecutionException rx) { 88 | // there could have been contention around the queue 89 | if (!((StandardExecutorQueue) getQueue()).force(command)) { 90 | submittedTasksCount.decrementAndGet(); 91 | getRejectedExecutionHandler().rejectedExecution(command, this); 92 | } 93 | } 94 | } 95 | 96 | public int getSubmittedTasksCount() { 97 | return this.submittedTasksCount.get(); 98 | } 99 | 100 | public int getMaxSubmittedTaskCount() { 101 | return maxSubmittedTaskCount; 102 | } 103 | 104 | @Override 105 | protected void afterExecute(Runnable r, Throwable t) { 106 | submittedTasksCount.decrementAndGet(); 107 | } 108 | } 109 | 110 | /** 111 | * LinkedTransferQueue 能保证更高性能,相比与LinkedBlockingQueue有明显提升 112 | *

113 | * 缺点:没有队列长度控制,需要在外层协助控制 114 | * 115 | * @author lry 116 | */ 117 | class StandardExecutorQueue extends LinkedTransferQueue { 118 | 119 | private StandardThreadExecutor threadPoolExecutor; 120 | 121 | StandardExecutorQueue() { 122 | super(); 123 | } 124 | 125 | void setStandardThreadExecutor(StandardThreadExecutor threadPoolExecutor) { 126 | this.threadPoolExecutor = threadPoolExecutor; 127 | } 128 | 129 | boolean force(Runnable o) { 130 | if (threadPoolExecutor.isShutdown()) { 131 | throw new RejectedExecutionException("Executor not running, can't force a command into the queue"); 132 | } 133 | 134 | // forces the item onto the queue, to be used if the task is rejected 135 | return super.offer(o); 136 | } 137 | 138 | @Override 139 | public boolean offer(Runnable o) { 140 | int poolSize = threadPoolExecutor.getPoolSize(); 141 | 142 | // we are maxed out on threads, simply queue the object 143 | if (poolSize == threadPoolExecutor.getMaximumPoolSize()) { 144 | return super.offer(o); 145 | } 146 | 147 | // we have idle threads, just add it to the queue note that we don't use getActiveCount(), see BZ 49730 148 | if (threadPoolExecutor.getSubmittedTasksCount() <= poolSize) { 149 | return super.offer(o); 150 | } 151 | 152 | // if we have less threads than maximum force creation of a new thread 153 | if (poolSize < threadPoolExecutor.getMaximumPoolSize()) { 154 | return false; 155 | } 156 | 157 | // if we reached here, we need to add it to the queue 158 | return super.offer(o); 159 | } 160 | 161 | } -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/extension/Extension.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.extension; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Extension Annotation 7 | *

8 | * When SPI has multiple implementations, it can be filtered, sorted and returned according to conditions. 9 | * 10 | * @author lry 11 | */ 12 | @Documented 13 | @Target(ElementType.TYPE) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface Extension { 16 | 17 | /** 18 | * Implementation ID 19 | **/ 20 | String value() default ""; 21 | 22 | /** 23 | * The smaller the order number, the higher the position in the returned instance list. 24 | */ 25 | int order() default 20; 26 | 27 | /** 28 | * SPI category, matching according to category when obtaining SPI list. 29 | *

30 | * When there is a search-category to be filtered in category, the matching is successful. 31 | */ 32 | String[] category() default ""; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/extension/ExtensionLoader.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.extension; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.lang.reflect.Constructor; 10 | import java.lang.reflect.Modifier; 11 | import java.net.URL; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.*; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.concurrent.ConcurrentMap; 16 | 17 | /** 18 | * ExtensionLoader 19 | * 20 | * @param 21 | * @author lry 22 | */ 23 | @Slf4j 24 | public class ExtensionLoader { 25 | 26 | private Class type; 27 | private ClassLoader classLoader; 28 | private volatile boolean init = false; 29 | private static final String PREFIX_DEFAULT = "META-INF/"; 30 | private static final String PREFIX_NEURAL = PREFIX_DEFAULT + "neural/"; 31 | private static final String PREFIX_SERVICES = PREFIX_DEFAULT + "services/"; 32 | private ConcurrentMap singletonInstances = null; 33 | private ConcurrentMap> extensionClasses = null; 34 | private static ConcurrentMap, ExtensionLoader> extensionLoaders = new ConcurrentHashMap<>(); 35 | 36 | private ExtensionLoader(Class type, ClassLoader classLoader) { 37 | this.type = type; 38 | this.classLoader = classLoader; 39 | } 40 | 41 | private void checkInit() { 42 | if (!init) { 43 | loadExtensionClasses(); 44 | } 45 | } 46 | 47 | public Class getExtensionClass(String name) { 48 | this.checkInit(); 49 | return extensionClasses.get(name); 50 | } 51 | 52 | public T getExtension() { 53 | this.checkInit(); 54 | 55 | SPI spi = type.getAnnotation(SPI.class); 56 | if (spi.value().length() == 0) { 57 | throw new RuntimeException(type.getName() + ": The default implementation ID(@SPI.value()) is not set"); 58 | } else { 59 | try { 60 | if (spi.single()) { 61 | return this.getSingletonInstance(spi.value()); 62 | } else { 63 | Class clz = extensionClasses.get(spi.value()); 64 | if (clz == null) { 65 | return null; 66 | } 67 | 68 | return clz.newInstance(); 69 | } 70 | } catch (Exception e) { 71 | throw new RuntimeException(type.getName() + ": Error when getExtension ", e); 72 | } 73 | } 74 | } 75 | 76 | public T getExtension(String name) { 77 | this.checkInit(); 78 | if (name == null) { 79 | return null; 80 | } 81 | 82 | try { 83 | SPI spi = type.getAnnotation(SPI.class); 84 | if (spi.single()) { 85 | return this.getSingletonInstance(name); 86 | } else { 87 | Class clz = extensionClasses.get(name); 88 | if (clz == null) { 89 | return null; 90 | } 91 | 92 | return clz.newInstance(); 93 | } 94 | } catch (Exception e) { 95 | throw new RuntimeException(type.getName() + ": Error when getExtension ", e); 96 | } 97 | } 98 | 99 | private T getSingletonInstance(String name) throws InstantiationException, IllegalAccessException { 100 | T obj = singletonInstances.get(name); 101 | if (obj != null) { 102 | return obj; 103 | } 104 | 105 | Class clz = extensionClasses.get(name); 106 | if (clz == null) { 107 | return null; 108 | } 109 | 110 | synchronized (singletonInstances) { 111 | obj = singletonInstances.get(name); 112 | if (obj != null) { 113 | return obj; 114 | } 115 | obj = clz.newInstance(); 116 | singletonInstances.put(name, obj); 117 | } 118 | 119 | return obj; 120 | } 121 | 122 | public void addExtensionClass(Class clz) { 123 | if (clz == null) { 124 | return; 125 | } 126 | 127 | checkInit(); 128 | checkExtensionType(clz); 129 | String spiName = getSpiName(clz); 130 | synchronized (extensionClasses) { 131 | if (extensionClasses.containsKey(spiName)) { 132 | throw new RuntimeException(clz.getName() + ": Error spiName already exist " + spiName); 133 | } else { 134 | extensionClasses.put(spiName, clz); 135 | } 136 | } 137 | } 138 | 139 | private synchronized void loadExtensionClasses() { 140 | if (init) { 141 | return; 142 | } 143 | 144 | extensionClasses = this.loadExtensionClasses(PREFIX_DEFAULT); 145 | ConcurrentMap> neuralExtensionClasses = this.loadExtensionClasses(PREFIX_NEURAL); 146 | if (!neuralExtensionClasses.isEmpty()) { 147 | extensionClasses.putAll(neuralExtensionClasses); 148 | } 149 | ConcurrentMap> serviceExtensionClasses = this.loadExtensionClasses(PREFIX_SERVICES); 150 | if (!serviceExtensionClasses.isEmpty()) { 151 | extensionClasses.putAll(serviceExtensionClasses); 152 | } 153 | 154 | singletonInstances = new ConcurrentHashMap<>(); 155 | init = true; 156 | } 157 | 158 | public static ExtensionLoader getLoader(Class type) { 159 | return getLoader(type, Thread.currentThread().getContextClassLoader()); 160 | } 161 | 162 | @SuppressWarnings("unchecked") 163 | public static ExtensionLoader getLoader(Class type, ClassLoader classLoader) { 164 | if (type == null) { 165 | throw new RuntimeException("Error extension type is null"); 166 | } 167 | if (!type.isAnnotationPresent(SPI.class)) { 168 | throw new RuntimeException(type.getName() + ": Error extension type without @SPI annotation"); 169 | } 170 | 171 | ExtensionLoader loader = (ExtensionLoader) extensionLoaders.get(type); 172 | if (loader == null) { 173 | loader = initExtensionLoader(type, classLoader); 174 | } 175 | 176 | return loader; 177 | } 178 | 179 | public static synchronized ExtensionLoader initExtensionLoader(Class type) { 180 | return initExtensionLoader(type, Thread.currentThread().getContextClassLoader()); 181 | } 182 | 183 | @SuppressWarnings("unchecked") 184 | public static synchronized ExtensionLoader initExtensionLoader(Class type, ClassLoader classLoader) { 185 | ExtensionLoader loader = (ExtensionLoader) extensionLoaders.get(type); 186 | if (loader == null) { 187 | loader = new ExtensionLoader(type, classLoader); 188 | extensionLoaders.putIfAbsent(type, loader); 189 | loader = (ExtensionLoader) extensionLoaders.get(type); 190 | } 191 | 192 | return loader; 193 | } 194 | 195 | public List getExtensions() { 196 | return this.getExtensions(""); 197 | } 198 | 199 | /** 200 | * 有些地方需要spi的所有激活的instances,所以需要能返回一个列表的方法
201 | *
202 | * 注意:
203 | * 1 SpiMeta 中的active 为true
204 | * 2 按照spiMeta中的order进行排序
205 | *
206 | * FIXME: 是否需要对singleton来区分对待,后面再考虑 fishermen 207 | */ 208 | public List getExtensions(String key) { 209 | checkInit(); 210 | if (extensionClasses.size() == 0) { 211 | return Collections.emptyList(); 212 | } 213 | 214 | // 如果只有一个实现,直接返回 215 | List exts = new ArrayList(extensionClasses.size()); 216 | // 多个实现,按优先级排序返回 217 | for (Map.Entry> entry : extensionClasses.entrySet()) { 218 | Extension extension = entry.getValue().getAnnotation(Extension.class); 219 | if (key == null || key.length() == 0) { 220 | exts.add(getExtension(entry.getKey())); 221 | } else if (extension != null) { 222 | for (String k : extension.category()) { 223 | if (key.equals(k)) { 224 | exts.add(getExtension(entry.getKey())); 225 | break; 226 | } 227 | } 228 | } 229 | } 230 | 231 | // order 大的排在后面,如果没有设置order的排到最前面 232 | exts.sort((o1, o2) -> { 233 | Extension p1 = o1.getClass().getAnnotation(Extension.class); 234 | Extension p2 = o2.getClass().getAnnotation(Extension.class); 235 | if (p1 == null) { 236 | return 1; 237 | } else if (p2 == null) { 238 | return -1; 239 | } else { 240 | return p1.order() - p2.order(); 241 | } 242 | }); 243 | 244 | return exts; 245 | } 246 | 247 | private void checkExtensionType(Class clz) { 248 | // 1) is public class 249 | if (!type.isAssignableFrom(clz)) { 250 | throw new RuntimeException(clz.getName() + ": Error is not instanceof " + type.getName()); 251 | } 252 | 253 | // 2) contain public constructor and has not-args constructor 254 | Constructor[] constructors = clz.getConstructors(); 255 | if (constructors == null || constructors.length == 0) { 256 | throw new RuntimeException(clz.getName() + ": Error has no public no-args constructor"); 257 | } 258 | 259 | for (Constructor constructor : constructors) { 260 | if (Modifier.isPublic(constructor.getModifiers()) && constructor.getParameterTypes().length == 0) { 261 | // 3) check extension clz instanceof Type.class 262 | if (!type.isAssignableFrom(clz)) { 263 | throw new RuntimeException(clz.getName() + ": Error is not instanceof " + type.getName()); 264 | } 265 | 266 | return; 267 | } 268 | } 269 | 270 | throw new RuntimeException(clz.getName() + ": Error has no public no-args constructor"); 271 | } 272 | 273 | private ConcurrentMap> loadExtensionClasses(String prefix) { 274 | String fullName = prefix + type.getName(); 275 | List classNames = new ArrayList<>(); 276 | 277 | try { 278 | Enumeration urls; 279 | if (classLoader == null) { 280 | urls = ClassLoader.getSystemResources(fullName); 281 | } else { 282 | urls = classLoader.getResources(fullName); 283 | } 284 | 285 | if (urls == null || !urls.hasMoreElements()) { 286 | return new ConcurrentHashMap<>(); 287 | } 288 | 289 | while (urls.hasMoreElements()) { 290 | URL url = urls.nextElement(); 291 | this.parseUrl(type, url, classNames); 292 | } 293 | } catch (Exception e) { 294 | throw new RuntimeException("ExtensionLoader loadExtensionClasses error, prefix: " + 295 | prefix + " type: " + type, e); 296 | } 297 | 298 | return loadClass(classNames); 299 | } 300 | 301 | @SuppressWarnings("unchecked") 302 | private ConcurrentMap> loadClass(List classNames) { 303 | ConcurrentMap> map = new ConcurrentHashMap<>(); 304 | for (String className : classNames) { 305 | try { 306 | Class clz; 307 | if (classLoader == null) { 308 | clz = (Class) Class.forName(className); 309 | } else { 310 | clz = (Class) Class.forName(className, true, classLoader); 311 | } 312 | 313 | this.checkExtensionType(clz); 314 | String spiName = this.getSpiName(clz); 315 | if (map.containsKey(spiName)) { 316 | throw new RuntimeException(clz.getName() + ": Error spiName already exist " + spiName); 317 | } else { 318 | map.put(spiName, clz); 319 | } 320 | } catch (Exception e) { 321 | log.error(type.getName() + ": Error load spi class", e); 322 | } 323 | } 324 | 325 | return map; 326 | 327 | } 328 | 329 | private String getSpiName(Class clz) { 330 | Extension extension = clz.getAnnotation(Extension.class); 331 | return (extension != null && !"".equals(extension.value())) ? extension.value() : clz.getSimpleName(); 332 | } 333 | 334 | private void parseUrl(Class type, URL url, List classNames) throws ServiceConfigurationError { 335 | InputStream inputStream = null; 336 | BufferedReader reader = null; 337 | try { 338 | inputStream = url.openStream(); 339 | reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); 340 | 341 | String line; 342 | while ((line = reader.readLine()) != null) { 343 | this.parseLine(type, url, line, classNames); 344 | } 345 | } catch (Exception e) { 346 | log.error(type.getName() + ": Error reading spi configuration file", e); 347 | } finally { 348 | try { 349 | if (reader != null) { 350 | reader.close(); 351 | } 352 | if (inputStream != null) { 353 | inputStream.close(); 354 | } 355 | } catch (IOException e) { 356 | log.error(type.getName() + ": Error closing spi configuration file", e); 357 | } 358 | } 359 | } 360 | 361 | private void parseLine(Class type, URL url, String line, List names) 362 | throws IOException, ServiceConfigurationError { 363 | int ci = line.indexOf('#'); 364 | if (ci >= 0) { 365 | line = line.substring(0, ci); 366 | } 367 | 368 | line = line.trim(); 369 | if (line.length() <= 0) { 370 | return; 371 | } 372 | if ((line.indexOf(' ') >= 0) || (line.indexOf('\t') >= 0)) { 373 | throw new RuntimeException(type.getName() + ": " + 374 | url + ":" + line + ": Illegal spi configuration-file syntax: " + line); 375 | } 376 | 377 | int cp = line.codePointAt(0); 378 | if (!Character.isJavaIdentifierStart(cp)) { 379 | throw new RuntimeException(type.getName() + ": " + 380 | url + ":" + line + ": Illegal spi provider-class name: " + line); 381 | } 382 | 383 | for (int i = Character.charCount(cp); i < line.length(); i += Character.charCount(cp)) { 384 | cp = line.codePointAt(i); 385 | if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) { 386 | throw new RuntimeException(type.getName() + ": " + 387 | url + ":" + line + ": Illegal spi provider-class name: " + line); 388 | } 389 | } 390 | 391 | if (!names.contains(line)) { 392 | names.add(line); 393 | } 394 | } 395 | 396 | } 397 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/extension/SPI.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.extension; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * SPI 7 | * 8 | * 9 | * 功能特性:
10 | * 1.支持自定义实现类为单例/多例
11 | * 2.支持设置默认的实现类
12 | * 3.支持实现类order排序
13 | * 4.支持实现类定义特征属性category,用于区分多维度的不同类别
14 | * 5.支持根据category属性值来搜索实现类
15 | * 6.支持自动扫描实现类
16 | * 7.支持手动添加实现类
17 | * 8.支持获取所有实现类
18 | * 9.支持只创建所需实现类,解决JDK原生的全量方式
19 | * 10.支持自定义ClassLoader来加载class
20 | *
21 | * 22 | * @author lry 23 | * @since 3.0.0 24 | */ 25 | @Documented 26 | @Target(ElementType.TYPE) 27 | @Retention(RetentionPolicy.RUNTIME) 28 | public @interface SPI { 29 | 30 | /** 31 | * Default implementation ID 32 | **/ 33 | String value() default ""; 34 | 35 | /** 36 | * Declares whether a new object needs to be created each time an 37 | * implementation class is acquired, that is, whether it is singleton 38 | **/ 39 | boolean single() default false; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/filter/AbstractFilter.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.filter; 2 | 3 | import org.micro.lemon.common.LemonConfig; 4 | import org.micro.lemon.server.LemonContext; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | /** 8 | * AbstractFilter 9 | * 10 | * @author lry 11 | */ 12 | @Slf4j 13 | public abstract class AbstractFilter implements IFilter { 14 | 15 | @Override 16 | public void initialize(LemonConfig lemonConfig) { 17 | 18 | } 19 | 20 | @Override 21 | public void preFilter(LemonChain chain, LemonContext context) throws Throwable { 22 | chain.doFilter(context); 23 | } 24 | 25 | @Override 26 | public void postFilter(LemonChain chain, LemonContext context) throws Throwable { 27 | chain.doFilter(context); 28 | } 29 | 30 | @Override 31 | public void destroy() { 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/filter/IFilter.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.filter; 2 | 3 | import org.micro.lemon.common.LemonConfig; 4 | import org.micro.lemon.server.LemonContext; 5 | import org.micro.lemon.extension.SPI; 6 | 7 | /** 8 | * IFilter 9 | * 10 | * @author lry 11 | */ 12 | @SPI 13 | public interface IFilter { 14 | 15 | /** 16 | * The initialize 17 | * 18 | * @param lemonConfig {@link LemonConfig} 19 | */ 20 | default void initialize(LemonConfig lemonConfig) { 21 | 22 | } 23 | 24 | /** 25 | * The pre filter 26 | * 27 | * @param chain {@link LemonChain} 28 | * @param context {@link LemonContext} 29 | * @throws Throwable throw exception 30 | */ 31 | default void preFilter(LemonChain chain, LemonContext context) throws Throwable { 32 | 33 | } 34 | 35 | /** 36 | * The post filter 37 | * 38 | * @param chain {@link LemonChain} 39 | * @param context {@link LemonContext} 40 | * @throws Throwable throw exception 41 | */ 42 | default void postFilter(LemonChain chain, LemonContext context) throws Throwable { 43 | 44 | } 45 | 46 | /** 47 | * The destroy 48 | */ 49 | default void destroy() { 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/filter/LemonChain.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.filter; 2 | 3 | import org.micro.lemon.common.LemonStatusCode; 4 | import org.micro.lemon.server.LemonContext; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.slf4j.MDC; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | /** 14 | * LemonChain 15 | * 16 | * @author lry 17 | */ 18 | @Slf4j 19 | public class LemonChain { 20 | 21 | private AtomicInteger index = new AtomicInteger(0); 22 | private AtomicBoolean flag = new AtomicBoolean(true); 23 | 24 | private LemonContext lemonContext; 25 | private int filterMaxIndex = 0; 26 | private List filters = new ArrayList<>(); 27 | 28 | /** 29 | * The do filter 30 | * 31 | * @param lemonContext {@link LemonContext} 32 | */ 33 | public LemonChain(LemonContext lemonContext) { 34 | List filters = LemonFactory.INSTANCE.getFilters(); 35 | if (!filters.isEmpty()) { 36 | this.filterMaxIndex = filters.size() - 1; 37 | this.filters.addAll(filters); 38 | } 39 | this.lemonContext = lemonContext; 40 | } 41 | 42 | /** 43 | * The start filter chain 44 | */ 45 | public void start0() { 46 | MDC.put(LemonContext.LEMON_ID_KEY, lemonContext.getRequest().getRequestId()); 47 | 48 | try { 49 | doFilter(lemonContext); 50 | } catch (Throwable t) { 51 | log.error(LemonStatusCode.INTERNAL_SERVER_ERROR.getMessage(), t); 52 | lemonContext.callback(LemonStatusCode.INTERNAL_SERVER_ERROR); 53 | } 54 | } 55 | 56 | /** 57 | * The do filter 58 | * 59 | * @param context {@link LemonContext} 60 | * @throws Throwable throw exception 61 | */ 62 | public void doFilter(LemonContext context) throws Throwable { 63 | if (flag.get()) { 64 | int tempIndex = index.getAndIncrement(); 65 | if (tempIndex >= filterMaxIndex) { 66 | flag.set(false); 67 | } 68 | 69 | IFilter filter = filters.get(tempIndex); 70 | if (filter != null) { 71 | filter.preFilter(this, context); 72 | } 73 | } else { 74 | if (index.get() <= 0) { 75 | return; 76 | } 77 | 78 | int tempIndex = index.decrementAndGet(); 79 | IFilter filter = filters.get(tempIndex); 80 | if (filter != null) { 81 | filter.postFilter(this, context); 82 | } 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/filter/LemonFactory.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.filter; 2 | 3 | import lombok.Getter; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.micro.lemon.common.LemonConfig; 6 | import org.micro.lemon.extension.Extension; 7 | import org.micro.lemon.extension.ExtensionLoader; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * LemonFactory 16 | * 17 | * @author lry 18 | */ 19 | @Slf4j 20 | @Getter 21 | public enum LemonFactory { 22 | 23 | // ==== 24 | 25 | INSTANCE; 26 | 27 | public final static String ROUTER = "ROUTER"; 28 | 29 | private final List filters = new ArrayList<>(); 30 | private final Map, IFilter> filterMap = new ConcurrentHashMap<>(); 31 | 32 | /** 33 | * The initialize filter chain 34 | * 35 | * @param lemonConfig {@link LemonConfig} 36 | */ 37 | public void initialize(LemonConfig lemonConfig) { 38 | List filterList = ExtensionLoader.getLoader(IFilter.class).getExtensions(); 39 | if (filterList.size() > 0) { 40 | for (IFilter filter : filterList) { 41 | Extension extension = filter.getClass().getAnnotation(Extension.class); 42 | if (extension == null) { 43 | continue; 44 | } 45 | 46 | // calculation filter id 47 | String id = extension.value(); 48 | if (id.trim().length() == 0) { 49 | id = filter.getClass().getSimpleName(); 50 | } 51 | 52 | // exclude filter by id 53 | if (!lemonConfig.getExcludeFilters().isEmpty()) { 54 | if (lemonConfig.getExcludeFilters().contains(id)) { 55 | continue; 56 | } 57 | } 58 | 59 | // include filter by id 60 | if (!lemonConfig.getIncludeFilters().isEmpty()) { 61 | if (!lemonConfig.getIncludeFilters().contains(id)) { 62 | continue; 63 | } 64 | } 65 | filters.add(filter); 66 | filterMap.put(filter.getClass(), filter); 67 | } 68 | } 69 | 70 | for (IFilter filter : filters) { 71 | filter.initialize(lemonConfig); 72 | log.info("The filter[{}] initialize is success.", filter); 73 | } 74 | } 75 | 76 | /** 77 | * The destroy filter chain 78 | */ 79 | public void destroy() { 80 | for (IFilter filter : filters) { 81 | filter.destroy(); 82 | log.info("The filter[{}] destroy.", filter); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/filter/support/LemonExceptionFilter.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.filter.support; 2 | 3 | import org.micro.lemon.common.LemonStatusCode; 4 | import org.micro.lemon.filter.IFilter; 5 | import org.micro.lemon.filter.LemonChain; 6 | import org.micro.lemon.server.LemonContext; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.micro.lemon.extension.Extension; 9 | 10 | /** 11 | * LemonExceptionFilter 12 | * 13 | * @author lry 14 | */ 15 | @Slf4j 16 | @Extension(value = "exception", order = 0) 17 | public class LemonExceptionFilter implements IFilter { 18 | 19 | @Override 20 | public void preFilter(LemonChain chain, LemonContext context) throws Throwable { 21 | try { 22 | chain.doFilter(context); 23 | } catch (Throwable t) { 24 | log.error("Lemon exception filter", t); 25 | context.callback(LemonStatusCode.INTERNAL_SERVER_ERROR); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/filter/support/LemonInvokeFilter.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.filter.support; 2 | 3 | import org.micro.lemon.common.LemonInvoke; 4 | import org.micro.lemon.common.LemonStatusCode; 5 | import org.micro.lemon.common.LemonConfig; 6 | import org.micro.lemon.common.ServiceMapping; 7 | import org.micro.lemon.filter.IFilter; 8 | import org.micro.lemon.filter.LemonChain; 9 | import org.micro.lemon.filter.LemonFactory; 10 | import org.micro.lemon.server.LemonContext; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.micro.lemon.extension.Extension; 13 | import org.micro.lemon.extension.ExtensionLoader; 14 | 15 | import java.util.HashSet; 16 | import java.util.List; 17 | import java.util.Set; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | import java.util.concurrent.ConcurrentMap; 20 | 21 | /** 22 | * LemonInvokeFilter 23 | * 24 | * @author lry 25 | */ 26 | @Slf4j 27 | @Extension(value = "invoke", order = 100, category = LemonFactory.ROUTER) 28 | public class LemonInvokeFilter implements IFilter { 29 | 30 | private final ConcurrentMap lemonInvokes = new ConcurrentHashMap<>(); 31 | 32 | @Override 33 | public void initialize(LemonConfig lemonConfig) { 34 | // 计算配置文件中的协议 35 | Set serviceProtocol = new HashSet<>(); 36 | for (ServiceMapping serviceMapping : lemonConfig.getServices()) { 37 | serviceProtocol.add(serviceMapping.getProtocol()); 38 | } 39 | 40 | List lemonInvokeList = ExtensionLoader.getLoader(LemonInvoke.class).getExtensions(); 41 | for (LemonInvoke lemonInvoke : lemonInvokeList) { 42 | Extension extension = lemonInvoke.getClass().getAnnotation(Extension.class); 43 | if (extension != null) { 44 | // 只启动配置文件中的配置 45 | if (serviceProtocol.contains(extension.value())) { 46 | lemonInvokes.put(extension.value(), lemonInvoke); 47 | lemonInvoke.initialize(lemonConfig); 48 | } 49 | } 50 | } 51 | } 52 | 53 | @Override 54 | public void preFilter(LemonChain chain, LemonContext context) throws Throwable { 55 | LemonInvoke lemonInvoke = lemonInvokes.get("dubbo"); 56 | if (lemonInvoke == null) { 57 | context.callback(LemonStatusCode.NOT_FOUND); 58 | return; 59 | } 60 | 61 | context.setFuture(lemonInvoke.invokeAsync(context)); 62 | if (context.getFuture() == null) { 63 | log.error("The completable future is null by context:{}", context); 64 | return; 65 | } 66 | 67 | context.getFuture().whenComplete((result, throwable) -> { 68 | if (throwable != null) { 69 | log.error("Invoke exception", throwable); 70 | context.callback(lemonInvoke.failure(context, throwable)); 71 | return; 72 | } 73 | 74 | try { 75 | chain.doFilter(context); 76 | context.callback(LemonStatusCode.SUCCESS); 77 | } catch (Throwable t) { 78 | throw new RuntimeException(t); 79 | } 80 | }); 81 | } 82 | 83 | @Override 84 | public void destroy() { 85 | for (ConcurrentMap.Entry entry : lemonInvokes.entrySet()) { 86 | entry.getValue().destroy(); 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/filter/support/LemonJwtFilter.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.filter.support; 2 | 3 | import org.micro.lemon.common.LemonConfig; 4 | import org.micro.lemon.common.LemonStatusCode; 5 | import org.micro.lemon.common.config.JwtConfig; 6 | import org.micro.lemon.filter.IFilter; 7 | import org.micro.lemon.filter.LemonChain; 8 | import org.micro.lemon.server.LemonContext; 9 | import com.auth0.jwt.JWT; 10 | import com.auth0.jwt.JWTVerifier; 11 | import com.auth0.jwt.algorithms.Algorithm; 12 | import com.auth0.jwt.exceptions.*; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.micro.lemon.extension.Extension; 15 | 16 | import java.io.UnsupportedEncodingException; 17 | 18 | /** 19 | * LemonJwtFilter 20 | * 21 | * @author lry 22 | */ 23 | @Slf4j 24 | @Extension(value = "jwt", order = 20) 25 | public class LemonJwtFilter implements IFilter { 26 | 27 | private JwtConfig jwtConfig; 28 | private Algorithm algorithm; 29 | private JWTVerifier verifier; 30 | 31 | @Override 32 | public void initialize(LemonConfig lemonConfig) { 33 | this.jwtConfig = lemonConfig.getJwt(); 34 | if (!jwtConfig.isEnable()) { 35 | return; 36 | } 37 | 38 | try { 39 | switch (jwtConfig.getAlgorithm()) { 40 | case HMAC256: 41 | this.algorithm = Algorithm.HMAC256(jwtConfig.getSecret()); 42 | break; 43 | case HMAC384: 44 | this.algorithm = Algorithm.HMAC384(jwtConfig.getSecret()); 45 | break; 46 | case HMAC512: 47 | this.algorithm = Algorithm.HMAC512(jwtConfig.getSecret()); 48 | break; 49 | default: 50 | } 51 | this.verifier = JWT.require(algorithm).build(); 52 | } catch (UnsupportedEncodingException e) { 53 | log.error("LemonJwtFilter initialize exception", e); 54 | } 55 | } 56 | 57 | @Override 58 | public void preFilter(LemonChain chain, LemonContext context) throws Throwable { 59 | if (jwtConfig.isEnable()) { 60 | Object token = context.getRequest().getHeaderValue(jwtConfig.getKey()); 61 | if (token == null) { 62 | context.callback(LemonStatusCode.BAD_REQUEST, "'" + jwtConfig.getKey() + "' is null or empty"); 63 | return; 64 | } 65 | 66 | try { 67 | verifier.verify(String.valueOf(token)); 68 | } catch (TokenExpiredException e) { 69 | context.callback(LemonStatusCode.PAYMENT_REQUIRED, "Token expired"); 70 | return; 71 | } catch (JWTVerificationException e) { 72 | context.callback(LemonStatusCode.UNAUTHORIZED, "Verify that the token is illegal"); 73 | return; 74 | } 75 | } 76 | 77 | chain.doFilter(context); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/filter/support/LemonLogFilter.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.filter.support; 2 | 3 | import org.micro.lemon.filter.IFilter; 4 | import org.micro.lemon.filter.LemonChain; 5 | import org.micro.lemon.server.LemonContext; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.micro.lemon.extension.Extension; 8 | 9 | /** 10 | * LemonLogFilter 11 | * 12 | * @author lry 13 | */ 14 | @Slf4j 15 | @Extension(value = "log", order = 10) 16 | public class LemonLogFilter implements IFilter { 17 | 18 | @Override 19 | public void preFilter(LemonChain chain, LemonContext context) throws Throwable { 20 | log.info("The pre filter: {}", context.getRequest().getHeaders()); 21 | chain.doFilter(context); 22 | } 23 | 24 | @Override 25 | public void postFilter(LemonChain chain, LemonContext context) throws Throwable { 26 | log.info("The post filter: {}", context.getRequest().getHeaders()); 27 | chain.doFilter(context); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/DubboInvoke.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo; 2 | 3 | import lombok.Getter; 4 | import org.apache.dubbo.common.URL; 5 | import org.apache.dubbo.common.extension.ExtensionLoader; 6 | import org.apache.dubbo.registry.RegistryFactory; 7 | import org.apache.dubbo.registry.RegistryService; 8 | import org.micro.lemon.common.LemonConfig; 9 | import org.micro.lemon.common.LemonInvoke; 10 | import org.micro.lemon.common.LemonStatusCode; 11 | import org.micro.lemon.common.ServiceMapping; 12 | import org.micro.lemon.common.config.OriginalConfig; 13 | import org.micro.lemon.common.utils.NetUtils; 14 | import org.micro.lemon.proxy.dubbo.metadata.MetadataCollectorFactory; 15 | import org.micro.lemon.server.LemonContext; 16 | import com.alibaba.fastjson.JSON; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.apache.dubbo.config.ApplicationConfig; 19 | import org.apache.dubbo.config.ReferenceConfig; 20 | import org.apache.dubbo.config.RegistryConfig; 21 | import org.apache.dubbo.config.utils.ReferenceConfigCache; 22 | import org.apache.dubbo.rpc.RpcContext; 23 | import org.apache.dubbo.rpc.RpcException; 24 | import org.apache.dubbo.rpc.service.GenericService; 25 | import org.micro.lemon.extension.Extension; 26 | import org.micro.lemon.server.LemonRequest; 27 | 28 | import java.nio.charset.StandardCharsets; 29 | import java.util.*; 30 | import java.util.concurrent.ConcurrentMap; 31 | 32 | /** 33 | * DubboInvoke 34 | * 35 | * @author lry 36 | */ 37 | @Slf4j 38 | @Getter 39 | @Extension("dubbo") 40 | public class DubboInvoke implements LemonInvoke { 41 | 42 | private static final String GROUP_KEY = "group"; 43 | private static final String VERSION_KEY = "version"; 44 | 45 | private LemonConfig lemonConfig; 46 | 47 | private URL serverUrl; 48 | private RegistryConfig registryConfig; 49 | private RegistryService registryService; 50 | private MetadataCollectorFactory metadataCollectorFactory; 51 | private RegistryServiceSubscribe registryServiceSubscribe; 52 | 53 | 54 | @Override 55 | public void initialize(LemonConfig lemonConfig) { 56 | this.lemonConfig = lemonConfig; 57 | 58 | // 创建注册中心配置 59 | RegistryConfig registryConfig = new RegistryConfig(); 60 | registryConfig.setAddress(lemonConfig.getDubbo().getRegistryAddress()); 61 | this.registryConfig = registryConfig; 62 | 63 | // 启动注册中心,并注册服务本身至注册中心 64 | URL url = URL.valueOf(lemonConfig.getRegistryAddress()); 65 | RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(url.getProtocol()); 66 | this.registryService = registryFactory.getRegistry(url); 67 | this.serverUrl = new URL("http", NetUtils.getLocalHost(), lemonConfig.getPort(), lemonConfig.getApplication()); 68 | registryService.register(serverUrl); 69 | 70 | this.registryServiceSubscribe = new RegistryServiceSubscribe(); 71 | registryServiceSubscribe.initialize(registryService); 72 | 73 | // 启动Metadata数据收集器 74 | this.metadataCollectorFactory = MetadataCollectorFactory.INSTANCE; 75 | metadataCollectorFactory.initialize(lemonConfig); 76 | } 77 | 78 | @Override 79 | public LemonContext invoke(LemonContext lemonContext) { 80 | LemonRequest request = lemonContext.getRequest(); 81 | OriginalConfig originalConfig = lemonConfig.getOriginal(); 82 | 83 | // setter request header list 84 | for (Map.Entry entry : request.getHeaders().entrySet()) { 85 | // originalReqHeaders contains or starts with 'X-' 86 | if (originalConfig.getReqHeaders().contains(entry.getKey()) 87 | || entry.getKey().startsWith(LemonContext.HEADER_PREFIX)) { 88 | RpcContext.getContext().setAttachment(entry.getKey(), String.valueOf(entry.getValue())); 89 | } 90 | } 91 | 92 | // call original remote 93 | ServiceMapping serviceMapping = parseServiceMapping(request); 94 | byte[] bytes = (byte[]) request.getContent(); 95 | String body = new String(bytes, StandardCharsets.UTF_8); 96 | 97 | List paramValues = new ArrayList<>(); 98 | paramValues.add(JSON.isValid(body) ? JSON.parse(bytes) : body); 99 | serviceMapping.setParamValues(paramValues.toArray(new Object[0])); 100 | 101 | GenericService genericService = buildGenericService(request, serviceMapping); 102 | Object result = genericService.$invoke(serviceMapping.getMethod(), 103 | serviceMapping.getParamTypes(), serviceMapping.getParamValues()); 104 | 105 | // setter response header list 106 | Map headers = new HashMap<>(); 107 | for (Map.Entry entry : RpcContext.getContext().getAttachments().entrySet()) { 108 | headers.put(entry.getKey(), entry.getValue()); 109 | } 110 | 111 | lemonContext.getResponse().addHeader(headers); 112 | lemonContext.getResponse().setContent(result); 113 | return lemonContext; 114 | } 115 | 116 | @Override 117 | public LemonStatusCode failure(LemonContext context, Throwable throwable) { 118 | if (throwable instanceof RpcException) { 119 | RpcException e = (RpcException) throwable; 120 | if (e.isTimeout()) { 121 | return LemonStatusCode.CALL_ORIGINAL_TIMEOUT; 122 | } else if (e.isBiz()) { 123 | return LemonStatusCode.CALL_ORIGINAL_BIZ_ERROR; 124 | } else if (e.isNetwork()) { 125 | return LemonStatusCode.CALL_ORIGINAL_NETWORK_ERROR; 126 | } else if (e.isSerialization()) { 127 | return LemonStatusCode.CALL_ORIGINAL_SERIALIZATION; 128 | } 129 | } 130 | 131 | return LemonStatusCode.CALL_ORIGINAL_UNKNOWN; 132 | } 133 | 134 | @Override 135 | public void destroy() { 136 | if (registryService != null) { 137 | registryService.unregister(serverUrl); 138 | } 139 | } 140 | 141 | /** 142 | * The build {@link GenericService} by {@link ServiceMapping} 143 | * 144 | * @param request {@link LemonRequest} 145 | * @param serviceMapping {@link ServiceMapping} 146 | * @return {@link GenericService} 147 | */ 148 | private GenericService buildGenericService(LemonRequest request, ServiceMapping serviceMapping) { 149 | ReferenceConfig referenceConfig = new ReferenceConfig<>(); 150 | referenceConfig.setApplication(new ApplicationConfig(serviceMapping.getApplication())); 151 | referenceConfig.setGroup(serviceMapping.getGroup()); 152 | referenceConfig.setVersion(serviceMapping.getVersion()); 153 | referenceConfig.setRegistry(registryConfig); 154 | referenceConfig.setInterface(serviceMapping.getServiceName()); 155 | referenceConfig.setGeneric(true); 156 | 157 | if (serviceMapping.getParamTypes() == null) { 158 | metadataCollectorFactory.wrapperTypesFromMetadata(request, serviceMapping); 159 | } 160 | 161 | return ReferenceConfigCache.getCache().get(referenceConfig); 162 | } 163 | 164 | /** 165 | * The wrapper {@link ServiceMapping} by {@link LemonContext} 166 | * 167 | * @param request {@link LemonRequest} 168 | */ 169 | private ServiceMapping parseServiceMapping(LemonRequest request) { 170 | List paths = Arrays.asList(request.getContextPath().split(LemonContext.URL_DELIMITER)); 171 | if (paths.size() != 5) { 172 | throw new IllegalArgumentException("Illegal Request"); 173 | } 174 | 175 | ServiceMapping serviceMapping = new ServiceMapping(); 176 | serviceMapping.setApplication(paths.get(2)); 177 | serviceMapping.setService(paths.get(3)); 178 | serviceMapping.setMethod(paths.get(4)); 179 | 180 | // wrapper service name 181 | serviceMapping.setServiceName(this.getServiceName(serviceMapping)); 182 | 183 | Map parameters = request.getHeaders(); 184 | if (parameters.containsKey(GROUP_KEY)) { 185 | serviceMapping.setGroup(String.valueOf(parameters.get(GROUP_KEY))); 186 | } 187 | if (parameters.containsKey(VERSION_KEY)) { 188 | serviceMapping.setVersion(String.valueOf(parameters.get(VERSION_KEY))); 189 | } 190 | 191 | return serviceMapping; 192 | } 193 | 194 | /** 195 | * The get service name 196 | * 197 | * @param serviceMapping {@link ServiceMapping} 198 | * @return service name 199 | */ 200 | private String getServiceName(ServiceMapping serviceMapping) { 201 | ConcurrentMap serviceNames = 202 | registryServiceSubscribe.getServiceNames().get(serviceMapping.getApplication()); 203 | if (serviceNames == null || serviceNames.isEmpty()) { 204 | return serviceMapping.getService(); 205 | } 206 | 207 | String serviceName = serviceNames.get(serviceMapping.getService()); 208 | if (serviceName == null || serviceName.length() == 0) { 209 | return serviceMapping.getService(); 210 | } 211 | 212 | return serviceName; 213 | } 214 | 215 | } -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/MetadataCollector.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo; 2 | 3 | import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier; 4 | import org.micro.lemon.common.utils.URL; 5 | import org.micro.lemon.extension.SPI; 6 | 7 | /** 8 | * Metadata Collector 9 | * 10 | * @author lry 11 | */ 12 | @SPI("zookeeper") 13 | public interface MetadataCollector { 14 | 15 | /** 16 | * The initialize 17 | * 18 | * @param url {@link URL} 19 | */ 20 | void initialize(URL url); 21 | 22 | /** 23 | * The pull metaData 24 | * 25 | * @param metadataIdentifier {@link MetadataIdentifier} 26 | * @return meta data 27 | */ 28 | String pullMetaData(MetadataIdentifier metadataIdentifier); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/RegistryServiceSubscribe.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import lombok.Getter; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.dubbo.common.URL; 7 | import org.apache.dubbo.common.constants.CommonConstants; 8 | import org.apache.dubbo.common.constants.RegistryConstants; 9 | import org.apache.dubbo.common.utils.NetUtils; 10 | import org.apache.dubbo.common.utils.StringUtils; 11 | import org.apache.dubbo.registry.NotifyListener; 12 | 13 | import org.apache.dubbo.registry.RegistryService; 14 | import org.apache.dubbo.remoting.Constants; 15 | 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.concurrent.*; 20 | import java.util.concurrent.atomic.AtomicLong; 21 | 22 | /** 23 | * Registry Service Subscribe 24 | * 25 | * @author lry 26 | */ 27 | @Slf4j 28 | @Getter 29 | public class RegistryServiceSubscribe implements NotifyListener { 30 | 31 | private static final AtomicLong ID = new AtomicLong(); 32 | private static final URL SUBSCRIBE = new URL( 33 | org.apache.dubbo.registry.Constants.ADMIN_PROTOCOL, 34 | NetUtils.getLocalHost(), 0, "", 35 | CommonConstants.INTERFACE_KEY, CommonConstants.ANY_VALUE, 36 | CommonConstants.GROUP_KEY, CommonConstants.ANY_VALUE, 37 | CommonConstants.VERSION_KEY, CommonConstants.ANY_VALUE, 38 | CommonConstants.CLASSIFIER_KEY, CommonConstants.ANY_VALUE, 39 | RegistryConstants.CATEGORY_KEY, RegistryConstants.PROVIDERS_CATEGORY + "," 40 | + RegistryConstants.CONSUMERS_CATEGORY + "," 41 | + RegistryConstants.ROUTERS_CATEGORY + "," 42 | + RegistryConstants.CONFIGURATORS_CATEGORY, 43 | CommonConstants.ENABLED_KEY, CommonConstants.ANY_VALUE, 44 | Constants.CHECK_KEY, String.valueOf(false)); 45 | 46 | private RegistryService registryService; 47 | private final ConcurrentHashMap URL_IDS_MAPPER = new ConcurrentHashMap<>(); 48 | private final ConcurrentMap> serviceNames = new ConcurrentHashMap<>(); 49 | /** 50 | * ConcurrentMap>> 51 | */ 52 | private final ConcurrentMap>> registryCache = new ConcurrentHashMap<>(); 53 | private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 54 | 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), 55 | new ThreadFactoryBuilder().setDaemon(true).setNameFormat("lemon-registry-subscribe").build()); 56 | 57 | public void initialize(RegistryService registryService) { 58 | this.registryService = registryService; 59 | threadPoolExecutor.submit(() -> registryService.subscribe(SUBSCRIBE, RegistryServiceSubscribe.this)); 60 | } 61 | 62 | public void update(URL oldUrl, URL newUrl) { 63 | registryService.unregister(oldUrl); 64 | registryService.register(newUrl); 65 | } 66 | 67 | public void unregister(URL url) { 68 | URL_IDS_MAPPER.remove(url.toFullString()); 69 | registryService.unregister(url); 70 | } 71 | 72 | public void register(URL url) { 73 | registryService.register(url); 74 | } 75 | 76 | public void destroy() { 77 | registryService.unsubscribe(SUBSCRIBE, this); 78 | } 79 | 80 | /** 81 | * 收到的通知对于 ,同一种类型数据(override、subscribe、route、其它是provider),同一个服务的数据是全量的 82 | */ 83 | @Override 84 | public void notify(List urls) { 85 | if (urls == null || urls.isEmpty()) { 86 | return; 87 | } 88 | 89 | final Map>> categories = new HashMap<>(); 90 | for (URL url : urls) { 91 | String category = url.getParameter(RegistryConstants.CATEGORY_KEY, RegistryConstants.PROVIDERS_CATEGORY); 92 | // 注意:empty协议的group和version为* 93 | if (RegistryConstants.EMPTY_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { 94 | ConcurrentMap> services = registryCache.get(category); 95 | if (services != null) { 96 | String group = url.getParameter(CommonConstants.GROUP_KEY); 97 | String version = url.getParameter(CommonConstants.VERSION_KEY); 98 | // 注意:empty协议的group和version为* 99 | if (!CommonConstants.ANY_VALUE.equals(group) && !CommonConstants.ANY_VALUE.equals(version)) { 100 | services.remove(url.getServiceKey()); 101 | } else { 102 | for (Map.Entry> serviceEntry : services.entrySet()) { 103 | String service = serviceEntry.getKey(); 104 | if (getInterface(service).equals(url.getServiceInterface()) && 105 | (CommonConstants.ANY_VALUE.equals(group) || StringUtils.isEquals(group, getGroup(service))) && 106 | (CommonConstants.ANY_VALUE.equals(version) || StringUtils.isEquals(version, getVersion(service)))) { 107 | services.remove(service); 108 | } 109 | } 110 | } 111 | } 112 | } else { 113 | Map> services = categories.computeIfAbsent(category, k -> new HashMap<>()); 114 | String service = generateServiceKey(url); 115 | Map ids = services.computeIfAbsent(service, k -> new HashMap<>()); 116 | //保证ID对于同一个URL的不可变 117 | if (URL_IDS_MAPPER.containsKey(url.toFullString())) { 118 | ids.put(URL_IDS_MAPPER.get(url.toFullString()), url); 119 | } else { 120 | long currentId = ID.incrementAndGet(); 121 | ids.put(currentId, url); 122 | URL_IDS_MAPPER.putIfAbsent(url.toFullString(), currentId); 123 | } 124 | 125 | if (RegistryConstants.PROVIDERS_CATEGORY.equalsIgnoreCase(category)) { 126 | String application = url.getParameter(CommonConstants.APPLICATION_KEY); 127 | if (StringUtils.isBlank(application)) { 128 | continue; 129 | } 130 | Map appServiceNames = serviceNames.computeIfAbsent(application, k -> new ConcurrentHashMap<>()); 131 | String serviceName = getSimpleServiceName(url.getServiceInterface()); 132 | String serviceInterface = url.getServiceInterface(); 133 | String hasServiceName = appServiceNames.get(serviceName); 134 | if (appServiceNames.containsKey(serviceName)) { 135 | log.warn("The service name is same[{}] and[{}].", hasServiceName, serviceInterface); 136 | return; 137 | } 138 | appServiceNames.put(serviceName, serviceInterface); 139 | } 140 | } 141 | } 142 | 143 | for (Map.Entry>> categoryEntry : categories.entrySet()) { 144 | String category = categoryEntry.getKey(); 145 | ConcurrentMap> services = registryCache.get(category); 146 | if (services == null) { 147 | services = new ConcurrentHashMap<>(); 148 | registryCache.put(category, services); 149 | } 150 | services.putAll(categoryEntry.getValue()); 151 | } 152 | } 153 | 154 | private String getSimpleServiceName(String serviceInterface) { 155 | String name = serviceInterface.substring(serviceInterface.lastIndexOf(".") + 1); 156 | if (name.endsWith("Service")) { 157 | name = name.substring(0, name.length() - 7); 158 | } 159 | 160 | return toLowerCaseFirstOne(name); 161 | } 162 | 163 | public static String toLowerCaseFirstOne(String s) { 164 | if (Character.isLowerCase(s.charAt(0))) { 165 | return s; 166 | } else { 167 | return Character.toLowerCase(s.charAt(0)) + s.substring(1); 168 | } 169 | } 170 | 171 | private String generateServiceKey(URL url) { 172 | String inf = url.getServiceInterface(); 173 | if (inf == null) { 174 | return null; 175 | } 176 | StringBuilder buf = new StringBuilder(); 177 | String group = url.getParameter(CommonConstants.GROUP_KEY); 178 | if (group != null && group.length() > 0) { 179 | buf.append(group).append("/"); 180 | } 181 | buf.append(inf); 182 | String version = url.getParameter(CommonConstants.VERSION_KEY); 183 | if (version != null && version.length() > 0) { 184 | buf.append(":").append(version); 185 | } 186 | 187 | return buf.toString(); 188 | } 189 | 190 | private String getInterface(String serviceKey) { 191 | if (StringUtils.isEmpty(serviceKey)) { 192 | throw new IllegalArgumentException("serviceKey must not be null"); 193 | } 194 | 195 | // serviceKey serviceKey=group/interface:version 196 | int groupIndex = serviceKey.indexOf("/"); 197 | int versionIndex = serviceKey.indexOf(":"); 198 | if (groupIndex > 0 && versionIndex > 0) { 199 | return serviceKey.substring(groupIndex + 1, versionIndex); 200 | } else if (groupIndex > 0 && versionIndex < 0) { 201 | return serviceKey.substring(groupIndex + 1); 202 | } else if (groupIndex < 0 && versionIndex > 0) { 203 | return serviceKey.substring(0, versionIndex); 204 | } else { 205 | return serviceKey; 206 | } 207 | } 208 | 209 | private String getGroup(String serviceKey) { 210 | if (StringUtils.isEmpty(serviceKey)) { 211 | throw new IllegalArgumentException("serviceKey must not be null"); 212 | } 213 | 214 | // serviceKey serviceKey=group/interface:version 215 | int groupIndex = serviceKey.indexOf("/"); 216 | if (groupIndex > 0) { 217 | return serviceKey.substring(0, groupIndex); 218 | } else { 219 | return null; 220 | } 221 | } 222 | 223 | private String getVersion(String serviceKey) { 224 | if (StringUtils.isEmpty(serviceKey)) { 225 | throw new IllegalArgumentException("serviceKey must not be null"); 226 | } 227 | 228 | // serviceKey serviceKey=group/interface:version 229 | int versionIndex = serviceKey.indexOf(":"); 230 | if (versionIndex > 0) { 231 | return serviceKey.substring(versionIndex + 1); 232 | } else { 233 | return null; 234 | } 235 | } 236 | 237 | } -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/extension/AbstractExtensionMetadataReport.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.extension; 2 | 3 | import org.apache.dubbo.common.URL; 4 | import org.apache.dubbo.common.utils.ClassUtils; 5 | import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier; 6 | import org.apache.dubbo.metadata.report.support.AbstractMetadataReport; 7 | import org.micro.lemon.proxy.dubbo.metadata.annotation.LemonService; 8 | 9 | /** 10 | * Abstract Extension Metadata Report 11 | * 12 | * @author lry 13 | */ 14 | public abstract class AbstractExtensionMetadataReport extends AbstractMetadataReport { 15 | 16 | public AbstractExtensionMetadataReport(URL reportServerUrl) { 17 | super(reportServerUrl); 18 | } 19 | 20 | protected String wrapperStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) { 21 | try { 22 | Class clazz = ClassUtils.forName(providerMetadataIdentifier.getServiceInterface()); 23 | LemonService lemonService = clazz.getDeclaredAnnotation(LemonService.class); 24 | } catch (Exception e) { 25 | e.printStackTrace(); 26 | } 27 | 28 | return serviceDefinitions; 29 | } 30 | 31 | protected String wrapperStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String serviceParameterString) { 32 | return serviceParameterString; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/extension/RedisMetadataReport.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.extension; 2 | 3 | import lombok.ToString; 4 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 5 | import org.apache.dubbo.common.URL; 6 | import org.apache.dubbo.common.logger.Logger; 7 | import org.apache.dubbo.common.logger.LoggerFactory; 8 | import org.apache.dubbo.metadata.MetadataConstants; 9 | import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum; 10 | import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier; 11 | import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier; 12 | import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier; 13 | import org.apache.dubbo.rpc.RpcException; 14 | import redis.clients.jedis.*; 15 | 16 | import java.util.HashSet; 17 | import java.util.List; 18 | import java.util.Set; 19 | 20 | import static org.apache.dubbo.common.constants.CommonConstants.*; 21 | 22 | /** 23 | * Redis Metadata Report 24 | * 25 | * @author lry 26 | */ 27 | @ToString 28 | public class RedisMetadataReport extends AbstractExtensionMetadataReport { 29 | 30 | private final static Logger logger = LoggerFactory.getLogger(RedisMetadataReport.class); 31 | 32 | private JedisPool pool; 33 | private Set jedisClusterNodes; 34 | private int timeout; 35 | private String password; 36 | 37 | public RedisMetadataReport(URL url) { 38 | super(url); 39 | timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT); 40 | if (url.getParameter(CLUSTER_KEY, false)) { 41 | jedisClusterNodes = new HashSet<>(); 42 | List urls = url.getBackupUrls(); 43 | for (URL tmpUrl : urls) { 44 | jedisClusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort())); 45 | } 46 | } else { 47 | pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), timeout, url.getPassword()); 48 | } 49 | } 50 | 51 | @Override 52 | protected void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) { 53 | this.storeMetadata(providerMetadataIdentifier, serviceDefinitions); 54 | } 55 | 56 | @Override 57 | protected void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String value) { 58 | this.storeMetadata(consumerMetadataIdentifier, value); 59 | } 60 | 61 | @Override 62 | protected void doSaveMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url) { 63 | 64 | } 65 | 66 | @Override 67 | protected void doRemoveMetadata(ServiceMetadataIdentifier metadataIdentifier) { 68 | 69 | } 70 | 71 | @Override 72 | protected List doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier) { 73 | return null; 74 | } 75 | 76 | @Override 77 | protected void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr) { 78 | 79 | } 80 | 81 | @Override 82 | protected String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) { 83 | return null; 84 | } 85 | 86 | private void storeMetadata(MetadataIdentifier metadataIdentifier, String v) { 87 | if (pool != null) { 88 | storeMetadataStandalone(metadataIdentifier, v); 89 | } else { 90 | storeMetadataInCluster(metadataIdentifier, v); 91 | } 92 | } 93 | 94 | private void storeMetadataInCluster(MetadataIdentifier metadataIdentifier, String v) { 95 | try (JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig())) { 96 | jedisCluster.set(metadataIdentifier.getIdentifierKey() + MetadataConstants.META_DATA_STORE_TAG, v); 97 | } catch (Throwable e) { 98 | logger.error("Failed to put " + metadataIdentifier + " to redis cluster " + v + ", cause: " + e.getMessage(), e); 99 | throw new RpcException("Failed to put " + metadataIdentifier + " to redis cluster " + v + ", cause: " + e.getMessage(), e); 100 | } 101 | } 102 | 103 | private void storeMetadataStandalone(MetadataIdentifier metadataIdentifier, String v) { 104 | try (Jedis jedis = pool.getResource()) { 105 | jedis.set(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), v); 106 | } catch (Throwable e) { 107 | logger.error("Failed to put " + metadataIdentifier + " to redis " + v + ", cause: " + e.getMessage(), e); 108 | throw new RpcException("Failed to put " + metadataIdentifier + " to redis " + v + ", cause: " + e.getMessage(), e); 109 | } 110 | } 111 | 112 | @Override 113 | public String getServiceDefinition(MetadataIdentifier metadataIdentifier) { 114 | return null; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/extension/RedisMetadataReportFactory.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.extension; 2 | 3 | import org.apache.dubbo.common.URL; 4 | import org.apache.dubbo.metadata.report.MetadataReport; 5 | import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory; 6 | 7 | /** 8 | * Redis Metadata Report Factory 9 | * 10 | * @author lry 11 | */ 12 | public class RedisMetadataReportFactory extends AbstractMetadataReportFactory { 13 | 14 | @Override 15 | public MetadataReport createMetadataReport(URL url) { 16 | return new RedisMetadataReport(url); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/extension/ZookeeperMetadataReport.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.extension; 2 | 3 | import lombok.ToString; 4 | import org.apache.dubbo.common.URL; 5 | import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum; 6 | import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier; 7 | import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier; 8 | import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier; 9 | import org.apache.dubbo.remoting.zookeeper.ZookeeperClient; 10 | import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter; 11 | 12 | import java.util.List; 13 | 14 | import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; 15 | import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR; 16 | 17 | /** 18 | * Zookeeper Metadata Report 19 | * 20 | * @author lry 21 | */ 22 | @ToString 23 | public class ZookeeperMetadataReport extends AbstractExtensionMetadataReport { 24 | 25 | private final String root; 26 | private final ZookeeperClient zkClient; 27 | 28 | public ZookeeperMetadataReport(URL url, ZookeeperTransporter zookeeperTransporter) { 29 | super(url); 30 | if (url.isAnyHost()) { 31 | throw new IllegalStateException("registry address == null"); 32 | } 33 | String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT); 34 | if (!group.startsWith(PATH_SEPARATOR)) { 35 | group = PATH_SEPARATOR + group; 36 | } 37 | this.root = group; 38 | zkClient = zookeeperTransporter.connect(url); 39 | } 40 | 41 | private String toRootDir() { 42 | if (root.equals(PATH_SEPARATOR)) { 43 | return root; 44 | } 45 | return root + PATH_SEPARATOR; 46 | } 47 | 48 | @Override 49 | protected void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) { 50 | storeMetadata(providerMetadataIdentifier, super.wrapperStoreProviderMetadata(providerMetadataIdentifier, serviceDefinitions)); 51 | } 52 | 53 | @Override 54 | protected void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String value) { 55 | storeMetadata(consumerMetadataIdentifier, super.wrapperStoreConsumerMetadata(consumerMetadataIdentifier, value)); 56 | } 57 | 58 | @Override 59 | protected void doSaveMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url) { 60 | 61 | } 62 | 63 | @Override 64 | protected void doRemoveMetadata(ServiceMetadataIdentifier metadataIdentifier) { 65 | 66 | } 67 | 68 | @Override 69 | protected List doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier) { 70 | return null; 71 | } 72 | 73 | @Override 74 | protected void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr) { 75 | 76 | } 77 | 78 | @Override 79 | protected String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) { 80 | return null; 81 | } 82 | 83 | private void storeMetadata(MetadataIdentifier metadataIdentifier, String v) { 84 | zkClient.create(getNodePath(metadataIdentifier), v, false); 85 | } 86 | 87 | private String getNodePath(MetadataIdentifier metadataIdentifier) { 88 | return toRootDir() + metadataIdentifier.getUniqueKey(KeyTypeEnum.PATH); 89 | } 90 | 91 | @Override 92 | public String getServiceDefinition(MetadataIdentifier metadataIdentifier) { 93 | return null; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/extension/ZookeeperMetadataReportFactory.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.extension; 2 | 3 | import org.apache.dubbo.common.URL; 4 | import org.apache.dubbo.metadata.report.MetadataReport; 5 | import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory; 6 | import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter; 7 | 8 | /** 9 | * Zookeeper Metadata Report Factory 10 | * 11 | * @author lry 12 | */ 13 | public class ZookeeperMetadataReportFactory extends AbstractMetadataReportFactory { 14 | 15 | private ZookeeperTransporter zookeeperTransporter; 16 | 17 | public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) { 18 | this.zookeeperTransporter = zookeeperTransporter; 19 | } 20 | 21 | @Override 22 | public MetadataReport createMetadataReport(URL url) { 23 | return new ZookeeperMetadataReport(url, zookeeperTransporter); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/metadata/MetadataCollectorFactory.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.metadata; 2 | 3 | import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier; 4 | import org.micro.lemon.common.LemonConfig; 5 | import org.micro.lemon.common.ServiceMapping; 6 | import org.micro.lemon.common.utils.URL; 7 | import org.micro.lemon.proxy.dubbo.MetadataCollector; 8 | import org.micro.lemon.server.LemonContext; 9 | import com.alibaba.fastjson.JSON; 10 | import com.google.common.cache.Cache; 11 | import com.google.common.cache.CacheBuilder; 12 | import org.apache.dubbo.common.constants.CommonConstants; 13 | import org.apache.dubbo.common.utils.StringUtils; 14 | import org.apache.dubbo.metadata.definition.model.FullServiceDefinition; 15 | import org.apache.dubbo.metadata.definition.model.MethodDefinition; 16 | import org.micro.lemon.extension.ExtensionLoader; 17 | import org.micro.lemon.server.LemonRequest; 18 | 19 | import java.util.ArrayList; 20 | import java.util.HashSet; 21 | import java.util.List; 22 | import java.util.Set; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | /** 26 | * Metadata Collector Factory 27 | * 28 | * @author lry 29 | */ 30 | public enum MetadataCollectorFactory { 31 | 32 | // ==== 33 | 34 | INSTANCE; 35 | 36 | private final static String MAXIMUM_KEY = "cacheMaximum"; 37 | private final static String DURATION_KEY = "cacheDuration"; 38 | private LemonConfig lemonConfig; 39 | private MetadataCollector metadataCollector; 40 | private Cache cache = null; 41 | 42 | /** 43 | * The initialize 44 | * 45 | * @param lemonConfig {@link LemonConfig} 46 | */ 47 | public void initialize(LemonConfig lemonConfig) { 48 | this.lemonConfig = lemonConfig; 49 | if (StringUtils.isNotEmpty(lemonConfig.getDubbo().getMetadataAddress())) { 50 | URL metadataUrl = URL.valueOf(lemonConfig.getDubbo().getMetadataAddress()); 51 | 52 | CacheBuilder builder = CacheBuilder.newBuilder(); 53 | builder.maximumSize(metadataUrl.getParameter(MAXIMUM_KEY, 2000)); 54 | builder.expireAfterAccess(metadataUrl.getParameter( 55 | DURATION_KEY, 2 * 60 * 60L), TimeUnit.MILLISECONDS); 56 | 57 | this.cache = builder.build(); 58 | this.metadataCollector = ExtensionLoader.getLoader( 59 | MetadataCollector.class).getExtension(metadataUrl.getProtocol()); 60 | metadataCollector.initialize(metadataUrl); 61 | } 62 | } 63 | 64 | /** 65 | * The invalidate cache 66 | * 67 | * @param serviceMappings service definition list 68 | */ 69 | public void invalidates(List serviceMappings) { 70 | if (serviceMappings == null || serviceMappings.isEmpty()) { 71 | cache.invalidateAll(); 72 | } else if (serviceMappings.size() == 1) { 73 | ServiceMapping serviceMapping = serviceMappings.get(0); 74 | MetadataIdentifier identifier = new MetadataIdentifier( 75 | serviceMapping.getService(), serviceMapping.getVersion(), 76 | serviceMapping.getGroup(), CommonConstants.PROVIDER_SIDE, serviceMapping.getApplication()); 77 | cache.invalidate(identifier.getIdentifierKey()); 78 | } else { 79 | Set keys = new HashSet<>(); 80 | for (ServiceMapping serviceMapping : serviceMappings) { 81 | MetadataIdentifier identifier = build(serviceMapping); 82 | keys.add(identifier.getIdentifierKey()); 83 | } 84 | 85 | cache.invalidateAll(keys); 86 | } 87 | } 88 | 89 | /** 90 | * The wrapper types from metadata 91 | * 92 | * @param request {@link LemonRequest} 93 | * @param serviceMapping {@link ServiceMapping} 94 | */ 95 | public void wrapperTypesFromMetadata(LemonRequest request, ServiceMapping serviceMapping) { 96 | MetadataIdentifier identifier = build(serviceMapping); 97 | 98 | // whether to clear cached access 99 | String invalidateCache = request.getHeaderValue(LemonContext.INVALIDATE_CACHE); 100 | if (!StringUtils.isBlank(invalidateCache)) { 101 | if (Boolean.parseBoolean(invalidateCache)) { 102 | String lemonToken = request.getHeaderValue(LemonContext.LEMON_TOKEN); 103 | if (lemonConfig.getToken().equals(lemonToken)) { 104 | cache.invalidate(identifier.getIdentifierKey()); 105 | } 106 | } 107 | } 108 | 109 | FullServiceDefinition fullServiceDefinition; 110 | try { 111 | fullServiceDefinition = cache.get(identifier.getIdentifierKey(), () -> { 112 | String metadata = metadataCollector.pullMetaData(identifier); 113 | if (!StringUtils.isBlank(metadata)) { 114 | return JSON.parseObject(metadata, FullServiceDefinition.class); 115 | } 116 | 117 | return null; 118 | }); 119 | } catch (Exception e) { 120 | throw new RuntimeException(e.getMessage(), e); 121 | } 122 | 123 | List methods = fullServiceDefinition.getMethods(); 124 | if (methods == null) { 125 | return; 126 | } 127 | for (MethodDefinition m : methods) { 128 | if (!m.getName().equals(serviceMapping.getMethod()) || 129 | m.getParameterTypes().length != serviceMapping.getParamValues().length) { 130 | continue; 131 | } 132 | 133 | List parameterTypes = new ArrayList<>(); 134 | for (String parameterType : m.getParameterTypes()) { 135 | if (parameterType.contains("<")) { 136 | parameterTypes.add(parameterType.substring(0, parameterType.indexOf("<"))); 137 | } else { 138 | parameterTypes.add(parameterType); 139 | } 140 | } 141 | 142 | serviceMapping.setParamTypes(parameterTypes.toArray(new String[0])); 143 | } 144 | } 145 | 146 | /** 147 | * The build {@link ServiceMapping} 148 | * 149 | * @param serviceMapping {@link ServiceMapping} 150 | * @return {@link MetadataIdentifier} 151 | */ 152 | private MetadataIdentifier build(ServiceMapping serviceMapping) { 153 | return new MetadataIdentifier( 154 | serviceMapping.getServiceName(), 155 | serviceMapping.getVersion(), 156 | serviceMapping.getGroup(), 157 | CommonConstants.PROVIDER_SIDE, 158 | serviceMapping.getApplication()); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/metadata/RedisMetadataCollector.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.metadata; 2 | 3 | import org.apache.dubbo.metadata.MetadataConstants; 4 | import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum; 5 | import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier; 6 | import org.micro.lemon.common.utils.URL; 7 | import org.micro.lemon.proxy.dubbo.MetadataCollector; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 10 | import org.apache.dubbo.common.constants.CommonConstants; 11 | import org.apache.dubbo.rpc.RpcException; 12 | import org.micro.lemon.extension.Extension; 13 | import redis.clients.jedis.*; 14 | 15 | import java.util.HashSet; 16 | import java.util.List; 17 | import java.util.Set; 18 | 19 | /** 20 | * Redis Metadata Collector 21 | * 22 | * @author lry 23 | */ 24 | @Slf4j 25 | @Extension("redis") 26 | public class RedisMetadataCollector implements MetadataCollector { 27 | 28 | private Set clusterNodes; 29 | private JedisPool pool; 30 | private int timeout; 31 | private URL url; 32 | 33 | @Override 34 | public void initialize(URL url) { 35 | this.url = url; 36 | this.timeout = url.getParameter(CommonConstants.TIMEOUT_KEY, CommonConstants.DEFAULT_TIMEOUT); 37 | if (url.getParameter(CommonConstants.CLUSTER_KEY, false)) { 38 | this.clusterNodes = new HashSet<>(); 39 | List urls = url.getBackupUrls(); 40 | for (URL tmpUrl : urls) { 41 | clusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort())); 42 | } 43 | } else { 44 | this.pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), timeout, url.getPassword()); 45 | } 46 | } 47 | 48 | @Override 49 | public String pullMetaData(MetadataIdentifier metadataIdentifier) { 50 | if (pool != null) { 51 | return getMetadataInStandAlone(metadataIdentifier); 52 | } else { 53 | return getMetadataInCluster(metadataIdentifier); 54 | } 55 | } 56 | 57 | /** 58 | * The get metadata in StandAlone by {@link MetadataIdentifier} 59 | * 60 | * @param metadataIdentifier {@link MetadataIdentifier} 61 | * @return metadata json 62 | */ 63 | private String getMetadataInStandAlone(MetadataIdentifier metadataIdentifier) { 64 | try (Jedis jedis = pool.getResource()) { 65 | return jedis.get(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY)); 66 | } catch (Throwable e) { 67 | throw new RpcException("Failed to get " + metadataIdentifier + " to redis, cause: " + e.getMessage(), e); 68 | } 69 | } 70 | 71 | /** 72 | * The get metadata in Cluster by {@link MetadataIdentifier} 73 | * 74 | * @param metadataIdentifier {@link MetadataIdentifier} 75 | * @return metadata json 76 | */ 77 | private String getMetadataInCluster(MetadataIdentifier metadataIdentifier) { 78 | try (JedisCluster jedisCluster = new JedisCluster(clusterNodes, timeout, 79 | timeout, 2, url.getPassword(), new GenericObjectPoolConfig())) { 80 | return jedisCluster.get(metadataIdentifier.getIdentifierKey() + MetadataConstants.META_DATA_STORE_TAG); 81 | } catch (Throwable e) { 82 | throw new RpcException("Failed to get " + metadataIdentifier + " to redis cluster, cause: " + e.getMessage(), e); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/metadata/ZookeeperMetadataCollector.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.metadata; 2 | 3 | import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum; 4 | import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier; 5 | import org.micro.lemon.common.utils.URL; 6 | import org.micro.lemon.proxy.dubbo.MetadataCollector; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.curator.framework.CuratorFramework; 9 | import org.apache.curator.framework.CuratorFrameworkFactory; 10 | import org.apache.curator.retry.ExponentialBackoffRetry; 11 | import org.apache.dubbo.common.constants.CommonConstants; 12 | import org.micro.lemon.extension.Extension; 13 | 14 | /** 15 | * Zookeeper Metadata Collector 16 | * 17 | * @author lry 18 | */ 19 | @Slf4j 20 | @Extension("zookeeper") 21 | public class ZookeeperMetadataCollector implements MetadataCollector { 22 | 23 | private CuratorFramework client; 24 | private String root; 25 | private final static String DEFAULT_ROOT = "dubbo"; 26 | 27 | @Override 28 | public void initialize(URL url) { 29 | String group = url.getParameter(CommonConstants.GROUP_KEY, DEFAULT_ROOT); 30 | if (!group.startsWith(CommonConstants.PATH_SEPARATOR)) { 31 | group = CommonConstants.PATH_SEPARATOR + group; 32 | } 33 | 34 | this.root = group; 35 | this.client = CuratorFrameworkFactory.newClient(url.getAddress(), 36 | new ExponentialBackoffRetry(1000, 3)); 37 | client.start(); 38 | } 39 | 40 | @Override 41 | public String pullMetaData(MetadataIdentifier metadataIdentifier) { 42 | try { 43 | String path = getNodePath(metadataIdentifier); 44 | if (client.checkExists().forPath(path) == null) { 45 | return null; 46 | } 47 | 48 | return new String(client.getData().forPath(path)); 49 | } catch (Exception e) { 50 | log.error(e.getMessage(), e); 51 | } 52 | 53 | return null; 54 | } 55 | 56 | /** 57 | * The get node path 58 | * 59 | * @param metadataIdentifier {@link MetadataIdentifier} 60 | * @return metadata json 61 | */ 62 | private String getNodePath(MetadataIdentifier metadataIdentifier) { 63 | String rootDir = root; 64 | if (!root.equals(CommonConstants.PATH_SEPARATOR)) { 65 | rootDir = root + CommonConstants.PATH_SEPARATOR; 66 | } 67 | 68 | return rootDir + metadataIdentifier.getUniqueKey(KeyTypeEnum.PATH); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/metadata/annotation/LemonMethod.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.metadata.annotation; 2 | 3 | public class LemonMethod { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/dubbo/metadata/annotation/LemonService.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.dubbo.metadata.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * LemonService 10 | * 11 | * @author lry 12 | */ 13 | @Target(ElementType.TYPE) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface LemonService { 16 | 17 | /** 18 | * The service api path 19 | * 20 | * @return return api path 21 | */ 22 | String value() default ""; 23 | 24 | /** 25 | * The service api name 26 | * 27 | * @return name document 28 | */ 29 | String name() default ""; 30 | 31 | /** 32 | * The true indicates that access requires authentication 33 | * 34 | * @return true is authentication 35 | */ 36 | boolean auth() default false; 37 | 38 | /** 39 | * The service api description 40 | * 41 | * @return description document 42 | */ 43 | String msg() default ""; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/proxy/http/JsoupInvoke.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.proxy.http; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.jsoup.Connection; 7 | import org.jsoup.Jsoup; 8 | import org.micro.lemon.common.LemonConfig; 9 | import org.micro.lemon.common.LemonInvoke; 10 | import org.micro.lemon.common.LemonStatusCode; 11 | import org.micro.lemon.common.ServiceMapping; 12 | import org.micro.lemon.common.utils.AntPathMatcher; 13 | import org.micro.lemon.extension.Extension; 14 | import org.micro.lemon.server.LemonContext; 15 | import org.micro.lemon.server.LemonRequest; 16 | 17 | import java.io.IOException; 18 | import java.nio.charset.StandardCharsets; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | import java.util.concurrent.ConcurrentMap; 24 | 25 | /** 26 | * Jsoup Lemon Invoke 27 | * 28 | * @author lry 29 | */ 30 | @Slf4j 31 | @Extension("jsoup") 32 | public class JsoupInvoke implements LemonInvoke { 33 | 34 | private LemonConfig lemonConfig; 35 | private final AntPathMatcher antPathMatcher = new AntPathMatcher(); 36 | private final ConcurrentMap mappings = new ConcurrentHashMap<>(); 37 | 38 | @Override 39 | public void initialize(LemonConfig lemonConfig) { 40 | this.lemonConfig = lemonConfig; 41 | List services = lemonConfig.getServices(); 42 | for (ServiceMapping serviceMapping : services) { 43 | mappings.put(serviceMapping.getService(), serviceMapping); 44 | } 45 | } 46 | 47 | @Override 48 | public LemonContext invoke(LemonContext lemonContext) { 49 | LemonRequest request = lemonContext.getRequest(); 50 | ServiceMapping mapping = null; 51 | for (ConcurrentMap.Entry entry : mappings.entrySet()) { 52 | if (antPathMatcher.match(entry.getKey(), request.getContextPath())) { 53 | mapping = entry.getValue(); 54 | } 55 | } 56 | 57 | String originalUrl = mapping.getUrl(); 58 | if (mapping.isFullUrl()) { 59 | originalUrl += request.getContextPath(); 60 | } else { 61 | String servicePrefix = mapping.getService(); 62 | servicePrefix = servicePrefix.substring(0, servicePrefix.lastIndexOf("/")); 63 | originalUrl += request.getContextPath().substring(servicePrefix.length()); 64 | } 65 | 66 | Connection connection = Jsoup.connect(originalUrl); 67 | Connection.Request sendRequest = connection.request(); 68 | sendRequest.method(ConnectionMethod.valueOf(request.getHttpMethod()).getMethod()); 69 | for (Map.Entry entry : request.getHeaders().entrySet()) { 70 | sendRequest.header(entry.getKey(), String.valueOf(entry.getValue())); 71 | } 72 | byte[] bytes = (byte[]) request.getContent(); 73 | if (request.getContent() != null && bytes.length > 0) { 74 | sendRequest.requestBody(new String(bytes, StandardCharsets.UTF_8)); 75 | } 76 | 77 | // setter timeout(ms) 78 | Long timeout = mapping.getTimeout(); 79 | if (timeout == null) { 80 | timeout = lemonConfig.getOriginal().getTimeout(); 81 | } 82 | if (timeout > 0) { 83 | sendRequest.timeout(timeout.intValue()); 84 | } 85 | 86 | try { 87 | Connection.Response response = connection.execute(); 88 | Map headers = new HashMap<>(response.headers()); 89 | lemonContext.getResponse().addHeader(headers); 90 | lemonContext.getResponse().setContent(response.bodyAsBytes()); 91 | return lemonContext; 92 | } catch (IOException e) { 93 | throw new RuntimeException(e.getMessage(), e); 94 | } 95 | } 96 | 97 | @Override 98 | public LemonStatusCode failure(LemonContext context, Throwable throwable) { 99 | return LemonStatusCode.CALL_ORIGINAL_UNKNOWN; 100 | } 101 | 102 | @Override 103 | public void destroy() { 104 | 105 | } 106 | 107 | @Getter 108 | @AllArgsConstructor 109 | public enum ConnectionMethod { 110 | 111 | // ==== 112 | 113 | GET(Connection.Method.GET), 114 | POST(Connection.Method.POST), 115 | PUT(Connection.Method.PUT), 116 | DELETE(Connection.Method.DELETE), 117 | PATCH(Connection.Method.PATCH), 118 | HEAD(Connection.Method.HEAD), 119 | OPTIONS(Connection.Method.OPTIONS), 120 | TRACE(Connection.Method.TRACE); 121 | 122 | private Connection.Method method; 123 | 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/server/LemonCallback.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.server; 2 | 3 | import org.micro.lemon.common.LemonStatusCode; 4 | 5 | /** 6 | * LemonCallback 7 | * 8 | * @author lry 9 | */ 10 | public interface LemonCallback { 11 | 12 | /** 13 | * The callback 14 | * 15 | * @param statusCode {@link LemonStatusCode} 16 | */ 17 | default void callback(LemonStatusCode statusCode) { 18 | callback(statusCode, statusCode.getMessage()); 19 | } 20 | 21 | /** 22 | * The callback 23 | * 24 | * @param statusCode {@link LemonStatusCode} 25 | * @param message message 26 | */ 27 | default void callback(LemonStatusCode statusCode, String message) { 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/server/LemonChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.server; 2 | 3 | import io.netty.channel.ChannelInitializer; 4 | import io.netty.channel.ChannelPipeline; 5 | import io.netty.channel.socket.SocketChannel; 6 | import io.netty.handler.codec.http.HttpObjectAggregator; 7 | import io.netty.handler.codec.http.HttpRequestDecoder; 8 | import io.netty.handler.codec.http.HttpResponseEncoder; 9 | import io.netty.handler.stream.ChunkedWriteHandler; 10 | import org.micro.lemon.common.LemonConfig; 11 | 12 | /** 13 | * LemonChannelInitializer 14 | * 15 | * @author lry 16 | */ 17 | public class LemonChannelInitializer extends ChannelInitializer { 18 | 19 | private LemonConfig lemonConfig; 20 | private LemonServerHandler lemonServerHandler; 21 | 22 | public LemonChannelInitializer(LemonConfig lemonConfig) { 23 | this.lemonConfig = lemonConfig; 24 | this.lemonServerHandler = new LemonServerHandler(lemonConfig); 25 | } 26 | 27 | @Override 28 | protected void initChannel(SocketChannel ch) throws Exception { 29 | ChannelPipeline pipeline = ch.pipeline(); 30 | pipeline.addLast("http-decoder", new HttpRequestDecoder()); 31 | // Convert multiple requests from HTTP to FullHttpRequest/FullHttpResponse 32 | pipeline.addLast("http-aggregator", new HttpObjectAggregator(lemonConfig.getMaxContentLength())); 33 | pipeline.addLast("http-encoder", new HttpResponseEncoder()); 34 | pipeline.addLast("http-chunked", new ChunkedWriteHandler()); 35 | pipeline.addLast("serverHandler", lemonServerHandler); 36 | } 37 | 38 | public void destroy() { 39 | if (lemonServerHandler != null) { 40 | lemonServerHandler.destroy(); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/server/LemonContext.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.server; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import java.util.concurrent.CompletableFuture; 8 | 9 | /** 10 | * LemonContext 11 | * 12 | * @author lry 13 | */ 14 | @Data 15 | @Slf4j 16 | @ToString 17 | public class LemonContext implements LemonCallback { 18 | 19 | public final static String LEMON_ID_KEY = "X-Lemon-Id"; 20 | public final static String URI_KEY = "X-Lemon-Uri"; 21 | public final static String APP_PATH_KEY = "X-Lemon-Application"; 22 | public final static String CONTEXT_PATH_KEY = "X-Lemon-ContextPath"; 23 | public final static String PATH_KEY = "X-Lemon-Path"; 24 | public final static String METHOD_KEY = "X-Lemon-Method"; 25 | public final static String KEEP_ALIVE_KEY = "X-Lemon-KeepAlive"; 26 | public final static String CONTENT_LENGTH_KEY = "X-Lemon-ContentLength"; 27 | public final static String CLIENT_HOST_KEY = "X-Lemon-Host"; 28 | 29 | public final static String LEMON_CODE_KEY = "X-Lemon-Code"; 30 | public final static String LEMON_CODE_MESSAGE = "X-Lemon-Message"; 31 | 32 | public final static String URL_DELIMITER = "/"; 33 | public final static String HEADER_PREFIX = "X-"; 34 | public final static String INVALIDATE_CACHE = "X-Invalidate-Cache"; 35 | public final static String LEMON_TOKEN = "X-Lemon-Token"; 36 | 37 | private final LemonRequest request = new LemonRequest(); 38 | private final LemonResponse response = new LemonResponse(); 39 | private CompletableFuture future; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/server/LemonRequest.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.server; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import java.io.Serializable; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * LemonRequest 14 | * 15 | * @author lry 16 | */ 17 | @Data 18 | @ToString 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class LemonRequest implements Serializable { 22 | 23 | private Object content; 24 | private final Map headers = new HashMap<>(); 25 | 26 | public void addHeader(Map headers) { 27 | this.headers.putAll(headers); 28 | } 29 | 30 | public String getHeaderValue(String headerKey) { 31 | return headers.containsKey(headerKey) ? String.valueOf(headers.get(headerKey)) : null; 32 | } 33 | 34 | public String getRequestId() { 35 | return this.getHeaderValue(LemonContext.LEMON_ID_KEY); 36 | } 37 | 38 | public String getUri() { 39 | return this.getHeaderValue(LemonContext.URI_KEY); 40 | } 41 | 42 | public String getApplicationPath() { 43 | return this.getHeaderValue(LemonContext.APP_PATH_KEY); 44 | } 45 | 46 | public String getContextPath() { 47 | return this.getHeaderValue(LemonContext.CONTEXT_PATH_KEY); 48 | } 49 | 50 | public String getHttpMethod() { 51 | return this.getHeaderValue(LemonContext.METHOD_KEY); 52 | } 53 | 54 | public String getPath() { 55 | return this.getHeaderValue(LemonContext.PATH_KEY); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/server/LemonResponse.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.server; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import lombok.ToString; 6 | 7 | import java.io.Serializable; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * LemonResponse 13 | * 14 | * @author lry 15 | */ 16 | @Data 17 | @ToString 18 | @NoArgsConstructor 19 | public class LemonResponse implements Serializable { 20 | 21 | private Object content; 22 | private final Map headers = new HashMap<>(); 23 | 24 | public LemonResponse(Map headers, Object content) { 25 | this.headers.putAll(headers); 26 | this.content = content; 27 | } 28 | 29 | public void addHeader(Map headers) { 30 | this.headers.putAll(headers); 31 | } 32 | 33 | public String getHeaderValue(String headerKey) { 34 | return headers.containsKey(headerKey) ? String.valueOf(headers.get(headerKey)) : null; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/server/LemonServer.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.server; 2 | 3 | import org.micro.lemon.common.LemonConfig; 4 | import org.micro.lemon.filter.LemonFactory; 5 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.*; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.nio.NioServerSocketChannel; 10 | import io.netty.handler.logging.LogLevel; 11 | import io.netty.handler.logging.LoggingHandler; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | import java.util.concurrent.ThreadFactory; 15 | 16 | /** 17 | * LemonServer 18 | * 19 | * @author lry 20 | */ 21 | @Slf4j 22 | public class LemonServer { 23 | 24 | private Channel channel; 25 | private EventLoopGroup bossGroup; 26 | private EventLoopGroup workerGroup; 27 | private LemonChannelInitializer channelInitializer; 28 | 29 | /** 30 | * The initialize 31 | */ 32 | public void initialize() { 33 | LemonConfig lemonConfig = LemonConfig.loadConfig(); 34 | LemonFactory.INSTANCE.initialize(lemonConfig); 35 | log.info("The starting open server by config:{}", lemonConfig); 36 | 37 | ThreadFactory ioThreadFactory = new ThreadFactoryBuilder().setDaemon(true).setNameFormat("lemon-io").build(); 38 | ThreadFactory workThreadFactory = new ThreadFactoryBuilder().setDaemon(true).setNameFormat("lemon-work").build(); 39 | this.channelInitializer = new LemonChannelInitializer(lemonConfig); 40 | 41 | try { 42 | this.bossGroup = new NioEventLoopGroup(lemonConfig.getIoThread(), ioThreadFactory); 43 | this.workerGroup = new NioEventLoopGroup(lemonConfig.getWorkThread(), workThreadFactory); 44 | ServerBootstrap serverBootstrap = new ServerBootstrap() 45 | .group(bossGroup, workerGroup) 46 | .channel(NioServerSocketChannel.class) 47 | .option(ChannelOption.SO_BACKLOG, 1024) 48 | .childOption(ChannelOption.SO_KEEPALIVE, true) 49 | .handler(new LoggingHandler(LogLevel.INFO)) 50 | .childHandler(channelInitializer); 51 | 52 | ChannelFuture channelFuture = serverBootstrap.bind(lemonConfig.getPort()).sync(); 53 | channelFuture.addListener((future) -> log.info("The start server is success")); 54 | this.channel = channelFuture.channel(); 55 | 56 | Runtime.getRuntime().addShutdownHook(new Thread(LemonServer.this::destroy)); 57 | if (lemonConfig.isHold()) { 58 | channel.closeFuture().sync(); 59 | } 60 | } catch (Exception e) { 61 | log.error("The start server is fail", e); 62 | } 63 | } 64 | 65 | /** 66 | * The destroy 67 | */ 68 | public void destroy() { 69 | try { 70 | log.info("The starting close server..."); 71 | if (channel != null) { 72 | channel.close(); 73 | } 74 | if (bossGroup != null) { 75 | bossGroup.shutdownGracefully(); 76 | } 77 | if (workerGroup != null) { 78 | workerGroup.shutdownGracefully(); 79 | } 80 | if (channelInitializer != null) { 81 | channelInitializer.destroy(); 82 | } 83 | LemonFactory.INSTANCE.destroy(); 84 | } catch (Exception e) { 85 | log.error("The destroy server is fail", e); 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/micro/lemon/server/LemonServerHandler.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.server; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelHandler; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.ChannelInboundHandlerAdapter; 10 | import io.netty.handler.codec.http.*; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.micro.lemon.common.LemonConfig; 13 | import org.micro.lemon.common.LemonStatusCode; 14 | import org.micro.lemon.common.config.BizTaskConfig; 15 | import org.micro.lemon.common.utils.StandardThreadExecutor; 16 | import org.micro.lemon.filter.LemonChain; 17 | import org.slf4j.MDC; 18 | 19 | import java.net.InetSocketAddress; 20 | import java.net.SocketAddress; 21 | import java.nio.charset.StandardCharsets; 22 | import java.util.*; 23 | import java.util.concurrent.*; 24 | 25 | /** 26 | * LemonServerHandler 27 | * 28 | * @author lry 29 | */ 30 | @Slf4j 31 | @ChannelHandler.Sharable 32 | public class LemonServerHandler extends ChannelInboundHandlerAdapter { 33 | 34 | private LemonConfig lemonConfig; 35 | private ConcurrentMap channels; 36 | private StandardThreadExecutor standardThreadExecutor; 37 | 38 | public LemonServerHandler(LemonConfig lemonConfig) { 39 | this.lemonConfig = lemonConfig; 40 | this.channels = new ConcurrentHashMap<>(lemonConfig.getMaxConnection()); 41 | 42 | // create biz thread pool 43 | BizTaskConfig bizTaskConfig = lemonConfig.getBiz(); 44 | if (bizTaskConfig.getCoreThread() > 0) { 45 | ThreadFactory bizThreadFactory = new ThreadFactoryBuilder().setDaemon(true).setNameFormat("lemon-biz").build(); 46 | this.standardThreadExecutor = new StandardThreadExecutor( 47 | bizTaskConfig.getCoreThread(), bizTaskConfig.getMaxThread(), 48 | bizTaskConfig.getKeepAliveTime(), TimeUnit.MILLISECONDS, bizTaskConfig.getQueueCapacity(), 49 | bizThreadFactory, bizTaskConfig.getRejectedStrategy().getHandler()); 50 | standardThreadExecutor.prestartAllCoreThreads(); 51 | } 52 | } 53 | 54 | @Override 55 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 56 | Channel channel = ctx.channel(); 57 | if (channels.size() > lemonConfig.getMaxConnection()) { 58 | // Direct close connection beyond maximum connection limit 59 | log.warn("The connected channel size out of limit: limit={} current={}", 60 | lemonConfig.getMaxConnection(), channels.size()); 61 | channel.close(); 62 | } else { 63 | String channelKey = getChannelKey(channel.localAddress(), channel.remoteAddress()); 64 | channels.put(channelKey, channel); 65 | ctx.fireChannelRegistered(); 66 | } 67 | } 68 | 69 | @Override 70 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 71 | if (msg instanceof FullHttpRequest) { 72 | // build context 73 | final LemonContext lemonContext = new LemonContext() { 74 | @Override 75 | public void callback(LemonStatusCode statusCode, String message) { 76 | FullHttpResponse response = buildResponse(statusCode, message, getResponse()); 77 | ctx.writeAndFlush(response).addListener(future -> MDC.remove(LemonContext.LEMON_ID_KEY)); 78 | } 79 | }; 80 | 81 | // check application uri 82 | final FullHttpRequest request = (FullHttpRequest) msg; 83 | if (!request.uri().startsWith(LemonContext.URL_DELIMITER + 84 | lemonConfig.getApplication() + LemonContext.URL_DELIMITER)) { 85 | lemonContext.callback(LemonStatusCode.NOT_FOUND); 86 | return; 87 | } 88 | 89 | // wrapper request context 90 | this.wrapperRequest(lemonContext, request); 91 | 92 | // submit request task 93 | if (standardThreadExecutor == null) { 94 | new LemonChain(lemonContext).start0(); 95 | } else { 96 | try { 97 | standardThreadExecutor.submit(() -> new LemonChain(lemonContext).start0()); 98 | } catch (RejectedExecutionException e) { 99 | log.error(LemonStatusCode.TOO_MANY_REQUESTS.getMessage(), e); 100 | lemonContext.callback(LemonStatusCode.TOO_MANY_REQUESTS); 101 | } 102 | } 103 | } 104 | } 105 | 106 | @Override 107 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 108 | Channel channel = ctx.channel(); 109 | String channelKey = getChannelKey(channel.localAddress(), channel.remoteAddress()); 110 | log.error("The handler channel[" + channelKey + "] exception caught: " + cause.getMessage(), cause); 111 | if (channel.isOpen()) { 112 | channel.close(); 113 | } 114 | if (!ctx.isRemoved()) { 115 | ctx.close(); 116 | } 117 | } 118 | 119 | @Override 120 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 121 | Channel channel = ctx.channel(); 122 | String channelKey = getChannelKey(channel.localAddress(), channel.remoteAddress()); 123 | channels.remove(channelKey); 124 | ctx.fireChannelUnregistered(); 125 | } 126 | 127 | public void destroy() { 128 | if (standardThreadExecutor != null) { 129 | standardThreadExecutor.shutdown(); 130 | } 131 | } 132 | 133 | /** 134 | * key = local address + remote address 135 | * 136 | * @param localSocketAddress {@link SocketAddress} 137 | * @param remoteSocketAddress {@link SocketAddress} 138 | * @return channel key 139 | */ 140 | private String getChannelKey(SocketAddress localSocketAddress, SocketAddress remoteSocketAddress) { 141 | InetSocketAddress local = (InetSocketAddress) localSocketAddress; 142 | InetSocketAddress remote = (InetSocketAddress) remoteSocketAddress; 143 | 144 | String key; 145 | if (remote == null || remote.getAddress() == null) { 146 | key = "unknown->"; 147 | } else { 148 | key = remote.getAddress().getHostAddress() + ":" + remote.getPort() + "->"; 149 | } 150 | 151 | if (local == null || local.getAddress() == null) { 152 | key += "unknown"; 153 | } else { 154 | key += local.getAddress().getHostAddress() + ":" + local.getPort(); 155 | } 156 | 157 | return key; 158 | } 159 | 160 | /** 161 | * The wrapper chain context 162 | * 163 | * @param lemonContext {@link LemonContext} 164 | * @param request {@link FullHttpRequest} 165 | */ 166 | private void wrapperRequest(LemonContext lemonContext, FullHttpRequest request) { 167 | Map> originHeaders = new HashMap<>(); 168 | 169 | // read headers 170 | HttpHeaders httpHeaders = request.headers(); 171 | String requestId = httpHeaders.get(LemonContext.LEMON_ID_KEY, 172 | UUID.randomUUID().toString().replace("-", "")); 173 | MDC.put(LemonContext.LEMON_ID_KEY, requestId); 174 | for (Map.Entry entry : httpHeaders.entries()) { 175 | if (!lemonConfig.getIgnoreHeaders().contains(entry.getKey())) { 176 | originHeaders.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(entry.getValue()); 177 | } 178 | } 179 | 180 | // read uri 181 | QueryStringDecoder decoder = new QueryStringDecoder(request.uri()); 182 | String path = decoder.path(); 183 | originHeaders.putAll(decoder.parameters()); 184 | 185 | // read content 186 | int contentLength; 187 | byte[] content; 188 | ByteBuf byteBuf = null; 189 | try { 190 | byteBuf = request.content(); 191 | contentLength = byteBuf.readableBytes(); 192 | content = new byte[contentLength]; 193 | byteBuf.readBytes(content); 194 | } finally { 195 | if (byteBuf != null) { 196 | byteBuf.release(); 197 | } 198 | } 199 | 200 | // build context 201 | final Map headers = new HashMap<>(); 202 | for (Map.Entry> entry : originHeaders.entrySet()) { 203 | headers.put(entry.getKey(), entry.getValue().size() == 1 ? entry.getValue().get(0) : entry.getValue()); 204 | } 205 | headers.put(LemonContext.LEMON_ID_KEY, requestId); 206 | headers.put(LemonContext.URI_KEY, request.uri()); 207 | headers.put(LemonContext.CLIENT_HOST_KEY, httpHeaders.contains("X-Forwarded-For") ? 208 | httpHeaders.get("X-Forwarded-For") : httpHeaders.get("Host")); 209 | headers.put(LemonContext.APP_PATH_KEY, path.substring(0, path.indexOf(LemonContext.URL_DELIMITER, 1))); 210 | headers.put(LemonContext.CONTEXT_PATH_KEY, path.substring(path.indexOf(LemonContext.URL_DELIMITER, 1))); 211 | headers.put(LemonContext.PATH_KEY, path); 212 | headers.put(LemonContext.METHOD_KEY, request.method().name()); 213 | headers.put(LemonContext.KEEP_ALIVE_KEY, HttpUtil.isKeepAlive(request)); 214 | headers.put(LemonContext.CONTENT_LENGTH_KEY, contentLength); 215 | lemonContext.getRequest().addHeader(headers); 216 | lemonContext.getRequest().setContent(content); 217 | } 218 | 219 | /** 220 | * The write and flush 221 | * 222 | * @param statusCode {@link LemonStatusCode} 223 | * @param message custom message 224 | * @param response {@link LemonResponse} 225 | */ 226 | private FullHttpResponse buildResponse(LemonStatusCode statusCode, String message, LemonResponse response) { 227 | // build content 228 | ByteBuf byteBuf; 229 | if (response.getContent() == null) { 230 | byteBuf = Unpooled.buffer(0); 231 | } else if (response.getContent() instanceof ByteBuf) { 232 | byteBuf = (ByteBuf) response.getContent(); 233 | } else if (response.getContent() instanceof byte[]) { 234 | byteBuf = Unpooled.wrappedBuffer((byte[]) response.getContent()); 235 | } else { 236 | byteBuf = Unpooled.wrappedBuffer(String.valueOf(response.getContent()).getBytes(StandardCharsets.UTF_8)); 237 | } 238 | 239 | // build http response 240 | FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf); 241 | for (Map.Entry entry : response.getHeaders().entrySet()) { 242 | httpResponse.headers().set(entry.getKey(), entry.getValue()); 243 | } 244 | 245 | // build response customize header 246 | httpResponse.headers().set(LemonContext.LEMON_CODE_KEY, statusCode.getCode()); 247 | if (!response.getHeaders().containsKey(com.google.common.net.HttpHeaders.CONTENT_TYPE)) { 248 | httpResponse.headers().set(com.google.common.net.HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON); 249 | } 250 | 251 | // build response fixed header 252 | httpResponse.headers().set(com.google.common.net.HttpHeaders.CONNECTION, HttpHeaderValues.KEEP_ALIVE); 253 | httpResponse.headers().set(com.google.common.net.HttpHeaders.ACCEPT_ENCODING, HttpHeaderValues.GZIP_DEFLATE); 254 | httpResponse.headers().set(LemonContext.LEMON_CODE_MESSAGE, 255 | (message != null && message.trim().length() > 0) ? message : statusCode.getMessage()); 256 | httpResponse.headers().set(com.google.common.net.HttpHeaders.CONTENT_LENGTH, 257 | (httpResponse.content() == null ? 0 : httpResponse.content().readableBytes())); 258 | return httpResponse; 259 | } 260 | 261 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.store.MetadataReportFactory: -------------------------------------------------------------------------------- 1 | zookeeper=org.micro.lemon.proxy.dubbo.extension.ZookeeperMetadataReportFactory 2 | redis=org.micro.lemon.proxy.dubbo.extension.RedisMetadataReportFactory 3 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.micro.lemon.common.LemonInvoke: -------------------------------------------------------------------------------- 1 | org.micro.lemon.proxy.http.JsoupInvoke 2 | org.micro.lemon.proxy.dubbo.DubboInvoke 3 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.micro.lemon.filter.IFilter: -------------------------------------------------------------------------------- 1 | org.micro.lemon.filter.support.LemonExceptionFilter 2 | org.micro.lemon.filter.support.LemonLogFilter 3 | org.micro.lemon.filter.support.LemonJwtFilter 4 | org.micro.lemon.filter.support.LemonInvokeFilter -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.micro.lemon.proxy.dubbo.MetadataCollector: -------------------------------------------------------------------------------- 1 | org.micro.lemon.proxy.dubbo.metadata.ZookeeperMetadataCollector 2 | org.micro.lemon.proxy.dubbo.metadata.RedisMetadataCollector 3 | -------------------------------------------------------------------------------- /src/main/resources/lemon.yml: -------------------------------------------------------------------------------- 1 | # base 2 | token: lemon 3 | application: lemon 4 | port: 9000 5 | hold: true 6 | io-thread: 0 7 | work-thread: 0 8 | max-channel: 100000 9 | max-content-length: 67108864 10 | 11 | ignore-headers: [Host, Cookie, Accept, User-Agent, Connection, Cache-Control, Upgrade-Insecure-Requests, content-length, 12 | Sec-Fetch-Dest, Sec-Fetch-Site, Sec-Fetch-User, Sec-Fetch-Mode, Accept-Encoding, Accept-Language] 13 | 14 | # biz I/O 15 | biz: 16 | core-thread: 20 17 | max-thread: 200 18 | queue-capacity: 800 19 | keep-alive-time: 60000 20 | rejected-strategy: ABORT_POLICY 21 | 22 | # original 23 | original: 24 | timeout: 30000 25 | req-headers: [Connection, Content-Type, Set-Cookie, Call-Code, Call-Message] 26 | res-headers: [Connection, Content-Type, Set-Cookie, Call-Code, Call-Message] 27 | 28 | # jwt 29 | jwt: 30 | enable: true 31 | key: Token 32 | secret: lemon 33 | key-addr: HEADER 34 | algorithm: HMAC256 35 | 36 | # dubbo 37 | dubbo: 38 | service-simple-name: true 39 | registry-address: zookeeper://127.0.0.1:2181 40 | metadata-address: zookeeper://127.0.0.1:2181 41 | 42 | # lemon 43 | registry-address: zookeeper://127.0.0.1:2181 44 | exclude-filters: ["jwt"] 45 | include-filters: [] 46 | resHeaders: 47 | -DConnection: keep-alive 48 | -DAccept-Encoding: gzip,deflate 49 | -DContent-Type: application/json;charset=UTF-8 50 | services: 51 | - protocol: jsoup 52 | service: /baidu/** 53 | url: https://www.baidu.com 54 | - protocol: dubbo 55 | service: /hello/** 56 | serviceName: org.micro.lemon.dubbo.GreetingsService -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | logs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%class{36}] [%L] [%M]: %msg%xEx%n 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/tools/as.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM ---------------------------------------------------------------------------- 4 | REM program : Arthas 5 | REM author : Core Engine @ Taobao.com 6 | REM date : 2015-11-11 7 | REM version : 3.0 8 | REM ---------------------------------------------------------------------------- 9 | 10 | 11 | 12 | set ERROR_CODE=0 13 | 14 | :init 15 | REM Decide how to startup depending on the version of windows 16 | 17 | REM -- Win98ME 18 | if NOT "%OS%"=="Windows_NT" goto Win9xArg 19 | 20 | REM set local scope for the variables with windows NT shell 21 | if "%OS%"=="Windows_NT" setlocal 22 | goto WinNTGetScriptDir 23 | 24 | :Win9xArg 25 | REM Slurp the command line arguments. This loop allows for an unlimited number 26 | REM of arguments (up to the command line limit, anyway). 27 | set BASEDIR=%CD% 28 | goto repoSetup 29 | 30 | :WinNTGetScriptDir 31 | set BASEDIR=%~dp0 32 | 33 | :repoSetup 34 | set AGENT_JAR=%BASEDIR%\arthas-agent.jar 35 | set CORE_JAR=%BASEDIR%\arthas-core.jar 36 | 37 | set PID=%1 38 | 39 | REM Setup JAVA_HOME 40 | if "%JAVA_HOME%" == "" goto noJavaHome 41 | if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome 42 | if not exist "%JAVA_HOME%\lib\tools.jar" goto noJavaHome 43 | set JAVACMD="%JAVA_HOME%\bin\java" 44 | set BOOT_CLASSPATH="-Xbootclasspath/a:%JAVA_HOME%\lib\tools.jar" 45 | goto okJava 46 | 47 | :noJavaHome 48 | echo The JAVA_HOME environment variable is not defined correctly. 49 | echo It is needed to run this program. 50 | echo NB: JAVA_HOME should point to a JDK not a JRE. 51 | goto exit 52 | 53 | :okJava 54 | set JAVACMD="%JAVA_HOME%"\bin\java 55 | 56 | REM Reaching here means variables are defined and arguments have been captured 57 | :endInit 58 | 59 | %JAVACMD% -Dfile.encoding=UTF-8 %BOOT_CLASSPATH% -jar "%CORE_JAR%" -pid "%PID%" -target-ip 127.0.0.1 -telnet-port 3658 -http-port 8563 -core "%CORE_JAR%" -agent "%AGENT_JAR%" 60 | if %ERRORLEVEL% NEQ 0 goto error 61 | goto attachSuccess 62 | 63 | :error 64 | if "%OS%"=="Windows_NT" endlocal 65 | set ERROR_CODE=%ERRORLEVEL% 66 | goto endNT 67 | 68 | :attachSuccess 69 | REM %JAVACMD% -Dfile.encoding=UTF-8 -Djava.awt.headless=true -cp "%CORE_JAR%" com.taobao.arthas.core.ArthasConsole 127.0.0.1 3658 70 | telnet 127.0.0.1 3658 71 | 72 | REM set local scope for the variables with windows NT shell 73 | if "%OS%"=="Windows_NT" goto endNT 74 | 75 | REM For old DOS remove the set variables from ENV - we assume they were not set 76 | REM before we started - at least we don't leave any baggage around 77 | goto postExec 78 | 79 | :endNT 80 | REM If error code is set to 1 then the endlocal was done already in :error. 81 | if %ERROR_CODE% EQU 0 endlocal 82 | 83 | 84 | :postExec 85 | 86 | if "%FORCE_EXIT_ON_ERROR%" == "on" ( 87 | if %ERROR_CODE% NEQ 0 exit %ERROR_CODE% 88 | ) 89 | 90 | exit /B %ERROR_CODE% 91 | -------------------------------------------------------------------------------- /src/main/resources/tools/as.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # WIKI: https://alibaba.github.io/arthas 4 | # This script only supports bash, do not support posix sh. 5 | # If you have the problem like Syntax error: "(" unexpected (expecting "fi"), 6 | # Try to run "bash -version" to check the version. 7 | # Try to visit WIKI to find a solution. 8 | 9 | # program : Arthas 10 | # author : Core Engine @ Taobao.com 11 | # date : 2018-09-17 12 | 13 | # current arthas script version 14 | ARTHAS_SCRIPT_VERSION=3.0.4 15 | 16 | # define arthas's home 17 | ARTHAS_HOME=${HOME}/.arthas 18 | 19 | # define arthas's lib 20 | ARTHAS_LIB_DIR=${ARTHAS_HOME}/lib 21 | 22 | # define arthas's temp dir 23 | TMP_DIR=/tmp 24 | 25 | # last update arthas version 26 | ARTHAS_VERSION= 27 | 28 | # arthas remote url 29 | ARTHAS_REMOTE_VERSION_URL="http://search.maven.org/solrsearch/select?q=g:%22com.taobao.arthas%22+AND+a:%22arthas-packaging%22" 30 | ARTHAS_REMOTE_DOWNLOAD_URL="http://search.maven.org/classic/remotecontent?filepath=com/taobao/arthas/arthas-packaging" 31 | 32 | # update timeout(sec) 33 | SO_TIMEOUT=5 34 | 35 | # define default target ip 36 | DEFAULT_TARGET_IP="127.0.0.1" 37 | 38 | # define default target port 39 | DEFAULT_TELNET_PORT="3658" 40 | DEFAULT_HTTP_PORT="8563" 41 | 42 | # define JVM's OPS 43 | JVM_OPTS="" 44 | 45 | # define default batch mode 46 | BATCH_MODE=false 47 | 48 | # if true, the script will only attach the agent to target jvm. 49 | ATTACH_ONLY=false 50 | 51 | # define batch script location 52 | BATCH_SCRIPT= 53 | 54 | ARTHAS_OPTS="-Djava.awt.headless=true" 55 | 56 | # exit shell with err_code 57 | # $1 : err_code 58 | # $2 : err_msg 59 | exit_on_err() 60 | { 61 | [[ ! -z "${2}" ]] && echo "${2}" 1>&2 62 | exit ${1} 63 | } 64 | 65 | 66 | # get with default value 67 | # $1 : target value 68 | # $2 : default value 69 | default() 70 | { 71 | [[ ! -z "${1}" ]] && echo "${1}" || echo "${2}" 72 | } 73 | 74 | 75 | # check arthas permission 76 | check_permission() 77 | { 78 | [ ! -w ${HOME} ] \ 79 | && exit_on_err 1 "permission denied, ${HOME} is not writeable." 80 | } 81 | 82 | 83 | # reset arthas work environment 84 | # reset some options for env 85 | reset_for_env() 86 | { 87 | 88 | # init ARTHAS' lib 89 | mkdir -p ${ARTHAS_LIB_DIR} \ 90 | || exit_on_err 1 "create ${ARTHAS_LIB_DIR} fail." 91 | 92 | # if env define the JAVA_HOME, use it first 93 | # if is alibaba opts, use alibaba ops's default JAVA_HOME 94 | [ -z ${JAVA_HOME} ] && JAVA_HOME=/opt/taobao/java 95 | 96 | # iterater throught candidates to find a proper JAVA_HOME at least contains tools.jar which is required by arthas. 97 | if [ ! -d ${JAVA_HOME} ]; then 98 | JAVA_HOME_CANDIDATES=($(ps aux | grep java | grep -v 'grep java' | awk '{print $11}' | sed -n 's/\/bin\/java$//p')) 99 | for JAVA_HOME_TEMP in ${JAVA_HOME_CANDIDATES[@]}; do 100 | if [ -f ${JAVA_HOME_TEMP}/lib/tools.jar ]; then 101 | JAVA_HOME=${JAVA_HOME_TEMP} 102 | break 103 | fi 104 | done 105 | fi 106 | 107 | # maybe 1.8.0_162 , 11-ea 108 | local JAVA_VERSION_STR=$(${JAVA_HOME}/bin/java -version 2>&1|awk -F '"' '$2>"1.5"{print $2}') 109 | # check the jvm version, we need 1.6+ 110 | [[ ! -x ${JAVA_HOME} || -z ${JAVA_VERSION_STR} ]] && exit_on_err 1 "illegal ENV, please set \$JAVA_HOME to JDK6+" 111 | 112 | local JAVA_VERSION 113 | if [[ $JAVA_VERSION_STR = "1."* ]]; then 114 | JAVA_VERSION=$(echo $veJAVA_VERSION_STRr | sed -e 's/1\.\([0-9]*\)\(.*\)/\1/; 1q') 115 | else 116 | JAVA_VERSION=$(echo $JAVA_VERSION_STR | sed -e 's/\([0-9]*\)\(.*\)/\1/; 1q') 117 | fi 118 | 119 | # when java version greater than 9, there is no tools.jar 120 | if [[ "$JAVA_VERSION" -lt 9 ]];then 121 | # check tools.jar exists 122 | if [ ! -f ${JAVA_HOME}/lib/tools.jar ]; then 123 | exit_on_err 1 "${JAVA_HOME}/lib/tools.jar does not exist, arthas could not be launched!" 124 | else 125 | BOOT_CLASSPATH=-Xbootclasspath/a:${JAVA_HOME}/lib/tools.jar 126 | fi 127 | fi 128 | 129 | # reset CHARSET for alibaba opts, we use GBK 130 | [[ -x /opt/taobao/java ]] && JVM_OPTS="-Dinput.encoding=GBK ${JVM_OPTS} " 131 | 132 | } 133 | 134 | # get latest version from local 135 | get_local_version() 136 | { 137 | ls ${ARTHAS_LIB_DIR} | sort | tail -1 138 | } 139 | 140 | # get latest version from remote 141 | get_remote_version() 142 | { 143 | curl -sLk --connect-timeout ${SO_TIMEOUT} "${ARTHAS_REMOTE_VERSION_URL}" | sed 's/{.*latestVersion":"*\([0-9a-zA-Z\\.\\-]*\)"*,*.*}/\1/' 144 | } 145 | 146 | # make version format to comparable format like 000.000.(0){15} 147 | # $1 : version 148 | to_comparable_version() 149 | { 150 | echo ${1}|awk -F "." '{printf("%d.%d.%d\n",$1,$2,$3)}' 151 | } 152 | 153 | # update arthas if necessary 154 | update_if_necessary() 155 | { 156 | local update_version=$1 157 | 158 | if [ ! -d ${ARTHAS_LIB_DIR}/${update_version} ]; then 159 | echo "updating version ${update_version} ..." 160 | 161 | local temp_target_lib_dir="$TMP_DIR/temp_${update_version}_$$" 162 | local temp_target_lib_zip="${temp_target_lib_dir}/arthas-${update_version}-bin.zip" 163 | local target_lib_dir="${ARTHAS_LIB_DIR}/${update_version}/arthas" 164 | mkdir -p ${target_lib_dir} 165 | 166 | # clean 167 | rm -rf ${temp_target_lib_dir} 168 | rm -rf ${target_lib_dir} 169 | 170 | mkdir -p "${temp_target_lib_dir}" \ 171 | || exit_on_err 1 "create ${temp_target_lib_dir} fail." 172 | 173 | # download current arthas version 174 | curl \ 175 | -#Lk \ 176 | --connect-timeout ${SO_TIMEOUT} \ 177 | -o ${temp_target_lib_zip} \ 178 | "${ARTHAS_REMOTE_DOWNLOAD_URL}/${update_version}/arthas-packaging-${update_version}-bin.zip" \ 179 | || return 1 180 | 181 | # unzip arthas lib 182 | unzip ${temp_target_lib_zip} -d ${temp_target_lib_dir} || (rm -rf ${temp_target_lib_dir} && return 1) 183 | 184 | # rename 185 | mv ${temp_target_lib_dir} ${target_lib_dir} || return 1 186 | 187 | # print success 188 | echo "update completed." 189 | fi 190 | } 191 | 192 | # the usage 193 | usage() 194 | { 195 | echo " 196 | Usage: 197 | $0 [-b [-f SCRIPT_FILE]] [debug] [--use-version VERSION] [--attach-only] [@IP:TELNET_PORT:HTTP_PORT] 198 | [debug] : start the agent in debug mode 199 | : the target Java Process ID 200 | [IP] : the target's IP 201 | [TELNET_PORT] : the target's PORT for telnet 202 | [HTTP_PORT] : the target's PORT for http 203 | [-b] : batch mode, which will disable interactive process selection. 204 | [-f] : specify the path to batch script file. 205 | [--attach-only] : only attach the arthas agent to target jvm. 206 | [--use-version] : use the specified arthas version to attach. 207 | 208 | Example: 209 | ./as.sh 210 | ./as.sh @[IP] 211 | ./as.sh @[IP:PORT] 212 | ./as.sh debug 213 | ./as.sh -b 214 | ./as.sh -b -f /path/to/script 215 | ./as.sh --attach-only 216 | ./as.sh --use-version 2.0.20161221142407 217 | 218 | Here is the list of possible java process(es) to attatch: 219 | 220 | $(${JAVA_HOME}/bin/jps -l | grep -v sun.tools.jps.Jps) 221 | " 222 | } 223 | 224 | # parse the argument 225 | parse_arguments() 226 | { 227 | if ([ "$1" = "-h" ] || [ "$1" = "--help" ] || [ "$1" = "-help" ]) ; then 228 | usage 229 | exit 0 230 | fi 231 | 232 | if [ "$1" = "-b" ]; then 233 | BATCH_MODE=true 234 | shift 235 | if [ "$1" = "-f" ]; then 236 | if [ "x$2" != "x" ] && [ -f $2 ]; then 237 | BATCH_SCRIPT=$2 238 | echo "Using script file for batch mode: $BATCH_SCRIPT" 239 | shift # -f 240 | shift # /path/to/script 241 | else 242 | echo "Invalid script file $2." 243 | return 1 244 | fi 245 | fi 246 | fi 247 | 248 | if [ "$1" = "debug" ] ; then 249 | if [ -z "$JPDA_TRANSPORT" ]; then 250 | JPDA_TRANSPORT="dt_socket" 251 | fi 252 | if [ -z "$JPDA_ADDRESS" ]; then 253 | JPDA_ADDRESS="8888" 254 | fi 255 | if [ -z "$JPDA_SUSPEND" ]; then 256 | JPDA_SUSPEND="n" 257 | fi 258 | if [ -z "$JPDA_OPTS" ]; then 259 | JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND" 260 | fi 261 | ARTHAS_OPTS="$JPDA_OPTS $ARTHAS_OPTS" 262 | shift 263 | fi 264 | 265 | # use custom version 266 | if [ "$1" = "--use-version" ]; then 267 | shift 268 | ARTHAS_VERSION=$1 269 | shift 270 | fi 271 | 272 | # attach only mode 273 | if [ "$1" = "--attach-only" ]; then 274 | ATTACH_ONLY=true 275 | shift 276 | fi 277 | 278 | TARGET_PID=$(echo ${1}|awk -F "@" '{print $1}'); 279 | TARGET_IP=$(echo ${1}|awk -F "@|:" '{print $2}'); 280 | TELNET_PORT=$(echo ${1}|awk -F ":" '{print $2}'); 281 | HTTP_PORT=$(echo ${1}|awk -F ":" '{print $3}'); 282 | 283 | # check pid 284 | if [ -z ${TARGET_PID} ] && [ ${BATCH_MODE} = false ]; then 285 | # interactive mode 286 | IFS=$'\n' 287 | CANDIDATES=($(${JAVA_HOME}/bin/jps -l | grep -v sun.tools.jps.Jps | awk '{print $0}')) 288 | 289 | if [ ${#CANDIDATES[@]} -eq 0 ]; then 290 | echo "Error: no available java process to attach." 291 | return 1 292 | fi 293 | 294 | echo "Found existing java process, please choose one and hit RETURN." 295 | 296 | index=0 297 | suggest=1 298 | # auto select tomcat/pandora-boot process 299 | for process in "${CANDIDATES[@]}"; do 300 | index=$(($index+1)) 301 | if [ $(echo ${process} | grep -c org.apache.catalina.startup.Bootstrap) -eq 1 ] \ 302 | || [ $(echo ${process} | grep -c com.taobao.pandora.boot.loader.SarLauncher) -eq 1 ] 303 | then 304 | suggest=${index} 305 | break 306 | fi 307 | done 308 | 309 | index=0 310 | for process in "${CANDIDATES[@]}"; do 311 | index=$(($index+1)) 312 | if [ ${index} -eq ${suggest} ]; then 313 | echo "* [$index]: ${process}" 314 | else 315 | echo " [$index]: ${process}" 316 | fi 317 | done 318 | 319 | read choice 320 | 321 | if [ -z ${choice} ]; then 322 | choice=${suggest} 323 | fi 324 | 325 | TARGET_PID=`echo ${CANDIDATES[$(($choice-1))]} | cut -d ' ' -f 1` 326 | 327 | elif [ -z ${TARGET_PID} ]; then 328 | # batch mode is enabled, no interactive process selection. 329 | echo "Illegal arguments, the is required." 1>&2 330 | return 1 331 | fi 332 | 333 | # reset ${ip} to default if empty 334 | [ -z ${TARGET_IP} ] && TARGET_IP=${DEFAULT_TARGET_IP} 335 | 336 | # reset ${port} to default if empty 337 | [ -z ${TELNET_PORT} ] && TELNET_PORT=${DEFAULT_TELNET_PORT} 338 | [ -z ${HTTP_PORT} ] && HTTP_PORT=${DEFAULT_HTTP_PORT} 339 | 340 | return 0 341 | 342 | } 343 | 344 | 345 | # attach arthas to target jvm 346 | # $1 : arthas_local_version 347 | attach_jvm() 348 | { 349 | local arthas_version=$1 350 | local arthas_lib_dir=${ARTHAS_LIB_DIR}/${arthas_version}/arthas 351 | 352 | echo "Attaching to ${TARGET_PID} using version ${1}..." 353 | 354 | if [ ${TARGET_IP} = ${DEFAULT_TARGET_IP} ]; then 355 | if [[ "${arthas_version}" > "3.0" ]]; then 356 | ${JAVA_HOME}/bin/java \ 357 | ${ARTHAS_OPTS} ${BOOT_CLASSPATH} ${JVM_OPTS} \ 358 | -jar ${arthas_lib_dir}/arthas-core.jar \ 359 | -pid ${TARGET_PID} \ 360 | -target-ip ${TARGET_IP} \ 361 | -telnet-port ${TELNET_PORT} \ 362 | -http-port ${HTTP_PORT} \ 363 | -core "${arthas_lib_dir}/arthas-core.jar" \ 364 | -agent "${arthas_lib_dir}/arthas-agent.jar" 365 | else 366 | # for compatibility 367 | ${JAVA_HOME}/bin/java \ 368 | ${ARTHAS_OPTS} ${BOOT_CLASSPATH} ${JVM_OPTS} \ 369 | -jar ${arthas_lib_dir}/arthas-core.jar \ 370 | -pid ${TARGET_PID} \ 371 | -target ${TARGET_IP}":"${TELNET_PORT} \ 372 | -core "${arthas_lib_dir}/arthas-core.jar" \ 373 | -agent "${arthas_lib_dir}/arthas-agent.jar" 374 | 375 | # verify_pid 376 | echo "help" > /tmp/command 377 | PID=`${JAVA_HOME}/bin/java -cp ${arthas_lib_dir}/arthas-core.jar ${ARTHAS_OPTS}\ 378 | com.taobao.arthas.core.ArthasConsole ${TARGET_IP} ${TELNET_PORT} -b -f /tmp/command \ 379 | | grep PID | awk '{print $2}'` 380 | rm /tmp/command 381 | if [ ! -z ${PID} ] && [ "${PID}" != "${TARGET_PID}" ]; then 382 | echo "WARNING: Arthas server is running on ${PID} instead of ${TARGET_PID}, exiting." 383 | exit 1 384 | fi 385 | fi 386 | fi 387 | } 388 | 389 | sanity_check() { 390 | # 0 check whether the pid exist 391 | local pid=$(ps -p ${TARGET_PID} -o pid=) 392 | if [ -z ${pid} ]; then 393 | exit_on_err 1 "The target pid (${TARGET_PID}) does not exist!" 394 | fi 395 | 396 | # 1 check the current user matches the process owner 397 | local current_user=$(id -u -n) 398 | # the last '=' after 'user' eliminates the column header 399 | local target_user=$(ps -p "${TARGET_PID}" -o user=) 400 | if [ "$current_user" != "$target_user" ]; then 401 | echo "The current user ($current_user) does not match with the owner of process ${TARGET_PID} ($target_user)." 402 | echo "To solve this, choose one of the following command:" 403 | echo " 1) sudo su $target_user && ./as.sh" 404 | echo " 2) sudo -u $target_user -EH ./as.sh" 405 | exit_on_err 1 406 | fi 407 | } 408 | 409 | # active console 410 | # $1 : arthas_local_version 411 | active_console() 412 | { 413 | local arthas_version=$1 414 | local arthas_lib_dir=${ARTHAS_LIB_DIR}/${arthas_version}/arthas 415 | 416 | if [[ "${arthas_version}" > "3.0" ]]; then 417 | if [ "${BATCH_MODE}" = "true" ]; then 418 | ${JAVA_HOME}/bin/java ${ARTHAS_OPTS} ${JVM_OPTS} \ 419 | -jar ${arthas_lib_dir}/arthas-client.jar \ 420 | ${TARGET_IP} \ 421 | -p ${TELNET_PORT} \ 422 | -f ${BATCH_SCRIPT} 423 | elif type telnet 2>&1 >> /dev/null; then 424 | # use telnet 425 | telnet ${TARGET_IP} ${TELNET_PORT} 426 | else 427 | echo "'telnet' is required." 1>&2 428 | return 1 429 | fi 430 | else 431 | # for compatibility 432 | # use default console 433 | ARGS="${TARGET_IP} ${TELNET_PORT}" 434 | if [ ${BATCH_MODE} = true ]; then 435 | ARGS="$ARGS -b" 436 | fi 437 | if [ ! -z ${BATCH_SCRIPT} ]; then 438 | ARGS="$ARGS -f $BATCH_SCRIPT" 439 | fi 440 | eval ${JAVA_HOME}/bin/java ${ARTHAS_OPTS} \ 441 | -cp ${arthas_lib_dir}/arthas-core.jar \ 442 | com.taobao.arthas.core.ArthasConsole \ 443 | ${ARGS} 444 | fi 445 | } 446 | 447 | # the main 448 | main() 449 | { 450 | echo "Arthas script version: $ARTHAS_SCRIPT_VERSION" 451 | 452 | check_permission 453 | reset_for_env 454 | 455 | parse_arguments "${@}" \ 456 | || exit_on_err 1 "$(usage)" 457 | 458 | local remote_version=$(get_remote_version) 459 | 460 | if [ -z ${ARTHAS_VERSION} ]; then 461 | update_if_necessary ${remote_version} || echo "update fail, ignore this update." 1>&2 462 | else 463 | update_if_necessary ${ARTHAS_VERSION} || echo "update fail, ignore this update." 1>&2 464 | fi 465 | 466 | local arthas_local_version=$(get_local_version) 467 | 468 | if [ ! -z ${ARTHAS_VERSION} ]; then 469 | arthas_local_version=${ARTHAS_VERSION} 470 | fi 471 | 472 | if [ ! -d ${ARTHAS_LIB_DIR}/${arthas_local_version} ]; then 473 | exit_on_err 1 "arthas not found, please check your network." 474 | fi 475 | 476 | sanity_check 477 | 478 | echo "Calculating attach execution time..." 479 | time (attach_jvm ${arthas_local_version} || exit 1) 480 | 481 | if [ $? -ne 0 ]; then 482 | exit_on_err 1 "attach to target jvm (${TARGET_PID}) failed, check ${HOME}/logs/arthas/arthas.log or stderr of target jvm for any exceptions." 483 | fi 484 | 485 | echo "Attach success." 486 | 487 | if [ ${ATTACH_ONLY} = false ]; then 488 | echo "Connecting to arthas server... current timestamp is `date +%s`" 489 | active_console ${arthas_local_version} 490 | fi 491 | } 492 | 493 | 494 | 495 | main "${@}" 496 | -------------------------------------------------------------------------------- /src/main/resources/tools/assembly.xml: -------------------------------------------------------------------------------- 1 | 5 | dist 6 | 7 | tar.gz 8 | 9 | true 10 | 11 | 12 | 13 | target/dist/jsw/app/bin 14 | bin 15 | 16 | 0755 17 | 0755 18 | 19 | 20 | target/dist/jsw/app/conf/tools 21 | bin 22 | 0755 23 | 0755 24 | 25 | **.sh 26 | **.bat 27 | 28 | 29 | 30 | target/dist/jsw/app/conf 31 | conf 32 | 33 | 34 | 35 | *.yml 36 | *.xml 37 | *.properties 38 | static/** 39 | 40 | *.conf 41 | 42 | 43 | 44 | tools/** 45 | 46 | 0644 47 | 0744 48 | 49 | 50 | target/dist/jsw/app/lib 51 | lib 52 | 0644 53 | 0744 54 | 55 | 56 | target/dist/jsw/app/logs 57 | logs 58 | 0644 59 | 0744 60 | 61 | 62 | target/dist/jsw/app/tmp 63 | tmp 64 | 0644 65 | 0744 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/GenericInvokeDemo.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon; 2 | 3 | import org.apache.dubbo.config.ApplicationConfig; 4 | import org.apache.dubbo.config.ReferenceConfig; 5 | import org.apache.dubbo.config.RegistryConfig; 6 | import org.apache.dubbo.rpc.service.GenericService; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class GenericInvokeDemo { 14 | 15 | public static void main(String[] args) { 16 | RegistryConfig registryConfig = new RegistryConfig(); 17 | registryConfig.setAddress("zookeeper://127.0.0.1:2181"); 18 | 19 | ReferenceConfig referenceConfig = new ReferenceConfig<>(); 20 | referenceConfig.setApplication(new ApplicationConfig("micro-dubbo-gateway")); 21 | referenceConfig.setRegistry(registryConfig); 22 | referenceConfig.setInterface("cn.micro.biz.dubbo.provider.DemoService"); 23 | referenceConfig.setGeneric(true); 24 | 25 | GenericService genericService = referenceConfig.get(); 26 | Map person = new HashMap<>(); 27 | person.put("name", "张三"); 28 | person.put("age", 22); 29 | 30 | List list = new ArrayList<>(); 31 | list.add(person); 32 | 33 | Object result = genericService.$invoke("demo", new String[]{"java.util.List"}, new Object[]{list}); 34 | System.out.println(result); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/ProviderDemo.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon; 2 | 3 | import org.micro.lemon.provider.DemoService; 4 | import org.micro.lemon.provider.DemoServiceImpl; 5 | import org.apache.dubbo.config.*; 6 | 7 | public class ProviderDemo { 8 | 9 | public static void main(String[] args) throws Exception { 10 | // Implementation 11 | DemoService demoService = new DemoServiceImpl(); 12 | 13 | // Application Info 14 | ApplicationConfig application = new ApplicationConfig(); 15 | application.setName("micro-dubbo-provider"); 16 | 17 | // Registry Info 18 | RegistryConfig registry = new RegistryConfig(); 19 | registry.setAddress("zookeeper://127.0.0.1:2181"); 20 | 21 | // Metadata Report Info 22 | MetadataReportConfig metadataReportConfig = new MetadataReportConfig(); 23 | metadataReportConfig.setAddress("zookeeper://127.0.0.1:2181"); 24 | 25 | // Protocol 26 | ProtocolConfig protocol = new ProtocolConfig(); 27 | protocol.setName("dubbo"); 28 | protocol.setPort(12345); 29 | protocol.setThreads(200); 30 | 31 | // Exporting: In case of memory leak, please cache. 32 | ServiceConfig service = new ServiceConfig<>(); 33 | service.setApplication(application); 34 | // Use setRegistries() for multi-registry case 35 | service.setRegistry(registry); 36 | // Use setProtocols() for multi-protocol case 37 | service.setProtocol(protocol); 38 | service.setMetadataReportConfig(metadataReportConfig); 39 | service.setInterface(DemoService.class); 40 | service.setRef(demoService); 41 | 42 | // Local export and register 43 | service.export(); 44 | System.in.read(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/dubbo/ConsumerApplication.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.dubbo; 2 | 3 | import org.apache.dubbo.config.ApplicationConfig; 4 | import org.apache.dubbo.config.ReferenceConfig; 5 | import org.apache.dubbo.config.RegistryConfig; 6 | 7 | public class ConsumerApplication { 8 | 9 | public static void main(String[] args) { 10 | ReferenceConfig reference = new ReferenceConfig<>(); 11 | reference.setApplication(new ApplicationConfig("first-dubbo-consumer")); 12 | reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181")); 13 | reference.setInterface(DemoService.class); 14 | DemoService service = reference.get(); 15 | String message = service.sayHi("dubbo"); 16 | System.out.println(message); 17 | } 18 | } -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/dubbo/DemoService.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.dubbo; 2 | 3 | public interface DemoService { 4 | String sayHi(String name); 5 | } -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/dubbo/DemoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.dubbo; 2 | 3 | public class DemoServiceImpl implements DemoService { 4 | 5 | @Override 6 | public String sayHi(String name) { 7 | System.out.println("收到:" + name); 8 | return "hi, " + name; 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/dubbo/GenericConsumerApplication.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.dubbo; 2 | 3 | import org.apache.dubbo.config.ReferenceConfig; 4 | import org.apache.dubbo.rpc.service.GenericService; 5 | 6 | public class GenericConsumerApplication { 7 | 8 | public static void main(String[] args) { 9 | ReferenceConfig reference = new ReferenceConfig<>(); 10 | reference.setInterface("org.micro.lemon.dubbo.DemoService"); 11 | reference.setVersion("1.0.0"); 12 | reference.setGeneric(true); 13 | GenericService genericService = reference.get(); 14 | Object result = genericService.$invoke("sayHello", 15 | new String[]{"java.lang.String"}, new Object[]{"world===="}); 16 | System.out.println(result); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/dubbo/ProviderApplication.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.dubbo; 2 | 3 | import org.apache.dubbo.config.ApplicationConfig; 4 | import org.apache.dubbo.config.RegistryConfig; 5 | import org.apache.dubbo.config.ServiceConfig; 6 | 7 | import java.util.concurrent.CountDownLatch; 8 | 9 | public class ProviderApplication { 10 | 11 | public static void main(String[] args) throws Exception { 12 | ServiceConfig service = new ServiceConfig<>(); 13 | service.setApplication(new ApplicationConfig("first-dubbo-provider")); 14 | service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181")); 15 | service.setInterface(DemoService.class); 16 | service.setRef(new DemoServiceImpl()); 17 | service.export(); 18 | 19 | System.out.println("dubbo service started"); 20 | new CountDownLatch(1).await(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/provider/DemoService.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.provider; 2 | 3 | import org.micro.lemon.proxy.dubbo.metadata.annotation.LemonService; 4 | 5 | import java.util.List; 6 | 7 | @LemonService 8 | public interface DemoService { 9 | 10 | String sayHello(String name); 11 | 12 | User test(User user); 13 | 14 | List testList(User user); 15 | 16 | List demo(List users); 17 | 18 | } -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/provider/DemoServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.provider; 2 | 3 | import org.apache.dubbo.config.annotation.Service; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | @Service 9 | public class DemoServiceImpl implements DemoService { 10 | 11 | @Override 12 | public String sayHello(String name) { 13 | System.out.println("1===" + name); 14 | return "Hello " + name; 15 | } 16 | 17 | @Override 18 | public User test(User user) { 19 | System.out.println("2===" + user); 20 | return user; 21 | } 22 | 23 | @Override 24 | public List testList(User user) { 25 | System.out.println("3===" + user); 26 | List users = new ArrayList<>(); 27 | users.add(user); 28 | return users; 29 | } 30 | 31 | @Override 32 | public List demo(List users) { 33 | System.out.println("4===" + users); 34 | return users; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/test/java/org/micro/lemon/provider/User.java: -------------------------------------------------------------------------------- 1 | package org.micro.lemon.provider; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | import java.io.Serializable; 7 | 8 | @Data 9 | @ToString 10 | public class User implements Serializable { 11 | 12 | private String name; 13 | private int age; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/lemon.yml: -------------------------------------------------------------------------------- 1 | port: 8080 2 | application: lemon 3 | ioThread: 0 4 | workThread: 0 5 | # 64 * 1024 * 1024 = 64 MB 6 | maxContentLength: 67108864 7 | maxChannel: 100000 8 | bizCoreThread: 20 9 | bizMaxThread: 200 10 | bizQueueCapacity: 800 11 | bizKeepAliveTime: 60000 12 | resHeaders: 13 | Connection: keep-alive 14 | Accept-Encoding: gzip,deflate 15 | Content-Type: application/json;charset=UTF-8 16 | originalReqHeaders: [Connection, Content-Type, Set-Cookie, Call-Code, Call-Message] 17 | originalResHeaders: [Connection, Content-Type, Set-Cookie, Call-Code, Call-Message] 18 | dubbo: 19 | registryAddress: zookeeper://127.0.0.1:2181 20 | metadataAddress: zookeeper://127.0.0.1:2181 21 | services: 22 | - category: jsoup 23 | service: /baidu/** 24 | url: https://www.baidu.com 25 | - category: jsoup 26 | service: /oschina/** 27 | url: https://www.oschina.net -------------------------------------------------------------------------------- /src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | logs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%class{36}] [%L] [%M]: %msg%xEx%n 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | --------------------------------------------------------------------------------