Skip to content

Latest commit

 

History

History
257 lines (175 loc) · 13 KB

developer-guide.md

File metadata and controls

257 lines (175 loc) · 13 KB

🎓 Developer Guide



📌 框架/中间件集成TTL传递

框架/中间件集成TTL传递,通过TransmittableThreadLocal.Transmitter 抓取当前线程的所有TTL值并在其他线程进行回放;在回放线程执行完业务操作后,恢复为回放线程原来的TTL值。

TransmittableThreadLocal.Transmitter提供了所有TTL值的抓取、回放和恢复方法(即CRR操作):

  1. capture方法:抓取线程(线程A)的所有TTL值。
  2. replay方法:在另一个线程(线程B)中,回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份
  3. restore方法:恢复线程B执行replay方法之前的TTL值(即备份)

示例代码:

// ===========================================================================
// 线程 A
// ===========================================================================

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");

// (1) 抓取当前线程的所有TTL值
final Object captured = TransmittableThreadLocal.Transmitter.capture();

// ===========================================================================
// 线程 B(异步线程)
// ===========================================================================

// (2) 在线程 B中回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份
final Object backup = TransmittableThreadLocal.Transmitter.replay(captured);
try {
    // 你的业务逻辑,这里你可以获取到外面设置的TTL值
    String value = context.get();

    System.out.println("Hello: " + value);
    ...
    String result = "World: " + value;
} finally {
    // (3) 恢复线程 B执行replay方法之前的TTL值(即备份)
    TransmittableThreadLocal.Transmitter.restore(backup);
}

更多TTL传递的代码实现示例,参见 TtlRunnable.javaTtlCallable.java

当然可以使用TransmittableThreadLocal.Transmitter的工具方法runSupplierWithCapturedrunCallableWithCaptured和可爱的Java 8 Lambda语法 来简化replayrestore操作,示例代码:

// ===========================================================================
// 线程 A
// ===========================================================================

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");

// (1) 抓取当前线程的所有TTL值
final Object captured = TransmittableThreadLocal.Transmitter.capture();

// ===========================================================================
// 线程 B(异步线程)
// ===========================================================================

String result = runSupplierWithCaptured(captured, () -> {
    // 你的业务逻辑,这里你可以获取到外面设置的TTL值
    String value = context.get();
    System.out.println("Hello: " + value);
    ...
    return "World: " + value;
}); // (2) + (3)

📟 关于Java Agent

Java Agent方式对应用代码无侵入

User Guide - 2.3 使用Java Agent来修饰JDK线程池实现类 说到了,相对修饰Runnable或是线程池的方式,Java Agent方式是对应用代码无侵入的。下面做一些展开说明。

构架图

按框架图,把前面示例代码操作可以分成下面几部分:

  1. 读取信息设置到TTL
    这部分在容器中完成,无需应用参与。
  2. 提交Runnable到线程池。要有修饰操作Runnable(无论是直接修饰Runnable还是修饰线程池)。
    这部分操作一定是在用户应用中触发。
  3. 读取TTL,做业务检查。
    SDK中完成,无需应用参与。

只有第2部分的操作和应用代码相关。

如果不通过Java Agent修饰线程池,则修饰操作需要应用代码来完成。

使用Java Agent方式,应用无需修改代码,即做到 相对应用代码 透明地完成跨线程池的上下文传递。

更多关于应用场景的了解说明参见文档需求场景

已有Java Agent中嵌入TTL Agent

这样可以减少Java启动命令行上的Agent的配置。

在自己的Agent中加上TTL Agent的逻辑,示例代码如下(YourXxxAgent.java):

import com.alibaba.ttl.threadpool.agent.TtlAgent;
import com.alibaba.ttl.threadpool.agent.TtlTransformer;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.logging.Logger;

public final class YourXxxAgent {
    private static final Logger logger = Logger.getLogger(YourXxxAgent.class.getName());

    public static void premain(String agentArgs, Instrumentation inst) {
        TtlAgent.premain(agentArgs, inst); // add TTL Transformer

        // add your Transformer
        ...
    }
}

关于Java AgentClassFileTransformer的如何实现可以参考:TtlAgent.javaTtlTransformer.java

注意,在bootclasspath上,还是要加上TTL Jar

-Xbootclasspath/a:/path/to/transmittable-thread-local-2.x.y.jar:/path/to/your/agent/jar/files

👢 Bootstrap ClassPath上添加通用库Jar的问题及其解决方法

TTL Agent的使用方式,需要将TTL Jar加到Bootstrap ClassPath上(通过Java命令行参数-Xbootclasspath);这样TTL的类与JDK的标准库的类(如java.lang.String)的ClassLoader是一样的,都在Bootstrap ClassPath上。

Bootstrap ClassPath上的类会优先于应用ClassPathJar被加载,并且加载ClassLoader不能被改。
# 当然技术上严格地说,通过Bootstrap ClassPath上的类(如标准库的类)是可以改ClassLoader的,但这样做一般只会带来各种麻烦的问题。关于ClassLoader及其使用注意的介绍说明 可以参见ClassLoader委托关系的完备配置

TTL Agent自己内部实现使用了Javassist,即在Bootstrap ClassPath上也需要添加Javassist。如果应用中也使用了Javassist,由于运行时会优先使用TTL Agent配置Bootstrap ClassPath上的Javassist,应用逻辑运行时实际不能选择/指定应用自己的Javassist的版本,带来了 应用需要的JavassistTTL Agent用的Javassist之间的兼容性风险。

可以通过 repackage依赖(即 重命名/改写 依赖类的包名)来解决这个问题。Maven提供了Shade插件,可以完成下面的操作:

  • repackage Javassist的类文件
  • 添加repackage过的JavassistTTL Jar

这样操作后,TTL Agent不需要依赖外部的Javassist依赖,效果上这样的shade过的TTL Jar是自包含的、在使用上是编译/运行时0依赖的,自然也规避了依赖冲突的问题。

🔨 关于编译构建与IDE开发

如何编译构建

编译构建的环境要求: JDK 8~11;用Maven常规的方式执行编译构建即可:
# 在工程中已经包含了符合版本要求的Maven,直接运行 工程根目录下的mvnw;并不需要先手动自己安装好Maven

# 运行测试Case
./mvnw test
# 编译打包
./mvnw package
# 运行测试Case、编译打包、安装TTL库到Maven本地
./mvnw install

#####################################################
# 如果使用你自己安装的 maven,版本要求:maven 3.3.9+
mvn install

如何用IDE开发

TTL的代码实现使用了JDK 8的标准库类,但编译成Java 6版本的类文件。即

  • 编译Java文件的Java语言版本 是 Java 6
  • 而编译依赖的Java API/标准库(由JVM提供) 需要是 Java 8/JVM 8;高于Java语言版本。

现代的IDE(如IntelliJ IDEA)一般会缺省做 语言版本 与 API版本 的检查:

  • 如果使用了高于语言版本的标准库类,IDE会报错。
  • 以避免 在低语言版本JVM运行时使用高版本API/标准类找不到 的风险。

可以在IDE设置中,关闭这个『语言版本 与 API版本』检查。

IntelliJ IDEA关闭检查的方法

在设置中关闭【Inspections - Usages of API which isn't available at the configured language level】:
1-preferences-setting

当然通过【Find Actions... cmd + shift + A】,可以更方便快速完成设置:
2-action-setting

其它IDE的解决方法

其它IDE(如EclipseNetBeans)可以找一下设置方法,以关闭这个『语言版本 与 API版本』检查。

如果没有找到IDE的设置方法,也可以用下面的方法来 workaround: 😂

打开 工程根目录下的pom4ide.xml文件(修改了Java文件的语言版本),而不是pom.xml

发布操作列表

详见独立文档 发布操作列表

📚 相关资料

JDK core classes

Java Agent

Javassist

Maven Shade插件