系列
- Arthas入门篇
- Arthas功能介绍
- Arthas 启动过程分析
- Arthas使用Idea调试
- Arthas Command处理流程
- Arthas类查找和反编译原理
- Arthas内存动态编译原理
- Arthas动态重新加载类
- Arthas导出加载类
arthas组成模块
arthas-boot.jar和as.sh模块功能类似,分别使用java和shell脚本,下载对应的jar包,并生成服务端和客户端的启动命令启动客户端和服务端。
arthas-core.jar是服务端程序的启动入口类,会调用virtualMachine#attach到目标进程,并加载arthas-agent.jar作为agent jar包。
arthas-agent.jar既可以使用premain方式(进程启动之前),也可以通过agentmain方式(进程启动之后)。arthas-agent会使用自定义的classloader(ArthasClassLoader)加载arthas-core.jar里面的Configure类和ArthasBootstrap类。 同时程序运行的时候会使用arthas-spy.jar。
arthas-spy.jar里面只包含Spy类,目的是为了将Spy类使用BootstrapClassLoader来加载,从而使目标进程的java应用可以访问Spy类。通过ASM修改字节码,可以将Spy类的方法ON_BEFORE_METHOD、ON_RETURN_METHOD等编织到目标类里面。Spy类你可以简单理解为类似spring aop的Advice,有前置方法,后置方法等。
arthas-client.jar是客户端程序,用来连接arthas-core.jar启动的服务端代码,使用telnet方式。一般由arthas-boot.jar和as.sh来负责启动。
arthas启动过程
arthas下载
- arthas-boot或者as.sh在启动过程中会去maven仓库下载arthas-packaging的包,这个包里面包含了arthas相关的所有jar包。
https://maven.aliyun.com/repository/public/com/taobao/arthas/arthas-packaging/3.2.0/arthas-packaging-3.2.0-bin.zip
[INFO] Start download arthas from remote server: https://maven.aliyun.com/repository/public/com/taobao/arthas/arthas-packaging/3.2.0/arthas-packaging-3.2.0-bin.zip
[INFO] File size: 10.82 MB, downloaded size: 1.08 MB, downloading ...
[INFO] File size: 10.82 MB, downloaded size: 2.18 MB, downloading ...
[INFO] File size: 10.82 MB, downloaded size: 3.20 MB, downloading ...
[INFO] File size: 10.82 MB, downloaded size: 4.21 MB, downloading ...
[INFO] File size: 10.82 MB, downloaded size: 5.29 MB, downloading ...
[INFO] File size: 10.82 MB, downloaded size: 6.40 MB, downloading ...
[INFO] File size: 10.82 MB, downloaded size: 7.54 MB, downloading ...
[INFO] File size: 10.82 MB, downloaded size: 8.66 MB, downloading ...
[INFO] File size: 10.82 MB, downloaded size: 9.78 MB, downloading ...
解压目录
/Users/lebron374/.arthas/lib/3.2.0/arthas
目录结构
xiaozhideMacBook-Pro:arthas-packaging-3.2.0-bin lebron374$ tree -L 3
.
├── arthas-agent.jar
├── arthas-boot.jar
├── arthas-client.jar
├── arthas-core.jar
├── arthas-demo.jar
├── arthas-spy.jar
├── arthas.properties
├── as-service.bat
├── as.bat
├── as.sh
├── async-profiler
│ ├── libasyncProfiler-linux-arm.so
│ ├── libasyncProfiler-linux-x64.so
│ └── libasyncProfiler-mac-x64.so
├── install-local.sh
└── logback.xml
- arthas-boot下载过程的日志和arthas-packaging解压后的jar包文件如上所示。
arthas启动
package com.taobao.arthas.boot;
public class ProcessUtils {
public static void startArthasCore(long targetPid, List<String> attachArgs) {
String javaHome = findJavaHome();
File javaPath = findJava();
File toolsJar = findToolsJar();
List<String> command = new ArrayList<String>();
command.add(javaPath.getAbsolutePath());
if (toolsJar != null && toolsJar.exists()) {
command.add("-Xbootclasspath/a:" + toolsJar.getAbsolutePath());
}
command.addAll(attachArgs);
// "${JAVA_HOME}"/bin/java \
// ${opts} \
// -jar "${arthas_lib_dir}/arthas-core.jar" \
// -pid ${TARGET_PID} \
// -target-ip ${TARGET_IP} \
// -telnet-port ${TELNET_PORT} \
// -http-port ${HTTP_PORT} \
// -core "${arthas_lib_dir}/arthas-core.jar" \
// -agent "${arthas_lib_dir}/arthas-agent.jar"
ProcessBuilder pb = new ProcessBuilder(command);
try {
final Process proc = pb.start();
// 省略相关代码
} catch (Throwable e) {
// ignore
}
}
}
- arthas-boot的启动命令如上图所示,核心需要了解的是启动arthas-core的命令参数以及通过ProcessBuilder来完成启动。
- startArthasCore图中已经列出所有相关的启动参数,可以重点关注-jar后面的那个参数,指定的就是arthas-core.jar文件,负责启动arthas-core模块。
arthas-core
pom.xml指定main入口函数
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.taobao.arthas.core.Arthas</mainClass>
<manifestEntries>
<Created-By>core engine team, middleware group, alibaba inc.</Created-By>
<Implementation-Version>${project.version}</Implementation-Version>
<Implementation-Vendor-Id>com.taobao.arthas</Implementation-Vendor-Id>
<Specification-Version>${project.version}</Specification-Version>
<Specification-Title>arthas-core</Specification-Title>
</manifestEntries>
</transformer>
</transformers>
- arthas-core的pom.xml指定mainClass=com.taobao.arthas.core.Arthas,进入arthas-core的启动入口函数。
package com.taobao.arthas.core;
/**
* Arthas启动器
*/
public class Arthas {
private static final String DEFAULT_TELNET_PORT = "3658";
private static final String DEFAULT_HTTP_PORT = "8563";
public static void main(String[] args) {
try {
new Arthas(args);
} catch (Throwable t) {
}
}
private Arthas(String[] args) throws Exception {
// parse(args)解析参数并通过attachAgent启动
attachAgent(parse(args));
}
private void attachAgent(Configure configure) throws Exception {
VirtualMachineDescriptor virtualMachineDescriptor = null;
for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) {
String pid = descriptor.id();
if (pid.equals(Long.toString(configure.getJavaPid()))) {
virtualMachineDescriptor = descriptor;
}
}
// 1.绑定pid生成VirtualMachine
VirtualMachine virtualMachine = null;
try {
if (null == virtualMachineDescriptor) { // 使用 attach(String pid) 这种方式
virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());
} else {
virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);
}
// 省略相关其他代码
String arthasAgentPath = configure.getArthasAgent();
//convert jar path to unicode string
configure.setArthasAgent(encodeArg(arthasAgentPath));
configure.setArthasCore(encodeArg(configure.getArthasCore()));
// 2.VirtualMachine绑定agent对象
virtualMachine.loadAgent(arthasAgentPath,
configure.getArthasCore() + ";" + configure.toString());
} finally {
if (null != virtualMachine) {
virtualMachine.detach();
}
}
}
}
- arthas的启动过程中包含3个步骤:解析参数、绑定指定pid、attach指定agent。
- parse(args)负责解析参数。
- virtualMachine = VirtualMachine.attach("" + configure.getJavaPid())负责绑定指定pid生成VirtualMachine对象。
- virtualMachine.loadAgent()用于指定的virtualMachine加载agent对象。
- VirtualMachine的几个核心API的介绍,具体参考文章末尾引用的文章。
arthas-agent
<build>
<finalName>arthas-agent</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<encoding>UTF-8</encoding>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Premain-Class>com.taobao.arthas.agent3.AgentBootstrap</Premain-Class>
<Agent-Class>com.taobao.arthas.agent3.AgentBootstrap</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Specification-Title>${project.name}</Specification-Title>
<Specification-Version>${project.version}</Specification-Version>
<Implementation-Title>${project.name}</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
- arthas-agent的pom.xml指定Premain-Class和Agent-Class,对应AgentBootstrap类。
- Premain-Class在类文件加载且main方法执行之前修改,无法实现真正的运行时修改。
- Agent-Class使用agent attach api附到待更新的jvm上,然后动态加载agent,agent与premain里的几乎相同,只不过这里是在jvm已经运行起来以后加载。
package com.taobao.arthas.agent3;
public class AgentBootstrap {
private static final String RESET = "resetArthasClassLoader";
private static final String ARTHAS_SPY_JAR = "arthas-spy.jar";
private static final String ARTHAS_CORE_JAR = "arthas-core.jar";
private static final String ARTHAS_BOOTSTRAP = "com.taobao.arthas.core.server.ArthasBootstrap";
private static final String GET_INSTANCE = "getInstance";
private static final String IS_BIND = "isBind";
private static final String BIND = "bind";
public static void premain(String args, Instrumentation inst) {
main(args, inst);
}
public static void agentmain(String args, Instrumentation inst) {
main(args, inst);
}
private static synchronized void main(String args, final Instrumentation inst) {
try {
ps.println("Arthas server agent start...");
// 传递的args参数分两个部分:arthasCoreJar路径和agentArgs, 分别是Agent的JAR包路径和期望传递到服务端的参数
args = decodeArg(args);
String arthasCoreJar;
final String agentArgs;
int index = args.indexOf(';');
if (index != -1) {
arthasCoreJar = args.substring(0, index);
agentArgs = args.substring(index);
} else {
arthasCoreJar = "";
agentArgs = args;
}
File arthasCoreJarFile = new File(arthasCoreJar);
File spyJarFile = new File(arthasCoreJarFile.getParentFile(), ARTHAS_SPY_JAR);
// 1、获取ClassLoader
final ClassLoader agentLoader = getClassLoader(inst, spyJarFile, arthasCoreJarFile);
// 2、初始化initSpy()
initSpy();
Thread bindingThread = new Thread() {
@Override
public void run() {
try {
// 3、执行bind动作
bind(inst, agentLoader, agentArgs);
} catch (Throwable throwable) {
throwable.printStackTrace(ps);
}
}
};
bindingThread.setName("arthas-binding-thread");
bindingThread.start();
bindingThread.join();
} catch (Throwable t) {
}
}
private static ClassLoader getClassLoader(Instrumentation inst, File spyJarFile, File arthasCoreJarFile) throws Throwable {
ClassLoader parent = ClassLoader.getSystemClassLoader().getParent();
Class<?> spyClass = null;
if (parent != null) {
try {
parent.loadClass("java.arthas.Spy");
} catch (Throwable e) {
// ignore
}
}
// 将Spy添加到BootstrapClassLoader
if (spyClass == null) {
inst.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));
}
// 构造自定义的类加载器,尽量减少Arthas对现有工程的侵蚀
return loadOrDefineClassLoader(arthasCoreJarFile);
}
// 通过反射实现bind过程的调用
private static void bind(Instrumentation inst, ClassLoader agentLoader, String args) throws Throwable {
Class<?> bootstrapClass = agentLoader.loadClass(ARTHAS_BOOTSTRAP);
// 执行com.taobao.arthas.core.server.ArthasBootstrap#getInstance
Object bootstrap = bootstrapClass.getMethod(GET_INSTANCE, Instrumentation.class, String.class).invoke(null, inst, args);
boolean isBind = (Boolean) bootstrapClass.getMethod(IS_BIND).invoke(bootstrap);
if (!isBind) {
try {
ps.println("Arthas start to bind...");
// 执行com.taobao.arthas.core.server.ArthasBootstrap#bind
bootstrapClass.getMethod(BIND, String.class).invoke(bootstrap, args);
ps.println("Arthas server bind success.");
return;
} catch (Exception e) {
}
}
ps.println("Arthas server already bind.");
}
}
- AgentBootstrap的premain和agentmain内部统一执行main(args, inst)。
- AgentBootstrap#getClassLoader负责构造自定义的类加载器ArthasClassloader,尽量减少Arthas对现有工程的侵蚀。
- AgentBootstrap#bind通过反射执行com.taobao.arthas.core.server.ArthasBootstrap#getInstance和
com.taobao.arthas.core.server.ArthasBootstrap#bind。
arthas-core
public class ArthasBootstrap {
public static final String ARTHAS_HOME_PROPERTY = "arthas.home";
private static String ARTHAS_SHOME = null;
public static final String CONFIG_NAME_PROPERTY = "arthas.config.name";
public static final String CONFIG_LOCATION_PROPERTY = "arthas.config.location";
public static final String CONFIG_OVERRIDE_ALL= "arthas.config.overrideAll";
private static ArthasBootstrap arthasBootstrap;
private ArthasEnvironment arthasEnvironment;
private Configure configure;
private AtomicBoolean isBindRef = new AtomicBoolean(false);
private Instrumentation instrumentation;
private Thread shutdown;
private ShellServer shellServer;
private ExecutorService executorService;
private TunnelClient tunnelClient;
private File arthasOutputDir;
private static LoggerContext loggerContext;
private ArthasBootstrap(Instrumentation instrumentation, String args) throws Throwable {
this.instrumentation = instrumentation;
String outputPath = System.getProperty("arthas.output.dir", "arthas-output");
arthasOutputDir = new File(outputPath);
arthasOutputDir.mkdirs();
// 1. initSpy()
initSpy();
// 2. ArthasEnvironment
initArthasEnvironment(args);
// 3. init logger
loggerContext = LogUtil.initLooger(arthasEnvironment);
// 4. start agent server
bind(configure);
executorService = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
final Thread t = new Thread(r, "as-command-execute-daemon");
t.setDaemon(true);
return t;
}
});
shutdown = new Thread("as-shutdown-hooker") {
@Override
public void run() {
ArthasBootstrap.this.destroy();
}
};
Runtime.getRuntime().addShutdownHook(shutdown);
}
private static void initSpy() throws ClassNotFoundException, NoSuchMethodException {
Class<?> adviceWeaverClass = AdviceWeaver.class;
Method onBefore = adviceWeaverClass.getMethod(AdviceWeaver.ON_BEFORE, int.class, ClassLoader.class, String.class,
String.class, String.class, Object.class, Object[].class);
Method onReturn = adviceWeaverClass.getMethod(AdviceWeaver.ON_RETURN, Object.class);
Method onThrows = adviceWeaverClass.getMethod(AdviceWeaver.ON_THROWS, Throwable.class);
Method beforeInvoke = adviceWeaverClass.getMethod(AdviceWeaver.BEFORE_INVOKE, int.class, String.class, String.class, String.class, int.class);
Method afterInvoke = adviceWeaverClass.getMethod(AdviceWeaver.AFTER_INVOKE, int.class, String.class, String.class, String.class, int.class);
Method throwInvoke = adviceWeaverClass.getMethod(AdviceWeaver.THROW_INVOKE, int.class, String.class, String.class, String.class, int.class);
Spy.init(AdviceWeaver.class.getClassLoader(), onBefore, onReturn, onThrows, beforeInvoke, afterInvoke, throwInvoke);
}
private void initArthasEnvironment(String args) throws IOException {
if (arthasEnvironment == null) {
arthasEnvironment = new ArthasEnvironment();
}
Map<String, String> argsMap = FeatureCodec.DEFAULT_COMMANDLINE_CODEC.toMap(args);
// 给配置全加上前缀
Map<String, Object> mapWithPrefix = new HashMap<String, Object>(argsMap.size());
for (Entry<String, String> entry : argsMap.entrySet()) {
mapWithPrefix.put("arthas." + entry.getKey(), entry.getValue());
}
mapWithPrefix.put(ARTHAS_HOME_PROPERTY, arthasHome());
MapPropertySource mapPropertySource = new MapPropertySource("args", mapWithPrefix);
arthasEnvironment.addFirst(mapPropertySource);
tryToLoadArthasProperties();
configure = new Configure();
BinderUtils.inject(arthasEnvironment, configure);
}
public synchronized static ArthasBootstrap getInstance(Instrumentation instrumentation, String args) throws Throwable {
if (arthasBootstrap == null) {
arthasBootstrap = new ArthasBootstrap(instrumentation, args);
}
return arthasBootstrap;
}
}
- ArthasBootstrap#getInstance会创建ArthasBootstrap对象。
- ArthasBootstrap对象的创建过程中会执行initSpy、initArthasEnvironment、bind三个方法,这三个方法的作用暂时还没理解,待后面理解了再进行补充。
- ArthasBootstrap#bind(configure)负责启动Server端的监听。
package com.taobao.arthas.core.server;
public class ArthasBootstrap {
public void bind(Configure configure) throws Throwable {
long start = System.currentTimeMillis();
try {
ShellServerOptions options = new ShellServerOptions()
.setInstrumentation(instrumentation)
.setPid(PidUtils.currentLongPid())
.setSessionTimeout(configure.getSessionTimeout() * 1000);
if (agentId != null) {
Map<String, String> welcomeInfos = new HashMap<String, String>();
welcomeInfos.put("id", agentId);
options.setWelcomeMessage(ArthasBanner.welcome(welcomeInfos));
}
// 1、创建ShellServerImpl对象
shellServer = new ShellServerImpl(options, this);
// 2、绑定命令解析
BuiltinCommandPack builtinCommands = new BuiltinCommandPack();
List<CommandResolver> resolvers = new ArrayList<CommandResolver>();
resolvers.add(builtinCommands);
// 3、设置HttpTelnetTermServer
if (configure.getTelnetPort() > 0) {
shellServer.registerTermServer(new HttpTelnetTermServer(configure.getIp(), configure.getTelnetPort(),
options.getConnectionTimeout()));
}
// 4、设置HttpTermServer
if (configure.getHttpPort() > 0) {
shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(),
options.getConnectionTimeout()));
}
// 5、绑定命令解析resolver
for (CommandResolver resolver : resolvers) {
shellServer.registerCommandResolver(resolver);
}
// 6、启动监听listen
shellServer.listen(new BindHandler(isBindRef));
UserStatUtil.setStatUrl(configure.getStatUrl());
UserStatUtil.arthasStart();
} catch (Throwable e) {
}
}
}
<br>
- 创建ShellServerImpl对象,new ShellServerImpl(options, this)。
- 绑定命令解析, resolvers.add(builtinCommands)。
- 设置HttpTelnetTermServer, shellServer.registerTermServer(new HttpTelnetTermServer())。
- 设置HttpTermServer,shellServer.registerTermServer(new HttpTermServer())。
- 绑定命令解析resolver,shellServer.registerCommandResolver(resolver)。
- 启动监听listen,shellServer.listen()。
public class BuiltinCommandPack implements CommandResolver {
private static List<Command> commands = new ArrayList<Command>();
static {
initCommands();
}
private static void initCommands() {
commands.add(Command.create(HelpCommand.class));
commands.add(Command.create(KeymapCommand.class));
commands.add(Command.create(SearchClassCommand.class));
commands.add(Command.create(SearchMethodCommand.class));
commands.add(Command.create(ClassLoaderCommand.class));
commands.add(Command.create(JadCommand.class));
commands.add(Command.create(GetStaticCommand.class));
commands.add(Command.create(MonitorCommand.class));
commands.add(Command.create(StackCommand.class));
commands.add(Command.create(ThreadCommand.class));
commands.add(Command.create(TraceCommand.class));
commands.add(Command.create(WatchCommand.class));
commands.add(Command.create(TimeTunnelCommand.class));
commands.add(Command.create(JvmCommand.class));
commands.add(Command.create(PerfCounterCommand.class));
// commands.add(Command.create(GroovyScriptCommand.class));
commands.add(Command.create(OgnlCommand.class));
commands.add(Command.create(MemoryCompilerCommand.class));
commands.add(Command.create(RedefineCommand.class));
commands.add(Command.create(DashboardCommand.class));
commands.add(Command.create(DumpClassCommand.class));
commands.add(Command.create(HeapDumpCommand.class));
commands.add(Command.create(JulyCommand.class));
commands.add(Command.create(ThanksCommand.class));
commands.add(Command.create(OptionsCommand.class));
commands.add(Command.create(ClsCommand.class));
commands.add(Command.create(ResetCommand.class));
commands.add(Command.create(VersionCommand.class));
commands.add(Command.create(SessionCommand.class));
commands.add(Command.create(SystemPropertyCommand.class));
commands.add(Command.create(SystemEnvCommand.class));
commands.add(Command.create(VMOptionCommand.class));
commands.add(Command.create(LoggerCommand.class));
commands.add(Command.create(HistoryCommand.class));
commands.add(Command.create(CatCommand.class));
commands.add(Command.create(EchoCommand.class));
commands.add(Command.create(PwdCommand.class));
commands.add(Command.create(MBeanCommand.class));
commands.add(Command.create(GrepCommand.class));
commands.add(Command.create(TeeCommand.class));
commands.add(Command.create(ProfilerCommand.class));
commands.add(Command.create(ShutdownCommand.class));
commands.add(Command.create(StopCommand.class));
}
}
class BuiltinCommandResolver implements CommandResolver {
private Handler<CommandProcess> handler;
public BuiltinCommandResolver() {
this.handler = new NoOpHandler();
}
@Override
public List<Command> commands() {
return Arrays.asList(CommandBuilder.command("exit").processHandler(handler).build(),
CommandBuilder.command("quit").processHandler(handler).build(),
CommandBuilder.command("jobs").processHandler(handler).build(),
CommandBuilder.command("fg").processHandler(handler).build(),
CommandBuilder.command("bg").processHandler(handler).build(),
CommandBuilder.command("kill").processHandler(handler).build(),
CommandBuilder.command(PlainTextHandler.NAME).processHandler(handler).build(),
CommandBuilder.command(GrepHandler.NAME).processHandler(handler).build(),
CommandBuilder.command(WordCountHandler.NAME).processHandler(handler).build());
}
}
- 内部的命令解析对象BuiltinCommandResolver。
- 自定义的命令解析对象BuiltinCommandPack。
- 每个Command对应一个命令的实现方式。
监听过程分析
package com.taobao.arthas.core.shell.impl;
public class ShellServerImpl extends ShellServer {
private final CopyOnWriteArrayList<CommandResolver> resolvers;
private final InternalCommandManager commandManager;
private final List<TermServer> termServers;
private final long timeoutMillis;
private final long reaperInterval;
private String welcomeMessage;
private ArthasBootstrap bootstrap;
private Instrumentation instrumentation;
private long pid;
private boolean closed = true;
private final Map<String, ShellImpl> sessions;
private final Future<Void> sessionsClosed = Future.future();
private ScheduledExecutorService scheduledExecutorService;
private JobControllerImpl jobController = new GlobalJobControllerImpl();
@Override
public synchronized ShellServer registerCommandResolver(CommandResolver resolver) {
resolvers.add(0, resolver);
return this;
}
@Override
public synchronized ShellServer registerTermServer(TermServer termServer) {
termServers.add(termServer);
return this;
}
@Override
public ShellServer listen(final Handler<Future<Void>> listenHandler) {
// 1、获取需要启动的TermServer对象
final List<TermServer> toStart;
synchronized (this) {
if (!closed) {
throw new IllegalStateException("Server listening");
}
toStart = termServers;
}
Handler<Future<TermServer>> handler = new TermServerListenHandler(this, listenHandler, toStart);
for (TermServer termServer : toStart) {
// TermServerTermHandler的参数this=ShellServerImpl
termServer.termHandler(new TermServerTermHandler(this));
termServer.listen(handler);
}
return this;
}
}
- ShellServerImpl的bind针对所有的termServers执行listen操作。
- 以HttpTermServer为例描述termServer的listen过程。
public class HttpTermServer extends TermServer {
private Handler<Term> termHandler;
private NettyWebsocketTtyBootstrap bootstrap;
private String hostIp;
private int port;
private long connectionTimeout;
@Override
public TermServer termHandler(Handler<Term> handler) {
this.termHandler = handler;
return this;
}
@Override
public TermServer listen(Handler<Future<TermServer>> listenHandler) {
// listenHandler 是 TermServerListenHandler
bootstrap = new NettyWebsocketTtyBootstrap().setHost(hostIp).setPort(port);
try {
bootstrap.start(new Consumer<TtyConnection>() {
@Override
public void accept(final TtyConnection conn) {
// termHandler为TermServerTermHandler
termHandler.handle(new TermImpl(Helper.loadKeymap(), conn));
}
}).get(connectionTimeout, TimeUnit.MILLISECONDS);
listenHandler.handle(Future.<TermServer>succeededFuture());
} catch (Throwable t) {
}
return this;
}
}
- 执行NettyWebsocketTtyBootstrap的start进行监听。
- termHandler.handle()执行的是TermServerTermHandler对象,相当于调用TermServerTermHandler#hanndle方法。
public class TermServerTermHandler implements Handler<Term> {
private ShellServerImpl shellServer;
public TermServerTermHandler(ShellServerImpl shellServer) {
this.shellServer = shellServer;
}
@Override
public void handle(Term term) {
// 执行ShellServerImpl的handleTerm方法
shellServer.handleTerm(term);
}
}
- TermServerTermHandler#handle会执行ShellServerImpl#handleTerm方法。
public class ShellServerImpl extends ShellServer {
private final CopyOnWriteArrayList<CommandResolver> resolvers;
private final InternalCommandManager commandManager;
private final List<TermServer> termServers;
private final long timeoutMillis;
private final long reaperInterval;
private String welcomeMessage;
private ArthasBootstrap bootstrap;
private Instrumentation instrumentation;
private long pid;
private boolean closed = true;
private final Map<String, ShellImpl> sessions;
private final Future<Void> sessionsClosed = Future.future();
private ScheduledExecutorService scheduledExecutorService;
private JobControllerImpl jobController = new GlobalJobControllerImpl();
public void handleTerm(Term term) {
ShellImpl session = createShell(term);
session.setWelcome(welcomeMessage);
session.closedFuture.setHandler(new SessionClosedHandler(this, session));
session.init();
sessions.put(session.id, session); // Put after init so the close handler on the connection is set
// 执行ShellImpl的readline方法
session.readline();
}
@Override
public synchronized ShellImpl createShell(Term term) {
// 核心的commandManager对象
return new ShellImpl(this, term, commandManager, instrumentation, pid, jobController);
}
}
- ShellServerImpl#handleTerm内部执行createShell创建ShellImpl对象。
- 通过ShellImpl#readline解析命令。
public class ShellImpl implements Shell {
private JobControllerImpl jobController;
final String id;
final Future<Void> closedFuture;
private InternalCommandManager commandManager;
private Session session = new SessionImpl();
private Term term;
private String welcome;
private Job currentForegroundJob;
private String prompt;
public ShellImpl(ShellServer server, Term term, InternalCommandManager commandManager,
Instrumentation instrumentation, long pid, JobControllerImpl jobController) {
session.put(Session.COMMAND_MANAGER, commandManager);
session.put(Session.INSTRUMENTATION, instrumentation);
session.put(Session.PID, pid);
session.put(Session.SERVER, server);
session.put(Session.TTY, term);
this.id = UUID.randomUUID().toString();
session.put(Session.ID, id);
this.commandManager = commandManager;
this.closedFuture = Future.future();
this.term = term;
this.jobController = jobController;
if (term != null) {
term.setSession(session);
}
this.setPrompt();
}
public void readline() {
term.readline(prompt, new ShellLineHandler(this),
new CommandManagerCompletionHandler(commandManager));
}
}
- ShellImpl#readline执行的TermImpl#readline。
- TermImpl#readline的第二个参数是ShellLineHandler,ShellLineHandler()参数是ShellImpl。
public class TermImpl implements Term {
public TermImpl(Keymap keymap, TtyConnection conn) {
this.conn = conn;
readline = new Readline(keymap);
readline.setHistory(FileUtils.loadCommandHistory(new File(Constants.CMD_HISTORY_FILE)));
for (Function function : readlineFunctions) {
readline.addFunction(function);
}
echoHandler = new DefaultTermStdinHandler(this);
conn.setStdinHandler(echoHandler);
conn.setEventHandler(new EventHandler(this));
}
public void readline(String prompt, Handler<String> lineHandler, Handler<Completion> completionHandler) {
inReadline = true;
readline.readline(conn, prompt, new RequestHandler(this, lineHandler), new CompletionHandler(completionHandler, session));
}
}
- TermImpl#readline执行readline.readline。
- lineHandler指的是ShellLineHandler,执行ShellLineHandler#handle方法。
public class ShellLineHandler implements Handler<String> {
private ShellImpl shell;
private Term term;
public ShellLineHandler(ShellImpl shell) {
this.shell = shell;
this.term = shell.term();
}
@Override
public void handle(String line) {
if (line == null) {
// EOF
handleExit();
return;
}
List<CliToken> tokens = CliTokens.tokenize(line);
CliToken first = TokenUtils.findFirstTextToken(tokens);
if (first == null) {
// For now do like this
shell.readline();
return;
}
String name = first.value();
if (name.equals("exit") || name.equals("logout") || name.equals("q") || name.equals("quit")) {
handleExit();
return;
} else if (name.equals("jobs")) {
handleJobs();
return;
} else if (name.equals("fg")) {
handleForeground(tokens);
return;
} else if (name.equals("bg")) {
handleBackground(tokens);
return;
} else if (name.equals("kill")) {
handleKill(tokens);
return;
}
Job job = createJob(tokens);
if (job != null) {
job.run();
}
}
private Job createJob(List<CliToken> tokens) {
Job job;
try {
job = shell.createJob(tokens);
} catch (Exception e) {
term.echo(e.getMessage() + "\n");
shell.readline();
return null;
}
return job;
}
private void handleJobs() {
for (Job job : shell.jobController().jobs()) {
String statusLine = shell.statusLine(job, job.status());
term.write(statusLine);
}
shell.readline();
}
}
- 核心关注ShellLineHandler#createJob,最终会调用ShellImpl#createJob。
public class ShellImpl implements Shell {
public synchronized Job createJob(List<CliToken> args) {
Job job = jobController.createJob(commandManager, args, this);
return job;
}
}
public class JobControllerImpl implements JobController {
@Override
public Job createJob(InternalCommandManager commandManager, List<CliToken> tokens, ShellImpl shell) {
int jobId = idGenerator.incrementAndGet();
StringBuilder line = new StringBuilder();
for (CliToken arg : tokens) {
line.append(arg.raw());
}
boolean runInBackground = runInBackground(tokens);
Process process = createProcess(tokens, commandManager, jobId, shell.term());
process.setJobId(jobId);
JobImpl job = new JobImpl(jobId, this, process, line.toString(), runInBackground, shell);
jobs.put(jobId, job);
return job;
}
}
- JobControllerImpl#createJob负责根据命令生成对应的任务执行。