README-EN.md 16.2 KB
Newer Older
1
# 📌 TransmittableThreadLocal(TTL) 📌
oldratlee's avatar
oldratlee 已提交
2

oldratlee's avatar
oldratlee 已提交
3
[![Build Status](https://travis-ci.org/alibaba/transmittable-thread-local.svg?branch=master)](https://travis-ci.org/alibaba/transmittable-thread-local)
4
[![Windows Build Status](https://img.shields.io/appveyor/ci/oldratlee/transmittable-thread-local/master.svg?label=windows%20build)](https://ci.appveyor.com/project/oldratlee/transmittable-thread-local)
oldratlee's avatar
oldratlee 已提交
5
[![Coverage Status](https://img.shields.io/codecov/c/github/alibaba/transmittable-thread-local/master.svg)](https://codecov.io/gh/alibaba/transmittable-thread-local/branch/master)
oldratlee's avatar
oldratlee 已提交
6 7
[![Maintainability](https://api.codeclimate.com/v1/badges/de6af6136e538cf1557c/maintainability)](https://codeclimate.com/github/alibaba/transmittable-thread-local/maintainability)  
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
8 9
[![Javadocs](https://img.shields.io/github/release/alibaba/transmittable-thread-local.svg?label=javadoc&color=3d7c47)](https://alibaba.github.io/transmittable-thread-local/apidocs/)
[![Maven Central](https://img.shields.io/maven-central/v/com.alibaba/transmittable-thread-local.svg?color=2d545e)](https://search.maven.org/search?q=g:com.alibaba%20AND%20a:transmittable-thread-local&core=gav)
oldratlee's avatar
oldratlee 已提交
10
[![GitHub release](https://img.shields.io/github/release/alibaba/transmittable-thread-local.svg)](https://github.com/alibaba/transmittable-thread-local/releases)  
oldratlee's avatar
oldratlee 已提交
11
[![Chat at gitter.im](https://badges.gitter.im/alibaba/transmittable-thread-local.svg)](https://gitter.im/alibaba/transmittable-thread-local?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
oldratlee's avatar
oldratlee 已提交
12 13
[![GitHub Stars](https://img.shields.io/github/stars/alibaba/transmittable-thread-local)](https://github.com/alibaba/transmittable-thread-local/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/alibaba/transmittable-thread-local)](https://github.com/alibaba/transmittable-thread-local/fork)
oldratlee's avatar
oldratlee 已提交
14
[![GitHub issues](https://img.shields.io/github/issues/alibaba/transmittable-thread-local.svg)](https://github.com/alibaba/transmittable-thread-local/issues)
oldratlee's avatar
oldratlee 已提交
15
[![Percentage of issues still open](http://isitmaintained.com/badge/open/alibaba/transmittable-thread-local.svg)](https://github.com/alibaba/transmittable-thread-local/issues "Percentage of issues still open")
oldratlee's avatar
oldratlee 已提交
16

oldratlee's avatar
oldratlee 已提交
17
📖 English Documentation | [📖 中文文档](README.md)
oldratlee's avatar
oldratlee 已提交
18

oldratlee's avatar
oldratlee 已提交
19 20 21 22 23 24
----------------------------------------

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


oldratlee's avatar
oldratlee 已提交
25 26 27
- [🔧 Functions](#-functions)
- [🎨 Requirements](#-requirements)
- [👥 User Guide](#-user-guide)
oldratlee's avatar
oldratlee 已提交
28
    - [1. Simple usage](#1-simple-usage)
oldratlee's avatar
oldratlee 已提交
29 30 31
    - [2. Transmit value even using thread pool](#2-transmit-value-even-using-thread-pool)
        - [2.1 Decorate `Runnable` and `Callable`](#21-decorate-runnable-and-callable)
        - [2.2 Decorate thread pool](#22-decorate-thread-pool)
32
        - [2.3 Use Java Agent to decorate thread pool implementation class](#23-use-java-agent-to-decorate-thread-pool-implementation-class)
oldratlee's avatar
oldratlee 已提交
33
- [🔌 Java API Docs](#-java-api-docs)
oldratlee's avatar
oldratlee 已提交
34
- [🍪 Maven Dependency](#-maven-dependency)
35 36 37
- [🔨 About compilation, build and dev](#-about-compilation-build-and-dev)
    - [How to compile and build](#how-to-compile-and-build)
    - [How to development by `IDE`](#how-to-development-by-ide)
oldratlee's avatar
oldratlee 已提交
38 39 40
- [🗿 More Documentation](#-more-documentation)
- [📚 Related Resources](#-related-resources)
    - [JDK Core Classes](#jdk-core-classes)
oldratlee's avatar
oldratlee 已提交
41
- [👷 Contributors](#-contributors)
oldratlee's avatar
oldratlee 已提交
42 43 44 45 46

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

----------------------------------------

oldratlee's avatar
oldratlee 已提交
47
# 🔧 Functions
oldratlee's avatar
oldratlee 已提交
48

oldratlee's avatar
oldratlee 已提交
49
👉 The missing Java™ std lib(simple & 0-dependency) for framework/middleware,
oldratlee's avatar
oldratlee 已提交
50
provide an enhanced `InheritableThreadLocal` that transmits `ThreadLocal` value between threads even using thread pooling components.
oldratlee's avatar
oldratlee 已提交
51
Support `Java` 16/15/14/13/12/11/10/9/8/7/6.
oldratlee's avatar
oldratlee 已提交
52

oldratlee's avatar
oldratlee 已提交
53
Class [`InheritableThreadLocal`](https://docs.oracle.com/javase/10/docs/api/java/lang/InheritableThreadLocal.html) in `JDK`
54
can transmit value to child thread from parent thread.
oldratlee's avatar
oldratlee 已提交
55

56 57
But when use thread pool, thread is cached up and used repeatedly. Transmitting value from parent thread to child thread has no meaning.
Application need transmit value from the time task is created to the time task is executed.
oldratlee's avatar
oldratlee 已提交
58

oldratlee's avatar
oldratlee 已提交
59
If you have problem or question, please [submit Issue](https://github.com/alibaba/transmittable-thread-local/issues) or play [fork](https://github.com/alibaba/transmittable-thread-local/fork) and pull request dance.
oldratlee's avatar
oldratlee 已提交
60

oldratlee's avatar
oldratlee 已提交
61
# 🎨 Requirements
oldratlee's avatar
oldratlee 已提交
62

63
The Requirements listed below is also why I sort out `TransmittableThreadLocal` in my work.
oldratlee's avatar
oldratlee 已提交
64

oldratlee's avatar
oldratlee 已提交
65 66
- Application container or high layer framework transmit information to low layer sdk.
- Transmit context to logging without application code aware.
oldratlee's avatar
oldratlee 已提交
67

oldratlee's avatar
oldratlee 已提交
68
# 👥 User Guide
oldratlee's avatar
oldratlee 已提交
69

oldratlee's avatar
oldratlee 已提交
70
## 1. Simple usage
oldratlee's avatar
oldratlee 已提交
71 72

```java
oldratlee's avatar
oldratlee 已提交
73 74 75 76
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

oldratlee's avatar
oldratlee 已提交
77
// set in parent thread
oldratlee's avatar
oldratlee 已提交
78
context.set("value-set-in-parent");
oldratlee's avatar
oldratlee 已提交
79 80 81 82

// =====================================================

// read in child thread, value is "value-set-in-parent"
oldratlee's avatar
oldratlee 已提交
83
String value = context.get();
oldratlee's avatar
oldratlee 已提交
84 85
```

86 87
\# See the executable demo [`SimpleDemo.kt`](src/test/java/com/alibaba/demo/ttl/SimpleDemo.kt) with full source code.

oldratlee's avatar
oldratlee 已提交
88
This is the function of class [`InheritableThreadLocal`](https://docs.oracle.com/javase/10/docs/api/java/lang/InheritableThreadLocal.html), should use class [`InheritableThreadLocal`](https://docs.oracle.com/javase/10/docs/api/java/lang/InheritableThreadLocal.html) instead.
oldratlee's avatar
oldratlee 已提交
89

90 91
But when use thread pool, thread is cached up and used repeatedly. Transmitting value from parent thread to child thread has no meaning.
Application need transmit value from the time task is created to the point task is executed.
oldratlee's avatar
oldratlee 已提交
92 93 94

The solution is below usage.

oldratlee's avatar
oldratlee 已提交
95
## 2. Transmit value even using thread pool
oldratlee's avatar
oldratlee 已提交
96 97 98

### 2.1 Decorate `Runnable` and `Callable`

oldratlee's avatar
oldratlee 已提交
99 100
Decorate input `Runnable` and `Callable` by [`TtlRunnable`](/src/main/java/com/alibaba/ttl/TtlRunnable.java)
and [`TtlCallable`](src/main/java/com/alibaba/ttl/TtlCallable.java).
oldratlee's avatar
oldratlee 已提交
101 102 103 104

Sample code:

```java
oldratlee's avatar
oldratlee 已提交
105 106 107 108 109
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<>();

// =====================================================

// set in parent thread
oldratlee's avatar
oldratlee 已提交
110 111
parent.set("value-set-in-parent");

112
Runnable task = new RunnableTask();
113
// extra work, create decorated ttlRunnable object
oldratlee's avatar
oldratlee 已提交
114
Runnable ttlRunnable = TtlRunnable.get(task);
115
executorService.submit(ttlRunnable);
oldratlee's avatar
oldratlee 已提交
116 117 118 119

// =====================================================

// read in task, value is "value-set-in-parent"
oldratlee's avatar
oldratlee 已提交
120
String value = parent.get();
oldratlee's avatar
oldratlee 已提交
121 122 123 124 125
```

above code show how to dealing with `Runnable`, `Callable` is similar:

```java
oldratlee's avatar
oldratlee 已提交
126 127 128 129 130
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<>();

// =====================================================

// set in parent thread
oldratlee's avatar
oldratlee 已提交
131 132
parent.set("value-set-in-parent");

133
Callable call = new CallableTask();
134
// extra work, create decorated ttlCallable object
oldratlee's avatar
oldratlee 已提交
135
Callable ttlCallable = TtlCallable.get(call);
136
executorService.submit(ttlCallable);
oldratlee's avatar
oldratlee 已提交
137 138 139 140

// =====================================================

// read in call, value is "value-set-in-parent"
oldratlee's avatar
oldratlee 已提交
141
String value = parent.get();
oldratlee's avatar
oldratlee 已提交
142 143
```

144 145
\# See the executable demo [`TtlWrapperDemo.kt`](src/test/java/com/alibaba/demo/ttl/TtlWrapperDemo.kt) with full source code.

oldratlee's avatar
oldratlee 已提交
146 147 148 149 150
### 2.2 Decorate thread pool

Eliminating the work of `Runnable` and `Callable` Decoration every time it is submitted to thread pool. This work can completed in the thread pool.

Use util class
151
[`com.alibaba.ttl.threadpool.TtlExecutors`](src/main/java/com/alibaba/ttl/threadpool/TtlExecutors.java)
oldratlee's avatar
oldratlee 已提交
152 153
to decorate thread pool.

154
Util class `com.alibaba.ttl.threadpool.TtlExecutors` has below methods:
oldratlee's avatar
oldratlee 已提交
155

oldratlee's avatar
oldratlee 已提交
156 157 158
- `getTtlExecutor`: decorate interface `Executor`
- `getTtlExecutorService`: decorate interface `ExecutorService`
- `getTtlScheduledExecutorService`: decorate interface `ScheduledExecutorService`
oldratlee's avatar
oldratlee 已提交
159 160 161 162 163 164

Sample code:

```java
ExecutorService executorService = ...
// extra work, create decorated executorService object
oldratlee's avatar
oldratlee 已提交
165
executorService = TtlExecutors.getTtlExecutorService(executorService);
oldratlee's avatar
oldratlee 已提交
166

oldratlee's avatar
oldratlee 已提交
167 168 169 170 171
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<>();

// =====================================================

// set in parent thread
oldratlee's avatar
oldratlee 已提交
172 173
parent.set("value-set-in-parent");

174 175
Runnable task = new RunnableTask();
Callable call = new CallableTask();
oldratlee's avatar
oldratlee 已提交
176 177 178 179 180 181
executorService.submit(task);
executorService.submit(call);

// =====================================================

// read in Task or Callable, value is "value-set-in-parent"
oldratlee's avatar
oldratlee 已提交
182
String value = parent.get();
oldratlee's avatar
oldratlee 已提交
183 184
```

185 186
\# See the executable demo [`TtlExecutorWrapperDemo.kt`](src/test/java/com/alibaba/demo/ttl/TtlExecutorWrapperDemo.kt) with full source code.

187
### 2.3 Use Java Agent to decorate thread pool implementation class
oldratlee's avatar
oldratlee 已提交
188

189
In this usage, transmission is transparent\(no decoration operation\).
oldratlee's avatar
oldratlee 已提交
190 191 192 193

Sample code:

```java
194
// ## 1. upper layer logic of framework ##
oldratlee's avatar
oldratlee 已提交
195
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
196 197 198
context.set("value-set-in-parent");

// ## 2. biz logic ##
oldratlee's avatar
oldratlee 已提交
199 200
ExecutorService executorService = Executors.newFixedThreadPool(3);

201 202
Runnable task = new RunnableTask();
Callable call = new CallableTask();
oldratlee's avatar
oldratlee 已提交
203 204 205
executorService.submit(task);
executorService.submit(call);

206
// ## 3. underlayer logic of framework ##
oldratlee's avatar
oldratlee 已提交
207
// read in Task or Callable, value is "value-set-in-parent"
208
String value = context.get();
oldratlee's avatar
oldratlee 已提交
209 210
```

211
\# See the executable demo [`AgentDemo.kt`](src/test/java/com/alibaba/demo/ttl/agent/AgentDemo.kt) with full source code, run demo by the script [`scripts/run-agent-demo.sh`](scripts/run-agent-demo.sh).
oldratlee's avatar
oldratlee 已提交
212

oldratlee's avatar
oldratlee 已提交
213 214 215 216 217 218 219 220 221 222 223 224 225 226
At present, `TTL` agent has decorated below `JDK` execution components(aka. thread pool) implementation:

- `java.util.concurrent.ThreadPoolExecutor` and `java.util.concurrent.ScheduledThreadPoolExecutor`
    - decoration implementation code is in [`TtlExecutorTransformlet.java`](src/main/java/com/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlExecutorTransformlet.java).
- `java.util.concurrent.ForkJoinTask`(corresponding execution component is `java.util.concurrent.ForkJoinPool`
    - decoration implementation code is in [`TtlForkJoinTransformlet.java`](src/main/java/com/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlForkJoinTransformlet.java), supports since version **_`2.5.1`_**.
    - **_NOTE_**: [**_`CompletableFuture`_**](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html) and (parallel) [**_`Stream`_**](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/package-summary.html) introduced in Java 8 is executed through `ForkJoinPool` underneath, so after supporting `ForkJoinPool`, `TTL` also supports `CompletableFuture` and `Stream` transparently. 🎉
- `java.util.TimerTask`(corresponding execution component is `java.util.Timer`
    - decoration implementation code is in [`TtlTimerTaskTransformlet.java`](src/main/java/com/alibaba/ttl/threadpool/agent/internal/transformlet/impl/TtlTimerTaskTransformlet.java), supports since version **_`2.7.0`_**.
    - **_NOTE_**: Since version `2.11.2` decoration for `TimerTask` default is enable (because correctness is first concern, not the best practice like "It is not recommended to use `TimerTask`" :); before version `2.11.1` default is disable.
    - enabled/disable by agent argument `ttl.agent.enable.timer.task`:
        - `-javaagent:path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.enable.timer.task:true`
        - `-javaagent:path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.enable.timer.task:false`
    - more info about `TTL` agent arguments, see [the javadoc of `TtlAgent.java`](src/main/java/com/alibaba/ttl/threadpool/agent/TtlAgent.java).
oldratlee's avatar
oldratlee 已提交
227

oldratlee's avatar
oldratlee 已提交
228
Add start options on Java command:
oldratlee's avatar
oldratlee 已提交
229

oldratlee's avatar
oldratlee 已提交
230
- `-javaagent:path/to/transmittable-thread-local-2.x.x.jar`
oldratlee's avatar
oldratlee 已提交
231

oldratlee's avatar
oldratlee 已提交
232
**NOTE**
oldratlee's avatar
oldratlee 已提交
233

oldratlee's avatar
oldratlee 已提交
234
- Because TTL agent modified the `JDK` std lib classes, make code refer from std lib class to the TTL classes, so the TTL Agent jar must be added to `boot classpath`.
oldratlee's avatar
oldratlee 已提交
235
- Since `v2.6.0`, TTL agent jar will auto add self to `boot classpath`. But you **should _NOT_** modify the downloaded TTL jar file name in the maven repo(eg: `transmittable-thread-local-2.x.x.jar`).
236 237
    - if you modified the downloaded TTL jar file name(eg: `ttl-foo-name-changed.jar`),
        you must add TTL agent jar to `boot classpath` manually by java option `-Xbootclasspath/a:path/to/ttl-foo-name-changed.jar`.
oldratlee's avatar
oldratlee 已提交
238

oldratlee's avatar
oldratlee 已提交
239
The implementation of auto adding self agent jar to `boot classpath` use the `Boot-Class-Path` property of manifest file(`META-INF/MANIFEST.MF`) in the TTL Java Agent Jar:
oldratlee's avatar
oldratlee 已提交
240

oldratlee's avatar
oldratlee 已提交
241 242
> `Boot-Class-Path`
>
oldratlee's avatar
oldratlee 已提交
243 244 245 246 247
> A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms).
> These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed.

More info:

oldratlee's avatar
oldratlee 已提交
248
- [`Java Agent Specification` - `JavaDoc`文档](https://docs.oracle.com/javase/10/docs/api/java/lang/instrument/package-summary.html#package.description)
oldratlee's avatar
oldratlee 已提交
249
- [JAR File Specification - JAR Manifest](https://docs.oracle.com/javase/10/docs/specs/jar/jar.html#jar-manifest)
oldratlee's avatar
oldratlee 已提交
250
- [Working with Manifest Files - The Java™ TutorialsHide](https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html)
oldratlee's avatar
oldratlee 已提交
251 252 253 254

Java command example:

```bash
oldratlee's avatar
oldratlee 已提交
255 256
java -javaagent:transmittable-thread-local-2.x.x.jar \
    -cp classes \
257
    com.alibaba.demo.ttl.agent.AgentDemo
oldratlee's avatar
oldratlee 已提交
258 259 260 261 262
```

or

```bash
oldratlee's avatar
oldratlee 已提交
263 264
# if changed the TTL jar file name or the TTL version is before 2.6.0,
# should set argument -Xbootclasspath explicitly.
oldratlee's avatar
oldratlee 已提交
265 266
java -javaagent:path/to/ttl-foo-name-changed.jar \
    -Xbootclasspath/a:path/to/ttl-foo-name-changed.jar \
oldratlee's avatar
oldratlee 已提交
267
    -cp classes \
268
    com.alibaba.demo.ttl.agent.AgentDemo
oldratlee's avatar
oldratlee 已提交
269 270
```

oldratlee's avatar
oldratlee 已提交
271
Run the script [`scripts/run-agent-demo.sh`](scripts/run-agent-demo.sh)
oldratlee's avatar
oldratlee 已提交
272 273
to start demo of "Use Java Agent to decorate thread pool implementation class".

oldratlee's avatar
oldratlee 已提交
274
# 🔌 Java API Docs
275

oldratlee's avatar
oldratlee 已提交
276
The current version Java API documentation: <https://alibaba.github.io/transmittable-thread-local/apidocs/>
277

oldratlee's avatar
oldratlee 已提交
278
# 🍪 Maven Dependency
oldratlee's avatar
oldratlee 已提交
279 280 281

```xml
<dependency>
oldratlee's avatar
oldratlee 已提交
282 283
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
oldratlee's avatar
oldratlee 已提交
284
    <version>2.11.5</version>
oldratlee's avatar
oldratlee 已提交
285 286 287
</dependency>
```

288
Check available version at [search.maven.org](https://search.maven.org/search?q=g:com.alibaba%20AND%20a:transmittable-thread-local&core=gav).
oldratlee's avatar
oldratlee 已提交
289

290 291 292 293
# 🔨 About compilation, build and dev

## How to compile and build

oldratlee's avatar
oldratlee 已提交
294
Compilation/build environment require **_`JDK 8~11`_**; Compilation can be performed in the normal way of `Maven`.
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

\# The project already contains `Maven` that satisfied the required version, directly run **_`mvnw` in the project root directory_**; there is no need to manually install `Maven` by yourself.

```bash
# Run test case
./mvnw test
# Compile and package
./mvnw package
# Run test case, compile and package, install TTL library to local Maven
./mvnw install

##################################################
# If you use `Maven` installed by yourself, the version requirement: maven 3.3.9+

mvn install
```

## How to development by `IDE`

If you use `IDE` to develop (such as `IntelliJ IDEA`), note that:
open **_the `pom4ide.xml` file in the root directory of the project_** instead of `pom.xml` via `IDE`;
To avoid `IDE` complain using `JDK 8` standard library classes not found.

The reason that `IDE` support is not good / have to change a `POM` file, is:  
The code implementation of `TTL` uses the `JDK 8` standard library class, but it is compiled into a `Java 6` version class files.

oldratlee's avatar
oldratlee 已提交
321
# 🗿 More Documentation
oldratlee's avatar
oldratlee 已提交
322

oldratlee's avatar
oldratlee 已提交
323
- [🎓 Developer Guide](docs/developer-guide-en.md)
oldratlee's avatar
oldratlee 已提交
324

oldratlee's avatar
oldratlee 已提交
325
# 📚 Related Resources
oldratlee's avatar
oldratlee 已提交
326

oldratlee's avatar
oldratlee 已提交
327
## JDK Core Classes
oldratlee's avatar
oldratlee 已提交
328

oldratlee's avatar
oldratlee 已提交
329 330
- [WeakHashMap](https://docs.oracle.com/javase/10/docs/api/java/util/WeakHashMap.html)
- [InheritableThreadLocal](https://docs.oracle.com/javase/10/docs/api/java/lang/InheritableThreadLocal.html)
oldratlee's avatar
oldratlee 已提交
331

oldratlee's avatar
oldratlee 已提交
332 333 334 335
# 👷 Contributors

- Jerry Lee \<oldratlee at gmail dot com> [@oldratlee](https://github.com/oldratlee)
- Yang Fang \<snoop.fy at gmail dot com> [@driventokill](https://github.com/driventokill)
Z
Zava 已提交
336
- Zava Xu \<zava.kid at gmail dot com> [@zavakid](https://github.com/zavakid)
oldratlee's avatar
oldratlee 已提交
337
- wuwen \<wuwen.55 at aliyun dot com> [@wuwen5](https://github.com/wuwen5)
oldratlee's avatar
oldratlee 已提交
338
- Xiaowei Shi \<179969622 at qq dot com>  [@xwshiustc](https://github.com/xwshiustc)
oldratlee's avatar
oldratlee 已提交
339
- David Dai \<351450944 at qq dot com> [@LNAmp](https://github.com/LNAmp)
oldratlee's avatar
oldratlee 已提交
340
- Your name here :-)