上次我们在druid-spring-boot-starter里面看到有一个DruidSpringAopConfiguration的配置类,然后引入了DruidStatInterceptor这样一个切面逻辑。今天我们就来看一下这个类的实现。
DruidStatInterceptor
这个类的包路径下入com.alibaba.druid.support.spring.stat。它定义了一个切面,所有符合这个切面的切点表达式都会被拦截执行增强逻辑,这个切点定义可以在配置文件里面设定,通过spring.datasource.druid.aop-patterns
配置即可。
它定义了一个advice,其核心逻辑就是下面的invoke方法:
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
SpringMethodStat lastMethodStat = SpringMethodStat.current();
SpringMethodInfo methodInfo = getMethodInfo(invocation);
SpringMethodStat methodStat = springStat.getMethodStat(methodInfo, true);
if (methodStat != null) {
methodStat.beforeInvoke();
}
long startNanos = System.nanoTime();
Throwable error = null;
try {
return invocation.proceed();
} catch (Throwable e) {
error = e;
throw e;
} finally {
long endNanos = System.nanoTime();
long nanos = endNanos - startNanos;
if (methodStat != null) {
methodStat.afterInvoke(error, nanos);
}
SpringMethodStat.setCurrent(lastMethodStat);
}
}
这个方法里面有几个类,是druid自己定义的,简单解释下。
SpringMethodInfo
spring方法的抽象,记录了spring bean的方法信息,包括签名信息,目标类以及方法的Method对象。
SpringMethodStat
spring方法的状态抽象,记录了很多spring方法监控的指标,比如方法正在执行的次数,方法最大并发执行数,方法执行次数,方法执行时间,jdbc执行次数,更新条数等。下面是部分参数截图,实际还有很多其他的参数,有兴趣的可以自己研究下。
注意这里面的监控其实分两块,常规的监控其实只监视数据库侧,即这里jdbc相关的信息,方法执行其实是不会监控的,而定义了spring.datasource.druid.aop-patterns
还会监控方法的执行相关信息。然后因为web请求可能多个线程调用一个方法,所以用的一个ThreadLocal记录,避免每个线程记录的值互不影响。
invoke方法
我们来具体看下invoke方法,首先拿到当前线程的最后一次方法状态信息,然后拿到对应的方法信息,注意这里的方法默认最多只支持10层代理,如果超过10层代理,也只能拿到第10层的。然后你可以看到这个方法里面其实有两个SpringMethodStat,一个是lastMethodStat,这个好像没什么实际用处,也可能我没太看明白,有清楚的朋友可以评论区说明下;还有一个是methodStat,每次修改值其实是在这个里面。这个methodStat其实是从SpringStat这个静态类的一个concurrentMap里面根据SpringMethodInfo拿到对应的SpringMethodStat,如果你的SpringMethodInfo是同一个那么修改的状态也就是一样的。
如果methodStat有值,就执行前置逻辑:
public void beforeInvoke() {
currentLocal.set(this);
int running = runningCount.incrementAndGet();
for (; ; ) {
int max = concurrentMax.get();
if (running > max) {
if (concurrentMax.compareAndSet(max, running)) {
break;
}
} else {
break;
}
}
executeCount.incrementAndGet();
Profiler.enter(methodInfo.getSignature(), Profiler.PROFILE_TYPE_SPRING);
}
前置逻辑很简单对runningCount,concurrentMax,executeCount这三个值进行更新,将其暂时存到Profiler的ThreadLocal里面。然后执行切点方法逻辑,最后执行后置逻辑:
public void afterInvoke(Throwable error, long nanos) {
runningCount.decrementAndGet();
executeTimeNano.addAndGet(nanos);
histogramRecord(nanos);
if (error != null) {
executeErrorCount.incrementAndGet();
lastError = error;
lastErrorTimeMillis = System.currentTimeMillis();
}
Profiler.release(nanos);
}
后置方法会更新runningCount,executeTimeNano,histogramRecord,lastErrorTimeMillis等值,最后将结果存到Profiler里面的statsMapLocal里面,最后显示的数据会从Profiler里面拿。
这里代码有一个写的比较优雅的地方是,在执行当前逻辑的时候用的return invocation.proceed();
,后置逻辑是在finally块里面写的。充分利用了finally里面的代码一定会执行的特性,将代码写的很简洁,这块值得借鉴。
有的朋友可能会一问,关于jdbc的执行次数修改在哪实现的呢?这里完全没有看到呢?也是在这个类里面,它里面有个内部类SpringMethodContextListener就是实现的这块逻辑,拿到SpringMethodStat,然后执行对应的修改动作即可。
它在bean初始化的时候会被注册到StatFilterContext里面,然后在执行sql的时候会利用观察者模式调用所有监听器直接各自逻辑。
class SpringMethodContextListener extends StatFilterContextListenerAdapter {
@Override
public void addUpdateCount(int updateCount) {
SpringMethodStat springMethodStat = SpringMethodStat.current();
if (springMethodStat != null) {
springMethodStat.addJdbcUpdateCount(updateCount);
}
}
@Override
public void addFetchRowCount(int fetchRowCount) {
SpringMethodStat springMethodStat = SpringMethodStat.current();
if (springMethodStat != null) {
springMethodStat.addJdbcFetchRowCount(fetchRowCount);
}
}
@Override
public void executeBefore(String sql, boolean inTransaction) {
SpringMethodStat springMethodStat = SpringMethodStat.current();
if (springMethodStat != null) {
springMethodStat.incrementJdbcExecuteCount();
}
}
@Override
public void executeAfter(String sql, long nanos, Throwable error) {
SpringMethodStat springMethodStat = SpringMethodStat.current();
if (springMethodStat != null) {
springMethodStat.addJdbcExecuteTimeNano(nanos);
if (error != null) {
springMethodStat.incrementJdbcExecuteErrorCount();
}
}
}
------------------------省略-------------------------
}
总结
总得说起来这块实现还是挺清晰的,设定切点,然后在切点前后记录相关数据,其中在看源码的时候可能有的细节地方觉得不太清晰,不懂它干嘛的,可以写个单元测试或者demo,debug调试下,大概就能知道到底干嘛了。
记住看完源码能让你更好的使用框架这只是第一层,更多的是学习他们写代码的方式,学习优秀开源项目的设计思维,然后你就知道了怎么去写优秀的代码,看的多了,你代码实力自然就上去了,想写出“低质量“的代码,也难。