1、前序
本人使用過arthas工具,記得剛開始使用最讓驚艷的是能夠實現(xiàn)請求的鏈路追蹤和耗時分析,之前也研究過arthas源碼,但是沒有深入研究它的鏈路追蹤如何實現(xiàn)。skywalking也是公式在使用的一個工具,但是由于時間各種原因,一直沒有去分析原理。列個TODO:skywalking實現(xiàn)原理。
無意看到一個文章:如何基于spring的aop實現(xiàn)熱插拔,看了原理后就思考:這個東西我可以做一個類似arthas的鏈路追蹤能力,需要的時候織入切面,不需要的時候可以移除,也不影響性能。有想法,那就干起來。
2、直接看效果
2.1、未織入切面之前
http://localhost:8080/tracer/run

image.png
平平無奇,通過traceId看是否能夠查到請求過程的信息
http://localhost:8080/tracer/get/fa713bc1bc784e0fb3fc14465de11ba8
{
"code": 200,
"message": "success",
"traceId": "e94f805f9d584a2cb3abf5caac72b71c",
"result": {}
}
發(fā)現(xiàn)result中沒有數據。我們期望result中是本次請求的鏈路過程和耗時統(tǒng)計
2.1、織入切面之后
2.1.1、執(zhí)行織入
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

image.png
通過日志可以看出,有三個類被增強了。
2.1.2、執(zhí)行業(yè)務,查看請求鏈路
再次請求接口
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
}
}
每個方法通過休眠模式業(yè)務耗時
fun sleep 774ms
StudentService sleep 798ms
TeacherService sleep 384ms
CourseService sleep 55ms
可以看到時間基本準確,存在幾毫米的差距原因是,切面還有其他邏輯需要耗時。
3、核心實現(xiàn)
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;
/**
* 鏈路追蹤,耗時統(tǒng)計
*/
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;
}
}
實現(xiàn)請求鏈路追蹤的邏輯都在這里。
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;
}
}
這塊代碼能否實現(xiàn)基于注解或者表達式的切點構建。
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) {
// 循環(huán)每一個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 動態(tài)替換 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 動態(tài)代理,通過反射獲取代理對象的 '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());
}
}
這塊代碼實現(xiàn):遍歷每個bean,對匹配的方法的類,或者指定注解的類進行切面織入,并且如果bean已經是代理對象,也可以增強,bean如果不是代理對象,那就構建代理對象實現(xiàn)增強。如果一個bean被增強了,要掃描所有的bean,找到這個bean被引用的地方,是實現(xiàn)對象替換(否者@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) {
// 上一個節(jié)點的耗時時間減去當前節(jié)點的耗時時間為上一個節(jié)點本身的耗時時間
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 {
// 業(yè)務
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");
}
}