乐趣区

关于arthas:聊聊arthas的springbootstarter

本文次要钻研一下 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。

退出移动版