1、前序
本人使用过arthas工具,记得刚开始使用最让惊艳的是能够实现请求的链路追踪和耗时分析,之前也研究过arthas源码,但是没有深入研究它的链路追踪如何实现。skywalking也是公式在使用的一个工具,但是由于时间各种原因,一直没有去分析原理。列个TODO:skywalking实现原理。
无意看到一个文章:如何基于spring的aop实现热插拔,看了原理后就思考:这个东西我可以做一个类似arthas的链路追踪能力,需要的时候织入切面,不需要的时候可以移除,也不影响性能。有想法,那就干起来。
2、直接看效果
2.1、未织入切面之前
http://localhost:8080/tracer/run
平平无奇,通过traceId看是否能够查到请求过程的信息
http://localhost:8080/tracer/get/fa713bc1bc784e0fb3fc14465de11ba8
{
"code": 200,
"message": "success",
"traceId": "e94f805f9d584a2cb3abf5caac72b71c",
"result": {}
}
发现result中没有数据。我们期望result中是本次请求的链路过程和耗时统计
2.1、织入切面之后
2.1.1、执行织入
curl --location 'http://127.0.0.1:8080/advisor/add?interceptorClass=com.tracer.dynamic.inteceptor.TracerInterceptor&annotationClass=com.tracer.dynamic.annotation.TracerAnnotation'
2024-12-29 21:42:43.386 INFO 23752 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
替换成功
新对象的hashcode=589648775
当前被增强的bean的类名=class com.tracer.service.CourseService
新对象的名称=class com.tracer.service.CourseService$$EnhancerBySpringCGLIB$$c6659b67
需要被替换属性的类名称=com.tracer.service.TeacherService
替换成功
新对象的hashcode=29089716
当前被增强的bean的类名=class com.tracer.service.StudentService
新对象的名称=class com.tracer.service.StudentService$$EnhancerBySpringCGLIB$$349b0a75
需要被替换属性的类名称=com.tracer.controller.TracerController
替换成功
新对象的hashcode=212760089
当前被增强的bean的类名=class com.tracer.service.TeacherService
新对象的名称=class com.tracer.service.TeacherService$$EnhancerBySpringCGLIB$$c45eb380
需要被替换属性的类名称=com.tracer.service.StudentService
通过日志可以看出,有三个类被增强了。
2.1.2、执行业务,查看请求链路
再次请求接口
http://localhost:8080/tracer/fun
"code": 200,
"message": "success",
"traceId": "297b7941823342c7b11a46116994a71e",
"result": "student=zansan, teacher=zansan, CourseName=math"
}
通过traceId看是否能够查到请求过程的信息
http://localhost:8080/tracer/get/297b7941823342c7b11a46116994a71e
{
"code": 200,
"message": "success",
"traceId": "a82542072bc646dd994e52746e0d6adb",
"result": {
"com.tracer.controller.TracerController#fun": 783,
"com.tracer.service.StudentService#getStudentName": 801,
"com.tracer.service.TeacherService#getTeacherName": 393,
"com.tracer.service.CourseService#getCourseName": 58
}
}
每个方法通过休眠模式业务耗时
fun sleep 774ms
StudentService sleep 798ms
TeacherService sleep 384ms
CourseService sleep 55ms
可以看到时间基本准确,存在几毫米的差距原因是,切面还有其他逻辑需要耗时。
3、核心实现
3.1、TracerInterceptor
package com.tracer.dynamic.inteceptor;
import com.tracer.tracer.StatisticContext;
import com.tracer.tracer.TraceContext;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Stack;
/**
* 链路追踪,耗时统计
*/
public class TracerInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long beginTs = System.currentTimeMillis();
Map<String, String> map = TraceContext.tradeIdThreadLocal.get();
String tracerId = map.get("tracerId");
Method method = invocation.getMethod();
String methodName = method.getName();
// 如果是toString()方法或者equals方法跳过
if(methodName.equals("toString") || methodName.equals("equals")){
return invocation.proceed();
}
String className = method.getDeclaringClass().getName();
String currentPath = className + "#" + methodName;
Stack<String> path = (Stack<String>) TraceContext.pathThreadLocal.get();
if (path == null) {
path = new Stack<>();
TraceContext.pathThreadLocal.set(path);
}
path.push(currentPath); // 当前请求路径入栈
Object result = invocation.proceed();
TraceContext.TraceNode preNode = (TraceContext.TraceNode) TraceContext.tradeNodeThreadLocal.get();
TraceContext.TraceNode currentNode = new TraceContext.TraceNode(currentPath, System.currentTimeMillis() - beginTs, tracerId);
if (preNode == null) {
currentNode.next = null;
} else {
currentNode.next = preNode;
}
TraceContext.tradeNodeThreadLocal.set(currentNode);
path = (Stack<String>) TraceContext.pathThreadLocal.get();
path.pop(); // 当前请求路径出栈
if (path.isEmpty()) {
TraceContext.TraceNode traceNode = (TraceContext.TraceNode) TraceContext.tradeNodeThreadLocal.get();
StatisticContext.traceNodeHashMap.put(tracerId, traceNode);
TraceContext.tradeNodeThreadLocal.remove();
}
return result;
}
}
实现请求链路追踪的逻辑都在这里。
3.2、AdvisorController
package com.tracer.controller;
import cn.hutool.core.text.CharSequenceUtil;
import com.tracer.dynamic.DynamicProxy;
import com.tracer.dynamic.OperateEventEnum;
import com.tracer.dynamic.dynamicAdvisor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/advisor")
@Slf4j
public class AdvisorController {
@Resource
private DynamicProxy dynamicProxy;
private static final Map<String, dynamicAdvisor> advisorMap = new HashMap<>();
/**
* 基于注解:自定义注解或者spring的注解都可以
* http://127.0.0.1:8080/advisor/add?interceptorClass=com.tracer.dynamic.inteceptor.TracerInterceptor&annotationClass=com.tracer.dynamic.annotation.TracerAnnotation
* http://127.0.0.1:8080/advisor/add?interceptorClass=com.tracer.dynamic.inteceptor.TracerInterceptor&annotationClass=org.springframework.stereotype.Component
*
* 基于表达式:execution(* com.tracer.service.*.*()) // service包下所有类的无参数方法
*
* http://127.0.0.1:8080/advisor/add?interceptorClass=com.tracer.dynamic.inteceptor.TracerInterceptor&expression=execution(* com.tracer.service.*.*())
*
* @return
* @throws Exception
*/
@GetMapping(value = "/add")
public String add(String interceptorClass, String expression, String annotationClass) throws Exception {
if (CharSequenceUtil.isAllBlank(expression, annotationClass) || CharSequenceUtil.isBlank(interceptorClass)) {
return "the parameter is abnormal";
}
if (advisorMap.containsKey(interceptorClass + annotationClass) || advisorMap.containsKey(interceptorClass + expression)) {
return "advisor already exists";
}
MethodInterceptor methodInterceptor = (MethodInterceptor) Class.forName(interceptorClass).getDeclaredConstructor().newInstance();
dynamicAdvisor dynamicAdvisor;
// 以注解为主,有注解就用注解
if (CharSequenceUtil.isNotBlank(annotationClass)) {
Class<? extends Annotation> aClass = (Class<? extends Annotation>) Class.forName(annotationClass);
dynamicAdvisor = new dynamicAdvisor(aClass, methodInterceptor);
advisorMap.put(interceptorClass + annotationClass, dynamicAdvisor);
} else {
dynamicAdvisor = new dynamicAdvisor(expression, methodInterceptor);
advisorMap.put(interceptorClass + expression, dynamicAdvisor);
}
dynamicProxy.operateAdvisor(dynamicAdvisor, OperateEventEnum.ADD);
return "advisor add success";
}
@GetMapping(value = "/delete")
public String delete(String interceptorClass, String expression, String annotationClass) {
if (CharSequenceUtil.isAllBlank(expression, annotationClass) || CharSequenceUtil.isBlank(interceptorClass)) {
throw new IllegalArgumentException("参数异常");
}
if (!advisorMap.containsKey(interceptorClass + annotationClass) && !advisorMap.containsKey(interceptorClass + expression)) {
return "advisor not exists";
}
// 以注解为主,有注解就用注解
StringBuilder advisorKey = new StringBuilder(interceptorClass);
if (CharSequenceUtil.isNotBlank(annotationClass)) {
advisorKey.append(annotationClass);
} else {
advisorKey.append(expression);
}
dynamicAdvisor dynamicAdvisor = advisorMap.get(advisorKey.toString());
dynamicProxy.operateAdvisor(dynamicAdvisor, OperateEventEnum.DELETE);
advisorMap.remove(advisorKey.toString());
return "advisor delete success";
}
}
3.3、DynamicAdvisor
package com.tracer.dynamic;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class DynamicAdvisor extends AbstractPointcutAdvisor {
private final Advice advice; // interceptor
private final Pointcut pointcut;
public dynamicAdvisor(Class<? extends Annotation> annotationClass, MethodInterceptor interceptor) {
this.advice = interceptor;
this.pointcut = buildPointcut(annotationClass);
}
public dynamicAdvisor(String expression, MethodInterceptor interceptor) {
this.advice = interceptor;
this.pointcut = buildPointcut(expression);
}
/**
* 直接复制的@Async构建pointcut的代码
* @param annotationType interface com.tracer.dynamic.annotation.XdxAnnotation
* @return
*/
private Pointcut buildPointcut(Class<? extends Annotation> annotationType) {
Set<Class<? extends Annotation>> annotationTypes = new LinkedHashSet<>(2);
annotationTypes.add(annotationType);
ComposablePointcut result = null;
AnnotationMatchingPointcut mpc;
for(Iterator var3 = annotationTypes.iterator(); var3.hasNext(); result = result.union(mpc)) {
Class<? extends Annotation> asyncAnnotationType = (Class)var3.next();
Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);
mpc = new AnnotationMatchingPointcut(null, asyncAnnotationType, true);
if (result == null) {
result = new ComposablePointcut(cpc);
} else {
result.union(cpc);
}
}
return result != null ? result : Pointcut.TRUE;
}
private Pointcut buildPointcut(String expression) {
AspectJExpressionPointcut tmpPointcut = new AspectJExpressionPointcut();
tmpPointcut.setExpression(expression);
return tmpPointcut;
}
@Override
public Pointcut getPointcut() {
return pointcut;
}
@Override
public Advice getAdvice() {
return advice;
}
}
这块代码能否实现基于注解或者表达式的切点构建。
3.4、DynamicProxy
package com.tracer.dynamic;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.*;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Field;
@Configuration
public class DynamicProxy extends ProxyProcessorSupport implements BeanFactoryAware {
@Autowired
private ConfigurableApplicationContext context;
private static DefaultListableBeanFactory beanFactory;
public void operateAdvisor(DynamicAdvisor advisor, OperateEventEnum operateEventEnum) {
// 循环每一个bean
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
Object bean = beanFactory.getBean(beanDefinitionName);
// 判断当前bean是否匹配:注解或者表达式
if (!isEligible(bean, advisor)) {
continue;
}
// 判断当前bean是不是已经是代理对象了,是就直接进行 Advisor 操作
if (bean instanceof Advised) {
Advised advised = (Advised) bean;
if (operateEventEnum == OperateEventEnum.DELETE) {
advised.removeAdvisor(advisor);
} else if (operateEventEnum == OperateEventEnum.ADD) {
advised.addAdvisor(advisor);
}
setField(bean, advised);
continue;
}
// 生成 Advisor 的代理对象
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(bean);
ClassLoader classLoader = this.getProxyClassLoader();
Object proxy = proxyFactory.getProxy(classLoader);
// 遍历beanFactory所有的单例bean,找到bean的所有属性,如果属性和beanDefinitionName一样,就替换成代理对象
setField(bean, proxy);
// 删除已有的 Bean 定义
// beanFactory.removeBeanDefinition(beanDefinitionName);
// // 使用 BeanDefinitionRegistry 或 ConfigurableListableBeanFactory 动态替换 Bean
// RootBeanDefinition beanDefinition = new RootBeanDefinition(proxy.getClass());
// beanFactory.registerBeanDefinition(beanDefinitionName, beanDefinition);
// 如果已经存在,则先销毁
if (beanFactory.containsSingleton(beanDefinitionName)) {
unregisterSingleton(beanDefinitionName);
}
registerSingleton(beanDefinitionName, proxy, beanDefinitionName);
}
}
private static void setField(Object bean, Object newObject) {
// 遍历beanFactory所有的单例bean,找到bean的所有属性,如果属性和beanDefinitionName一样,就替换成代理对象
// Iterator<String> beanNamesIterator = beanFactory.getDependenciesForBean();
String[] names = beanFactory.getBeanDefinitionNames();
for (String name : names) {
Object b = beanFactory.getBean(name);
if (b == bean) {
continue;
}
// 原始对象
Object target=b;
Field[] fields = null;
// 如果b是代理对象,就获取原始对象的属性
if (AopUtils.isAopProxy(b)) {
try {
// 获取原始对象
target = getTargetObject(b);
fields = target.getClass().getDeclaredFields();
} catch (Exception e) {
e.printStackTrace();
}
} else {
fields = b.getClass().getDeclaredFields();
}
for (Field field : fields) {
if (field.getType() == bean.getClass()) {
try {
field.setAccessible(true);
field.set(target, newObject);
field.setAccessible(false);
System.out.println("替换成功");
System.out.println("新对象的hashcode="+newObject.hashCode());
System.out.println("当前被增强的bean的类名="+bean.getClass());
System.out.println("新对象的名称="+newObject.getClass());
System.out.println("需要被替换属性的类名称="+target.getClass().getName());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
// 获取代理对象的原始对象
private static Object getTargetObject(Object proxy) throws Exception {
if (AopUtils.isJdkDynamicProxy(proxy)) {
// 对于 JDK 动态代理,通过反射获取代理对象的 'h' 属性
try{
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();
return target;
} catch(Exception e){
e.printStackTrace();
}
} else {
// 对于 CGLIB 代理,通过反射获取目标对象
try{
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
return target;
} catch(Exception e){
e.printStackTrace();
}
}
return null;
}
/**
* 注销Bean
*
* @param beanName 名称
*/
public static void unregisterSingleton(String beanName) {
if (beanFactory instanceof DefaultListableBeanFactory) {
// 首先确保销毁该bean的实例(如果该bean实例是一个单例的话)
if (beanFactory.containsSingleton(beanName)) {
beanFactory.destroySingleton(beanName);
}
// 然后从容器的bean定义注册表中移除该bean定义
if (beanFactory.containsBeanDefinition(beanName)) {
beanFactory.removeBeanDefinition(beanName);
}
}
}
public static void registerSingleton(String beanName, Object proxy, String beanDefinitionName) {
if (beanFactory instanceof DefaultListableBeanFactory) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(proxy.getClass());
beanFactory.registerBeanDefinition(beanDefinitionName, beanDefinition);
beanFactory.registerSingleton(beanName, proxy);
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
DynamicProxy.beanFactory = (DefaultListableBeanFactory) beanFactory;
}
/**
* 复制的 @Async 的匹配逻辑
*/
private boolean isEligible(Object bean, Advisor advisor) {
return AopUtils.canApply(advisor, bean.getClass());
}
}
这块代码实现:遍历每个bean,对匹配的方法的类,或者指定注解的类进行切面织入,并且如果bean已经是代理对象,也可以增强,bean如果不是代理对象,那就构建代理对象实现增强。如果一个bean被增强了,要扫描所有的bean,找到这个bean被引用的地方,是实现对象替换(否者@Autowired的属性不会被替换)。
3.5、TraceContext
package com.tracer.tracer;
import java.util.Map;
import java.util.Stack;
public class TraceContext {
public static ThreadLocal tradeNodeThreadLocal = new ThreadLocal<TraceNode>();
public static ThreadLocal<Map<String,String>> tradeIdThreadLocal = new ThreadLocal<>();
// 请求路径
public static ThreadLocal pathThreadLocal = new ThreadLocal<Stack<String>>();
public static class TraceNode {
public TraceNode next;
public String path;
public String traceId;
public long time;
public TraceNode(String path, long time, String traceId) {
this.path = path;
this.time = time;
this.traceId = traceId;
}
}
}
3.6、TracerController
package com.tracer.controller;
import com.tracer.service.StudentService;
import com.tracer.tracer.StatisticContext;
import com.tracer.result.R;
import com.tracer.dynamic.annotation.TracerAnnotation;
import com.tracer.tracer.TraceContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Random;
@RestController
@RequestMapping("/tracer")
@Slf4j
public class TracerController {
/**
* http://localhost:8080/tracer/get/0407ab3dbd8d484690f2079c0ef72aae
*
* @param tracerId
* @return
*/
@RequestMapping("/get/{tracerId}")
public R traceIdTest(@PathVariable("tracerId") String tracerId) {
TraceContext.TraceNode node = (TraceContext.TraceNode) StatisticContext.traceNodeHashMap.get(tracerId);
HashMap<String, Long> path = new LinkedHashMap<>();
// 遍历node,获取所有的className和time
TraceContext.TraceNode preNode = null;
while (node != null) {
path.put(node.path, node.time);
if (preNode != null) {
// 上一个节点的耗时时间减去当前节点的耗时时间为上一个节点本身的耗时时间
path.put(preNode.path, preNode.time - node.time);
}
preNode = node;
node = node.next;
}
return R.restResult(path, 200, "success");
}
@Autowired
private StudentService studentService;
/**
* 用来测试效果的fun
*
* @return
*/
@GetMapping(value = "/fun")
@TracerAnnotation
public R fun() throws InterruptedException {
// 业务
int t = new Random().nextInt(1000);
System.out.println("fun sleep " + t + "ms");
Thread.sleep(t);
// StudentService s = ApplicationContextUtil.getBean(StudentService.class);
String studentName = studentService.getStudentName();
return R.restResult(studentName, 200, "success");
}
}