Druid源码解析-Plugin篇

前言:

    Filter几乎已经是中间件应用扩展功能的必备项了。

    无论是Dubbo、Spring、Tomcat等等优秀框架,几乎都有对应的Filter设计。Filter通常是FilterChain的形式串在一起,可以几乎无侵入的实现框架的扩展。

    既然Druid也宣称功能强大易扩展,那不可避免的也会使用到Filter的概念。实际在之前的博客中,笔者刻意没有贴出Filter相关代码。

    在本文中我们来从源码角度了解下Filter的使用。

1.代码准备

DruidDataSource dataSource = new DruidDataSource();
// 以下四个参数为必输项
dataSource.setUrl("jdbc:mysql://localhost:3306/db1");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");

// 1.特意添加Filter
dataSource.setFilters("stat,trace,log4j,encoding");
// 2.也可以不特意添加Filter,datasource在init时会扫描对应的Filter实现

Connection connection = dataSource.getConnection();
Statement stmt = connection.createStatement();
stmt.execute("select 1;");

ResultSet rs = stmt.getResultSet();
Assert.assertFalse(rs.isClosed());

1.1 dataSource.setFilters加载方式

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

// DruidDataSource.addFilters
public void addFilters(String filters) throws SQLException {
    String[] filterArray = filters.split("\\,");
    for (String item : filterArray) {
        // 分割后加载
        FilterManager.loadFilter(this.filters, item.trim());
    }
}

// FilterManager.loadFilter
public static void loadFilter(List<Filter> filters, String filterName) throws SQLException {
    if (filterName.length() == 0) {
        return;
    }
	// 从alias中获取,alias中的Filter是在static方法中加载的,
    // 具体见FilterManager.loadFilterConfig()方法
    String filterClassNames = getFilter(filterName);

    if (filterClassNames != null) {
        for (String filterClassName : filterClassNames.split(",")) {
			...
            Class<?> filterClass = Utils.loadClass(filterClassName);
            Filter filter;
            try {
                // 通过反射来获取对应的Filter类
                filter = (Filter) filterClass.newInstance();
            } 
			...
            filters.add(filter);
        }
        return;
    }
	...
}

注意:FilterManager.java static方法默认加载的Filter列表如下,默认从

META-INF/druid-filter.properties文件中加载,文件内容如下:
druid.filters.default=com.alibaba.druid.filter.stat.StatFilter
druid.filters.stat=com.alibaba.druid.filter.stat.StatFilter
druid.filters.mergeStat=com.alibaba.druid.filter.stat.MergeStatFilter
druid.filters.counter=com.alibaba.druid.filter.stat.StatFilter
druid.filters.encoding=com.alibaba.druid.filter.encoding.EncodingConvertFilter
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
druid.filters.log4j2=com.alibaba.druid.filter.logging.Log4j2Filter
druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.wall=com.alibaba.druid.wall.WallFilter
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
druid.filters.haRandomValidator=com.alibaba.druid.pool.ha.selector.RandomDataSourceValidateFilter

总结:可以看到,通过setFilters的方式,直接从FilterManager中已经加载好的Filter中来获取。

1.2 DruidDataSource.init方法主动加载可用Filter

// DruidDataSource.init
public void init() throws SQLException {
    if (inited) {
        return;
    }
    ...
    // DruidDataSource.setProxyFilters()方法对filters进行赋值
    for (Filter filter : filters) {
        filter.init(this);
    } 
    
    ...
    // 在这里通过SPI的方式来扫描
    initFromSPIServiceLoader();
    ...
}

// DruidDataSource.initFromSPIServiceLoader
private void initFromSPIServiceLoader() {
    if (loadSpifilterSkip) {
        return;
    }

    if (autoFilters == null) {
        List<Filter> filters = new ArrayList<Filter>();
        // 通过ServiceLoader的方式来加载Filter
        ServiceLoader<Filter> autoFilterLoader = ServiceLoader.load(Filter.class);

        // 解析Filter,并添加到filters中
        for (Filter filter : autoFilterLoader) {
            AutoLoad autoLoad = filter.getClass().getAnnotation(AutoLoad.class);
            if (autoLoad != null && autoLoad.value()) {
                filters.add(filter);
            }
        }
        autoFilters = filters;
    }
	...
}

总结:ServiceLoader有点类似于ClassLoader,只不过ServiceLoader只加载特定的类。这种低侵入加载类的方式被广泛应用在Dubbo和Druid中。

我们可以自定义一个Filter的实现,然后将其类全限定名放入META-INF/services目录下的com.alibaba.druid.filter.Filter文件中即可,ServiceLoader即会主动加载该实现

总结:通过上述两种方式,我们可以实现Filter的加载,以实现Druid功能的扩展。在我们项目中通常是通过第二种方式来主动无侵入的加载。

2.Filter在创建连接时的使用

    Filter在加载之后,什么时候会被使用到呢?我们来看下代码(采用上述1.1中的示例)

// DruidDataSource.getConnection
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
    init();

    // 若当前项目有Filter被加载到,则不再使用直接获取连接的方式
    if (filters.size() > 0) {
        FilterChainImpl filterChain = new FilterChainImpl(this);
        return filterChain.dataSource_connect(this, maxWaitMillis);
    } else {
        return getConnectionDirect(maxWaitMillis);
    }
}

// FilterChainImpl.dataSource_connect
public DruidPooledConnection dataSource_connect(DruidDataSource dataSource, long maxWaitMillis) throws SQLException {
    // 每次获取连接时FilterChainImpl都是新new出来的,则pos默认都是从0开始的
    if (this.pos < filterSize) {
        DruidPooledConnection conn = nextFilter().dataSource_getConnection(this, dataSource, maxWaitMillis);
        return conn;
    }

    return dataSource.getConnectionDirect(maxWaitMillis);
}

FilterChainImpl是责任链模式,我们来具体看下

// 1.LogFilter.dataSource_getConnection
public DruidPooledConnection dataSource_getConnection(FilterChain chain, DruidDataSource dataSource,
                                                      long maxWaitMillis) throws SQLException {
    // 调用chain.dataSource_connect方法,本质上执行下一个Filter.dataSource_getConnection方法
    DruidPooledConnection conn = chain.dataSource_connect(dataSource, maxWaitMillis);

    ConnectionProxy connection = (ConnectionProxy) conn.getConnectionHolder().getConnection();

    if (connectionConnectAfterLogEnable && isConnectionLogEnabled()) {
        connectionLog("{conn-" + connection.getId() + "} pool-connect");
    }

    return conn;
}

// 2.StatFilter.dataSource_getConnection
public DruidPooledConnection dataSource_getConnection(FilterChain chain, DruidDataSource dataSource,
                                                      long maxWaitMillis) throws SQLException {
    // 也是回调到chain来执行
    DruidPooledConnection conn = chain.dataSource_connect(dataSource, maxWaitMillis);
    if (conn != null) {
        conn.setConnectedTimeNano();
        StatFilterContext.getInstance().pool_connection_open();
    }
    return conn;
}
...
总结:由上可知,使用责任链模式来顺序执行所有的Filter,最终会先调用到
dataSource.getConnectionDirect(maxWaitMillis)方法,直接获取到连接后,
然后再执行对应Filter的后续方法(比如LogFilter会打印log,
StateFilter会创建连接的ConnectedTimeNano参数)

3.Filter在执行SQL时的使用

// 执行代码如下
Statement stmt = connection.createStatement();
stmt.execute("select 1;");

该Statement的实现类为StatementProxyImpl,我们来看下其execute方法

// StatementProxyImpl.execute
public boolean execute(String sql) throws SQLException {
	...
    // 同获取Connection一样,执行SQL前也是先获取一个FilterChain
    FilterChainImpl chain = createChain();
    firstResultSet = chain.statement_execute(this, sql);
    recycleFilterChain(chain);
    return firstResultSet;
}

// FilterChainImpl.statement_execute
public boolean statement_execute(StatementProxy statement, String sql) throws SQLException {
    // 这里是不是很眼熟,默认也会执行Filter.statement_execute方法
    if (this.pos < filterSize) {
        return nextFilter().statement_execute(this, statement, sql);
    }
    return statement.getRawObject().execute(sql);
}

继续责任链模式的使用

// 1.LogFilter.statement_execute
public boolean statement_execute(FilterChain chain, StatementProxy statement, String sql) throws SQLException {
    statementExecuteBefore(statement, sql);

    try {
        boolean firstResult = super.statement_execute(chain, statement, sql);
        statementExecuteAfter(statement, sql, firstResult);
        return firstResult;
    } 
    ...
}

// LogFilter.statementExecuteBefore
protected void statementExecuteBefore(StatementProxy statement, String sql) {
    // 写入Statement.lastExecuteStartNano参数
    statement.setLastExecuteStartNano();
    if (statement instanceof PreparedStatementProxy) {
        logParameter((PreparedStatementProxy) statement);
    }
}

// LogFilter.statementExecuteAfter
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
    logExecutableSql(statement, sql);

    if (statementExecuteAfterLogEnable && isStatementLogEnabled()) {
        statement.setLastExecuteTimeNano();
        double nanos = statement.getLastExecuteTimeNano();
        double millis = nanos / (1000 * 1000);

        // SQL执行完毕后,写入log,记录SQL执行时间
        statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
                     + millis + " millis. " + sql);
    }
}

可以看到,LogFilter即在执行SQL之前和之后记录当前时间,并将SQL的真正执行时间记录到日志中。

其他Filter也是类似的,笔者不再继续分析。

4.自定义Filter

    有时候我们需要对Druid功能进行扩展,最好的方式就是按照之前1.2的方式定义Filter,并将其放入META-INF/services目录下的com.alibaba.druid.filter.Filter文件中。

    Filter的实现并不复杂,并且Druid作者提供了几种Filter的实现接口和抽象类。

    1)Filter接口(我们需要实现所有的方法,比较麻烦,不建议直接用)

    2)FilterAdapter抽象类(实现了大部分的方法,基本都是statement的相关实现)

    3)FilterEventAdapter抽象类(更强大的实现,不仅实现了Filter接口的大部分方法,而且提供了更加规范的方式来完成对连接和statement的前置和后置处理),通过源码来感受下其强大

public abstract class FilterEventAdapter extends FilterAdapter {

    public FilterEventAdapter(){
    }

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

    public void connection_connectBefore(FilterChain chain, Properties info) {
    }

    public void connection_connectAfter(ConnectionProxy connection) {
    }

    @Override
    public StatementProxy connection_createStatement(FilterChain chain, ConnectionProxy connection) throws SQLException {
        StatementProxy statement = super.connection_createStatement(chain, connection);
        statementCreateAfter(statement);
        return statement;
    }
	...
}

总结:建议大家在自定义Filter时,实现FilterEventAdapter抽象类,以更好的扩展Druid

Guess you like

Origin blog.csdn.net/qq_26323323/article/details/121386391