(目录)


Agent探针模式下动态刷新源码阅读:

插件分析

①spring-piugin-common包:

image-20231101174524670

EventPublishingRunListenerEnvironmentPreparedInterceptor

EventPublishingRunListenerEnvironmentPreparedInterceptor:事件发布运行监听环境准备的监听器的拦截器

读取配置文件信息

image-20231101175019183

SpringEnvironmentSupport

进入SpringEnvironmentSupport类查看:添加了一个配置信息是否启用在非Agent模式下关闭

image-20231101175129561


SpringPropertiesLoader

SpringPropertiesLoader类:

加载属性源,并将这些属性源中的属性加载到一个Properties对象中。

最后,它将加载的属性集合作为Spring属性设置

image-20231101175455028


SpringThreadPoolRegisterSupport:线程池注册

源码分析:

用于注册线程池实例到ThreadPoolExecutorRegistry中,以便在发生线程池引用错误时进行恢复。

首先遍历referencedClassMap<ThreadPoolExecutor, Class<?>>,获取每个类的静态字段中是否引用了线程池实例,如果引用了则进行注册。

然后遍历context中带有Executor注解的bean,将其转换为ThreadPoolExecutor类型后进行注册。最后打印注册成功的日志信息。

public class SpringThreadPoolRegisterSupport {

    private static final Logger LOGGER = LoggerFactory.getLogger(SpringThreadPoolRegisterSupport.class);
    
     /**
     * 注册线程池实例到线程池注册中心
     * @param context 应用上下文
     */
    public static void registerThreadPoolInstances(ApplicationContext context) {
        Map<ThreadPoolExecutor, Class<?>> referencedClassMap = ThreadPoolExecutorRegistry.REFERENCED_CLASS_MAP;

        // 遍历引用类映射中的每个线程池实例和类
        for (Map.Entry<ThreadPoolExecutor, Class<?>> entry : referencedClassMap.entrySet()) {
            ThreadPoolExecutor enhancedInstance = entry.getKey();
            Class<?> declaredClass = entry.getValue();

            // 获取类中声明的包含线程池实例的字段
            List<Field> declaredFields = ReflectUtil.getStaticFieldsFromType(declaredClass, ThreadPoolExecutor.class);
            for (Field field : declaredFields) {
                try {
                    Object value = field.get(null);
                    // 如果字段的值等于当前线程池实例,则进行注册并退出循环
                    if (value == enhancedInstance) {
                        String threadPoolId = declaredClass.getName() + "#" + field.getName();
                        register(threadPoolId, enhancedInstance);
                        break;
                    }
                } catch (IllegalAccessException e) {
                    LOGGER.error("获取静态字段错误。", e);
                }
            }
        }

        // 遍历应用上下文中所有实现了Executor注解的 bean
        Map<String, Executor> beansWithAnnotation = context.getBeansOfType(Executor.class);
        for (Map.Entry<String, Executor> entry : beansWithAnnotation.entrySet()) {
            String beanName = entry.getKey();
            Executor bean = entry.getValue();

            // 根据bean类型进行处理
            ThreadPoolExecutor executor;
            if (DynamicThreadPoolAdapterChoose.match(bean)) {
                executor = DynamicThreadPoolAdapterChoose.unwrap(bean);
            } else {
                executor = (ThreadPoolExecutor) bean;
            }

            // 如果executor为空,则输出警告信息并忽略该bean的注册
            if (executor == null) {
                LOGGER.warn("[Hippo4j-Agent] 线程池为空,忽略bean注册。beanName={}, beanClass={}", beanName, bean.getClass().getName());
            } else {
                // 注册线程池
                register(beanName, executor);
            }
        }

        // 输出注册成功日志
        LOGGER.info("[Hippo4j-Agent] 线程池实例注册成功。");
    }

    
     /**
     * 注册线程池到线程池注册器
     * 
     * @param threadPoolId 线程池ID
     * @param executor 线程池执行器
     */
	public static void register(String threadPoolId, ThreadPoolExecutor executor) {
    // 如果传入的executor为空,则直接返回
    if (executor == null) {
        return;
    }
    
    // 构建线程池属性对象
    ExecutorProperties executorProperties = ExecutorProperties.builder()
            .threadPoolId(threadPoolId) // 线程池ID
            .corePoolSize(executor.getCorePoolSize()) // 核心线程数
            .maximumPoolSize(executor.getMaximumPoolSize()) // 最大线程数
            .allowCoreThreadTimeOut(BooleanUtil.toBoolean(String.valueOf(executor.allowsCoreThreadTimeOut()))) // 核心线程是否允许超时
            .blockingQueue(BlockingQueueTypeEnum.getBlockingQueueTypeEnumByName(executor.getQueue().getClass().getSimpleName()).getName()) // 阻塞队列类型
            .queueCapacity(executor.getQueue().remainingCapacity()) // 队列剩余容量
            .rejectedHandler(RejectedPolicyTypeEnum.getRejectedPolicyTypeEnumByName(executor.getRejectedExecutionHandler().getClass().getSimpleName()).getName()) // 拒绝策略
            .build();
    
    // 将线程池及其属性对象注册到线程池注册器中
    ThreadPoolExecutorRegistry.putHolder(threadPoolId, executor, executorProperties);
}

② spring-boot-2x-plugin包:

接下来分析这个包中的类

image-20231101214055146


插桩点:EventPublishingRunListenerInstrumentation

增强类方法

org.springframework.boot.context.event.EventPublishingRunListener#started()

org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared()

代码分析:

/**
 * Event publishing run listener instrumentation
 */
public class EventPublishingRunListenerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {

    private static final String ENHANCE_CLASS = "org.springframework.boot.context.event.EventPublishingRunListener";

    private static final String EVENT_PUBLISHING_FINISHED_INTERCEPTOR = "cn.hippo4j.agent.plugin.spring.boot.v2.interceptor.EventPublishingStartedInterceptor";
    private static final String EVENT_PUBLISHING_ENVIRONMENT_PREPARED_INTERCEPTOR = "cn.hippo4j.agent.plugin.spring.common.interceptor.EventPublishingRunListenerEnvironmentPreparedInterceptor";

    @Override
    protected ClassMatch enhanceClass() {
        return byName(ENHANCE_CLASS);
    }

    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return new ConstructorInterceptPoint[0];
    }

    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[]{
                new InstanceMethodsInterceptPoint() {

                    @Override
                    public ElementMatcher<MethodDescription> getMethodsMatcher() {
                        return named("started");
                    }

                    @Override
                    public String getMethodsInterceptor() {
                        return EVENT_PUBLISHING_FINISHED_INTERCEPTOR;
                    }

                    @Override
                    public boolean isOverrideArgs() {
                        return false;
                    }
                },
                new InstanceMethodsInterceptPoint() {

                    @Override
                    public ElementMatcher<MethodDescription> getMethodsMatcher() {
                        return named("environmentPrepared");
                    }

                    @Override
                    public String getMethodsInterceptor() {
                        return EVENT_PUBLISHING_ENVIRONMENT_PREPARED_INTERCEPTOR;
                    }

                    @Override
                    public boolean isOverrideArgs() {
                        return false;
                    }
                }
        };
    }

    @Override
    protected List<WitnessMethod> witnessMethods() {
        return Collections.singletonList(new WitnessMethod("org.springframework.boot.context.event.EventPublishingRunListener",
                named("started")));
    }
}

这段代码有两个拦截点:

  1. PREPARED_INTERCEPTOR 上面spring-plugin-common包中说过了,就是一个Agent模式的开关

  2. FINISHED_INTERCEPTOR 拦截的是 org.springframework.boot.context.event.EventPublishingRunListener#start()方法

    这个方法具体来说是,当应用程序启动时,会调用EventPublishingRunListener#started()方法。触发ApplicationStartingEvent事件,该事件将传递给所有注册的监听器。 通过这种方式,EventPublishingRunListener可以在应用程序启动时执行一些特定的操作,例如记录日志、发送通知等。


拦截器:EventPublishingStartedInterceptor

代码分析:

  1. 它获取传递给方法的第一个参数,并将其转换为ConfigurableApplicationContext对象。

  2. 它检查该上下文是否有父上下文,如果有,则进行调用**registerThreadPoolInstances(context)**线程池注册

  3. 加载Spring配置属性、创建线程池刷新处理器、注册动态线程池刷新监听器。最后,它返回方法的返回值。

@Slf4j
public class EventPublishingStartedInterceptor implements InstanceMethodsAroundInterceptor {

    private static final ILog LOGGER = LogManager.getLogger(EventPublishingStartedInterceptor.class);

    @Override
    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {

    }
	
    @Override
    public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {
        // 获取上下文应用对象
        ConfigurableApplicationContext context = (ConfigurableApplicationContext) allArguments[0];
        if (context.getParent() != null) {
            // 容器已启动,则执行线程池注册
            SpringThreadPoolRegisterSupport.registerThreadPoolInstances(context);
            return ret;
        }
        // 加载Spring属性文件
        SpringPropertiesLoader.loadSpringProperties(context.getEnvironment());
        // 注册动态线程池变化处理器
        ThreadPoolDynamicRefresh dynamicRefresh = new DynamicThreadPoolChangeHandlerSpring2x();
        dynamicRefresh.registerListener();
        // 注册抽象主题中心的线程池动态刷新监听器
        AbstractSubjectCenter.register(AbstractSubjectCenter.SubjectType.THREAD_POOL_DYNAMIC_REFRESH,
                new DynamicThreadPoolRefreshListener());

        return ret;
    }

    @Override
    public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {

    }
}

动态刷新处理器:DynamicThreadPoolChangeHandlerSpring2x

代码分析:

用于处理动态线程池的变化。

它继承了一个抽象类AbstractConfigThreadPoolDynamicRefresh,重写了其中的registerListener方法。

registerListener()方法首先从Apollo获取线程池的配置文件,然后注册一个配置文件的监听器回调。

当配置文件发生改变时,会获取新的配置值,并调用dynamicRefresh()方法进行线程池的动态刷新。

还实现了buildBootstrapProperties()方法,用于构建启动配置属性。

/**
 * Dynamic thread pool change handler spring 2x
 */
public class DynamicThreadPoolChangeHandlerSpring2x extends AbstractConfigThreadPoolDynamicRefresh {

    private static ILog LOGGER = LogManager.getLogger(DynamicThreadPoolChangeHandlerSpring2x.class);

/**
 * 注册监听器方法
 */
@Override
public void registerListener() {
    // 获取 Apollo 命名空间列表
    List<String> apolloNamespaces = SpringBootConfig.Spring.Dynamic.Thread_Pool.Apollo.NAMESPACE;
    // 获取命名空间
    String namespace = apolloNamespaces.get(0);
    // 获取配置文件类型
    String configFileType = SpringBootConfig.Spring.Dynamic.Thread_Pool.CONFIG_FILE_TYPE;
    // 获取配置
    Config config = ConfigService.getConfig(String.format("%s.%s", namespace, configFileType));
    // 创建配置变更监听器
    ConfigChangeListener configChangeListener = configChangeEvent -> {
        // 替换命名空间和配置文件类型
        String replacedNamespace = namespace.replaceAll("." + configFileType, "");
        // 根据配置文件类型获取配置文件格式
        ConfigFileFormat configFileFormat = ConfigFileFormat.fromString(configFileType);
        // 获取配置文件
        ConfigFile configFile = ConfigService.getConfigFile(replacedNamespace, configFileFormat);
        // 创建新的变更值映射
        Map<String, Object> newChangeValueMap = new HashMap<>();
        // 遍历配置变更事件的每个变更的 key
        configChangeEvent.changedKeys().stream().filter(each -> each.contains(SPRING_BOOT_CONFIG_PREFIX)).forEach(each -> {
            // 获取配置变更
            ConfigChange change = configChangeEvent.getChange(each);
            // 获取新的值
            String newValue = change.getNewValue();
            // 将新的变更的 key 和值添加到新的变更值映射中
            newChangeValueMap.put(each, newValue);
        });
        // 动态刷新线程池
        dynamicRefresh(configFileType, configFile.getContent(), newChangeValueMap);
    };
    // 添加配置变更监听器
    config.addChangeListener(configChangeListener);
    // 打印日志,表示成功添加 Apollo 监听器
	LOGGER.info("[Hippo4j-Agent] Dynamic thread pool refresher, add apollo listener success. namespace: {}", namespace);
}

    @Override
    public BootstrapConfigProperties buildBootstrapProperties(Map<Object, Object> configInfo) {
        // 创建一个 BootstrapConfigProperties 对象
        BootstrapConfigProperties bindableBootstrapConfigProperties = new BootstrapConfigProperties();
        // 创建一个 MapConfigurationPropertySource 对象,用于读取配置信息
        ConfigurationPropertySource sources = new MapConfigurationPropertySource(configInfo);
        // 创建一个 Binder 对象,用于将配置信息绑定到 BootstrapConfigProperties 对象上
        Binder binder = new Binder(sources);
        // 通过 Binder 对象将配置信息绑定到 BootstrapConfigProperties 对象上,并返回绑定后的对象
        return binder.bind(BootstrapConfigProperties.PREFIX, Bindable.ofInstance(bindableBootstrapConfigProperties)).get();
    }

}

image-20231101231628307

进入到 dynamicRefresh()这个方法中:源码分析

**AbstractSubjectCenter.notify()**这个方法就是发送配置更新事件给对应主题的监听者

/**
 * 动态刷新方法
 * @param configFileType 配置文件类型
 * @param configContent 配置文件内容
 * @param newValueChangeMap 新的配置值变更映射
 */
@Override
public void dynamicRefresh(String configFileType, String configContent, Map<String, Object> newValueChangeMap) {
    try {
        // 解析配置文件
        Map<Object, Object> configInfo = ConfigParserHandler.getInstance().parseConfig(configContent, configFileType);
        
        // 如果有新的配置值变更映射,则将其合并到configInfo中
        if (CollectionUtil.isNotEmpty(newValueChangeMap)) {
            Optional.ofNullable(configInfo).ifPresent(each -> each.putAll(newValueChangeMap));
        }
        
        // 构建启动配置属性
        BootstrapPropertiesInterface bootstrapProperties = buildBootstrapProperties(configInfo);
        
        // 通知SubjectCenter进行线程池动态刷新
      AbstractSubjectCenter.notify(AbstractSubjectCenter.SubjectType.THREAD_POOL_DYNAMIC_REFRESH, () -> bootstrapProperties);
    } catch (Exception ex) {
        log.error("Hippo4j config mode dynamic refresh failed.", ex);
    }
}

③ threadpool-pluin包:

image-20231102185419389

※ 线程池插件分析

插桩点:ThreadPoolExecutorInstrumentation

从这个图中不难看出,需要对线程池7个参数的构造方法进行增强

image-20231102185709108

之后看看拦截器的实现


拦截器:ThreadPoolExecutorConstructorMethodInterceptor

方法有点多逐个分析

image-20231102190410209

onConstruct方法:

  1. 获取当前调用函数的堆栈跟踪元素列表,如果列表不为空,则获取第一个堆栈跟踪元素的类名ClassName,并使用当前线程的上下文类加载器加载该类
  2. 然后,将ThreadPoolExecutor对象和对应的Class对象存入注册表中,以便在后续的操作中使用。
  3. ThreadPoolExecutorRegistry.REFERENCED_CLASS_MAP.put((ThreadPoolExecutor) objInst, declaredClass);
    @Override
    public void onConstruct(EnhancedInstance objInst, Object[] allArguments) throws Throwable {
        // 获取当前方法的调用链路信息
        List<StackTraceElement> stackTraceElements = getStackTraceElements();
        // 如果调用链路为空,则直接返回
        if (CollectionUtil.isEmpty(stackTraceElements)) {
            return;
        }
        // 获取调用该方法的类的StackTracElement元素
        StackTraceElement declaredClassStackTraceElement = stackTraceElements.get(0);
        // 获取调用该方法的类的名称
        String declaredClassName = declaredClassStackTraceElement.getClassName();
        // 根据类名加载对应的Class对象
        Class<?> declaredClass = Thread.currentThread().getContextClassLoader().loadClass(declaredClassName);
        // 将ThreadPoolExecutor对象和对应的Class对象存入注册表中
        ThreadPoolExecutorRegistry.REFERENCED_CLASS_MAP.put((ThreadPoolExecutor) objInst, declaredClass);
    }

**下面三个函数大概就是:**getStackTraceElements、isBusinessStackTraceClassName、isExcludeThreadPoolClass

获取函数调用栈帧,获取当前线程的堆栈跟踪信息,并根据给定的规则过滤掉特定类的堆栈信息,返回过滤后的堆栈跟踪元素列表。

首先获取当前线程的堆栈跟踪信息,然后通过遍历类名判断是否为业务堆栈类,如果是,则继续判断是否为需要排除的类,如果是则返回一个空的列表,否则跳出循环。

接着从堆栈跟踪信息中取出类名,判断是否需要排除该类,如果是则跳出循环,否则将该堆栈跟踪元素添加到结果列表中。最后返回结果列表。

    /**
     * 获取调用栈信息
     *
     * @return 调用栈元素的列表
     */
    private List<StackTraceElement> getStackTraceElements() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        int i;
        for (i = 0; i < stackTraceElements.length; i++) {
            String fullClassName = stackTraceElements[i].getClassName();
            if (isBusinessStackTraceClassName(fullClassName)) {
                if (isExcludeThreadPoolClass(fullClassName)) {
                    return Collections.emptyList();
                } else {
                    break;
                }
            }
        }

        List<StackTraceElement> result = new ArrayList<>(INITIAL_CAPACITY); // 查找三层以内

        for (int j = 0; i < stackTraceElements.length && j < INITIAL_CAPACITY; i++, j++) {
            String fullClassName = stackTraceElements[i].getClassName();
            if (isExcludeThreadPoolClass(fullClassName)) {
                break;
            } else {
                result.add(stackTraceElements[i]);
            }
        }

        return result;
    }

    /**
     * 判断类名是否是业务调用栈的类名
     *
     * @param className 类名
     * @return 如果是业务调用栈的类名返回true,否则返回false
     */
    private boolean isBusinessStackTraceClassName(String className) {
        for (String prefix : EXCLUDE_STACK_TRACE_ELEMENT_CLASS_PREFIX) {
            if (className.startsWith(prefix)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断类名是否需要被排除在线程池之外
     *
     * @param className 类名
     * @return 如果需要被排除在線程池之外返回true,否则返回false
     */
    private boolean isExcludeThreadPoolClass(String className) {
        if (StringUtil.isBlank(className)) {
            return true;
        }
        List<String> excludePackagePrefix = Config.Plugin.ThreadPool.EXCLUDE_PACKAGE_PREFIX;
        for (String excludePrefix : excludePackagePrefix) {
            if (className.startsWith(excludePrefix)) {
                return true;
            }
        }
        return false;
    }


要将配置文件中的属性绑定到一个已经存在的JavaBean对象上,你可以使用SpringBinder类的bind方法,并传递一个现有的对象作为目标,这个过程通常涉及以下步骤:

  1. 准备你的配置环境和现有的JavaBean对象。
  2. 创建Binder实例。
  3. 使用withExistingValue方法来指定你想绑定的已存在的JavaBean对象。

假设你已经有一个ExecutorProperties的实例列表existingExecutors,以下是如何将属性绑定到这个列表上的示例代码:

import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.StandardEnvironment;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// ...

StandardEnvironment environment = new StandardEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();

Map<String, Object> propertiesMap = new HashMap<>();
propertiesMap.put("spring.dynamic.thread-pool.executors[0].firstProperty", "value1");
propertiesMap.put("spring.dynamic.thread-pool.executors[0].secondProperty", "value2");
propertiesMap.put("spring.dynamic.thread-pool.executors[1].firstProperty", "value3");
propertiesMap.put("spring.dynamic.thread-pool.executors[1].secondProperty", "value4");

PropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new MapPropertySource("customProperties", propertiesMap));

Binder binder = new Binder(propertySources);

// 假设你有一个现有的ExecutorProperties列表
List<ExecutorProperties> existingExecutors = Arrays.asList(new ExecutorProperties(), new ExecutorProperties());

// 进行绑定
for (int i = 0; i < existingExecutors.size(); i++) {
    binder.bind("spring.dynamic.thread-pool.executors[" + i + "]", Bindable.ofInstance(existingExecutors.get(i)));
}

在上述代码中,我们首先创建了一个包含配置属性的Map并将其添加到PropertySources中。然后创建一个Binder实例,并使用循环对现有的JavaBean列表的每个对象进行绑定。每次循环,我们使用bind方法的ofInstance来指定绑定目标为列表中的现有对象。


如何使用Spring的 binder.bind()绑定配置javabean

在Spring框架中使用Binder类来绑定properties文件中的配置到Java Bean时,你需要遵循以下步骤:

  1. 创建配置类:首先,你需要有一个Java类(例如ExecutorProperties),该类应包含所有与配置文件中的键相对应的属性和相应的getter和setter方法。

  2. 使用Binder进行绑定:然后,你可以使用Spring的Binder类来绑定配置。以下是一个基本的示例代码:

    import org.springframework.boot.context.properties.bind.Binder;
    import org.springframework.core.env.StandardEnvironment;
    
    // ...
    
    StandardEnvironment environment = new StandardEnvironment();
    Binder binder = Binder.get(environment);
    
    // 假设你有一个ExecutorProperties类,其有firstProperty和secondProperty字段
    List<ExecutorProperties> executors = binder.bind("spring.dynamic.thread-pool.executors",
                                                      Bindable.listOf(ExecutorProperties.class))
                                               .orElse(new ArrayList<>());
    

    这段代码中,binder.bind()方法接收两个参数:配置的前缀和一个Bindable类型,用于指定绑定到的目标类型。orElse是在没有找到配置时返回一个默认值。

  3. 确保配置环境正确Binder需要一个Environment,这通常来自你的Spring应用上下文。在这个例子中,我使用了StandardEnvironment,但在实际应用中,你应该使用应用上下文中的Environment

  4. 处理异常:绑定过程可能会抛出异常(比如类型不匹配),确保你的代码能妥善处理这些情况。

请根据你的实际需要调整上面的代码。如果ExecutorProperties类有复杂的嵌套属性,确保所有相关的类都正确定义了对应的属性和getter/setter。


configInfo是刷新之后的配置信息

image-20231102224406795