Druid源码阅读-filter逻辑浅析

17 篇文章 1 订阅
12 篇文章 0 订阅

以前说过druid的连接池和spring-boot-starter实现,今天来看下它里面的Filter。它的Filter是采用责任链模式,这种模式十分适合拓展,用户自己自己定义各种Filter,实现接口逻辑,然后利用SPI或者配置文件在初始化的时候加载到DataSource的filter集合里,后面在做操作的时候就会执行你的逻辑进行增强。这块今天就看了半个小时,大概记录下。

Filter实现

责任链模式一般都会有一个FilterChain,它会持有所有的filter,然后会在它的方法里面去调用所有filter的具体实现。

所以在这里面有两个角色,filterChain和filter,前者持有后者,它们在Druid里面都有各自的接口实现,名字就是FilterChain和Filter,然后当你去看这两个接口定义时可以发现,它里面的方法特别多,足足有1000多行,切入点给的特别细,具体有哪些没细看(就是太多了,懒得看),后面等到用的时候再看吧。

Filter在Druid里面有一些默认实现,现成的你可以直接拿来用比如StatFilter,WallFilter,EncodingConvertFilter等,你自己也可以自定义Filter,利用SPI机制在启动的时候就会被加载到jvm,后面执行。

FilterChain在Drui里面只有唯一的实现FilterChainImpl,所有有关filter的执行都是从这里进行调用的。

Filter调用流程
初始化

初始化其实就是filter被加载到filters集合里面的过程,它的实际调用地方很多,在init里面有两个地方:

   private void initFromSPIServiceLoader() {
        if (loadSpifilterSkip) {
            return;
        }

        if (autoFilters == null) {
            List<Filter> filters = new ArrayList<Filter>();
            ServiceLoader<Filter> autoFilterLoader = ServiceLoader.load(Filter.class);

            for (Filter filter : autoFilterLoader) {
                AutoLoad autoLoad = filter.getClass().getAnnotation(AutoLoad.class);
                if (autoLoad != null && autoLoad.value()) {
                    filters.add(filter);
                }
            }
            autoFilters = filters;
        }

        for (Filter filter : autoFilters) {
            if (LOG.isInfoEnabled()) {
                LOG.info("load filter from spi :" + filter.getClass().getName());
            }
            addFilter(filter);
        }
    }
private void initFromWrapDriverUrl() throws SQLException {
    if (!jdbcUrl.startsWith(DruidDriver.DEFAULT_PREFIX)) {
        return;
    }

    DataSourceProxyConfig config = DruidDriver.parseConfig(jdbcUrl, null);
    this.driverClass = config.getRawDriverClassName();

    LOG.error("error url : '" + jdbcUrl + "', it should be : '" + config.getRawUrl() + "'");

    this.jdbcUrl = config.getRawUrl();
    if (this.name == null) {
        this.name = config.getName();
    }

    for (Filter filter : config.getFilters()) {
        addFilter(filter);
    }
}

前者是从SPI加载自定义filter,后者是加载一个jdbc的转码filter,避免乱码(网上查的,没验证过)。

还有就是DruidAbstractDataSource里面的setFilters方法:

public void setFilters(String filters) throws SQLException {
    if (filters != null && filters.startsWith("!")) {
        filters = filters.substring(1);
        this.clearFilters();
    }
    this.addFilters(filters);
}

这个是读取配置文件的filter,可以多个用逗号隔开。它的核心逻辑是在FilterManager的loadFilter方法里面,有兴趣可以自己看看。

至此所有的filter都被加载到了filters集合里。

调用

调用接口实现很多,我以其中用的比较多的获取连接为例,其他也是类似。

当连接池初始化之后要建立物理连接,会调用DruidAbstractDataSource里的createPhysicalConnection方法:

    public Connection createPhysicalConnection(String url, Properties info) throws SQLException {
        Connection conn;
        if (getProxyFilters().isEmpty()) {
            conn = getDriver().connect(url, info);
        } else {
            FilterChainImpl filterChain = createChain();
            conn = filterChain.connection_connect(info);
            recycleFilterChain(filterChain);
        }

        createCountUpdater.incrementAndGet(this);

        return conn;
    }

当检查到有filter的时候(getProxyFilters()这个方法就是返回的filters集合),会调用DruidDriver的connect方法,然后调用DataSourceProxyImpl的connect方法:

public ConnectionProxy connect(Properties info) throws SQLException {
    this.properties = info;

    PasswordCallback passwordCallback = this.config.getPasswordCallback();

    if (passwordCallback != null) {
        char[] chars = passwordCallback.getPassword();
        String password = new String(chars);
        info.put("password", password);
    }

    NameCallback userCallback = this.config.getUserCallback();
    if (userCallback != null) {
        String user = userCallback.getName();
        info.put("user", user);
    }

    FilterChain chain = new FilterChainImpl(this);
    return chain.connection_connect(info);
}

注意看该方法的最后两行,会构件一个FilterChainImpl(前面说的FilterChain的唯一实现),然后调用它的connection_connect方法。

这个方法干了啥呢?如下:

public ConnectionProxy connection_connect(Properties info) throws SQLException {
    if (this.pos < filterSize) {
        return nextFilter()
                .connection_connect(this, info);
    }

    Driver driver = dataSource.getRawDriver();
    String url = dataSource.getRawJdbcUrl();

    Connection nativeConnection = driver.connect(url, info);

    if (nativeConnection == null) {
        return null;
    }

    return new ConnectionProxyImpl(dataSource, nativeConnection, info, dataSource.createConnectionId());
}

很多人可能一开始看不懂这个代码干了啥,因为有点绕,代码基础不太行的看着会有点蒙。

pos是当前filterChain的指针,filterSize是整个filter的数量。nextFilter()就是拿到当前位置的filter然后将指针后移。connection_connect是调用Filter实现类的方法。

它这么写会形成一个类似套娃的效果,整个filterChain返回的ConnectionProxy其实是多有filter功能“包裹“起来的功能合集连接。你可以想象成一个洋葱,最里层是实际连接,然后每外面一层都是filter添加的额外逻辑。

说着有点抽象,举个例子你就懂了。

比如我现在配置了连个filter,StatFilter和EncodingConvertFilter。

下面是两个Filter的connection_connect方法实现,前面是StatFilter,后面是EncodingConvertFilter:

public ConnectionProxy connection_connect(FilterChain chain, Properties info) throws SQLException {
    ConnectionProxy connection = null;

    long startNano = System.nanoTime();
    long startTime = System.currentTimeMillis();

    long nanoSpan;
    long nowTime = System.currentTimeMillis();

    JdbcDataSourceStat dataSourceStat = chain.getDataSource().getDataSourceStat();
    dataSourceStat.getConnectionStat().beforeConnect();
    try {
        connection = chain.connection_connect(info);
        nanoSpan = System.nanoTime() - startNano;
    } catch (SQLException ex) {
        dataSourceStat.getConnectionStat().connectError(ex);
        throw ex;
    }
    dataSourceStat.getConnectionStat().afterConnected(nanoSpan);

    if (connection != null) {
        JdbcConnectionStat.Entry statEntry = getConnectionInfo(connection);

        dataSourceStat.getConnections().put(connection.getId(), statEntry);

        statEntry.setConnectTime(new Date(startTime));
        statEntry.setConnectTimespanNano(nanoSpan);
        statEntry.setEstablishNano(System.nanoTime());
        statEntry.setEstablishTime(nowTime);
        statEntry.setConnectStackTrace(new Exception());

        dataSourceStat.getConnectionStat().setActiveCount(dataSourceStat.getConnections().size());
    }

    return connection;
}
public ConnectionProxy connection_connect(FilterChain chain, Properties info) throws SQLException {
    ConnectionProxy conn = chain.connection_connect(info);

    CharsetParameter param = new CharsetParameter();
    param.setClientEncoding(info.getProperty(CharsetParameter.CLIENTENCODINGKEY));
    param.setServerEncoding(info.getProperty(CharsetParameter.SERVERENCODINGKEY));

    if (param.getClientEncoding() == null || "".equalsIgnoreCase(param.getClientEncoding())) {
        param.setClientEncoding(clientEncoding);
    }
    if (param.getServerEncoding() == null || "".equalsIgnoreCase(param.getServerEncoding())) {
        param.setServerEncoding(serverEncoding);
    }
    conn.putAttribute(ATTR_CHARSET_PARAMETER, param);
    conn.putAttribute(ATTR_CHARSET_CONVERTER,
            new CharsetConvert(param.getClientEncoding(), param.getServerEncoding()));

    return conn;
}

当你调用filterChain的connection_connect方法时,会经历下面几步:

  1. 第一次进入filterChain的connection_connect方法,filterSize是2,这时候pos是0,会走if逻辑,然后调用StatFilter的connection_connect方法。
  2. 执行StatFilter逻辑,前面逻辑不用管,是一些监控的逻辑,然后走到try的 connection = chain.connection_connect(info);语句时,会再次调用filterChain的connection_connect方法。(注意这条语句后面的方法都没执行,等待下面的方法返回结果
  3. 第二次进入filterChain的connection_connect方法,filterSize是2,这时候pos是1,还是会走if逻辑,继续调用EncodingConvertFilter的connection_connect方法。
  4. 执行EncodingConvertFilter逻辑,直接第一句ConnectionProxy conn = chain.connection_connect(info);就会在调用filterChain的connection_connect方法。(注意这条语句后面的方法都没执行,等待下面的方法返回结果
  5. 第三次进入filterChain的connection_connect方法,filterSize是2,这时候pos是2,这次会走后面的逻辑,生成一个物理连接返回。
  6. 回到EncodingConvertFilter里执行后续逻辑,返回“包裹“EncodingConvertFilter逻辑的连接。
  7. 回到StatFilter里执行后续逻辑,返回“包裹“EncodingConvertFilter和StatFilter逻辑的连接。
  8. 最后实际用到的所有连接都是这个“包裹“之后的连接。

这里可以看到包在越外层的代码前置方法越先执行,后置方法越后执行。现在的Druid里面的filter是无序的,如果多个filter之间要有顺序,现在是不支持的貌似,你应该要自己去写处理逻辑。

其实如果你刷题刷的多的话,这块其实就是一个递归的实现。而且还是尾递归。

总结

经过上面的讲解,相信你会比较清晰的了解filter的执行流程,如果你觉得理解的不太清楚。

可以思考一下在Druid里面,如果你自定义的filter在其他filter之前,且没有调用chain.connection_connect(info)会发生啥?

想明白这个你也就知道FilterAdapter的作用了。

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值