聊聊arthas的spring-boot-starter
序
本文主要研究一下arthas的spring-boot-starter
ArthasConfiguration
arthas-spring-boot-starter/src/main/java/com/alibaba/arthas/spring/ArthasConfiguration.java
@ConditionalOnProperty(name = "spring.arthas.enabled", matchIfMissing = true)
@EnableConfigurationProperties({ ArthasProperties.class })
public class ArthasConfiguration {
private static final Logger logger = LoggerFactory.getLogger(ArthasConfiguration.class);
@Autowired
ConfigurableEnvironment environment;
/**
* <pre>
* 1. 提取所有以 arthas.* 开头的配置项,再统一转换为Arthas配置
* 2. 避免某些配置在新版本里支持,但在ArthasProperties里没有配置的情况。
* </pre>
*/
@ConfigurationProperties(prefix = "arthas")
@ConditionalOnMissingBean(name="arthasConfigMap")
@Bean
public HashMap<String, String> arthasConfigMap() {
return new HashMap<String, String>();
}
@ConditionalOnMissingBean
@Bean
public ArthasAgent arthasAgent(@Autowired @Qualifier("arthasConfigMap") Map<String, String> arthasConfigMap,
@Autowired ArthasProperties arthasProperties) throws Throwable {
arthasConfigMap = StringUtils.removeDashKey(arthasConfigMap);
ArthasProperties.updateArthasConfigMapDefaultValue(arthasConfigMap);
/**
* @see org.springframework.boot.context.ContextIdApplicationContextInitializer#getApplicationId(ConfigurableEnvironment)
*/
String appName = environment.getProperty("spring.application.name");
if (arthasConfigMap.get("appName") == null && appName != null) {
arthasConfigMap.put("appName", appName);
}
// 给配置全加上前缀
Map<String, String> mapWithPrefix = new HashMap<String, String>(arthasConfigMap.size());
for (Entry<String, String> entry : arthasConfigMap.entrySet()) {
mapWithPrefix.put("arthas." + entry.getKey(), entry.getValue());
}
final ArthasAgent arthasAgent = new ArthasAgent(mapWithPrefix, arthasProperties.getHome(),
arthasProperties.isSlientInit(), null);
arthasAgent.init();
logger.info("Arthas agent start success.");
return arthasAgent;
}
}
ArthasConfiguration注册了arthasConfigMap及arthasAgent两个bean
ArthasAgent
arthas-agent-attach/src/main/java/com/taobao/arthas/agent/attach/ArthasAgent.java
public class ArthasAgent {
private static final int TEMP_DIR_ATTEMPTS = 10000;
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 String errorMessage;
private Map<String, String> configMap = new HashMap<String, String>();
private String arthasHome;
private boolean slientInit;
private Instrumentation instrumentation;
public ArthasAgent() {
this(null, null, false, null);
}
//......
public void init() throws IllegalStateException {
// 尝试判断arthas是否已在运行,如果是的话,直接就退出
try {
Class.forName("java.arthas.SpyAPI"); // 加载不到会抛异常
if (SpyAPI.isInited()) {
return;
}
} catch (Throwable e) {
// ignore
}
try {
if (instrumentation == null) {
instrumentation = ByteBuddyAgent.install();
}
// 检查 arthasHome
if (arthasHome == null || arthasHome.trim().isEmpty()) {
// 解压出 arthasHome
URL coreJarUrl = this.getClass().getClassLoader().getResource("arthas-bin.zip");
if (coreJarUrl != null) {
File tempArthasDir = createTempDir();
ZipUtil.unpack(coreJarUrl.openStream(), tempArthasDir);
arthasHome = tempArthasDir.getAbsolutePath();
} else {
throw new IllegalArgumentException("can not getResources arthas-bin.zip from classloader: "
+ this.getClass().getClassLoader());
}
}
// find arthas-core.jar
File arthasCoreJarFile = new File(arthasHome, ARTHAS_CORE_JAR);
if (!arthasCoreJarFile.exists()) {
throw new IllegalStateException("can not find arthas-core.jar under arthasHome: " + arthasHome);
}
AttachArthasClassloader arthasClassLoader = new AttachArthasClassloader(
new URL[] { arthasCoreJarFile.toURI().toURL() });
/**
* <pre>
* ArthasBootstrap bootstrap = ArthasBootstrap.getInstance(inst);
* </pre>
*/
Class<?> bootstrapClass = arthasClassLoader.loadClass(ARTHAS_BOOTSTRAP);
Object bootstrap = bootstrapClass.getMethod(GET_INSTANCE, Instrumentation.class, Map.class).invoke(null,
instrumentation, configMap);
boolean isBind = (Boolean) bootstrapClass.getMethod(IS_BIND).invoke(bootstrap);
if (!isBind) {
String errorMsg = "Arthas server port binding failed! Please check $HOME/logs/arthas/arthas.log for more details.";
throw new RuntimeException(errorMsg);
}
} catch (Throwable e) {
errorMessage = e.getMessage();
if (!slientInit) {
throw new IllegalStateException(e);
}
}
}
}
ArthasAgent的init方法先尝试加载java.arthas.SpyAPI,若SpyAPI.isInited()为true则直接返回;之后执行ByteBuddyAgent.install();对于arthasHome为null则尝试读取arthas-bin.zip文件,接着创建AttachArthasClassloader,加载com.taobao.arthas.core.server.ArthasBootstrap,执行其getInstance方法,再对实例执行isBind
ArthasEndPointAutoConfiguration
arthas-spring-boot-starter/src/main/java/com/alibaba/arthas/spring/endpoints/ArthasEndPointAutoConfiguration.java
@ConditionalOnProperty(name = "spring.arthas.enabled", matchIfMissing = true)
public class ArthasEndPointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
public ArthasEndPoint arthasEndPoint() {
return new ArthasEndPoint();
}
}
ArthasEndPointAutoConfiguration则创建ArthasEndPoint
ArthasEndPoint
arthas-spring-boot-starter/src/main/java/com/alibaba/arthas/spring/endpoints/ArthasEndPoint.java
@Endpoint(id = "arthas")
public class ArthasEndPoint {
@Autowired(required = false)
private ArthasAgent arthasAgent;
@Autowired(required = false)
private HashMap<String, String> arthasConfigMap;
@ReadOperation
public Map<String, Object> invoke() {
Map<String, Object> result = new HashMap<String, Object>();
if (arthasConfigMap != null) {
result.put("arthasConfigMap", arthasConfigMap);
}
String errorMessage = arthasAgent.getErrorMessage();
if (errorMessage != null) {
result.put("errorMessage", errorMessage);
}
return result;
}
}
ArthasEndPoint提供了一个读方法返回arthasConfigMap
小结
arthas的spring-boot-starter有两个自动配置,分别是ArthasConfiguration及ArthasEndPointAutoConfiguration,其中ArthasConfiguration注册了arthasConfigMap及arthasAgent两个bean,而ArthasEndPointAutoConfiguration则创建ArthasEndPoint。