Druid源码阅读-DruidStatInterceptor实现

上次我们在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执行次数,更新条数等。下面是部分参数截图,实际还有很多其他的参数,有兴趣的可以自己研究下。

Druid源码阅读-DruidStatInterceptor实现_第1张图片

注意这里面的监控其实分两块,常规的监控其实只监视数据库侧,即这里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调试下,大概就能知道到底干嘛了。

记住看完源码能让你更好的使用框架这只是第一层,更多的是学习他们写代码的方式,学习优秀开源项目的设计思维,然后你就知道了怎么去写优秀的代码,看的多了,你代码实力自然就上去了,想写出“低质量“的代码,也难。

你可能感兴趣的:(Druid,数据库,源码阅读,java,数据库)